PowerShell commands always return objects. Your own functions may simply return text. That's bad because only objects can be fed into other cmdlets, for example to sort, group or export the information. Fortunately, it's easy to create your very own objects. Use them to consolidate information from multiple sources and create functions that return information the PowerShell way. Have a look!
Consolidating Information From Various Sources
Let’s assume you are working on an inventory script, and you use WMI to collect some of the information you need to harvest.
Here are two sample calls that get you BIOS and Operating System details:
Get-WmiObject Win32_BIOS | Select-Object -ExpandProperty SerialNumber
Get-WmiObject Win32_OperatingSystem | Select-Object -ExpandProperty Version
Both calls produce two pieces of text information. Text information is bad because PowerShell is object oriented, and in order to interact with other cmdlets such as Sort-Object or Export-CSV, your information also needs to be objects.
Hash Tables to the Rescue!
Fortunately, simple hash tables provide universal containers that can store and organize just about any number of information you would like to consolidate. It is a simple three-step-process.
Step #1 is getting a new hash table. Let's call it “info”:
$info = @{}
Step #2 stores the information you want to consolidate inside this hash table.
Which raises the question: what exactly *is* a hash table? It more or less is an array that uses descriptive keywords instead of numeric indices. Simply “file” your pieces of information under an appropriate keyword:
$info.BIOSSerial = (Get-WmiObject Win32_BIOS).SerialNumber
$info.OSVersion = (Get-WmiObject Win32_OperatingSystem).Version
$info.ComputerName = $env:computername
The final step #3 is converting your hash table to a real object so other cmdlets can understand it.
That’s easy: use New-Object, and tell it to use your hash table as prototype for a new object:
New-Object PSObject -Property $info
OSVersion BIOSSerial ComputerName
--------- ---------- ------------
6.1.7600 ZAMA93HS600210 DEMO5
Cool Things You Could Do Now...
The past example created just one object. When you output it, it will display its properties in exactly the same way you are used to from any other object.
The fact that you just managed to wrap your results into pure objects becomes more evident when you produce more than one object because then you can play with the other cmdlets and see how they group, sort or export your results.
Just keep in mind that you need a new hashtable for every new object you want to create.
Here is a simple example that enumerates all your shortcuts in your start menu. The main logic is wrapped as a function called Get-Shortcut.
(Download the script from our library here: Get-Shortcut)
function Get-Shortcut {
$obj = New-Object -ComObject WScript.Shell
$pathUser = [System.Environment]::GetFolderPath('StartMenu')
$pathCommon = $obj.SpecialFolders.Item('AllUsersStartMenu')
dir $pathUser, $pathCommon -Filter *.lnk -Recurse |
ForEach-Object {
$link = $obj.CreateShortcut($_.FullName)
$info = @{}
$info.Hotkey = $link.Hotkey
$info.TargetPath = $link.TargetPath
$info.LinkPath = $link.FullName
$info.Arguments = $link.Arguments
$info.Target = try {Split-Path $info.TargetPath -Leaf } catch { 'n/a'}
$info.Link = try { Split-Path $info.LinkPath -Leaf } catch { 'n/a'}
$info.WindowStyle = $link.WindowStyle
$info.IconLocation = $link.IconLocation
New-Object PSObject -Property $info
}
}
It uses a COM object called WScript.Shell to find out the path to your common start menu folder, and it uses .NET to find your private start menu folder. Then, Dir searches recursively for all lnk-files in both places.
Now the interesting part begins.
A Foreach-Object loop processes each lnk file found by Dir. The COM object has a method called CreateShortcut() which does not necessarily create a new shortcut. Instead, if you provide the path to an existing shortcut, the method will happily use that file and returns all the shortcut details.
Now, for each shortcut the code creates a new hashtable and stores all the information found useful, then converts the hashtable to an object. The function produces therefore one new object per loop iteration, or in other words one new object per found shortcut. You can now easily enumerate all shortcuts:
Get-Shortcut
You can select the information about shortcuts to only a few pieces of information (note that with less than five pieces of information, PowerShell displays the result as table rather than list):
PS> Get-Shortcut | Select-Object Link, Target, Hotkey
Link Target Hotkey
---- ------ ------
Internet Explorer (64-b... iexplore.exe
Internet Explorer.lnk iexplore.exe
Sound Level Meter.lnk Sound Level Meter.exe
Command Prompt.lnk cmd.exe
Notepad.lnk notepad.exe
And you can even search for irregular or special shortcuts. For example, to find shortcuts with no (regular) target, use this:
Get-Shortcut | Where-Object { $_.Target -eq 'n/a' }
The shortcuts listed still work though because they have an internal Windows-Installer-controlled target which is not accessible through the COM technique used in this example.
The next line gets you all shortcuts with a keyboard hotkey:
PS> Get-Shortcut | Where-Object { $_.Hotkey } | Select-Object Link, Target, Hotkey
Link Target Hotkey
---- ------ ------
Windows PowerShell.lnk powershell.exe F11
If that command does not return anything, then none of your shortcuts has a keyboard shortcut. That is too bad, because assigning keyboard shortcuts allows you to press just that key combination to launch the shortcut. Wouldn’t it be cool if you could edit, change and review shortcut properties via PowerShell?
In Part 2, we’ll add a bunch more functions which allow you to open file property dialogs or change shortcut settings programmatically. Of course, you’ll learn a lot of general PowerShell techniques as well such as creating pipeline-aware functions that can receive data from previous pipeline commands.
Meanwhile, I hope you could see how powerful your own functions become when you return information wrapped inside objects rather than outputting simple text. By returning objects, you were able to easily sort, filter and group, using all the fine pipeline cmdlets like Select-Object, Sort-Object, Where-Object et al.
Have a great time with PowerShell! And from now on, make sure all your functions return results wrapped as objects, not plain text. It's worth it!
Tobias
Microsoft MVP PowerShell Germany
P.S.
If you live in Germany or other parts of Europe and your company would like to set up a truly great PowerShell training, just contact me! I regularly train mid- to large-size companies. Trainings are always a blast with tons of real-world-examples and solutions. Here's how to get in touch with me: tobias.weltner@scriptinternals.de
Posted
Sep 22 2010, 01:06 PM
by
Tobias