Summary: Guest blogger, Rohn Edwards, discusses creating a Windows PowerShell proxy function to display registry key time stamps.
Microsoft Scripting Guy, Ed Wilson, is here. Happy New Year to you all. Guest blogger, Rohn Edwards, is back with us today to continue his series about retrieving registry key last-modified time stamps. Be sure to read the following blog posts before you start reading the post today, if you have not already done so.
- Use PowerShell to Access Registry Last-Modified Time Stamp
- Reusing PowerShell Registry Time Stamp Code
Yesterday, we built a Windows PowerShell advanced function that gets information from registry keys that isn’t normally exposed to Windows PowerShell. Our function gets a key’s last-modified time class name. Today, we’re going to create a proxy function for Get-ChildItem that will let us get this information without calling the tool we built yesterday (it will still use it internally, though).
Before we get started, let me mention two ways that we can make the function behave:
1. We can make it automatically get the information any time you call Get-ChildItem.
2. We can make it get the information when you provide a switch parameter to the function. If that parameter isn’t provided, Get-ChildItem behaves normally.
We’re going to go with option #1 today, so any time you use the proxied Get-ChildItem on a registry path, you’ll get the LastWriteTime and the ClassName. If you want the behavior from option #2, feel free to modify the final script.
Note If you’re not familiar with proxy functions, I suggest you read this awesome Hey, Scripting Guy! Blog post by Shay Levy: Proxy Functions: Spice Up Your PowerShell Core Cmdlets.
The first thing we need to do is let Windows PowerShell generate a function that will call the cmdlet, and we do that with the following two lines:
$MetaData = New-Object System.Management.Automation.CommandMetadata (Get-Command Get-ChildItem)
[System.Management.Automation.ProxyCommand]::Create($MetaData) | clip.exe
By piping it to clip.exe, we’ve put the generated script in our clipboard. All you have to do is create a function declaration and paste the script inside of it:
function Get-ChildItem {
[Paste script here]
}
By running those two lines of Windows PowerShell script and creating the function declaration, you get just over 70 lines of code. It looks intimidating, but it’s really not that bad. If you were to run your script now, it should declare the proxy function. If you were to call Get-Command with the -All parameter, you would see that there is a function and a cmdlet named Get-ChildItem:
If you type Get-ChildItem at the prompt, it will call the function instead of the cmdlet. Because we haven’t modified the script that was generated, the function will pretty much behave exactly like the cmdlet. The parameters that were passed to the function will be forwarded to the original Get-ChildItem.
There is some functionality that is missing, though. Our proxy function doesn’t have any dynamic parameters. (For more information, see Use PowerShell to Find Dynamic Parameters.) The Get-ChildItem cmdlet has some incredibly useful ones, so we’re going to have to fix that.
To add the original dynamic parameters back into our proxy function, we’re going to need to add the dynamicparam keyword. This is a script block that is defined at the same level as the begin, process, and end blocks inside of a function. For more information, see the about_Functions_Advanced_Parameters Help topic.
Here’s what our dynamicparam block is going to look like:
dynamicparam {
# We need to find the path to use (if no path is specified, use the current path
# in the current provider:
if ($PSBoundParameters.Path) { $GciPath = $PSBoundParameters.Path }
elseif ($PSBoundParameters.LiteralPath) { $GciPath = $PSBoundParameters.LiteralPath }
else { $GciPath = "." }
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Get dynamic params that real Cmdlet would have:
$Parameters = Get-Command -CommandType Cmdlet -Name Get-ChildItem -ArgumentList $GciPath |
Select-Object -ExpandProperty Parameters
foreach ($Parameter in ($Parameters.GetEnumerator() | Where-Object { $_.Value.IsDynamic })) {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$Parameter.Key,
$Parameter.Value.ParameterType,
$Parameter.Value.Attributes
)
$DynParamDictionary.Add($Parameter.Key, $DynamicParameter)
}
# Return the dynamic parameters
$DynParamDictionary
}
All that’s going on here is that we make a call to Get-Command for the Get-ChildItem cmdlet, and we pass Get-Command the path we would pass to Get-ChildItem. Get-Command gives us back the command info, and we save the parameters collection from that output.
Next, we loop through each parameter that has the IsDynamic property set to True, create a new RuntimeDefinedParameter each time that has the same name, type, and attributes as the parameter info we got from Get-Command, and add that RuntimeDefinedParameter object to the parameter dictionary. Finally, we return the parameter dictionary after all of the dynamic parameters have been processed.
If you add the DynamicParam block to the function defintion, our proxy function should perfectly mimic the original Get-ChildItem cmdlet. Now it’s time to add the new functionality! Let’s take a look at the original begin block:
Take a look at the line where the $scriptCmd variable is defined. If you think about it, all we really need to do is add a call to our Add-RegKeyMember function from yesterday’s post when a registry path has been provided to Get-ChildItem. Before we can do that, though, we need to know when the function was called on a registry path. For that, we can turn to the Resolve-Path cmdlet:
So, it looks like we can use Resolve-Path to see the provider that a path uses. If the Registry provider is being used, we can change the $scriptCmd variable. Here’s the new begin block with the modifications:
begin
{
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet)
# We need to find the path to use (if no path is specified, use the current path
# in the current provider:
if ($PSBoundParameters.Path) { $GciPath = $PSBoundParameters.Path }
elseif ($PSBoundParameters.LiteralPath) { $GciPath = $PSBoundParameters.LiteralPath }
else { $GciPath = "." }
if ((Resolve-Path $GciPath).Provider.Name -eq "Registry") {
# Registry provider, so call function to get extra key info:
$scriptCmd = {& $wrappedCmd @PSBoundParameters | Add-RegKeyMember }
}
else {
# Don't do anything special; just call gci cmdlet:
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
}
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
And that’s it! To use it, save the two functions (Add-RegKeyMember and Get-ChildItem) to a .ps1 file and dot-source them. (For more information, see How to Reuse Windows PowerShell Functions in Scripts.)
If you want the function to always be available, you can dot-source the script in your profile. (For more information, see Understanding and Using PowerShell Profiles.) Or you can add the functions directly to your profile script and not worry about dot-sourcing.
Try out some of these examples:
# Show the last modified time of the keys under HKLM:\SOFTWARE:
dir HKLM:\SOFTWARE | Select Name, LastWriteTime
# Show keys under Lsa that have a class name:
dir HKLM:\SYSTEM\CurrentControlSet\Control\Lsa | where classname | select name, classname
# Get registry keys under HKLM:\Software that have been modified in the last day:
dir HKLM:\SOFTWARE -Recurse | ? lastwritetime -gt (Get-Date).AddDays(-1)
~Rohn
Thanks, Rohn, for sharing your time and knowledge. Join us tomorrow as Rohn brings us one more day of awesomeness.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
0 comments