Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Creating Your Own PowerShell Command

Posted on September 10, 2014September 10, 2014

atomic powershellLast 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.

Manage and Report Active Directory, Exchange and Microsoft 365 with
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"

osreport

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.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

2 thoughts on “Creating Your Own PowerShell Command”

  1. Philip Tomson (Avalon-Zone) says:
    September 11, 2014 at 1:35 am

    Interesting article ! (As always)… Didn’t knew about the “get-commandmetadata” cmdlet and I’ll give it a try 😉

    1. Jeffery Hicks says:
      September 11, 2014 at 8:19 am

      Get-CommandMetadata is a function that I wrote. Get it here: https://jdhitsolutions.com/blog/2014/09/friday-fun-creating-powershell-scripts-with-powershell/

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d