Starting your PowerShell journey

 By Richard Siddaway, PowerShell MVP and author on a number of books on PowerShell, Active Directory and Windows Administration. He is a regular speaker at the PowerShell Summit and user groups, and blogs regularly on his PowerShell blog.

One of the questions I’ve been asked many times is “How do I start using PowerShell?” More often than not, the questioner has started to learn PowerShell from a book or has been on a course and has at least a rough idea of what PowerShell can do, but doesn’t know how to start actually using the technology in their environment.

A lot of people will think of some big project where they have to write hundreds, or even thousands, of lines of code and produce a set of beautiful GUI interfaces for other people to use. This sort of approach will fail 99 times out of 100 as you discover that writing code takes time, but testing it takes even longer and the pressures of the day job eventually mean that you don’t have the time to complete the project.

A much better approach is to start small and work up to the big stuff. A set of small scripts can solve a bunch of problems in your environment, start saving you time, show value to your management and hopefully convince a number of your colleagues to learn PowerShell as well. If they haven’t started learning PowerShell show them my previous article.

 

Where do you start?

So having said all that where do you start? The simple answer is to pick a problem in your environment that is costing you time and effort. The optimum is to pick something that you have to do on a regular basis that is time consuming, repetitive and tedious – but that is of interest to your management. Something where you have to prepare a weekly or monthly report that takes you several hours to pull together would be ideal. Let’s look at an example problem.

You probably have a monitoring solution in place that will alert you if a disk reaches a threshold value – usually something like 70% full. But do you know how the disk space is growing across your machines and are any of them approaching the threshold?

Logging onto a number of machines and retrieving the disk details and then compiling a report would get the job done but if you’re talking about a significant number of machines you’re going to be spending a lot of time in front of the console.

You can get disk information from local and remote machines using WMI (also known as CIM).

Get-CimInstance -ClassName Win32_LogicalDisk

Get-CimInstance is the cmdlet to use to retrieve CIM (WMI) information. Introduced in PowerShell 3.0 the CIM cmdlets provide very similar functionality to the WMI cmdlets (still available) but use the new API and communicate with remote servers using WSMAN (same as PowerShell remoting) rather the older DCOM protocol of the WMI cmdlets.

The results look like this:

DeviceID DriveType ProviderName VolumeName Size         FreeSpace  

-------- --------- ------------ ---------- ----         ---------  

C:       3                                 511740735488 228363587584

D:       5                                                         

E:       2                      HEROES     257662976    256954368

There are a couple of issueshere. Firstly, the drive type is reported as an integer.

The types are:

  • 2 = Removable Disk
  • 3 = Local Disk
  • 5 = CD or DVD drive

Solving the issues

You can restrict the type of drive included in your report by using the Filter parameter of Get-CimInstance. The second problem is that the disk size and free space are reported in bytes which means you need to do some arithmetic. Solving both of those problems leads to:

Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3'|

Select-Object Deviceid,

@{N='Size(GB)'; E={[math]::Round($_.Size / 1GB, 2)}},

@{N='FreeSpace(GB)'; E={[math]::Round($_.FreeSpace / 1GB, 2)}},

@{N='PercentFree'; E={[math]::Round(($_.FreeSpace / $_.Size) * 100, 2)}}

With these results:

Deviceid Size(GB) FreeSpace(GB) PercentFree

-------- -------- ------------- -----------

C:          476.6        212.71       44.63

The Filter is applied to the instances of Win32_LogicalDisk to only pass those of DriveType 3 – local hard disks. Two calculated fields are used to change the Size and FreeSpacevalues to GB. Notice that the original value is divided by 1GB. PowerShell understands KB, MB, GB, TB and PB as Kilobyte. A third calculated field is used to determine the percentage of free space available - you can change this to show percentage used if you modify the calculation slightly.

 

Working with remote machines

You’ve now got a mechanism to show you if there are any looming problems with diskspace. Unfortunately, this only works on the local machine. You need to add the capability of working with remote machines. Enter the ComputerName.

remote machines. Enter the ComputerName.

Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' `

-ComputerName W12R2SUS |

Select-Object PSComputerName, Deviceid,

@{N='Size(GB)'; E={[math]::Round($_.Size / 1GB, 2)}},

@{N='FreeSpace(GB)'; E={[math]::Round($_.FreeSpace / 1GB, 2)}},

@{N='PercentFree'; E={[math]::Round(($_.FreeSpace / $_.Size) * 100, 2)}}

The ComputerName parameter supplies the name of the machine to check. The PSComputerNameproperty is displayed as it holds the name of the remote machine. Pulling disk information from 100 machines isn’t going to be much use if you don’t know which machine is which!

The results are:

PSComputerName : W12R2SUS

Deviceid       : C:

Size(GB)       : 126.48

FreeSpace(GB)  : 109.01

PercentFree    : 86.18

The results have changed to a list because we now have five properties to display. Under five and the default is a table display. Over five and the default is a list.

Running against multiple machines at once

You don’t want to be inputting each computer by hand so the next step is to run the command against multiple machines at once.

$servers = 'SERVER02', 'W12R2SCDC01', 'W12R2SUS', 'W12R2DSC', 'W12R2TGT', 'W12R2WEB01', 'W12R2WEB02', 'W12R2OD01'

 

Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' `

-ComputerName $servers |

Select-Object PSComputerName, Deviceid,

@{N='Size(GB)'; E={[math]::Round($_.Size / 1GB, 2)}},

@{N='FreeSpace(GB)'; E={[math]::Round($_.FreeSpace / 1GB, 2)}},

@{N='PercentFree'; E={[math]::Round(($_.FreeSpace / $_.Size) * 100, 2)}} |

Format-Table –AutoSize

The results look like this:

PSComputerName Deviceid Size(GB) FreeSpace(GB) PercentFree

-------------- -------- -------- ------------- -----------

SERVER02       C:          476.6        212.71       44.63

W12R2TGT       C:         126.48        114.95       90.88

W12R2DSC       C:         126.48        114.73        90.7

W12R2SCDC01    C:         126.48         118.2       93.45

W12R2SUS       C:         126.48        108.99       86.17

W12R2OD01      C:         126.48         71.25       56.33

W12R2WEB01     C:         126.48        115.09          91

W12R2WEB02     C:         126.48        115.29       91.15

This starts to become useful. A list of servers is defined (you can also put the list into a file, or even create the list from an Active Directory query) and CIM is used to get the data. It’s displayed as a table for ease of use.

To save you having to scan the table to find potential problems, you can get PowerShell to order the data in ascending order of percentage free space available:

$servers = 'SERVER02', 'W12R2SCDC01', 'W12R2SUS', 'W12R2DSC', 'W12R2TGT', 'W12R2WEB01', 'W12R2WEB02', 'W12R2OD01'

 

Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' `

-ComputerName $servers |

Select-Object PSComputerName, Deviceid,

@{N='Size(GB)'; E={[math]::Round($_.Size / 1GB, 2)}},

@{N='FreeSpace(GB)'; E={[math]::Round($_.FreeSpace / 1GB, 2)}},

@{N='PercentFree'; E={[math]::Round(($_.FreeSpace / $_.Size) * 100, 2)}} |

Sort-Object PercentFree |

Format-Table –AutoSize

With results like this:

PSComputerName Deviceid Size(GB) FreeSpace(GB) PercentFree

-------------- -------- -------- ------------- -----------

SERVER02       C:          476.6        212.71       44.63

W12R2OD01      C:         126.48         71.25       56.33

W12R2SUS       C:         126.48        108.99       86.17

W12R2DSC       C:         126.48        114.73        90.7

W12R2TGT       C:         126.48        114.95       90.88

W12R2WEB01     C:         126.48        115.09          91

W12R2WEB02     C:         126.48        115.29       91.15

W12R2SCDC01    C:         126.48         118.2       93.45

The disks with the lowest percentage of free space will be at the top of the report so they become more obvious.

 

Turning it into a tool or scheduled task

Ideally, you’d want to take this script and turn it into a tool that any of you colleagues could use, or even better, that could be run as a scheduled task so the report creation was automatic. In order to achieve this goal, you might want to do a number of things:

1. Make the code input independent – preferably so that your code takes input from the pipeline

2. Add some error handling

3. Report servers that couldn’t be reached

4. Preserve the output

5. Automatically email the results to your manager

6. Make the results available through a web site

The first three on your list are things that you can do in the code. The last three are dependent on the way you use the code and we’ll deal with those in a bit.

 

PowerShell modules

The best way to deliver PowerShell functionality is through a PowerShell module. Module creation will be dealt with in another article, but for now we’ll see how to turn our current code into an advanced function – one that acts just like a PowerShell cmdlet on the pipeline. Advanced functions are worth several articles in their own right so I’ll show you how to do things rather than giving a full explanation. If you want to really dig into PowerShell functions then I recommend PowerShell in Action, third edition and PowerShell in Depth, second edition – both from www.manning.com.

1-3 on the wishlist

Dealing with items 1 – 3 in the wish list you end up with something like this:

function Get-DiskDetail {

[CmdletBinding()]

param (

 

[Parameter(  

   Position=0,

   ValueFromPipeline=$true,

   ValueFromPipelineByPropertyName=$true)]

[string[]]$ComputerName

 

)

 

PROCESS {

 foreach ($computer in $ComputerName) {

 

   try {

    $ts = Test-WSMan -ComputerName $computer -ErrorAction Stop

   }

   catch {

    $data = "$computer can't be contacted"

    Write-Warning -Message $data

    Add-Content -Value "$(Get-Date) $data" -Path C:\Reports\Disk\Log.txt

    Continue

   }

  

   try {  

    Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' `

    -ComputerName $computer -ErrorAction Stop |

    Select-Object PSComputerName, Deviceid,

    @{N='Size(GB)'; E={[math]::Round($_.Size / 1GB, 2)}},

    @{N='FreeSpace(GB)'; E={[math]::Round($_.FreeSpace / 1GB, 2)}},

    @{N='PercentFree'; E={[math]::Round(($_.FreeSpace / $_.Size) * 100, 2)}}

   }

   catch {

    $data1 = "Error retreiving data from $computer"

    Write-Warning -Message $data

    Add-Content -Value "$(Get-Date) $data1" -Path C:\Reports\Disk\Log.txt

   }

 }

}

 

}

The code is presented as a function called Get-DiskDetail. This follows the PowerShell convention of a verb and singular noun. The function is set as an advanced function by using the [CmdletBinding()] attribute. A single parameter – ComputerName– is defined. It can take an array of strings containing computer names or accept input from the pipeline.

For each of the computer names passed to the function, it first attempts to contact the remote machine using Test-WSMan. If this is successful the call to Get-CimInstanceis processed. If Test-WSMan is unsuccessful an error is generated and the catch block will write a warning to screen and add an entry to a simple log file. Using Test-WSMan isn’t fool proof, but it does prove that your remote machine is running, on the network and that the WSMan service is available.

The call to Get-CimInstance is also wrapped in a try-catch block. If successful you get your data. If unsuccessful you get a warning on screen and an entry in the log file.

You can run the function like this:

$servers = 'SERVER02', 'W12R2SCDC01', 'W12R2SUS', 'W12R2DSC', 'W16Con01', 'W12R2TGT', 'W12R2WEB01', 'W12R2WEB02', 'W12R2OD01'

 

$servers | Get-DiskDetail |

Sort-Object PercentFree |

Format-Table –AutoSize

With results like this:

WARNING: W16Con01 can't be contacted

 

PSComputerName Deviceid Size(GB) FreeSpace(GB) PercentFree

-------------- -------- -------- ------------- -----------

SERVER02       C:          476.6         212.7       44.63

W12R2OD01      C:         126.48         71.25       56.33

W12R2SUS       C:         126.48        108.44       85.74

W12R2DSC       C:         126.48        114.73        90.7

W12R2TGT       C:         126.48        114.95       90.88

W12R2WEB01     C:         126.48        115.09          91

W12R2WEB02     C:         126.48        115.29       91.15

W12R2SCDC01    C:         126.48         118.2       93.45

The warning is automatically displayed in a different colour to make it more obvious. The log file has entries like this:

12/17/2015 15:56:17 W16Con01 can't be contacted

12/17/2015 16:06:17 W16Con01 can't be contacted

I know W16Con01can’t be contacted because it’s switched off!

4 on the wishlist

Item 4 on the wish list was preserving the output. The easiest way to do this is to send it to a CSV fileinstead of displaying on screen.

$file = "DiskDetail$(Get-Date -Format 'yyyyMMddHHmmss')" + '.csv'

$path = Join-Path -Path C:\Reports\Disk -ChildPath $file

 

$servers | Get-DiskDetail |

Sort-Object PercentFree |

Export-Csv -Path $path –NoTypeInformation

The file name is created by formatting the date as year-month-day-hour-minute-second and prepending the text ‘DiskDetail’ . This creates file names of the form DiskDetail20151217162431.csv which is guaranteed to be reasonably unique. Join the file name to the reports folder using Join-Path. Run your list of servers through Get-DiskDetail and sort as previously. This time use Export-Csvto create a file.

You can email the file using Send-MailMessage and create an HTML report using ConvertTo-Htmlto fulfil items 5 and 6 on your wishlist. Creating HTML reports is worthy of an article in its own right so I’ll leave that to later.

 

Conclusion

This article has shown how you can progress from a simple use of PowerShell at the command line through a reporting tool that you can give other people to use or run in an automated manner. You can take your time between the steps because you are generating value to the organisation right from the start. This model can be applied to a number of scenarios such as mailbox database sizes, patching status, members of AD groups or unused AD accounts. Break the problem down to its simplest answer and then add functionality. After doing that a few times you’ll be able to jump straight into creating the final version of the tool.