Create a PowerShell Module

Recently I came across an issue on our Hyper-V Cluster. One of the VM was stuck in the “Stopping” state. I had to force the VM to shutdown by kill its process on the Hyper-V host. To do so, I first find out the VM’s GUID and then kill the process with the same GUID. Needless to say, the whole process can be achieved with the PowerShell commands below.

\# Get the VM GUID and find the process with the GUID  
$VM \= Get-VM \-Name $VMName \-ErrorAction Stop  
$VMGUID \= $VM.Id  
$VMWMProc \= (Get-WmiObject Win32\_Process | Where-Object {$\_.Name \-match 'VMWP' \-and $\_.CommandLine \-match $VMGUID})  
      
\# Kill the VM process  
Stop-Process ($VMWMProc.ProcessId) Force  

This is a pretty short and simple script, which is perfect for making a module. We have number of Hyper-V hosts across our environment. Instead of copying the script around, module will help us better organize the code and make it more re-usable. I also like to share this small function with our global team, which module will serve nicely for this purpose.

Alright, let’s convert the script into a module then. Here’s the script Kill-VM.ps1. Let’s save it as Kill-VM.psm1. And now we have a module! That’s it?! Noooo… of course not. There are a few things we need to change with the script, before we can call it a proper module.

#requires -module Hyper-V  
#requires -runasadministrator  
  
\[CmdletBinding(SupportsShouldProcess=$True)\]  
Param(  
    \[Parameter(Mandatory\=$true,  
        HelpMessage\="The VM Name"  
        )\]  
    \[String\]$VMName  
)  
  
Try{  
    #Get a VM object from local HyperV host  
    $VM \= Get-VM \-Name $VMName \-ErrorAction Stop  
    $VMGUID \= $VM.Id  
    $VMWMProc \= (Get-WmiObject Win32\_Process | Where-Object {$\_.Name \-match 'VMWP' \-and $\_.CommandLine \-match $VMGUID})  
      
    \# Kill the VM process  
    Stop-Process ($VMWMProc.ProcessId) Force  
    Write-Output "$VMName is stopped successfully"  
}Catch{  
    $ErrorMsg \= $Error\[0\] #Get the latest Error  
    Write-Output $ErrorMsg  
}  

First, we need to give a name to our module. Let’s call it “VMKiller”! “VMTerminator” is cooler, but a bit too long for my like…

Create a folder named “VMKiller” under “C:\Windows\System32\WindowsPowerShell\v1.0\Modules”. There are a few other default folders for PowerShell modules. You can find them out by run the command below.

$env:PSModulePath  

Save our script as VMKiller.psm1 into the “VMKiller” folder.

In the psm1 file, wrap the parameter and try sections into a function call “Kill-VM”.

We will also add Begin, Process and End section to handle Pipeline input for the function. This is not essential in this particular case. But will be useful, if we have ForEach loop in the function. The Process section will help us properly handle multiple objects inputted from pipeline.

Below is the code in VMKiller.psm1 after above changes.

Function Kill-VM  
{  
    \[CmdletBinding(SupportsShouldProcess=$True)\]  
    Param(  
        \[Parameter(Mandatory\=$true,  
            ValueFromPipeline\=$True,  
            ValueFromPipelineByPropertyName\=$True,  
            HelpMessage\="The VM Name"  
            )\]  
        \[String\]$VMName  
    )  
    Begin{}  
    Process{  
        Try{  
            #Get a VM object from local HyperV host  
            $VM \= Get-VM \-Name $VMName \-ErrorAction Stop  
            $VMGUID \= $VM.Id  
            $VMWMProc \= (Get-WmiObject Win32\_Process | Where-Object {$\_.Name \-match 'VMWP' \-and $\_.CommandLine \-match $VMGUID})  
              
            \# Kill the VM process  
            Stop-Process ($VMWMProc.ProcessId) Force  
            Write-Verbose "$VMName is stopped successfully"  
        }Catch{  
            $ErrorMsg \= $\_.Exception.Message #Get the latest Error  
            Write-Verbose $ErrorMsg  
        }  
    }  
    End{}  
}  

Next, to make our module more understandable to future readers, we will add some Comment based Help information into the code. Comment based Help will allow users to read about the module function by using Get-Help command. You can read more about Comment Based Help from here.

<#  
.SYNOPSIS  
Kill a VM Process.  
  
.DESCRIPTION  
Use this module when a VM is stuck at Stopping state. It will kill the VM Process on the HyperV host.  
  
.PARAMETER VMName  
Name of the virtual machine.  
  
.EXAMPLE  
C:\\PS>Kill-VM -VMName "contoso-av1"  
  
#>  

The VMKiller module so far only contains a single function, and a single psm1 file. With what we have done so far, it is ready to be used. However, there are some additional bits I would like to show you. 

It is not necessary to create Module Manifest, while we have only one single psm1 file. Without the Manifest, PowerShell will try to load ModuleName.DLL first. If that is not successful, it will then try ModuleName.psm1. But in this case we will create one anyway. To create the manifest, use the command below.

New-ModuleManifest path .\\VMKiller.psd1  RootModule VMKiller.psm1  

With the module file, you only need to modify one line, on line 72, remove the wildcard ‘*’ and replace it with ‘Kill-VM’ function. This is to specifically tell PowerShell which function to be exposed from this module. When you have a lot of functions in a module, this will improve the module loading performance.

image

Now save all the files, let’s try load the VMKiller module and put it to use.

First, let’s load the module with Import-Module. You will get a warning about unapproved verb. This is because “Kill” is not something Microsoft will use in their command… They need to make sure everything is 200% politically correct. You can just ignore that, as I do not care.

image

Next, let’s see what users will see if they try to learn about the function with Get-Help.

image

Now they know how to use the cmdlet, let’s put it to real use. Let’s stop a running VM.

image

And that’s it! You now have a module can be deployed to any other Hyper-V hosts. You can even use DSC to make sure it is installed on all Hyper-V hosts!