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.
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.
Next, let’s see what users will see if they try to learn about the function with Get-Help.
Now they know how to use the cmdlet, let’s put it to real use. Let’s stop a running VM.
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!