PowerShell for Windows Admins


April 15, 2012  3:04 AM

Manning PowerShell book offer

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Manning are offering 50% off PowerShell in Practice using code dotd0415cc in the promotional code box at www.manning.com

 

The same code gets you 50% off PowerShell and WMI AND Advanced PowerShell

 

This is FOR TODAY ONLY

April 14, 2012  9:34 AM

Scripting Games 2012 comments: #8 Beginners Event 4

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/05/2012-scripting-games-beginner-event-4-compare-two-folders.aspx

Makes sure you run the script provided with the event to get the right folders. The script randomly removes 1 file from each folder – sneaky.

After last nights head scratching this one is much simpler. It’s solvable with one line of PowerShell.

For this one we have a data folder and a back up folder we need to compare. 

Requirements:

  • Content of backup folder is same as data folder
  • Only compare file names
  • Show files in one folder that don’t exist in other
  • Return file information objects as result of comparison
  • Don’t need complicated script
  • Unnecessary complexity costs points
  • Extra points for native PowerShell cmdlets and code simplicity

We need to deal with file names – so that implies Get-ChildItem.  We can see the contents of the files:

Get-ChildItem -Path c:\1
Get-ChildItem -Path c:\2

Do we have anything to do a compare?

Get-Command *compare*

shows we have compare-object.

If you haven’t seen this before – read the help.

In fact as a general rule read the help for all cmdlets before using in the games just to make sure you understand them and you’ve picked up on all the extras that might help you.

PS> get-help Compare-Object

SYNOPSIS
    Compares two sets of objects.

SYNTAX
    Compare-Object [-ReferenceObject] <PSObject[]> [-DifferenceObject] <PSObject[]>
   [-CaseSensitive] [-Culture <string>  ] [-ExcludeDifferent] [-IncludeEqual] [-PassThru]
   [-Property <Object[]>] [-SyncWindow <int>] [<CommonParameters>]

DESCRIPTION
    The Compare-Object cmdlet compares two sets of objects. One set of objects is the Reference set,
    and the other set is the Difference set.

    The result of the comparison indicates whether a property value appeared only in the object from
    the Reference set (indicated by the <= symbol), only in the object from the Difference set
   (indicated by the => symbol) or, if the IncludeEqual parameter is specified, in both objects
   (indicated by the == symbol).

 

This means we need to treat the folder contents as objects. PowerShell has a very clever trick we can use here. We put the

Get-ChildItem -Path c:\1
Get-ChildItem -Path c:\2

in parentheses which tells PowerShell to treat them as objects and we can use these directly as input to compare-object.

PS> Compare-Object -ReferenceObject (Get-ChildItem -Path c:\1) -DifferenceObject (Get-ChildItem -Path c:\2)

InputObject                                                 SideIndicator
———–                                                 ————-
13.txt                                                      =>
10.txt                                                      <=

That should make sense now you can see what I mean. The alternative would be to do this

PS> $list1 = Get-ChildItem -Path c:\1
PS> $list2 = Get-ChildItem -Path c:\2
PS> Compare-Object -ReferenceObject $list1 -DifferenceObject $list2

InputObject                                                 SideIndicator
———–                                                 ————-
13.txt                                                      =>
10.txt                                                      <=

which involves more typing and depending on how you define complexity is more complex.

We can do the compare but we need to restrict the comparison to file names – so we need the –Property parameter.  This is why you need to read the help file.

Compare-Object -ReferenceObject (Get-ChildItem -Path c:\1) -DifferenceObject (Get-ChildItem -Path c:\2)  -Property Name

This still produces the output above and we were told to output file objects. Once more into the help file and we find –Passthru which “Passes the objects that differed to the pipeline. By default, this cmdlet does not generate any output.”

PS> Compare-Object -ReferenceObject (Get-ChildItem -Path c:\1) -DifferenceObject (Get-ChildItem -Path c:\2)  -Property Name -PassThru

    Directory: C:\2

Mode                LastWriteTime     Length Name
—-                ————-     —— —-
-a—        14/04/2012     15:54          0 13.txt

    Directory: C:\1

Mode                LastWriteTime     Length Name
—-                ————-     —— —-
-a—        14/04/2012     15:54          0 10.txt

That solves the problem and is as simple as it gets. Oh you want aliases as well.

compare -R (ls c:\1) -Di (ls c:\2)  -Pr Name –Pa

And that is why I don’t like aliases because it is no where near as obvious what I am doing.


April 13, 2012  3:38 PM

Scripting Games 2012 comments: #7 update on beginners event 3

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

It was bugging me that I couldn’t get try-catch work on the folder where I didn’t have permissions. Finally thought of the reason – set the ErrorAction to Ignore. Heres the revised script

[CmdletBinding()]            
param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
Write-Verbose "Test path is valid"            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   Write-Verbose "Create Folder"            
   try {            
     $rt = New-Item -Path $path -ItemType Directory -ErrorAction Ignore            
   }            
   catch {            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
                 
     Write-Debug $path            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Write-Verbose "Write Process data"            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)            
            
<# 
.SYNOPSIS
Creates a folder to hold a file containing process data

.DESCRIPTION
A folder - by default "C:\2012sg\event3" - is created and a file called
"Process3.txt" by default is created holding process data. The process 
name and id is used to fill the file. 

Any existing file is over written

If the folder can't be created in the root of C: it is created in
$env:HOMEDRIVE\$env:HOMEPATH i.e. the users home drive

.PARAMETER  path

Path of the folder we want to create
Default is "C:\2012sg\event3"

.PARAMETER  file

File name of file to hold process data
Default is "Process3.txt"

.EXAMPLE
.\beg3.ps1

Runs script with default values

.EXAMPLE
.\beg3.ps1 -path "c:\testbeg3\event3" -file "testP3.txt"

Runs script with values for path and file supplied

.EXAMPLE
.\beg3.ps1 -Verbose

Runs script with verbose output

#>


April 13, 2012  2:52 PM

Scripting games 2012 comments: #6 Beginners event 3

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Onto event 3 http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/04/2012-scripting-games-beginner-event-3-create-a-file-in-a-folder.aspx where we have to create a file in a folder. Simple – well yes but not with the twists the Scripting Guy throws at you.

The requirements:

  • save file in c:\2012sg\event3
  • file contains process name and id running on local machine
  • file must be called Process3.txt
  • if folder doesn’t exist must create it
  • if don’t have permissions to root of c: create it somewhere where you do have permissions
  • script should not generate errors that are ignored
  • script doesn’t need command line parameters but get extra points for including them
  • if include command line parameters should include comment based help and sample of using script

Lets start with the file contents. We can find the process information like this

Get-Process

Use a select to restrict the data to the two properties we want
Get-Process | select Name, Id

That looks ugly so lets bring the two columns together so that it’s easier on the eye.
Get-Process | Format-Table Name, Id  -a

The PowerShell object police will be screaming at this point because I’m not outputting objects BUT the data is going to end up in a file so it doesn’t matter.

Now we need to get the data into the file

PS> Get-Process | Format-Table Name, Id  -a | Out-File -FilePath Process3.txt
PS> Get-Content Process3.txt

Name                      Id
—-                      —
armsvc                  1764
BingApp                 3216
BingBar                 3764
bingsurrogate           2692

etc

Even kept the case on the file name!

So we can create a file with the data we need. What about the folder?

It has to be a specific path and if the path doesn’t exist we have to create it. Need to test for existence and create folder if it doesn’t exist.

The trick here is to test for the whole folder path. Ignore the file we are going to create that anywhere. Don’t test for each element in the path – if  c:\2012sg\event3 exists then c:\2012 MUST exist. We don’t care about the file because it will be over written if it exists.

There are a number of cmdlets in PowerShell for working with paths

PS> Get-Command *path

CommandType     Name
———–     —-
Cmdlet          Convert-Path
Cmdlet          Join-Path
Cmdlet          Resolve-Path
Cmdlet          Split-Path
Cmdlet          Test-Path

We want test-path. If the path DOESN’T exist we need to create it. To get the neatest code we test on the negative of its existence and create the folder

$path = "C:\2012sg\event3"

if (-not (Test-Path -Path $path)){
  New-Item -Path "C:\2012sg\event3" -ItemType Directory
}

Test-Path -Path $path

This will return True when we test the second time. The folder information is written to screen

    Directory: C:\2012sg

Mode                LastWriteTime     Length  Name                              
—-                ————-     ——  —-

d—-         13/04/2012     19:47            event3                      

We can suppress this by adding Out-Null onto the New-Item line

New-Item -Path "C:\2012sg\event3" -ItemType Directory | Out-Null

At this time we have the basis of our entry

if (Test-Path -Path C:\2012sg){            
  Remove-Item -Path "C:\2012sg" -Recurse -Force             
}            
            
$path = "C:\2012sg\event3"            
$file = "Process3.txt"            
            
if (-not (Test-Path -Path $path)){            
  New-Item -Path "C:\2012sg\event3" -ItemType Directory |             
  Out-Null            
}            
            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

The first test-path deletes the folder AND contents if it exists. This is useful when testing to ensure you always end up testing the script properly but don’t forget to remove it at some stage so you can test that the script works if the folder exists!

I used Join-Path to create the full path to the file – could have done “$path\$file” or even worse $path + “\” + $file

That has dealt with  the first four objectives. Now what if we don’t have permissions to the root of C:

I’m not messing with the permissions on my C: drive but I’ve created a test folder and denied the permissions to create folders and files – typical permissions

This is what happens

PS> New-Item -Path c:\testperm -Name Myfolder -ItemType Directory

New-Item : Access to the path ‘Myfolder’ is denied.

At line:1 char:9

+ New-Item <<<<  -Path c:\testperm -Name Myfolder -ItemType Directory

    + CategoryInfo          : PermissionDenied: (C:\testperm\Myfolder:String) [New-Item], UnauthorizedAccessException

    + FullyQualifiedErrorId : CreateDirectoryUnauthorizedAccessError,Microsoft.PowerShell.Commands.NewItemCommand

Now we could use Get-Acl to test permissions

PS> Get-Acl -Path c:\testperm | select -ExpandProperty Access |

Format-Table IdentityReference, FileSystemRights, AccessControlType -AutoSize

IdentityReference                           FileSystemRights AccessControlType

—————–                           —————- —————–

BUILTIN\Administrators               CreateFiles, AppendData              Deny

BUILTIN\Administrators                           FullControl             Allow

BUILTIN\Administrators                             268435456             Allow

NT AUTHORITY\SYSTEM                              FullControl             Allow

NT AUTHORITY\SYSTEM                                268435456             Allow

BUILTIN\Users                    ReadAndExecute, Synchronize             Allow

NT AUTHORITY\Authenticated Users         Modify, Synchronize             Allow

NT AUTHORITY\Authenticated Users                  -536805376             Allow

But in reality we don’t know what permissions have been set so potentially we would have to test a lot of permissions. Not easy to get right and we don’t want to generate errors that are ignored.

We also need somewhere to put the file if we can’t get to C:\

You might think that this would work

path = "C:\Testperm\2012sg\event3"

$file = "Process3.txt"

if (-not (Test-Path -Path $path)){

  try {

    New-Item -Path $path -ItemType Directory |

    Out-Null

  }

  catch {

    write-host "oops"

  } 
}

Get-Process |

Format-Table Name, Id  -a |

Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

But it doesn’t because the error message thrown by New-Item isn’t a .NET exception. Its a message to say that the task hasn’t completed

Lets try this

$path = "C:\Testperm\2012sg\event3"

if (-not (Test-Path -Path $path)){

   $rt = New-Item -Path $path -ItemType Directory

}

$rt

$path = "C:\2012sg\event3"

if (-not (Test-Path -Path $path)){

   $rt = New-Item -Path $path -ItemType Directory

}

$rt

Which captures the two return messages

New-Item : Access to the path ‘event3′ is denied.

At line:4 char:18

+    $rt = New-Item <<<<  -Path $path -ItemType Directory

    + CategoryInfo          : PermissionDenied: (C:\Testperm\2012sg\event3:String) [New-Item], UnauthorizedAccessException

    + FullyQualifiedErrorId : CreateDirectoryUnauthorizedAccessError,Microsoft.PowerShell.Commands.NewItemCommand

 


    Directory: C:\2012sg


Mode                LastWriteTime     Length Name   
—-                ————-     —— —-  
d—-        13/04/2012     20:29            event3   

Working with either of these isn’t going to be easy BUT if the directory doesn’t create we get a error which we can access through the $error automatic variable

$path = "C:\2012sg\event3"            
$file = "Process3.txt"            
            
if (-not (Test-Path -Path $path)){            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

Set the folder creation to silently continue and test the last error message for part of the folder path – if its there something has gone wrong so we swing to creating the folder where we will have permissions – our home drive. A bit of manipulation with the path cmdlets and we have a new path where we can create the folder

get-process can then be used as before

That takes care of the folder creation if no permissions and the error messages being unhandled

At the moment the paths are hard coded but if we want to change then using  command line parameters is the best bet. Keep the current as defaults.

 

param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

We can use it like this to use the defaults

.\beg3.ps1

or like this to put the file somewhere else

.\beg3.ps1 -path "c:\testbeg3\event3" -file "testP3.txt"

Lets add some comments  – don’t use # – use write-debug and write-verbose

[CmdletBinding()]            
param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
Write-Verbose "Test path is valid"            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   Write-Verbose "Create Folder"            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
                 
     Write-Debug $path            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Write-Verbose "Write Process data"            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

Which means we can do this – all for adding [CmdletBinding()] to the top of the script

PS> .\beg3.ps1 -Verbose

VERBOSE: Test path is valid

VERBOSE: Write Process data

last job is add the help

[CmdletBinding()]            
param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
Write-Verbose "Test path is valid"            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   Write-Verbose "Create Folder"            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
                 
     Write-Debug $path            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Write-Verbose "Write Process data"            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)            
            
<# 
.SYNOPSIS
Creates a folder to hold a file containing process data

.DESCRIPTION
A folder - by default "C:\2012sg\event3" - is created and a file called
"Process3.txt" by default is created holding process data. The process 
name and id is used to fill the file. 

Any existing file is over written

If the folder can't be created in the root of C: it is created in
$env:HOMEDRIVE\$env:HOMEPATH i.e. the users home drive

.PARAMETER  path

Path of the folder we want to create
Default is "C:\2012sg\event3"

.PARAMETER  file

File name of file to hold process data
Default is "Process3.txt"

.EXAMPLE
.\beg3.ps1

Runs script with default values

.EXAMPLE
.\beg3.ps1 -path "c:\testbeg3\event3" -file "testP3.txt"

Runs script with values for path and file supplied

.EXAMPLE
.\beg3.ps1 -Verbose

Runs script with verbose output

#>

I always add help at the end. check about_comment_based help for further details


April 13, 2012  7:47 AM

UK PowerShell Group–24 April 2012

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

This months meeting is the first of two looking at the new PowerShell functionality in Windows Server 8. Yes, there is so much it will take two sessions!


When: Tuesday, Apr 24, 2012 7:30 PM (BST)


Where: Virtual

*~*~*~*~*~*~*~*~*~*

This is the first of two meetings looking at the new PowerShell functionality available in Windows Server 8

Notes


Richard Siddaway has invited you to attend an online meeting using Live Meeting.
Join the meeting.
Audio Information
Computer Audio
To use computer audio, you need speakers and microphone, or a headset.
First Time Users:
To save time before the meeting, check your system to make sure it is ready to use Microsoft Office Live Meeting.
Troubleshooting
Unable to join the meeting? Follow these steps:

  1. Copy this address and paste it into your web browser:
    https://www.livemeeting.com/cc/usergroups/join
  2. Copy and paste the required information:
    Meeting ID: S5NC59
    Entry Code: c^]”2]44W
    Location: https://www.livemeeting.com/cc/usergroups

If you still cannot enter the meeting, contact support

Notice
Microsoft Office Live Meeting can be used to record meetings. By participating in this meeting, you agree that your communications may be monitored or recorded at any time during the meeting.


April 13, 2012  5:20 AM

Scripting Games 2012 comments: #5 using WHERE

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

 

I have seen a few interesting variations on using where-object during the games. This is normally aliased to where. You can use ? if you are fanatical about aliases but to me your PowerShell starts to become unreadable if its heavily aliased.

Consider get-process.  It returns a set of process objects. Where-Object is used to put a filter on those objects to cut down the amount of data on the pipeline –

PS> get-process | where {$_.Handles -gt 500}

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
    558       9     1576       3424    46             384 csrss
    660      11     1768       6816   102             452 csrss
   1396      38    45412      58896   262    40.69   2776 explorer
    622      23    43196      63204   309    30.76   2164 iexplore
   1000      17     4988      10440    36             508 lsass
    509      14    37992      43308   202     4.06   4688 powershell
    938      35    42864      27516   131            3620 SearchIndexer
    628      16    16392      15968    90             824 svchost
    803      18    65548      73660   160             864 svchost
   1369      34    22884      34012   200             900 svchost
    504      17     6616      11640    52            1056 svchost
    784       0       48        968     3               4 System
    669      50   109832      78000   400    17.99   1700 WindowsLiveWriter
    526      18    17084      24020   150     4.06    788 wlcomm
   1951     178   222312     217488   689    96.39   3872 wlmail

or

PS> get-process | where {$_.CPU -gt 50}

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
    124       8    28828      24996   120   131.10   2744 dwm
    485      23    14940      37444   202    52.49   3272 iexplore
    363      26    25180      60088   335    78.14   5336 WINWORD
   1951     178   222312     217488   689    96.42   3872 wlmail

We often need to combine filters. These can apply both filters or either filter.

Both filters are applied using the –and operator

PS> get-process | where {$_.CPU -gt 50 -and $_.Handles -gt 500 }

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
   1956     179   222332     217508   689    96.47   3872 wlmail

In the situation where you want either the CPU or the Handles filter to apply you use the –or operator

PS> get-process | where {$_.CPU -gt 50 -or $_.Handles -gt 500 }

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
    532       9     1576       3368    46             384 csrss
    651      11     1768       6816   103             452 csrss
    124       8    27804      23964   119   132.18   2744 dwm
   1403      38    46744      60284   263    41.53   2776 explorer
    619      23    43128      63196   308    30.76   2164 iexplore
    488      23    14940      37444   202    52.51   3272 iexplore
    992      17     4988      10440    36             508 lsass
    566      14    34704      40004   202     4.68   4688 powershell
    918      35    42860      27436   131            3620 SearchIndexer
    617      16    16364      15952    89             824 svchost
    807      18    65548      73684   160             864 svchost
   1371      34    22932      34048   201             900 svchost
    508      17     6616      11632    52            1056 svchost
    782       0       48        968     3               4 System
    672      50   103560      76708   400    32.85   1700 WindowsLiveWriter
    363      26    25180      60088   335    78.16   5336 WINWORD
    520      18    17060      24000   150     4.09    788 wlcomm
   1953     178   222332     217508   689    96.52   3872 wlmail

 

Notice the very different outputs!

One thing I have seen in the games is the use of cascading where

PS> get-process | where {$_.CPU -gt 50 } | where {$_.Handles -gt 500 }

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
   1953     178   222312     217496   689    96.52   3872 wlmail

logically this is equivalent of where {$_.CPU -gt 50 -and $_.Handles -gt 500 }

Is there any difference in speed

1..100 | foreach {
Measure-Command -Expression {get-process |
where {$_.CPU -gt 50 -and $_.Handles -gt 500 }}
} | Measure-Object -Average TotalMilliseconds

produces an answer of 153.92904 milliseconds

1..100 |
foreach {Measure-Command -Expression {get-process |
  where {$_.CPU -gt 50 } | where {$_.Handles -gt 500 }}} |
Measure-Object -Average TotalMilliseconds

gives 152.905894 milliseconds

Not a massive difference. But does the order of the where statements make a difference?

1..100 | foreach {
Measure-Command -Expression {get-process | 
  where {$_.Handles -gt 500 -and $_.CPU -gt 50 }}} |
Measure-Object -Average TotalMilliseconds

produces an answer of 51.495388

1..100 | foreach {
Measure-Command -Expression {get-process |
  where {$_.Handles -gt 500 | where {$_.CPU -gt 50 }  }}} |
Measure-Object -Average TotalMilliseconds

gives 37.23507 milliseconds

Filtering on Handles first produces a quicker result because it is a heavier filter – it produces less out put so the second filter has less work to do. I wasn’t expecting such a big difference between the use of –and & the cascading where statements though – that was a surprise.

So what have learnt:

  • where filter clauses can be combined with –and OR –or
  • where filter clauses can be cascaded in multiple where statements
  • putting the heaviest filter first speeds the process
  • cascading where statements can be quicker than using –and

Be careful with the last statement and test any code before committing to using cascading wheres Also test the order of your filters very carefully.


April 12, 2012  2:46 AM

Scripting Games 2012 comments: #4 Beginners Event 2

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Second event is to find running services that can be stopped

http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/03/2012-scripting-games-beginner-event-2-find-stoppable-running-services.aspx

Lets start with the requirements we can pick out of the event information:

  • Find all services that are running that will accept a Stop Command
  • Need to run remotely against trusted remote computers
  • Assume appropriate ports are open of firewalls
  • Don’t need a second computer
  • Don’t need to test or configure firewall – assume its done
  • Use simplest command that will work
  • Can use standard aliases – don’t have to
  • Don’t use non-standard aliases

So what do we have that can work with services

PS> Get-Command *service

CommandType     Name
———–     —-
Cmdlet          Get-Service
Cmdlet          Invoke-WebService
Cmdlet          New-Service
Cmdlet          Restart-Service
Cmdlet          Resume-Service
Cmdlet          Set-Service
Cmdlet          Start-Service
Cmdlet          Stop-Service
Cmdlet          Suspend-Service

Get-Service looks a likely contender and it has a computername parameter

PS> Get-Help Get-Service -Parameter c*

-ComputerName <string[]>
    Gets the services running on the specified computers. The default is the local computer.

    Type the NetBIOS name, an IP address, or a fully qualified domain name of a remote computer. To specify the local computer, type the computer name, a dot (.), or "localhost".

    This parameter does not rely on Windows PowerShell remoting. You can use the ComputerName parameter of Get-Service even if your computer is not configured to run remote commands.

    Required?                    false
    Position?                    named
    Default value                Localhost
    Accept pipeline input?       true (ByPropertyName)
    Accept wildcard characters?  false

Can we find out if services can be stopped

PS> get-service | Get-Member -MemberType property

   TypeName: System.ServiceProcess.ServiceController

Name                MemberType
—-                ———-
CanPauseAndContinue Property
CanShutdown         Property
CanStop             Property
Container           Property
DependentServices   Property
DisplayName         Property
MachineName         Property
ServiceHandle       Property
ServiceName         Property
ServicesDependedOn  Property
ServiceType         Property
Site                Property
Status              Property

Looks like the Canstop will work for us. Its a Boolean i.e. only returns True or False so we have a little trick. Normally when we want to filter we use a where statement

get-service | where {$_.Status -eq "Running"}

The only objects that are passed along the pipeline are the ones where the filter evaluates to TRUE. So you might think this would work

get-service | where {$_.Canstop -eq "True"}

but check out this

PS> $true -eq "False"
True

What’s happening is that $_.CanStop is a Boolean so triggers the filter – the “-eq ‘True’” bit is ignored.

If you want to test Booleans use PowerShell’s $true & $false

get-service | where {$_.Canstop -eq $true}

or better still because CanStop is a Boolean we can just do a simple test

get-service | where {$_.Canstop}

To use the computer name

Get-Service -ComputerName $env:COMPUTERNAME | where {$_.Canstop }

see http://msmvps.com/blogs/richardsiddaway/archive/2012/04/11/scripting-games-2012-comments-3-beginners-event-1.aspx for a discussion on accessing the local machine using a computer name

At this point you may be asking but the scenario says we have to return running services that can be stopped.  Look at the output when you use CanStop – all the statuses are running.

It can be tested like this

Compare-Object -ReferenceObject $(get-service | where {$_.Canstop}) -DifferenceObject $(get-service | where {$_.Status -eq "Running" -and $_.Canstop}) –IncludeEqual

You will get a bunch of answers like this

InputObject                                                          SideIndicator

———–                                                          ————-                                                            
System.ServiceProcess.ServiceController                                     ==     

Which means they match

What about remote machines

Get-Service -ComputerName "Win7", "Win7test", "webr201" |
where {$_.Canstop }

will work but we don’t know which service is returned from which machine however there is a machine name property available so we could do this

Get-Service -ComputerName "Win7", "Win7test", "webr201" |
where {$_.Canstop } |
select Status, Name, Displayname, MachineName

or if you want a nice report format

Get-Service -ComputerName "Win7", "Win7test", "webr201" |
where {$_.Canstop } |
sort MachineName, Name |
Format-Table -GroupBy MachineName –AutoSize

The alternative is to use PowerShell remoting

Invoke-Command -ComputerName "Win7", "Win7test", "webr201" -ScriptBlock {
Get-Service |
where {$_.Canstop } }

This automatically gives the computername so we can do this

Invoke-Command -ComputerName "Win7", "Win7test", "webr201" -ScriptBlock {
Get-Service |
where {$_.Canstop } } |
Format-Table -GroupBy PSComputerName –AutoSize

In PoweShell v3 the syntax can be simplified

Get-Service | where CanStop

When you have a single property to test you can drop the $_ and {}

This can be aliased to

gsv | ? canstop

which must be esoteric enough for the most fanatical of alias’ fans.

If you must the other versions can be aliased

gsv -cn "Win7", "Win7test", "webr201" |
? {$_.Canstop} |
select Status, Name, Displayname, MachineName

or

icm -Cn "Win7", "Win7test", "webr201" -Sc {
gsv |
? {$_.Canstop } } |
ft -G PSComputerName –A


April 11, 2012  7:47 AM

Scripting Games 2012 Comments: #3 Beginners Event 1

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I thought I’d publish a series on how I would go about solving these plus some comments and observations on the submitted scripts.

First event of the games

http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/02/the-2012-scripting-games-beginner-event-1-use-windows-powershell-to-identify-a-working-set-of-processes.aspx

Firstly pick out the salient points of the event

Need to:

  • Identify Top 10 processes consuming memory resources on each computer
  • Decided that working set is the property to measure
  • Don’t need a script – implies a single pipeline will be acceptable
  • Should be capable of running remotely – not required to have multiple computers available
  • Aliases are acceptable – not compulsory though (we’ll come back to that statement)
  • Should return an object
  • Should be able to write results to file (but not a requirement)
  • Computers are Windows 7 & Windows 2008 R2
  • Single AD domain
  • Remoting is enabled

What do we have that can work with processes:

PS> Get-Command *process

CommandType     Name
———–     —-
Cmdlet          Debug-Process
Cmdlet          Get-Process
Cmdlet          Start-Process
Cmdlet          Stop-Process
Cmdlet          Wait-Process

 

Get-Process should work for us but it returns a whole bunch of processes and none of them are labelled working set! If we need to be able to find the top 10 working set consumers then need to be able to sort on working set

Time to use Get-Member

PS> Get-Process | Get-Member w* -MemberType property

   TypeName: System.Diagnostics.Process

Name         MemberType Definition
—-         ———- ———-
WorkingSet   Property   System.Int32 WorkingSet {get;}
WorkingSet64 Property   System.Int64 WorkingSet64 {get;}

One thing to be aware of is that sometimes the PowerShell team alias property names to make life easier for us. You also need to be aware that the column WS (K ) is workingset recast to kb instead of bytes

PS> Get-Process | Get-Member w* -MemberType aliasproperty

   TypeName: System.Diagnostics.Process

Name MemberType    Definition
—- ———-    ———-
WS   AliasProperty WS = WorkingSet

 

So we have a choice –  Workingset or WS its alias. Also we have to assume that the boss meant workingset even on 64 bit machines where WorkingSet64 may be a better answer

So lets sort on workingset

Get-Process | sort Workingset

This gives us the processes sorted in ascending order of working set usage. At this point we could just select the last 10

Get-Process | sort Workingset | select -Last 10

but that still leaves the results in ascending order which isn’t the way we usually look at things. So lets turn it round

Get-Process | sort Workingset -Descending | select -First 10

This gives us the top 10 consumers of working set on the local machine.

I have used aliases for sort-object (sort) and select-object (select) for a couple of reasons:

  • its easier
  • its still simple to read
  • its less typing
  • its accepted convention

I have left the rest of the script as full names so that if I save as script I can still read it.  Aliases are great at the command line if, and only if, you can remember them. Its better to use the full name and get the job done compared to hunting for the alias because its interactive work and some one says use aliases at the prompt.

If you are learning PowerShell stick with the full names (tab completion is a big help) until you are confident with a cmdlet – then introduce using aliases BUT ONLY FOR INTERACTIVE WORK.

So we can get the result for the local machine.  What about writing to a file?

Couple of ways to do that

Get-Process | sort Workingset -Descending | select -First 10 | Out-File top10proc.txt

gives the results in a text file – viewable with

Get-Content top10proc.txt

or use a csv file

Get-Process | sort Workingset -Descending | select -First 10 | Export-Csv top10proc.csv -NoTypeInformation
Import-Csv top10proc.csv

But notice you get the process information back formatted as a list with ALL of the properties presented.

If you want to be very clever here use tee-object which sends objects to a file and along the pipeline

Get-Process | sort Workingset -Descending | select -First 10 | Tee-Object -FilePath top10proc.txt

You get the display on screen and the data saved in a file

What about remote systems?

All your machines are in a domain for which you are the admin so presumably you have suitable permissions across all systems. Plus all machines are Windows 7 or Windows 2008 R2 so have PowerShell v2 available AND remoting is enabled.

There are two ways to approach this:

  • use the –computername parameter on get-process
  • use PowerShell remoting – probably invoke-command because we are only visiting a machine once

When you use the computername parameter and attempt to access the local machine there are three ways of accessing it without typing the name

Get-Process -ComputerName . | sort Workingset -Descending | select -First 10
Get-Process -ComputerName localhost | sort Workingset -Descending | select -First 10
Get-Process -ComputerName $env:COMPUTERNAME | sort Workingset -Descending | select -First 10

The second one doesn’t work on Windows 7 but does work on Windows 2008 R2.  There are other cmdlets that have problems with localhost so I advise against using it. My preference is to use $env:COMPUTERNAME

You might try this

"Win7", "Win7Test", "WebR201" |
Get-Process |
sort Workingset -Descending |
select -First 10

Note its still one pipeline – just split across multiple lines to make reading easier

But you’ll get an error

Get-Process : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of
the parameters that take pipeline input.

The problem is that the computername will only take pipeline input based on property name – so it expects an object that will have a computername property.

PS> get-help Get-Process -Parameter computername

-ComputerName <string[]>
    Gets the processes running on the specified computers. The default is the local computer.

    Type the NetBIOS name, an IP address, or a fully qualified domain name of one or more computers. To specify the loc
    al computer, type the computer name, a dot (.), or "localhost".

    This parameter does not rely on Windows PowerShell remoting. You can use the ComputerName parameter of Get-Process
    even if your computer is not configured to run remote commands.

    Required?                    false
    Position?                    named
    Default value
    Accept pipeline input?       true (ByPropertyName)
    Accept wildcard characters?  false

You can get round that like this

Get-Process -ComputerName "Win7", "Win7Test", "WebR201" 
sort Workingset -Descending |
select -First 10

but you then hit the problem that you can’t differentiate the results from each machine.

You also need to have a number of services running on the remote machine including the Remote Registry service AND the firewall configured to allow remote management.

This is starting to get messy.

Remember that remoting was enabled across the domain. This means we can do this

Invoke-Command -ComputerName "Win7", "Win7Test", "WebR201"  -ScriptBlock {
Get-Process |
sort Workingset -Descending |
select -First 10 }

Still a single pipeline.  The big advantage is that we get a property – PSComputerName – tacked onto the output that enables us to decide on the source of the data. That should answer all of requirements in the event. The computer names could be fed to Invoke-Command through a file or pipeline.

Change the script to this

Invoke-Command -ComputerName "Win7", "Win7Test", "WebR201"  -ScriptBlock {
Get-Process |
sort Workingset -Descending |
select -First 10 } |
Format-Table -GroupBy PSComputerName –AutoSize

and you have a nicely formatted output. Push that to a file

Invoke-Command -ComputerName "Win7", "Win7Test", "WebR201"  -ScriptBlock {            
Get-Process |            
sort Workingset -Descending |             
select -First 10 } |            
Format-Table -GroupBy PSComputerName -AutoSize |            
Out-File top10proc.txt

and you have a report ready to give to the boss.

Jobs a goodun!

Oh you want aliases.  Well if you must

icm -Cn "Win7", "Win7Test", "WebR201"  -Sc {

ps |

sort WS -Des |

select -F 10 }

trying to alias or abbreviate parameters can be problematic because the abbreviation must be resolvable unambiguously. Unless you really know the parameters on a cmdlet it can be confusing and take extra time to get right – its why I never bother and just use tab completion!


April 10, 2012  12:56 PM

Using aliases and the myth of the one-liner

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

The beginners section of the scripting games can be often answered with one pipeline of PowerShell.

Notice that I stated one pipeline not one line

Since the early days of PowerShell there has been an almost mystical reverence  paid to the concept of the “one-liner” ie boiling the PowerShell script down to a single line. That worked as a concept in PowerShell v1 where all we had was the console. When PowerShell v2 appeared with the ISEit was time to ditch the one line concept and switch our attention to what is important – using the PowerShell pipeline.

The pipeline symbol acts as a line continuation character so in ISE or any other PowerShell aware editor/environment you can split your pipeline over multiple lines of text BUT STILL HAVE A SINGLE PIPELINE.

Its more readable and easier to work with especially when debugging.

With PowerShell v3 and the changes to ISE it is finally time to kill the myth of the one liner and actually be honest about what we are trying to achieve – the one pipeline.

In a similar vein there is a lot of talk about how we should use aliases at the command prompt.

Why?

With tab completion it is almost as quick to use the full name for cmdlets and parameters especially if you have to look up the alias. If you like them fine but I prefer not to use them so that what ever I experiment with at the prompt can be copied straight into scripts.


April 9, 2012  2:44 AM

Scripting Games 2012 Comments: #2 Read the help file

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I’m seeing a lot of scripts where code is used to perform an action that is available as a parameter on a cmdlet that is used earlier in the pipeline.

 

Read the help file for the cmdlets you are going to use to see how you can get PowerShell to do the work for you instead you having to create and test code


Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: