Last week, I posted a PowerShell function that you could use as an accelerator to create your own PowerShell tools. My tool takes command metadata from an existing PowerShell cmdlet and gives you the structure to create your own tool wrapped around what is in essence a proxy function.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The advantage, besides saving a lot of typing, is that by using a proxy function you can take advantage of the wrapped cmdlet's features and parameters. But perhaps it would help to see an example. With the Get-CommandMetadata function loaded into the PowerShell ISE, I run this command:
get-commandmetadata get-ciminstance -NoHelp -NewName Get-CimOS
Instead of using CDXML, or writing my own function to call Get-Cimstance, I'm going to create a proxy to Get-Ciminstance that designed to retrieve operating system information. Here's what I start with.
#requires -version 4.0 Function Get-CimOS { [CmdletBinding(DefaultParameterSetName='ClassNameComputerSet')] param( [Parameter(ParameterSetName='QuerySessionSet', Mandatory=$true, ValueFromPipeline=$true)] [Parameter(ParameterSetName='CimInstanceSessionSet', Mandatory=$true, ValueFromPipeline=$true)] [Parameter(ParameterSetName='ClassNameSessionSet', Mandatory=$true, ValueFromPipeline=$true)] [Parameter(ParameterSetName='ResourceUriSessionSet', Mandatory=$true, ValueFromPipeline=$true)] [Microsoft.Management.Infrastructure.CimSession[]] $CimSession, [Parameter(ParameterSetName='ClassNameSessionSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ClassNameComputerSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] [string] $ClassName, [Parameter(ParameterSetName='CimInstanceSessionSet')] [Parameter(ParameterSetName='CimInstanceComputerSet')] [Parameter(ParameterSetName='QuerySessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='QueryComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriSessionSet', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriComputerSet', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [uri] $ResourceUri, [Parameter(ParameterSetName='QueryComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='CimInstanceComputerSet')] [Parameter(ParameterSetName='ClassNameComputerSet', ValueFromPipelineByPropertyName=$true)] [Alias('CN','ServerName')] [string[]] $ComputerName, [Parameter(ParameterSetName='ResourceUriSessionSet')] [Parameter(ParameterSetName='ClassNameSessionSet')] [Parameter(ParameterSetName='ResourceUriComputerSet')] [Parameter(ParameterSetName='ClassNameComputerSet')] [switch] $KeyOnly, [Parameter(ParameterSetName='ClassNameSessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='QuerySessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ClassNameComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='QueryComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriSessionSet', ValueFromPipelineByPropertyName=$true)] [string] $Namespace, [Alias('OT')] [uint32] $OperationTimeoutSec, [Parameter(ParameterSetName='CimInstanceComputerSet', Mandatory=$true, Position=0, ValueFromPipeline=$true)] [Parameter(ParameterSetName='CimInstanceSessionSet', Mandatory=$true, Position=0, ValueFromPipeline=$true)] [Alias('CimInstance')] [ciminstance] $InputObject, [Parameter(ParameterSetName='QueryComputerSet', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='QuerySessionSet', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string] $Query, [Parameter(ParameterSetName='QueryComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='QuerySessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ClassNameSessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ClassNameComputerSet', ValueFromPipelineByPropertyName=$true)] [string] $QueryDialect, [Parameter(ParameterSetName='ResourceUriSessionSet')] [Parameter(ParameterSetName='QueryComputerSet')] [Parameter(ParameterSetName='QuerySessionSet')] [Parameter(ParameterSetName='ResourceUriComputerSet')] [Parameter(ParameterSetName='ClassNameComputerSet')] [Parameter(ParameterSetName='ClassNameSessionSet')] [switch] $Shallow, [Parameter(ParameterSetName='ClassNameSessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ClassNameComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriSessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriComputerSet', ValueFromPipelineByPropertyName=$true)] [string] $Filter, [Parameter(ParameterSetName='ClassNameComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriComputerSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ClassNameSessionSet', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ResourceUriSessionSet', ValueFromPipelineByPropertyName=$true)] [Alias('SelectProperties')] [string[]] $Property) begin { try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-CimInstance', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { try { $steppablePipeline.Process($_) } catch { throw } } end { try { $steppablePipeline.End() } catch { throw } } <# .Synopsis PUT SYNTAX HERE .Description PUT DESCRIPTION HERE .Notes Created: 9/8/2014 .Example PS C:\> Get-CimOS .Link Get-CimInstance #> } #end function Get-CimOS
As with any PowerShell scripting project, you have to think about who will run your command and what expectations they might have. In my example, I simply want to be able to specify a computer name or a CIM Session and retrieve information from the Win32_Operatingsystem class. So to begin with I can delete all the parameter definitions except Computername and CIMSession. I'll also keep the OperationTimeOut parameter just in case. As you can see in the code sample, Get-CimInstance also has a number of parameter sets. Again, I can delete references to ones I'm not going to use.
Here's my revised parameter definition.
[CmdletBinding(DefaultParameterSetName='ClassNameComputerSet')] param( [Parameter(Position=0,ParameterSetName='CimInstanceSessionSet', Mandatory=$true, ValueFromPipeline=$true)] [Microsoft.Management.Infrastructure.CimSession[]]$CimSession, [Parameter(Position=0,ParameterSetName='ClassNameComputerSet', ValueFromPipelineByPropertyName=$true)] [Alias('CN','ServerName')] [string[]]$ComputerName = $env:Computername, [Alias('OT')] [uint32]$OperationTimeoutSec )
One change I made was to give the computername a default value for the local computer. I amd doing this so that the PSComputername property will always have a value.
PS C:\> get-ciminstance win32_bios | select name,pscomputername name PSComputerName ---- -------------- 76CN38WW PS C:\> get-ciminstance win32_bios -comp $env:computername | select name,pscomputername name PSComputerName ---- -------------- 76CN38WW WIN81-ENT-01
But you may be wondering, what about the classname? This is where the fun begins. I am going to hard-code other parameters into the function.
#Add hard-coded parameters $PSBoundParameters.Add("Classname","win32_operatingsystem") $PSBoundParameters.Add("Namespace","root\cimv2") $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-CimInstance', [System.Management.Automation.CommandTypes]::Cmdlet)
When the wrapped command is executed it will use these values which is what I want. At this point, I could save the function and run it. But the output would be the same as if I had run Get-Ciminstance and I have a specific output in mind. Here's how.
First, copy and paste a new version of the $scriptCmd line. Comment out the original line.
# $scriptCmd = {& $wrappedCmd @PSBoundParameters }
What I need to do is create my own script command, which must be a single pipelined expression.
$scriptCmd = { #select whatever Win32_OperatingSystem properties you need & $wrappedCmd @PSBoundParameters | Select-Object @{Name="Computername";Expression={$_.CSName}}, @{Name="OS";Expression={$_.Caption}},Version, @{Name="64bit";Expression={ if ($_.OSArchitecture -match "64") { $True } else { $False }}}, @{Name="SvcPack";Expression={$_.CSDVersion}},InstallDate } #scriptcmd
I still want the wrapped Get-Ciminstance command to run but then I'm going to pipe the results to Select-Object and specify some custom properties. That is the extent of my major changes, although I also will add some additional Write-Verbose lines for tracing and troubleshooting. After updating comment based help, here's my final command.
#requires -version 4.0 #this version selects the properties I want Function Get-CIMOS { <# .Synopsis Get operating system information. .Description This is a proxy version of Get-CimInstance designed to get operating system information from one or more computers. .Notes Last Updated: 9/4/2014 Learn more: PowerShell in Depth: An Administrator's Guide (http://www.manning.com/jones6/) PowerShell Deep Dives (http://manning.com/hicks/) Learn PowerShell in a Month of Lunches (http://manning.com/jones3/) Learn PowerShell Toolmaking in a Month of Lunches (http://manning.com/jones4/) PowerShell and WMI (http://www.manning.com/siddaway2/) **************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** .Example PS C:> get-cimos chi-dc04 Computername : CHI-DC04 OS : Microsoft Windows Server 2012 Datacenter Version : 6.2.9200 SvcPack : InstallDate : 9/10/2012 12:41:57 PM .Example PS C:\> get-cimsession | get-cimos | out-gridview -title "OS Report" .Link Get-CimInstance #> [CmdletBinding(DefaultParameterSetName='ClassNameComputerSet')] param( [Parameter(Position=0,ParameterSetName='CimInstanceSessionSet', Mandatory=$true, ValueFromPipeline=$true)] [Microsoft.Management.Infrastructure.CimSession[]]$CimSession, [Parameter(Position=0,ParameterSetName='ClassNameComputerSet', ValueFromPipelineByPropertyName=$true)] [Alias('CN','ServerName')] [string[]]$ComputerName = $env:Computername, [Alias('OT')] [uint32]$OperationTimeoutSec ) begin { #add my own verbose output Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" Write-verbose $PSCmdlet.ParameterSetName try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } #Add hard-coded parameters $PSBoundParameters.Add("Classname","win32_operatingsystem") $PSBoundParameters.Add("Namespace","root\cimv2") $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-CimInstance', [System.Management.Automation.CommandTypes]::Cmdlet) #the original line # $scriptCmd = {& $wrappedCmd @PSBoundParameters } #my modified scriptblock. The scriptblock can only contain a single pipelined expression $scriptCmd = { #select whatever Win32_OperatingSystem properties you need & $wrappedCmd @PSBoundParameters | Select-Object @{Name="Computername";Expression={$_.CSName}}, @{Name="OS";Expression={$_.Caption}},Version, @{Name="64bit";Expression={ if ($_.OSArchitecture -match "64") { $True } else { $False }}}, @{Name="SvcPack";Expression={$_.CSDVersion}},InstallDate } #scriptcmd $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) Write-Verbose "Begin steppable pipeline" $steppablePipeline.Begin($PSCmdlet) } catch { throw } } #begin block process { try { Write-Verbose "Processing" if ($PSCmdlet.ParameterSetName -eq 'ClassNameComputerSet') { $inputs = $ComputerName } else { $inputs = $cimSession.Computername } foreach ($computer in $inputs) { Write-Verbose "...$computer" } $steppablePipeline.Process($_) } catch { throw } } #process block end { Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" try { $steppablePipeline.End() } catch { throw } } #end block } #end function Get-CIMOS
Now to test it out:
PS C:\> get-cimos Computername : WIN81-ENT-01 OS : Microsoft Windows 8.1 Enterprise Version : 6.3.9600 64bit : True SvcPack : InstallDate : 11/26/2013 1:08:31 PM
I can specify a computername:
PS C:\> get-cimos -ComputerName chi-dc04 Computername : CHI-DC04 OS : Microsoft Windows Server 2012 Datacenter Version : 6.2.9200 64bit : True SvcPack : InstallDate : 9/10/2012 12:41:57 PM
Or use CIMSessions.
PS C:\> get-cimsession | get-cimos |out-gridview -title "OS Report"
I didn't have to write any code to use the CIMSessions. I let the wrapped cmdlet handle everything for me. The end result is that I now have a very complete PowerShell tool that didn't take much time to create. I probably spent more time getting the comment-based help written than anything.
Hopefully this gives you an idea of how to take your PowerShell toolmaking to the next level.
Interesting article ! (As always)… Didn’t knew about the “get-commandmetadata” cmdlet and I’ll give it a try 😉
Get-CommandMetadata is a function that I wrote. Get it here: https://jdhitsolutions.com/blog/2014/09/friday-fun-creating-powershell-scripts-with-powershell/