Automatic PowerShell Modules
In a previous email I demonstrated PowerShell code that generated custom PowerShell functions. This is a clever way to create commands with a very specific use case or a very specific target user. You can put your code in a script file that is dot-sources to create the set of tools.
But what if you want to create a module? This is where things get interesting. Keep in mind that what I want to demonstrate is for very specific purposes. This is not an all-purpose approach, although there may be parts of the technique that you can use in your own work.
Dynamic Modules
PowerShell has a little known feature that allows you to create a module on the fly. You can even do it from the console. The secret command is New-Module
. I'll trust you will take time to read full help and examples for this command. I'll just show you a simple example.
New-Module -Name MyDynamic -ScriptBlock {
Function Invoke-Hello {
Write-Host "Hello, World!" -ForegroundColor Green
}
} -function Invoke-Hello
This code creates a new module in memory called MyDynamic
. Nothing is written to disk. The module as a single function called Invoke-Hello
. You can use the function immediately.
The module isn't technically visible in the session, but the command is.
There is a module, but you can't view it.
PS C:\> Get-Command Invoke-Hello | Select-Object -ExpandProperty module | Select-Object Path,GUID
Path Guid
---- ----
C:\50f8426a-a8de-4739-9136-87d45e912d68 00000000-0000-0000-0000-000000000000
The path is listed, but doesn't exist.
How can we use this feature to create a dynamic module using the techniques from the previous article?
Proof of Concept
I'll test my approach first by copying and pasting into an interactive console session. I'm going to create a command for every computer in my test Active Directory domain.
$Names = (Get-ADComputer -Filter *).Name
I'll use this function as my template.
Function Get-ServerInfo {
[cmdletbinding()]
Param(
[parameter(Position = 0, Mandatory)]
[string]$ComputerName
)
Write-Verbose "Starting $($MyInvocation.MyCommand)"
Try {
Write-Verbose "Resolving DNS name for $($ComputerName.ToUpper())"
$r = Resolve-DnsName $ComputerName -Type A -ErrorAction Stop
Write-Verbose "Getting AD computer object for $($ComputerName.ToUpper())"
$ad = Get-ADComputer -Identity $ComputerName -Properties ManagedBy, Description, Enabled -ErrorAction Stop
}
Catch {
Throw $_
}
if ($r) {
[PSCustomObject]@{
PSTypeName = 'PSServerInfo'
ComputerName = $ComputerName.ToUpper()
DNSHostName = $r.Name.ToUpper()
DistinguishedName = $ad.DistinguishedName
Description = $ad.Description
ManagedBy = $ad.ManagedBy
IPAddress = $r.IPAddress
Enabled = $ad.Enabled
}
}
Write-Verbose "Ending $($MyInvocation.MyCommand)"
}
I need the function's definition.
$definition = (Get-Item Function:\Get-ServerInfo).definition
Next, I'll create an array of commands.
$cmds = @()
Foreach ($item in $Names) {
Write-Host "Creating function: $item" -ForegroundColor Green
$cmds+= "Function $item { $definition }"
$PSDefaultParameterValues["$($item):ComputerName"] = $item
}
Because I am using an existing function with a mandatory parameter, I'm also setting the default parameter value for the ComputerName
parameter. I showed you this last time.
Now to create the dynamic module.
New-Module -Name CompanyServerInfo -ScriptBlock ([scriptblock]::Create($cmds)) -function $Names
We already know the module is hidden. But the commands are not.
Creating a Module File
Now that this works, I need to write something that can be deployed. This time I'll explicitly define the function text. This allows me to include comment-based help. I can also adjust the code to hard-code the computer name within the command. Normally, we avoid this, but in this case, I'm creating targeted commands for a less-experienced PowerShell user.