PowerShell for Windows Admins


July 9, 2014  1:29 PM

PowerShell Summit Europe 2014 – - update 4

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Registration will open next Tuesday (15th July) – that’s less than 1 week

July 7, 2014  12:45 PM

Finding a CIM class

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
CIM, WMI

I was investigating something on my disks and started to look at the partitions:

£> Get-CimInstance -ClassName Win32_Partition
Get-CimInstance : Invalid class
At line:1 char:1
+ Get-CimInstance -ClassName Win32_Partition
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : MetadataError: (root\cimv2:Win32_Partition:String) [Get-CimInstance], CimException
+ FullyQualifiedErrorId : HRESULT 0×80041010,Microsoft.Management.Infrastructure.CimCmdlets.GetCimInstanceCommand

OK so my memory isn’t what it was and I got the class name wrong. How to find the correct class?

£> Get-CimClass *Partition*
NameSpace: ROOT/cimv2

CimClassName
————
CIM_DiskPartition
Win32_DiskPartition
Win32_SystemPartitions
CIM_LogicalDiskBasedOnPartition
Win32_LogicalDiskToPartition
CIM_RealizesDiskPartition
Win32_DiskDriveToDiskPartition
Win32_PerfFormattedData_HvStats_…
Win32_PerfRawData_HvStats_HyperV…
Win32_PerfFormattedData_HvStats_…
Win32_PerfRawData_HvStats_HyperV…
Win32_PerfFormattedData_VidPerfP…
Win32_PerfRawData_VidPerfProvide…

 

I’ve truncated the display horizontally as not interested in methods & properties at this point
So the class I want is Win32_DiskPartition.

Get-CimClass is one of the biggest benefits from PowerShell 3.0


July 6, 2014  3:22 PM

CIM or WMI – - using methods

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
CIM, WMI

The CIM and WMI cmdlets both provide a way to use the methods on CIM classes namely Invoke-CimMethod and Invoke-WmiMethod. The cmdlets are very similar in operation.

$vol = Get-WmiObject -Class Win32_Volume -Filter “DriveLetter = ‘D:’”

Invoke-WmiMethod -InputObject $vol -Name Chkdsk -ArgumentList $false, $true, $true, $false, $false, $false

 

The argumenst list isn’t very informative – unless you know the class, read the documentation or investigate with Get-CimClass

 

Using the CIM cmdlets is a bit more informative as to what is going on.

$vol = Get-CimInstance -ClassName Win32_Volume -Filter “DriveLetter = ‘D:’”

Invoke-CimMethod -InputObject $vol -MethodName Chkdsk -Arguments @{FixErrors=$false; ForceDismount=$false; OkToRunAtB
ootUp = $false; RecoverBadSectors = $false; SkipFolderCycle = $true; VigorousIndexCheck = $true}

 

You present the arguments as a hash table – this means you can create the hash table and pass it to the method

$margs = @{
FixErrors=$false
ForceDismount=$false
OkToRunAtBootUp = $false
RecoverBadSectors = $false
SkipFolderCycle = $true
VigorousIndexCheck = $true
}
Invoke-CimMethod -InputObject $vol -MethodName Chkdsk -Arguments $margs

 

This also means that you can create a default set of values and manipulate them in your scripts very easily

 

Using Invoke-CimMethod involves more typing but I think that’s worth it for the clarity. Of course if you are going to be using the methods of class a lot then I’d recommend that you create a CDXML module from the class – but that’s a whole different set of articles.


July 6, 2014  8:50 AM

Workflows 7: checkpointing workflows

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Workflow

Consider this workflow

workflow chkpt1 {

Get-Process

foreach ($x in 1..20){
$x
}

}

 

It will dump out the process information then output the numbers 1 to 20.  Not a particularly enlightening workflow but it forms  a nice basis for demonstrating checkpoints.

A checkpoint saves the state and data in the workflow. If the workflow is suspended or interrupted the work flow can be restarted from the most recent checkpoint rather than a complete restart from the beginning.

 

Change the workflow to give a long running activity

workflow chkpt1 {

Get-Process
foreach ($x in 1..1000){
$x
}

}

Let the process data be displayed and then stop execution once the numbers start displaying – this is easiest if you use ISE. If you want to restart the workflow you have to start it right from the beginning.

Add a checkpoint to the workflow. This can be achieved in a number of ways.

Use the Checkpoint-Workflow activity to the workflow.

workflow chkpt1 {

Get-Process
Checkpoint-Workflow

foreach ($x in 1..1000){
$x
}

}

 

or use the –PSPersist parameter

workflow chkpt1 {

Get-Process -PSPersist
foreach ($x in 1..1000){
$x
}

}

 

I prefer to use Checkpoint-Workflow as it is more obvious to me when I review the workflow.

 

if you want to checkpoint your workflows – you have to start them as a job

 

chkpt1 –AsJob

 

Then shut down ISE

Open another PowerShell session with elevated privileges. Use Get-Job to see the suspended job.

View the data in the job

Receive-Job -Id 5 –Keep

 

Restart the job

Resume-Job -Id 5

 

Once the job finishes view the data and you’ll see the process data and the list of numbers.

Use Checkpoint-Workflow as many time as necessary to protect your data in long running workflows. A checkpoint is a good idea any time that it would be expensive to restart the whole process.


July 3, 2014  2:58 PM

and finally

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

If you’re old enough and seen UK TV you’ll recognise the title but this post is about using try – catch blocks.

Using try-catch this is a fairly normal construction

try {
Get-CimInstance -ClassName Win32_LogicalDisk  -ErrorAction Stop
}
catch {
Throw “something went wrong”
}

The number of commands within the try block should be minimised so that you stand a chance of knowing what you are going to catch.

if you introduce an error – for instance using the wrong classname

£> try {
Get-CimInstance -ClassName Win32_LogicalDrive  -ErrorAction Stop
}
catch {
Throw “something went wrong”
}
something went wrong
At line:5 char:2
+  Throw “something went wrong”
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (something went wrong:String) [], RuntimeException
+ FullyQualifiedErrorId : something went wrong

 

You can control what happens and even recover.

I don’t see the third element of a try-catch block being used much – that’s the finally block. The finally block will run irrespectively of whether the try block works or not. It’s your clean up block

Consider

£> try {
$cs = New-CimSession -ComputerName $env:COMPUTERNAME
Get-CimInstance -ClassName Win32_LogicalDrive  -CimSession $cs -ErrorAction Stop
}
catch {
Throw “something went wrong”
}
something went wrong
At line:6 char:2
+  Throw “something went wrong”
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (something went wrong:String) [], RuntimeException
+ FullyQualifiedErrorId : something went wrong
£> Get-CimSession
Id           : 1
Name         : CimSession1
InstanceId   : 68a4f534-d222-4e52-b6f9-ba65b1d57b3b
ComputerName : RSSURFACEPRO2
Protocol     : WSMAN

 

This try block fails but it leaves a CIM session hanging about. Not good practice. I know it will get cleaned up when you close the powershell session but that could be hours away. Better to clean up now.

£> try {
$cs = New-CimSession -ComputerName $env:COMPUTERNAME
Get-CimInstance -ClassName Win32_LogicalDrive  -CimSession $cs -ErrorAction Stop
}
catch {
Throw “something went wrong”
}
finally {
Remove-CimSession -CimSession $cs
}

Get-CimSession
something went wrong
At line:6 char:2
+  Throw “something went wrong”
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (something went wrong:String) [], RuntimeException
+ FullyQualifiedErrorId : something went wrong
no sign of a CIM session even if you try specifically.

 

£> Get-CimSession

£>

if you correct the code

try {
$cs = New-CimSession -ComputerName $env:COMPUTERNAME
Get-CimInstance -ClassName Win32_LogicalDisk  -CimSession $cs -ErrorAction Stop
}
catch {
Throw “something went wrong”
}
finally {
Remove-CimSession -CimSession $cs
}

Get-CimSession

 

You’ll find the the CIM session is still removed. A finally block will always run

When you are using try blocks think about your environment and use a finally block to clean up.


July 2, 2014  1:20 PM

CIM or WMI?

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
WMI

Working with WMI became a whole easier when PowerShell came on the scene. If you ever spent hours typing all of the Echo commands that were required with VBScript to produce output you’ll be aware of what I mean.  There are still a few awkward areas in the WMI cmdlets. One of the most awkward is date/time handling.

Consider looking for the last boot up time of your favourite computer:

£> Get-WmiObject -Class Win32_OperatingSystem | select -ExpandProperty LastBootUpTime
20140702083855.487133+060

 

That breaks down as year (2014), month (07), day (02), hour (08), minute (38), second (55) and fraction of a second (.487133). The +060 denotes the minutes offset from GMT (UTC if you prefer) – I’m in the UK on daylight saving time.  So, once you know how to read it the answer is understandable but not easy to work with.

The PowerShell team introduced a method on Get-WmiObject that will convert the date to a more understandable format:

 

£> $os = Get-WmiObject -Class Win32_OperatingSystem
£> $os.ConvertToDateTime($os.LastBootUpTime)

02 July 2014 08:38:55

 

You can also use the method in select-object or the format cmdlets by using a calculated field:

£> Get-WmiObject -Class Win32_OperatingSystem | Format-List PSComputerName, Caption, @{N=’BootTime’; E={$_.ConvertToDate
Time($_.LastBootUpTime)}}
PSComputerName : RSSURFACEPRO2
Caption        : Microsoft Windows 8.1 Pro
BootTime       : 02/07/2014 08:38:55

 

There is an easier way – use the CIM cmdlets:

£> Get-CimInstance -ClassName Win32_OperatingSystem | select -ExpandProperty LastBootUpTime

02 July 2014 08:38:55

 

The automatic date conversion is more than sufficient incentive for me to use Get-CimInstance in preference to Get-WmiObject.


July 1, 2014  2:32 PM

PowerShell Summit Europe 2014 – - update 3

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Registration for the Summit opens in 14 days – 15th July

If you need some help selling the idea of attending to your boss cheeck out this post http://richardspowershellblog.wordpress.com/2014/06/21/powershell-summit-europe-2014-reasons-to-attend-1-7/


June 30, 2014  12:55 PM

Workflows 6: suspending jobs

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Workflow

One of the great things about work flows is that you can stop and start them. A workflow can be stopped, on a temporary basis, by using the Suspend-Workflow activity.

workflow suspend1 {
Get-Service

Suspend-Workflow

Get-Process

}
suspend1

 

This will run the Get-Service activity – and produce output to the console. The workflow will suspend and automatically create a job. You will see output like this:

HasMoreData     : True
StatusMessage   :
Location        : localhost
StartParameters : {}
Command         : suspend1
JobStateInfo    : Suspended
Finished        : System.Threading.ManualResetEvent
InstanceId      : bbe0903d-1720-46da-a6dd-e0a927aa9e11
Id              : 8
Name            : Job8
ChildJobs       : {Job9}
PSBeginTime     : 30/06/2014 19:40:29
PSEndTime       : 30/06/2014 19:40:29
PSJobTypeName   : PSWorkflowJob
Output          : {}
Error           : {}
Progress        : {}
Verbose         : {}
Debug           : {}
Warning         : {}
State           : Suspended

 

Notice the state.

You can  manage the job with the standard job cmdlets

£> Get-Job

Id Name PSJobTypeName State     HasMoreData Location  Command
– —- ————- —–     ———– ——–  ——-
8  Job8 PSWorkflowJob Suspended True        localhost suspend1

 

The job is restarted using Resume-Job. Once the job has finished you can use Receive-Job to get the rest of the data.


June 27, 2014  2:02 PM

Expanding server names

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I had a comment left on my recent post – “Bad practices – making scripts needlessly interactive” asking how to deal with the situation of N servers consecutively numbered e.g. server01 to server05. Taking the script from that post as an example:

 

[CmdletBinding()]
param (
[string[]]$computername
)

foreach ($computer in $computername){
Restart-Computer -ComputerName $computer
}

 

the questioner wanted to be able to do something like

./reboot –computername server0[1-5]

which would have the same effect as typing

./reboot –computername server01, server02, server03, server04, server05

 

There are two approaches that come to mind. The first approach would be to put some code to perform the expansion into the script and use parameter sets to differentiate between a set of full names or a group of names to expand.  This would give you something like this:

[CmdletBinding()]
param (

[parameter(ParameterSetName="Names")]
[string[]]$computername,

[parameter(ParameterSetName="Group")]
[string]$computergroup

)

switch ($psCmdlet.ParameterSetName) {
“Names”  {
foreach ($computer in $computername){
Restart-Computer -ComputerName $computer
}
}
“Group”  {
$groupdata = $computergroup.Replace(“]”, “”) -split “\["
$prefix = $groupdata[0]
$nums = $groupdata[1] -split “-”

$nums[0]..$nums[1] |
foreach {
Restart-Computer -ComputerName “$prefix$_”
}

}
default {Write-Error “Error!!! Should not be here” }
}

 

Two parameters are defined. The computername parameter takes one or more computer names. The computergroup parameter takes some kind of shorthand notation to describe a group of consecutively named machines such as server0[1-5]. Pameter sets are used to make the parameters mutually exclusive .

Processing is based on a switch determined by parameter set name.

For a set of computer names the processing is as previously.

For a shorthand notation the data is split to provide the prefix – ‘server0’  in this case.

The numbers are then split on the “-“ to effectively give a first and last which are used in a range operator and passed through foreach-object to the restart-computer cmdlet where the computer name is created.

This approach works but it has one major drawback. You would need to put the code to expand the server names into each and every script or function you wrote. That’s extra work we can avoid.

The essence of PowerShell cmdlets is that they are a small piece of code that perform a discrete job. If we follow that pattern we can split the expansion of the server names out into a reusable piece of code that then passes computer names to our other scripts or functions. I would make the expand code a function so that you can load it through a module or your profile.

function expand-servername {
[CmdletBinding()]
param (

[string]$computergroup

)
$computers = @()

$groupdata = $computergroup.Replace(“]”, “”) -split “\["
$prefix = $groupdata[0]
$nums = $groupdata[1] -split “-”

$nums[0]..$nums[1] |
foreach {
$computers += “$prefix$_”
}

Write-Output $computers
}

 

using the function generates the server names:

£> . .\expand-servername.ps1
£> expand-servername -computergroup server0[1-5]
server01
server02
server03
server04
server05

 

You can use the reboot script like this:

.\reboot.ps1 -computername (expand-servername -computergroup server0[1-5])

 

If I was doing this for my production environment I would make the reboot script into an advanced function that accepted pipeline input. The reboot and expand-servername functions would be part of a module that could auto load. Alternatively. make expand-servername part of a module that you auto load. You could expand the options in expand-servername to accomodate multiple patterns of names.

 


June 26, 2014  11:34 AM

Workflows: 5a CDXML modules update

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Workflow

In my last post I questioned why commands from CDXML didn’t fail as thee weren’t any activities defined for them.  Turns out that functions and other commands that don’t explicitly have their own workflow activities are implicitly wrapped in the inline script activity.  As CDXML modules effectively create a function – that means that workflows accept them and run.

Thanks to Steve Murawski for the answer.


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: