Flying to NYC
I'll be spending the nest 10 days in our company's web farm in NYC.
Its going to be an 11 hours flight from Israel to NY :-(
I'm going to install some new web servers and 2 load balancers.
See ya next week, or so :-)
If you repeat it [$CRIPT] it!
I'll be spending the nest 10 days in our company's web farm in NYC.
Its going to be an 11 hours flight from Israel to NY :-(
I'm going to install some new web servers and 2 load balancers.
See ya next week, or so :-)
Posted by Shay Levy at 9/27/2007 07:09:00 PM 1 comments
"Get-Databases" function creates a custom "view" of some database properties on the specified SQL server.
function Get-SQLDatabases{
param(
[string]$server=$(throw "Please specify SQL server name.")
)
[void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$smo = new-object Microsoft.SqlServer.Management.Smo.Server $server
$count=0;
$smo.databases | foreach {
$db = $smo.databases[$_.name];
$obj = new-object psobject;
add-member -inp $obj NoteProperty "#" ($count+1);
add-member -inp $obj NoteProperty "Server" ([string][regex]::replace($db.Parent,"\[|\]","")).toupper();
add-member -inp $obj NoteProperty "DB Name" $db.Name;
add-member -inp $obj NoteProperty "ID" $db.id;
add-member -inp $obj NoteProperty "Size(MB)" $db.size.tostring("N2");
add-member -inp $obj NoteProperty "Free(MB)" ($db.SpaceAvailable/1024).tostring("N2") ;
add-member -inp $obj NoteProperty "Status" $db.Status;
add-member -inp $obj NoteProperty "Last Backup" $db.LastBackupDate;
add-member -inp $obj NoteProperty "SystemDB" $db.IsSystemObject;
$obj;
$count++
}
}
PS > Get-SQLDatabases shayl\sqlexpress | format-table -autosize
# Server DB Name ID Size(MB) Free(MB) Status Last Backup SystemDB
- ------ ------- -- -------- -------- ------ ----------- --------
1 SHAYL\SQLEXPRESS master 1 6.75 1.44 Normal 01/01/0001 00:00:00 True
2 SHAYL\SQLEXPRESS model 3 3.19 1.08 Normal 01/01/0001 00:00:00 True
3 SHAYL\SQLEXPRESS msdb 4 9.44 2.00 Normal 01/01/0001 00:00:00 True
4 SHAYL\SQLEXPRESS tempdb 2 2.69 1.03 Normal 01/01/0001 00:00:00 True
5 SHAYL\SQLEXPRESS test 5 7.13 0.20 Normal 01/01/0001 00:00:00 False
Posted by Shay Levy at 9/20/2007 12:55:00 PM 1 comments
Labels: PowerShell, SMO, SQL
The "New-DummyTree" function creates a directory tree duplicate. The new directory tree will be an exact copy of the source directory, including file names and there's date attributes, only files will be 0 bytes long. Then you can test your scripting scenarios on its structure.
I used it only once when I tested a mass file processing activity (copy, delete, rename, archive (with New-RarArchive) and so on.
I couldn't test it against the real directory tree since it was based on the files date attributes, not to mention that the source files were very big in size, and I wanted to test the script as one piece and not just part of it in separate files.
It also demonstrates how you can alter a file or directory date attributes, such as LastWriteTime, etc.
So, for what it's worth...
function New-DummyTree{
param(
[string]$source=$(throw "Invalid source directory path"),
[string]$destination
)
if( -not (Test-Path $source -pathType container)){
write-error "Invalid source directory path: <$source>";
break;
}
get-childitem $source -recurse | where {-not $_.psiscontainer} | foreach {
$path="{0}{1}" -f $destination,$($_.directoryname.remove(0,$source.length));
$file=new-item -path $path -name $_.name -type file -force;
$file.CreationTime=$_.CreationTime;
$file.LastWriteTime=$_.LastWriteTime;
$file.LastAccessTime=$_.LastAccessTime;
}
#set-location $destination
}
Note, You don't need to create the destination directory, It will be created on the fly if it doesn't exist.
Usage:
New-DummyTree <SourcePath> <DestinationPath>
Posted by Shay Levy at 9/20/2007 01:07:00 AM 0 comments
Labels: PowerShell
There has been some talking on the Windows PowerShell in Action Manning forum about a new PowerShell book titled "Windows Powershell in Practice". Windows PowerShell in Action by Bruce Payette was fantastic. Personally, it was and still my PowerShell bible. I'm sure the new companion book will be as exciting as its big brother ;-)
Bruce comments when asked when the book will release:
"Windows PowerShell in Practice" is a new book that is in development at Manning. I'm not sure when it will be available. It will cover things like the PowerShell SDK (writing cmdlets, providers, embedding the PowerShell engine in other applications, etc.) as well as larger, more domain specific examples and advance scripting techniques.
I'm not currently a direct contributor (but I do kibitz on occasion.) Jim Truher (who co-designed the language with me) and MOW are working on this book. I'll ping them and see how it's going...
-bruce
Posted by Shay Levy at 9/19/2007 10:28:00 PM 0 comments
Labels: books, PowerShell
function New-RarArchive{
param(
[string] $source=$(throw "Invalid source parameter"),
[string] $destination=$(throw "Invalid destination parameter"),
[string] $name=$(throw "Invalid name parameter"),
[string] $winrar = "$env:ProgramFiles\WinRAR\WinRAR.exe",
[switch] $directory
)
if($directory){
if( -not (Test-Path $source -pathType container)){
write-error "Invalid directory path <$source>";
break;
}
} else {
if( -not (Test-Path $source -pathType leaf)){
write-error "Invalid file path <$source>";
break;
}
}
if( -not (Test-Path $destination -pathType container)){
write-error "Invalid destination path <$destination>";
break;
}
# winrar switches:
# A - Add specified files and folders to an archive.
# IBCK - Minimize WinRAR to tray, runs in the background.
# Y - Yes will be the default and automatic reply to all queries.
# R - recurse subfolders.
# ILOG[name] - log errors to file
if($directory){
& $winrar A -IBCK -Y -R "$destination\$name" $("$source\*.*") | out-null
} else {
& $winrar A -IBCK -Y -R "$destination\$name" $source | out-null
}
if($LASTEXITCODE -match "[01]"){
# 0 - Successful operation.
# 1 - Warning. Non fatal error(s) occurred.
write-host "New archive created <$destination\$name>" -foreground green
$true;
} else {
switch($LASTEXITCODE){
2 {write-error "A fatal error occurred."}
3 {write-error "CRC error occurred when unpacking."}
4 {write-error "Attempt to modify a locked archive."}
5 {write-error "Write error."}
6 {write-error "File open error."}
7 {write-error "Wrong command line option."}
8 {write-error "Not enough memory."}
9 {write-error "File create error."}
255 {write-error "User break."}
}
$false;
}
}
Archive c:\scripts directory to \\server\share
>> New-RarArchive -source "c:\scripts" -destination \\server\share -name "test.rar" -directory
Archive a file
>> New-RarArchive -source "C:\Scripts\test.exe" -destination "D:\Backup" -name "test.rar"
WinRAR -ibkp switch adds a visual indicator in the windows system tray.
Posted by Shay Levy at 9/17/2007 10:49:00 AM 2 comments
Labels: PowerShell
Remember the old MS-DOS choice command? It prompts the user to make a choice in a batch program by displaying a prompt and pausing for the user to choose from a set of user-option keys.
In PowerShell, you can provide the same mechanism with .NET built-in classes without the need for external files. You can see this method in action on every cmdlet declared with -confirm parameter.
PS > Get-Process s* | Stop-Process -confirm
Confirm
Are you sure you want to perform this action?
Performing operation "Stop-Process" on Target "services (520)".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
The user is presented with "plain old message box kind" prompt, built from a caption, message and number of choices to choose from:
Confirm = Caption
Are you sure you want to... = Message
And the choices:
[Y] Yes
[A] Yes to All
[N] No
[L] No to All
[S] Suspend
In my scripts I used to build this mechanism manually and write to the host the caption/message and choices until I 'found' this while digging on Mighty MoW's PowerTab scripts. The method is also described on MSDN PSHostUserInterface.PromptForChoice.
PS > $host.ui | gm
TypeName: System.Management.Automation.Internal.Host.InternalHostUserInterface
Name MemberType
---- ----------
...
Prompt Method
PromptForChoice Method
PromptForCredential Method
...
The definition for PromptForChoice is:
System.Int32 PromptForChoice(String caption, String message, Collection`1 choices, Int32 defaultChoice)
So, how can you implement it in your scripts? Here is my variation:
$caption = "Confirm";
$message = "Are you sure?";
$yes = new-Object System.Management.Automation.Host.ChoiceDescription "&Yes","help";
$no = new-Object System.Management.Automation.Host.ChoiceDescription "&No","help";
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no);
$answer = $host.ui.PromptForChoice($caption,$message,$choices,0)
switch ($answer){
0 {"You entered 0"; break}
1 {"You entered 1"; break}
}
write-host "Rest of script goes here..."
Here is the output:
PS C:\Scripts> .\PromptForChoice.ps1
Confirm
Are you sure?
[Y] Yes [N] No [?] Help (default is "Y"): n
You entered 1
Rest of script goes here...
PS >
Note that the special character "&" (ampersand) might be embedded in the label string to indicate that the next character is a "hot key" (for example, "Yes to &All"). The hot key allows the user to quickly set the input focus to this choice.
The default choice is determined by the order/index of the collection variables ($choices). To indicate no default, set defaultChoice to -1:
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no);
$yes = 0
$no = 1
There is also the "Help" option, which we didn't explicitly define.
The strings "help" in the $yes and $no variables are the help text to display when the user types "?"
$no = new-Object System.Management.Automation.Host.ChoiceDescription "&No","help";
I also wanted to implement the Suspend feature. Suspending the process is totally awesome. You have another chance to pause operation and get up to date, verified information, before proceeding. I knew that there is something called "Nested Prompt" and something inside felt that this is the place where it might be used. So I made some tests and here's the code with Suspend.
$caption = "Confirm";
$message = "Are you sure?";
$yes = new-Object System.Management.Automation.Host.ChoiceDescription "&Yes","help";
$no = new-Object System.Management.Automation.Host.ChoiceDescription "&No","help";
$Suspend = new-Object System.Management.Automation.Host.ChoiceDescription "&Suspend","help";
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no,$suspend);
$Answer = $host.ui.PromptForChoice($caption,$message,$Choices,0)
switch ($Answer){
2 {"You entered 2, entering nested prompt, type exit to return"; $Host.EnterNestedPrompt()}
}
write-host "Rest of script goes here..."
Here is the output:
PS C:\Scripts> .\PromptForChoice.ps1
Confirm
Are you sure?
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"): s
You entered 2, entering nested prompt, type exit to return
PS >>> exit
Rest of script goes here...
PS >
Note that the PowerShell prompt is visually changing, colored in yellow ( adding >> to the default prompt) indicating that you're inside a nested prompt. To exit nested prompts simply type exit.
Posted by Shay Levy at 9/16/2007 10:34:00 PM 2 comments
Labels: PowerShell, prompt
New complete eBook : The Shortcut Guide to Exchange Server 2007 Storage Systems, by Jim McBee, is available for download on Realtime Publishers website. This web site is a great resource of free eBooks covering all aspects of IT. Register now and download it.
Here's the description for this title:
The conventional way of planning for Exchange Server storage is to throw a lot of disk storage at the server and hope that is sufficient. Mail storage requirements continue to grow for even average users; these users place more and more capacity requirements not only on disk storage but also on disk I/O capacity. As current capacity requirements are exceeded, the storage system must provide scalability. The Shortcut Guide to Exchange Server 2007 Storage Systems, authored by Microsoft Exchange MVP Jim McBee, covers Exchange Server storage capacity requirement planning, the basics of using iSCSI SANs, and best practices for scalability and SAN operations.
Posted by Shay Levy at 9/16/2007 08:08:00 AM 0 comments
<a href="city.html?n=110">Jerusalem</a> *</td><td class="r">Sat 5:44 PM</td>
Based on that line we can build a regular expression to match each place and its local time (yellowed text).
Regex break through:
'href="city.html\?n=\d+">(?<place>[^/]*)</a>\s?\*?</td><td class="r">(?<time>[^/]*)</td>'
Starts with href="city.html
Followed by ?n= (The ? sign is a meta character so escape it, \?n=)
Followed by one or more digits \d+"> with a quote and a closing tag bracket (110)
Followed by (?<place>[^/]*) (equals to Jerusalem, match and capture)
Followed by closing a tag </a>, and (?:\s\*)? (optional space and *, don't insert into $matches)
Followed by </td><td class="r">
Followed by (?<time>[^/]*) (equals to Sat 5:44 PM, match and capture)
Followed by </td>
Captures legend:
(exp) Match exp and capture it in an automatically numbered group
(?<name>exp) Match exp and capture it in a group named name
(?:exp) Match exp, but do not capture it
#####################################
function Get-WorldClock{
param (
[string]$continent="",
[string]$filter="*"
)
$baseurl = http://www.timeanddate.com/worldclock/
# define values and shortcuts for -continent parameter
switch($continent){
"as" {$url+="$baseurl/custom.html?continent=asia"; break}
"asia" {$url+="$baseurl/custom.html?continent=asia"; break}
"af" {$url+="$baseurl/custom.html?continent=africa"; break}
"africa" {$url+="$baseurl/custom.html?continent=africa"; break}
"na" {$url+="$baseurl/custom.html?continent=namerica"; break}
"namerica" {$url+="$baseurl/custom.html?continent=namerica"; break}
"sa" {$url+="$baseurl/custom.html?continent=samerica"; break}
"samerica" {$url+="$baseurl/custom.html?continent=samerica"; break}
"au" {$url+="$baseurl/custom.html?continent=australasia"; break}
"australasia" {$url+="$baseurl/custom.html?continent=australasia"; break}
"pa" {$url+="$baseurl/custom.html?continent=australasia"; break}
"pacific" {$url+="$baseurl/custom.html?continent=australasia"; break}
"eu" {$url+="$baseurl/custom.html?continent=europe"; break}
"europe" {$url+="$baseurl/custom.html?continent=europe"; break}
default {$url=$baseurl; break}
}
trap{throw "Error: Can't connect to $url`n $_"; break}
$wc=(new-object net.webclient).DownloadString($url);
$regex=([regex]href="city.html\?n=\d+">(?<place>[^/]*)</a>(?:\s\*)?</td><td class="r">(?<time>[^/]*)</td>').matches($wc);
$times = $regex | select @{n="Place";e={$_.groups['place'].value}}, @{n="Time";e={$_.groups['time'].value}}
if($filter -eq "*"){
$times | sort place | format-table -autosize
} else {
$times | where {$_.place -like "*$filter*"} | sort place | format-table -autosize
}
}
#################
You can also download the script file from here.
Examples:
1. Current local times around the world (Main list)
PS > Get-WorldClock
Place Time
----- ----
Addis Ababa Sat 6:01 PM
Adelaide Sun 12:31 AM
Aden Sat 6:01 PM
Algiers Sat 4:01 PM
Amman Sat 6:01 PM
Amsterdam Sat 5:01 PM
Anadyr Sun 4:01 AM
Anchorage Sat 7:01 AM
Ankara Sat 6:01 PM
Antananarivo Sat 6:01 PM
Asuncion Sat 11:01 AM
Athens Sat 6:01 PM
Atlanta Sat 11:01 AM
Auckland Sun 3:01 AM
...
...
2. Current local times in Europe (eu or europe)
PS > Get-WorldClock -continent eu
...
...
Beijing Sat 11:32 PM
Beirut Sat 6:32 PM
Belgrade Sat 5:32 PM
Berlin Sat 5:32 PM
Bogota Sat 10:32 AM
Boston Sat 11:32 AM
Brasilia Sat 12:32 PM
Brisbane Sun 1:32 AM
Brussels Sat 5:32 PM
Bucharest Sat 6:32 PM
Budapest Sat 5:32 PM
Buenos Aires Sat 12:32 PM
...
...
2. Current local time in Jerusalem (Asia)
PS > Get-WorldClock -continent asia -filter jeru
Place Time
----- ----
Jerusalem Sat 6:42 PM
Posted by Shay Levy at 9/15/2007 06:00:00 PM 2 comments
Labels: PowerShell
I removed some definition columns for readability. During Get-Command exploration I found some interesting things on my environment. Check yours :-)
To get a cmdlet syntax, not so readable
PS > get-command get-childitem -syntax
Get-ChildItem [[-Path] <String[]>] [[-Filter] <String>] [-Include <String[]>] [-Exclude <String[]>] [-Recurse] [-Force] [-Name] [-Verbose]
[-Debug] [-ErrorAction <ActionPreference>] [-ErrorVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Get-ChildItem [-LiteralPath] <String[]> [[-Filter] <String>] [-Include <String[]>] [-Exclude <String[]>] [-Recurse] [-Force] [-Name] [-Verb
ose] [-Debug] [-ErrorAction <ActionPreference>] [-ErrorVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Same goes here . It is better to run "get-help cmdlet-name -full"
PS C:\Scripts> get-command get-childitem -definition
CommandType Name Definition
----------- ---- ----------
Cmdlet Get-ChildItem Get-ChildItem [[-Path] <String[]>] [[-Filter] <String>] [-Include <String[]>] [-Exclude <String[]>] [-Recurse]...
Get the most popular cmdlets by verb. And the winner is...
PS > get-command | group verb
Count Name
----- ----
134 Get
87 Set
60 Remove
49 New
17 Enable
16 Add
15 Disable
15 Test
11 Update
10 Export
8 Move
7 Write
6 Out
6 Start
5 Stop
5 Suspend
5 Resume
5 Clear
5 Import
4 Format
3 Copy
...
...
List commands were "email" appears in the command noun part
PS > get-command -noun *email*
CommandType Name
----------- ----
Cmdlet Get-EmailAddressPolicy
Cmdlet New-EmailAddressPolicy
Cmdlet Remove-EmailAddressPolicy
Cmdlet Set-EmailAddressPolicy
Cmdlet Update-EmailAddressPolicy
The -commandType property can take one (or more of this values)
Alias
Cmdlet
ExternalScript
Function
Filter
Application
Script
All
So, get all registered filters
PS > get-command -commandType Filter
CommandType Name Definition
----------- ---- ----------
Function A: Set-Location A:
Function B: Set-Location B:
Function backup-profile copy-item $PROFILE "C:\Scripts\PoSH Profile" -force;...
Function C: Set-Location C:
Function Clear-Host $spaceType = [System.Management.Automation...
Function Clear-Temp remove-item -r -fo $env:temp\* -ea 0;...
Function D: Set-Location D:
Function e & "explorer.exe" $args[0];
Function E: Set-Location E:
Function echo-args for($i=0;$i -lt $args.length;$i++){...
Function edit-profile & $textpad $PROFILE;
Function F: Set-Location F:
Function G: Set-Location G:
Function get-def ($args[0] | gm $args[1]).definition.split(',');...
Function Get-Params param(...
Function Get-RemoteEventLog [System.Diagnostics.Eventlog]::GetEventLogs(...
Function Get-RemoteProcess [System.Diagnostics.Process]::GetProcesses(...
Function Get-RemoteService [void][System.Reflection.Assembly]::LoadWithPartial...
...
...
To return all scripts that are found in your PATH environment variable. Interesting ... I installed the Exchange 2007 management pack and it registered some scripts:
PS > get-command -commandType ExternalScript
CommandType Name
----------- ----
ExternalScript AddReplicaToPFRecursive.ps1
ExternalScript AddUsersToPFRecursive.ps1
ExternalScript CheckInvalidRecipients.ps1
ExternalScript ConfigureAdam.ps1
ExternalScript configure-SMBIPsec.ps1
ExternalScript enable-CrossForestConnector.ps1
ExternalScript Exchange.ps1
...
...
-CommandType can also get multiple types, separated by a comma
PS C:\Scripts> Get-Command -commandType Filter,ExternalScript
CommandType Name
----------- ----
Function A:
ExternalScript AddReplicaToPFRecursive.ps1
ExternalScript AddUsersToPFRecursive.ps1
Function B:
Function backup-profile
Function C:
ExternalScript CheckInvalidRecipients.ps1
Function Clear-Host
Function Clear-Temp
ExternalScript ConfigureAdam.ps1
ExternalScript configure-SMBIPsec.ps1
Function D:
Function e
Function E:
Function echo-args
...
...
Now, Get ExternalScript file paths:
PS > get-command -commandType ExternalScript | foreach {$_.path}
C:\Program Files\Microsoft\Exchange Server\Scripts\AddReplicaToPFRecursive.ps1
C:\Program Files\Microsoft\Exchange Server\Scripts\AddUsersToPFRecursive.ps1
C:\Program Files\Microsoft\Exchange Server\Scripts\CheckInvalidRecipients.ps1
C:\Program Files\Microsoft\Exchange Server\Scripts\ConfigureAdam.ps1
C:\Program Files\Microsoft\Exchange Server\Scripts\configure-SMBIPsec.ps1
...
...
To add your own scripts directory type (e.g., c:\scripts)
PS > $env:path+=";c:\scripts"
Now you can execute all scripts in your scripts directory, typing only its name (with/out .ps1 extension) and you don't need to be in the scripts working directory. I'll introduce Get-WorldClock on my next blog post :-)
PS D:\> Get-WorldClock
Place Time
----- ----
Addis Ababa Sat 4:19 PM
Adelaide Sat 10:49 PM
Aden Sat 4:19 PM
Algiers Sat 2:19 PM
Amman Sat 4:19 PM
Amsterdam Sat 3:19 PM
Anadyr Sun 2:19 AM
Anchorage Sat 5:19 AM
Ankara Sat 4:19 PM
Antananarivo Sat 4:19 PM
Asuncion Sat 9:19 AM
Athens Sat 4:19 PM
Atlanta Sat 9:19 AM
Auckland Sun 1:19 AM
...
...
Group all commndlets by its PSSnapIn property
PS > get-command | where {$_.CommandType -eq "Cmdlet"} | group PSSnapIn | select count,name
Count Name
----- ----
381 Microsoft.Exchange.Management.PowerShell.Admin
47 Microsoft.PowerShell.Management
12 Microsoft.PowerShell.Core
58 Microsoft.PowerShell.Utility
10 Microsoft.PowerShell.Security
2 Microsoft.PowerShell.Host
Another way to list only Exchange 2007 command-lets
PS > get-command | where {$_.PSSnapIn.name -match "exchange"}
CommandType Name
----------- ----
Cmdlet Add-ADPermission
Cmdlet Add-AvailabilityAddressSpace
Cmdlet Add-ContentFilterPhrase
Cmdlet Add-DistributionGroupMember
Cmdlet Add-ExchangeAdministrator
Cmdlet Add-IPAllowListEntry
Cmdlet Add-IPAllowListProvider
Cmdlet Add-IPBlockListEntry
Cmdlet Add-IPBlockListProvider
Cmdlet Add-MailboxPermission
...
...
Or even better, filter with the pssnapin parameter
PS > get-command -pssnapin Microsoft.Exchange.Management.PowerShell.Admin
CommandType Name
----------- ----
Cmdlet Add-ADPermission
Cmdlet Add-AvailabilityAddressSpace
Cmdlet Add-ContentFilterPhrase
Cmdlet Add-DistributionGroupMember
Cmdlet Add-ExchangeAdministrator
Cmdlet Add-IPAllowListEntry
Cmdlet Add-IPAllowListProvider
Cmdlet Add-IPBlockListEntry
Cmdlet Add-IPBlockListProvider
Cmdlet Add-MailboxPermission
...
...
Get all properties for a command-let
PS > (get-command select-string).parametersets[0].parameters | select name,ParameterType,aliases
Name ParameterType Aliases
---- ------------- -------
InputObject System.Management.Automation.PSObject {}
Pattern System.String[] {}
SimpleMatch System.Management.Automation.SwitchParameter {}
CaseSensitive System.Management.Automation.SwitchParameter {}
Quiet System.Management.Automation.SwitchParameter {}
List System.Management.Automation.SwitchParameter {}
Include System.String[] {}
Exclude System.String[] {}
Verbose System.Management.Automation.SwitchParameter {vb}
Debug System.Management.Automation.SwitchParameter {db}
ErrorAction System.Management.Automation.ActionPreference {ea}
ErrorVariable System.String {ev}
OutVariable System.String {ov}
OutBuffer System.Int32 {ob}
I also wrote a function Get-Params which takes a cmdlet name and calculates the
shortest alias name for each cmdlet parameter. Very useful when typing commands interactively. It also emits some additional important columns.
>> Get-Params select-string
-----------------
- select-string -
-----------------
Name Alias Position Type
---- ----- -------- ----
caseSensitive -c named SwitchParameter
exclude -e named string[]
include -inc named string[]
inputObject -inp named psobject
list -l named SwitchParameter
path -path 2 string[]
pattern -patt 1 string[]
quiet -q named SwitchParameter
simpleMatch -s named SwitchParameter
text -t 2 string[]
Posted by Shay Levy at 9/15/2007 04:32:00 PM 0 comments
Labels: PowerShell
There has been a lot of questions in the PowerShell news group regarding aged files. To simplify the search process I wrote the "Get-AgedFiles" function. It's simple to use and also pipeline friendly :-)
Get-AgedFiles searches for aged files based on this parameters:
-path, String, starting directory path ,wildcards supported (e.g., c:\scripts\*.ps1)
-age, Integer, file age , default value: 1.
-timePart, String, can be one of this shortcuts values, default value "d":
y = Years
m = Months
d = Days
h = Hours
mm = Minutes
s = seconds
t = Ticks
-property, String, The file property for dates comparison , default value "LastWriteTime":
CreationTime
CreationTimeUtc
LastAccessTime
LastAccessTimeUtc
LastWriteTime
LastWriteTimeUtc
-recurse, switch parameter, when declared, performs the search on sub directories.
//TODO:
Add support to get-childitem's include,exclude,filter parameters.
Usage:
# Get all files from <D:\Backup>, 1 day old (only -path is required) based on <LastWriteTime> property
>> Get-AgedFiles -path "D:\Backup"
# Get all *.bkp files from <D:\SQLBackup\*.bkp> 10 Days old based on <LastWriteTime> property
>> Get-AgedFiles -path "D:\SQLBackup\*.bkp" -age 10 -timePart d
# Get all files, recursively, from <c:\scripts> 6 Months old based on <CreationTime> property
>> Get-AgedFiles -path "c:\scripts" -age 6 -timePart m -property CreationTime -recurse
# Get all files from current directory, 7 Days old based on <LastWriteTime> property
# delete all
>> Get-AgedFiles . 7 d | remove-item -whatif
# Get all files ,recursively, from current directory, 1 Year old based on <LastWriteTime>
# property, move all to c:\scripts\temp
>> Get-AgedFiles . 1 y -recurse | move-item -destination c:\scripts\temp -whatif
The function file is also available for download here.
######################################
function Get-AgedFiles{
param(
[string]$path=$(throw "needs a path"),
[int]$age=1,
[string]$timePart="TotalDays",
[string]$property="LastWriteTime",
[switch]$recurse
)
switch ($timePart){
"y" {$timePart="TotalDays"; $age*=365 ; break}
"m" {$timePart="TotalDays"; $age*=30 ; break}
"d" {$timePart="TotalDays"; break}
"h" {$timePart="TotalHours"; break}
"mm" {$timePart="TotalMinutes"; break}
"s" {$timePart="TotalSeconds"; break}
"t" {$timePart="Ticks"; break}
}
if($recurse){
get-childitem $path -recurse | where {
!$_.psiscontainer -and ((get-date).subtract($_.$property).$timePart -ge $age)
}
} else {
get-childitem $path | where {
!$_.psiscontainer -and ((get-date).subtract($_.$property).$timePart -ge $age)
}
}
}
########################
Posted by Shay Levy at 9/13/2007 06:14:00 PM 0 comments
Labels: PowerShell, search
Sapien technologies just released a com DLL to support simple FTP transfers. It supports the get/put FTP verbs, and I already tested them successfully. Its free to use and can be downloaded from Sapien's website. Thanks Sapien!
The download file includes two VBScripts files, one for upload and one for downloads. Here's a simple conversion to PowerShell.
Before starting using the functions, read the readme file, copy the DLL to system32 directory and register it:
regsvr32.exe ftp.dll
Cool, lets start
###############3
function Get-FTPFile{
param(
[string]$url,
[string]$port=21,
[string]$user,
[string]$pass,
[string]$RemoteFilePath,
[string]$localPath
)
$ftp = new-object -com Primalscript.FTPTransfer;
$ftp.Passive = 1;
$ftp.port = $port;
if($ftp.Connect($url,$user,$pass) -eq 0){
"Can't connect. " + $ftp.Status;
} else {
"Starting download"
If($ftp.Get($RemoteFilePath,$localPath) -eq 0){
"Error downloading file " + $ftp.Status;
} Else {
"Download complete";
}
$ftp.Disconnect();
}
}
Usage:
Get-FTPFile <ftpUrl> <port> <username> <password> <RemoteFilePath> <localDonloadPath>
############################
function Put-FTPFile{
param(
[string]$url,
[string]$port=21,
[string]$user,
[string]$pass,
[string]$localFile,
[string]$RemotePath
)
$ftp = new-object -com Primalscript.FTPTransfer;
$ftp.Passive = 1;
$ftp.port = $port;
if($ftp.Connect($url,$user,$pass) -eq 0){
"Can't connect. " + $ftp.Status;
} else {
if($ftp.Put($RemotePath,$localFile) -eq 1){
"success upload";
} else {
"Error uploading file. " +$ftp.Status;
}
$ftp.Disconnect();
}
}
Usage:
Get-FTPFile <ftpUrl> <port> <username> <password> <localUploadFile> <RemoteUploadPath>
##############
I'm maybe missing something, but I noticed when using the Put-FTPFile that even if the upload was successful, Put() returns 1 (error???) instead of 0. I commented about it in Spain's website FTP tool page.
UPDATE 09/19/2007:
Here's Don Jones reply:
0 is bad, 1 is good news.
1 = success (C++ TRUE)
0 = failure (C++ FALSE)
After all, I did miss something :-)
Thanks Don.
Posted by Shay Levy at 9/13/2007 04:06:00 PM 13 comments
Labels: automate, ftp, PowerShell
Posted by Shay Levy at 9/12/2007 02:28:00 PM 0 comments
Labels: PowerShell, remoting
Scott Hanselman recorded another PowerShell introduction video on dnrTV!.
In the video you can see how to install and use MoW's super cool PowerTab and some other stuff like loading DLL and .NET classes along with basic PowerShell.
The video can be downloaded (~180MB).
Posted by Shay Levy at 9/12/2007 02:03:00 PM 0 comments
Labels: PowerShell, video
I released this scripts about a week ago, in respond to a discussion in the Windows PowerShell news group. The goal is to read a script content and replace all command-let names to there alias's and vice versa.
The two functions below takes a script file path as the argument and convert it respectively.
The first function, "Convert-CmdletToAlias", is more *secure* to use as it search for all cmdlet names (which doesn't contain non special chars) and replaces each one it finds with the first corresponding alias (there can be more than one).
The second function, "Convert-AliasToCmdlet", is more *dangerous*. Richard Siddaway reported that there is one major draw back. If you used the "%" sign as an alias for the foreach-object cmdlet, things may go wrong and behave unexpectedly. The reason is that "%" is also the symbol for modulo.
Keith hill suggested to parse the script before the final conversion. Didn't find a way so far so any suggestions/modifications are welcomed.
#### EDIT (11/01/07) #####
Check out Keith's post on parsing the script for errors prior to executing it
PowerShell QuickTip: Preparsing Scripts to Check for Syntax Errors
#########################
Anyway, the conversion is not written back to the script file and only displayed back to the console. If you decide to use the functions, make sure to create a backup copy of the original scripts and always test the final converted script before using it in production.
Usage:
Convert-CmdletToAlias <filepath.ps1>
Convert-AliasToCmdlet <filepath.ps1>
###############################
function Convert-CmdletToAlias{
param([string]$filepath)
if(!(test-path $filepath)){
write-warning "Error: File doesnt exist";
break;
}
$src = [System.IO.File]::ReadAllText($filepath);
$cmdlets = get-command | where {$_.CommandType -eq "Cmdlet"} | select name
$cmdlets | foreach {
$cmd = $_.name;
if($src -match "\b$cmd\b") {
$alias = @(get-alias | where {$_.definition -eq $cmd});
$src=$src -replace($cmd,$alias[0]);
}
}
$src;
}
#####################
function Convert-AliasToCmdlet{
param([string]$filepath)
if(!(test-path $filepath)) {
write-warning "Error: File doesnt exist";
break;
}
#cls
$src = [System.IO.File]::ReadAllText($filepath);
$aliases = get-alias | select Name,Definition
$aliases | foreach {
trap{write-host "ERROR cant process alias: <$alias> -f red -b black"; continue}
$alias = @($_)[0];
$regex=[regex]::escape($alias.name);
# for some reason this is not working "\b" +$regex +"\b"
if($src -match "\s" +$regex +"\s"){
$src=$src -replace($regex,$alias.definition);
}
}
$src;
}
Posted by Shay Levy at 9/09/2007 10:04:00 AM 0 comments
Labels: convert, functions, PowerShell
There are two major differences between functions and filters in PowerShell:
Both procedures can be used in pipelines.
When a function is used, the special variable $input is available to process through the input collection. For filters, the special variable $_ contains the current pipeline object.
Filter, as its name suggests will pass on only certain objects down the pipeline.
To demonstrate it, here are two filters to help where IP address are involved.
I have a text file were each line contains an IP address.
###### ip.txt #####
10.0.0.138
1.1.1.1111
192.168.2.1
10.0.0.101
10.0.0.102
10.0.0.103
###### EOF #######
I want to get the content of this file and operate ONLY on live hosts.
The first filter will validate the IP address and stream down the pipe only valid ones.
The second filter will ping each valid host passed and test its availability,.
The third, ForEach cmdlet, will run some code on the filtered IPs.
filter Validate-IPAddress {
trap{write-host "$_ <$input>" -f red -b black; continue;}
[System.Net.IPAddress]::Parse($_).IPAddressToString;
}
filter Ping-Host{
begin{
# instantiate once for all piped objects
$ping = new-object System.Net.NetworkInformation.Ping;
}
process{
# ping test
$stat=$ping.Send($_).status;
if($stat -ne "Success"){write-warning "$_ is not available <$stat>";}
else {$_;}
}
}
# cat = Get-Content
# -f = foreground
# -b = background
cat ip.txt | Validate-IPAddress | Ping-Host | foreach {
write-host "$_ is ALIVE, run some code" -f green -b black
}
# colored output , just for demonstrating
10.0.0.138 is ALIVE, run some code
An invalid IP address was specified. <1.1.1.1111>
WARNING: 192.168.2.1 is not available <TimedOut>
10.0.0.101 is ALIVE, run some code
WARNING: 10.0.0.102 is not available <TimedOut>
10.0.0.103 is ALIVE, run some code
################################################
Here's the filters final version. You can see that only validated IPs and live hosts are processed.
filter Validate-IPAddress {
trap{continue;}
[System.Net.IPAddress]::Parse($_).IPAddressToString;
}
filter Ping-Host{
begin{ $ping = new-object System.Net.NetworkInformation.Ping; }
process{ if($ping.Send($_).status -eq "Success") {$_;} }
}
cat ip.txt | Validate-IPAddress | Ping-Host | foreach {
write-host "$_ is ALIVE, run some code" -f green -b black
}
# output
10.0.0.138 is ALIVE, run some code
10.0.0.101 is ALIVE, run some code
10.0.0.103 is ALIVE, run some code
Posted by Shay Levy at 9/07/2007 05:34:00 PM 2 comments
Labels: filters, functions, ip, PowerShell, validation
Does the installation tampers PowerShell core files? Why? What's wrong?
I restarted PowerShell and the error showed up again, Hmm.. I need to find out what went wrong, which cmdlet deals with execution restrictions...ahhhh...hmmm...right...
Get-ExecutionPolicy! So I run it:
PS C:\Scripts> Get-ExecutionPolicy
AllSigned
It seems that the SDK setup process reverts PowerShell excecution policy back to its default "out-of-the-box" state - AllSigned, which means only scripts that were digitally signed can execute.
PS C:\Scripts> Set-ExecutionPolicy RemoteSigned
PS C:\Scripts>
Restarted, and the message has gone.
Posted by Shay Levy at 9/06/2007 11:26:00 PM 0 comments
Labels: PowerShell
You can play sounds in PowerShell in different ways and integrate them in your scripts to provide some kind of indication. One way is with write-host. You can pass the escape sequence `a to write host. `a is the Alert corresponding special character:
# play a screech sound (through the internal speaker, not useful on laptops)
write-host "`a";
Another way is with a .NET class (.wav format).
# play the file once
$sound = new-Object System.Media.SoundPlayer;
$sound.SoundLocation="c:\WINDOWS\Media\notify.wav";
$sound.Play();
Note that the following process is asynchronously, meaning that the script wont hang until sound has finished playing. This example plays the file repeatedly until a condition met
$sound = new-Object System.Media.SoundPlayer;
$sound.SoundLocation="c:\WINDOWS\Media\notify.wav";
$sound.PlayLooping();
$flag=$false;
1..10 | foreach {
if($_ -gt 5){$flag=$true} else{sleep -s 1}
if($flag) { $sound.Stop() }
}
write-host "Done";
There is another class called System.Media.SystemSounds. Pipe the class to get-member to reflect its static members:
PS C:\Scripts> [System.Media.SystemSounds] | gm -static
TypeName: System.Media.SystemSounds
Name MemberType Definition
---- ---------- ----------
(...)
Asterisk Property static System.Media.SystemSound Asterisk {get;}
Beep Property static System.Media.SystemSound Beep {get;}
Exclamation Property static System.Media.SystemSound Exclamation {get;}
Hand Property static System.Media.SystemSound Hand {get;}
Question Property static System.Media.SystemSound Question {get;}
Each property emitted represents a system sound type. Now, Pipe each property to get-member
PS C:\Scripts> [System.Media.SystemSounds]::Asterisk | gm
TypeName: System.Media.SystemSound
Name MemberType Definition
---- ---------- ----------
(...)
Play Method System.Void Play()
ToString Method System.String ToString()
Great, we can use the Play method,test each line in your console:
[System.Media.SystemSounds]::Asterisk.Play();
[System.Media.SystemSounds]::Beep.Play();
[System.Media.SystemSounds]::Exclamation.Play();
[System.Media.SystemSounds]::Hand.Play();
[System.Media.SystemSounds]::Question.Play();
Finally, here is a one-liner for the job. It Utilizes the SoundPlayer(String) Constructor. It Initializes a new instance of the SoundPlayer class, and attaches the specified .wav file.
(new-object Media.SoundPlayer "C:\WINDOWS\Media\notify.wav").play();
Posted by Shay Levy at 9/06/2007 02:36:00 PM 6 comments
Labels: one-liner, PowerShell, sound
Three articles on remoting published on The Code Project website.
As Jeffrey Snover stated on his last interview with SearchWinIT.com ('remoting' will be built into the heart of the PowerShell engine).
At the meantime here are some others techniques in .NET.
A simple .NET Remoting example in three parts: the interface, the server, and the client. The client asks for your name, then sends it to the server. The server returns a personal greeting.
Remote Administration Tool (RAT) in C# - Part 3 (Reverse Connection Shell)
How to access server behind a gateway firewall using reverse connection shell technique
RemoteConsole
Remote console that allows to send commands to remote computer located in the Internet.
All articles includes source file in C#
Posted by Shay Levy at 9/06/2007 10:55:00 AM 3 comments
There is an excellent six part article walk through on Database Journal website by Muthusamy Anantha Kumar (aka The MAK) on PowerShell and SQL Server 2005 SMO. Everything you need to know in order to operate on SQL. The first parts introducing PowerShell concepts and some cmdlets to work with. From part III and on, there are fine examples on connecting to SQL server, collecting server information from different servers, create and backup databases and a lot more. A must read!
Check it out:
PowerShell and SQL Server 2005 SMO – Part I
PowerShell and SQL Server 2005 SMO – Part II
PowerShell and SQL Server 2005 SMO – Part III
PowerShell and SQL Server 2005 SMO – Part IV
PowerShell and SQL Server 2005 SMO – Part V
PowerShell and SQL Server 2005 SMO – Part VI
Posted by Shay Levy at 9/05/2007 12:18:00 PM 2 comments
Labels: PowerShell, SMO, SQL
Thanks to Shahar Gvirtz for recording this 30 minutes webcast. Now the Israeli audience have a first glimpse (in Hebrew) to the power of the shell. I saw it and learned a few things. Cheers!.
Shahar explains the following topics:
You can download the webcast from here.
Posted by Shay Levy at 9/03/2007 10:08:00 PM 8 comments
Labels: hebrew, PowerShell
Sounds impossible, right... WRONNNG.
I saw this on a hebrew security/hacking blog by Guy Mizrahi: Zull, A Hacker's diary. This is the starting paragraph of the original news:
"The first time Scott Lunsford offered to hack into a nuclear power station, he was told it would be impossible. There was no way, the plant's owners claimed, that their critical components could be accessed from the Internet. Lunsford, a researcher for IBM's Internet Security Systems, found otherwise."
I wonder what other things we dont know about? One day we all will vanish in a click of a key|mouse|button and wont even notice. Read the rest on Forbes website.
Posted by Shay Levy at 9/02/2007 10:39:00 PM 0 comments
O'Reilly offers parts of the book for online reading (check the appendixes starting at Regular Expression Reference and downward).
Lets start cooking!
Posted by Shay Levy at 9/02/2007 08:48:00 PM 0 comments
Labels: books, PowerShell
By nature, Exchange 2007 is strongly integrated with Active Directory (AD) and as such, it exposes new types to interact with AD. A lot of the user object properties across all the AD forest are made available through the Get-User command-let.
If you need to pass strings as "DistinguishedName" this function can help you validate them.
function Validate-DistinguishedName{
param([string]$dn)
[Microsoft.Exchange.Data.Directory.ADObjectId]::IsValidDistinguishedName($dn)
}
>> Validate-DistinguishedName "CN=Administrator,CN=Users,DC=homelab,DC=loc"
True
>> Validate-DistinguishedName "CN=Administrator,CN=Users,DC=homelab"
True
>> Validate-DistinguishedName "Shay@homelab.loc"
False
>> Validate-DistinguishedName "AB=CD,EF= G H"
True
Posted by Shay Levy at 9/01/2007 10:19:00 PM 0 comments
Labels: Exchange, PowerShell, validation
The other day I was looking for an option to colorize certain rows when using Format-* cmdlets. None of the cmdlets (including out-host, out-default) gives you the option to do that unless you define a custom PowerShell format/view file (ps1xml). I wish the Format-* cmdlets had a scriptblock parameter to which I can pass a piece of code that will colorize certain rows based on a criteria (maybe I'll fill a suggestion on Microsoft connect).
The major drawback of this custom format files is that you have to build them (requires some experience) for each type you want to print out, loading them into your environment via Update-FormatData and then executing them with the -view parameter of the format-table or format-custom cmdlets.
This method involves too much configuration and I was looking for a way to make the process more portable, depend less and operatable on any given command-let, so I can easily use it on any PowerShell machine or script.
Lets say you want to colorize all text file names that begins with "s".
PS C:\Scripts> Get-ChildItem $env:WINDIR *.txt
Directory: Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 7/28/2007 3:25 PM 8184 ModemLog_Agere Systems HDA Modem.txt
-a--- 6/21/2007 11:43 AM 38280 ModemLog_HTC USB Modem.txt
-a--- 3/5/2007 6:17 PM 3075 OEWABLog.txt
-a--- 8/30/2007 10:51 AM 32622 SchedLgU.Txt
-a--- 3/1/2007 1:56 AM 1012440 setuplog.txt
-a--- 7/31/2006 7:41 PM 726072 SIGVERIF.TXT
Get-ChildItem $env:WINDIR *.txt | foreach {
if($_.name -match "^s.*"){
write-host $_ -ForegroundColor "green";
} else {
write-host $_ -ForegroundColor "gray"
}
}
ModemLog_Agere Systems HDA Modem.txt
ModemLog_HTC USB Modem.txt
OEWABLog.txt
SchedLgU.Txt
setuplog.txt
SIGVERIF.TXT
As you can see, the foreach output contains only file names, no table headers nor additional property columns. It sure doesn't look like Format-Table's output.
For about an hour or so I was struggling with PowerShell commands and made a lot of experiments. Finally, I was able to construct a filter. The filter accepts three parameters.
Color, property name to match against and a regular expression pattern.
Surprisingly, the solution was pretty simple. Without the filter the code looks like:
Get-ChildItem $env:WINDIR *.txt | foreach {
if($_.name -match "^s.*"){
[console]::ForegroundColor="green"; $_;
} else {
[console]::ForegroundColor="white"; $_;
}
}
Well, here's the code for the filter:
filter colorize-row{
param(
[string]$color="green",
[string]$prop="name",
[string]$regex=$(throw "must supply regular expression pattern")
)
# save current console colors
#$bgc=[console]::BackgroundColor;
$fgc=[console]::ForegroundColor;
if($_.$prop -match $regex){[console]::ForegroundColor=$color; $_}
else{[console]::ForegroundColor=$fgc; $_}
# revert to saved console colors
#[console]::BackgroundColor=$bgc;
[console]::ForegroundColor=$fgc;
}
# get all text files in c:\windows that begins with "s", use default filter color
PS C:\Scripts> Get-ChildItem $env:WINDIR *.txt | colorize-row -regex "^s.*"
Directory: Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS
Mode LastWriteTime Length Name
Now with get-process:
# get all processes which contains "svchost" in their names, colorize in yellow
PS C:\Scripts> get-process | colorize-row -regex "svchost" -prop "name" -color yellow
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
...
...
68 3 816 1264 14 0.02 524 sqlbrowser
311 9 34172 20632 1100 1.02 436 sqlservr
84 2 1064 1616 20 0.44 548 sqlwriter
1894 246 34804 38528 217 197.80 180 svchost
104 5 1772 2336 33 10.56 332 svchost
126 4 2616 2160 36 3.80 580 svchost
284 12 8124 5084 47 2.45 1152 svchost
75 3 2332 1468 31 0.06 1512 svchost
230 6 3464 2776 65 72.11 1640 svchost
428 14 2368 2852 40 15.72 1692 svchost
95 10 1796 1460 37 0.28 2696 svchost
807 0 0 244 2 359.94 4 System
207 7 6608 9788 62 114.42 5828 TextPad
...
...
About PowerShell Console Colors
You can specify one of the following enumeration values to the filter -color parameter. To get all possible enumeration color names/values:
>> [consolecolor]::GetNames([System.ConsoleColor])
-or-
>> [consolecolor]::GetValues([System.ConsoleColor])
-or- better use the following to generate a table of color names and there corresponding numeric values:
$colors=@([enum]::GetNames([consolecolor]));
0..($colors.length-1) | select @{n="Name";e={[string][enum]::parse([ConsoleColor],$_)}}, @{n="Constant";e={$_}} | format-table -autosize
Name Constant
---- --------
Black 0
DarkBlue 1
DarkGreen 2
DarkCyan 3
DarkRed 4
DarkMagenta 5
DarkYellow 6
Gray 7
DarkGray 8
Blue 9
Green 10
Cyan 11
Red 12
Magenta 13
Yellow 14
White 15
Posted by Shay Levy at 9/01/2007 06:32:00 PM 1 comments
Labels: filter, PowerShell