Tag Archives: Invoke-Expression

Adding a Help Parameter to a Function

Edit: There’s a quick addition at the bottom of this post. I tried something else, it worked, and so I decided to include it.

I started writing a PowerShell function to replicate the work of an old executable called ICSWEEP. ICSWEEP “is a command-line utility to clear the Temporary Internet Files Cache and/or the TEMP files folder of ALL user profiles that are NOT in use when this command is executed.” ICSWEEP Information. I may or may not walk through the process of writing this function here; it’s to be determined. What I do want to discuss for sure, however, is the last switch in the above image. It’s /?.

We cannot add ? parameter to a function. Below is an image of the message when we attempt to do that in VS Code.

That’s right, right!? You do remember the $? variable. It stores the execution status of the last command as True or False. Instead of ? as a parameter, I used the value of Help. Therefore, someone can invoke my function and use -Help to get help with the function. But could they?

I sat there, wondering, can you create a parameter of a function so that it’ll return its own, comment-based help? It turns out you can. Here’s the early stage of this function. It consists of the comment-based help, a few parameters, and a small amount of code.

function Clear-UserTemporaryFile {
<#
.SYNOPSIS
    The Clear-UserTemporaryFile command ...
.DESCRIPTION
.PARAMETER <Parameter>
.EXAMPLE
.NOTES
    Name: Clear-UserTemporaryFile
    Author: Tommy Maynard
    Comments: --
    Last Edit: 12/13/2022 [1.0.0]
    Version: 1.0.0
#>
    Param (
        [Parameter()]
        [switch]$ALL,
        [Parameter()]
        [string]$TIF,
        [Parameter()]
        [string]$TMP,
        [Parameter()]
        [string]$SIZE,
        [Parameter()]
        [switch]$HELP
        
    )

    if ($HELP) {
       Get-Help -Name "$($MyInvocation.MyCommand.Name)"
       Exit
    }
}

In the above code, I added a simple if statement. It monitors whether or not the Help parameter is used when the function is invoked. If it is, it runs the Get-Help cmdlet against the name of the executing function—itself—using the $MyInvocation variable and then exits the function.

Clear-UserTemporaryFile -Help
NAME
    Clear-UserTemporaryFile

SYNOPSIS
    The Clear-UserTemporaryFile command ...

SYNTAX
    Clear-UserTemporaryFile [-ALL] [[-TIF] <String>] [[-TMP] <String>] [[-SIZE] <String>] [-HELP] [<CommonParameters>]

DESCRIPTION

RELATED LINKS

REMARKS
    To see the examples, type: "Get-Help Clear-UserTemporaryFile -Examples"
    For more information, type: "Get-Help Clear-UserTemporaryFile -Detailed"
    For technical information, type: "Get-Help Clear-UserTemporaryFile -Full"

At this point, some of you may already know where I’m going to go next. There was something else I had forgotten about every cmdlet and function written. They all have something in common. And what’s that? There is a way to return a command’s help using, of all things, the question mark! Take a look using the built-in Get-Verb and Get-Command cmdlets below.

Get-Verb -?
NAME
    Get-Verb

SYNTAX
    Get-Verb [[-Verb] <string[]>] [[-Group] {Common | Communications | Data | Diagnostic | Lifecycle | Other |
    Security}] [<CommonParameters>]

ALIASES
    None

REMARKS
    Get-Help cannot find the Help files for this cmdlet on this computer. It is displaying only partial help.
        -- To download and install Help files for the module that includes this cmdlet, use Update-Help.
        -- To view the Help topic for this cmdlet online, type: "Get-Help Get-Verb -Online" or
           go to https://go.microsoft.com/fwlink/?LinkID=2097026.
Get-Command -?
NAME
    Get-Command

SYNTAX
    Get-Command [[-ArgumentList] <Object[]>] [-Verb <string[]>] [-Noun <string[]>] [-Module <string[]>]
    [-FullyQualifiedModule <ModuleSpecification[]>] [-TotalCount <int>] [-Syntax] [-ShowCommandInfo] [-All]
    [-ListImported] [-ParameterName <string[]>] [-ParameterType <PSTypeName[]>] [<CommonParameters>]

    Get-Command [[-Name] <string[]>] [[-ArgumentList] <Object[]>] [-Module <string[]>] [-FullyQualifiedModule
    <ModuleSpecification[]>] [-CommandType {Alias | Function | Filter | Cmdlet | ExternalScript | Application | Script
    | Configuration | All}] [-TotalCount <int>] [-Syntax] [-ShowCommandInfo] [-All] [-ListImported] [-ParameterName
    <string[]>] [-ParameterType <PSTypeName[]>] [-UseFuzzyMatching] [-UseAbbreviationExpansion] [<CommonParameters>]

ALIASES
    gcm

REMARKS
    Get-Help cannot find the Help files for this cmdlet on this computer. It is displaying only partial help.
        -- To download and install Help files for the module that includes this cmdlet, use Update-Help.
        -- To view the Help topic for this cmdlet online, type: "Get-Help Get-Command -Online" or
           go to https://go.microsoft.com/fwlink/?LinkID=2096579.

This means I don’t even need a Help parameter. PowerShell already has this covered, and here’s the proof.

Clear-UserTemporaryFile -?
NAME
    Clear-UserTemporaryFile

SYNOPSIS
    The Clear-UserTemporaryFile command ...

SYNTAX
    Clear-UserTemporaryFile [-ALL] [[-TIF] <String>] [[-TMP] <String>] [[-SIZE] <String>] [-HELP] [<CommonParameters>]

DESCRIPTION

RELATED LINKS

REMARKS
    To see the examples, type: "Get-Help Clear-UserTemporaryFile -Examples"
    For more information, type: "Get-Help Clear-UserTemporaryFile -Detailed"
    For technical information, type: "Get-Help Clear-UserTemporaryFile -Full"

Edit: We know that we were able to use this PowerShell to return the help for our function,

Get-Help -Name "$($MyInvocation.MyCommand.Name)"

but we could’ve just as easily done this:

Invoke-Expression -Command "$($MyInvocation.MyCommand.Name) -?"

The first option invokes the Get-Help cmdlet against our function. The second option invokes the same function that’s already being invoked, adding a -? to the end of the complete command.

AWS Vendor-Written Generated Code


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on September 13, 2019.


Sometimes you read an error message, or in this case, come across some vendor-written code that you can’t find anywhere else on the Internet. It’s been years, but once PowerShell generated an error I had never seen. I couldn’t find a hit for it online anywhere either. I felt that once I had figured out the problem behind that error message, it was my duty to write about it—to help get that error message picked up by search engines, as well as my experience. I feel nearly the same way about the below code to which I was recently introduced, written by AWS, or Amazon Web Services. I’ll share it now. Just a note. This is exactly how it was found. There were no indentations. I’m not too concerned about the lack of indentations—I don’t have to stare at it each day. Perhaps it’s generated by something that may not be preserving the spacing/tabs. That’s just a guess.

try { 
Get-Service Ec2Config 
$EC2SettingsFile='C:\Program Files\Amazon\Ec2ConfigService\Settings\Config.xml' 
$xml = [xml](get-content $EC2SettingsFile) 
$xmlElement = $xml.get_DocumentElement() 
$xmlElementToModify = $xmlElement.Plugins 
foreach ($element in $xmlElementToModify.Plugin){ 
if ($element.name -eq 'Ec2SetPassword') {$element.State='Enabled'} 
elseif ($element.name -eq 'Ec2HandleUserData') {$element.State='Enabled'} 
elseif ($element.name -eq 'Ec2DynamicBootVolumeSize') {$element.State='Enabled'} 
} 
$xml.Save($EC2SettingsFile) 
} 
catch 
{ 
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File 'C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1' -Schedule 
} 
finally 
{ 
New-Item -Path HKLM:\Software\Amazon -Name WarmBoot 
Invoke-Expression -Command:'shutdown.exe /s /t 0' 
} 
while ($true){ 
}

It’s rare that I ever manually launch an AWS EC2 instance (a virtual server). Well, I was doing that recently for some quick testing and my UserData PowerShell script was not landing in the C:\Program Files\Amazon\Ec2ConfigService\Scripts\UserScript.ps1 file on my Windows Server 2012 R2 instance, as it should have been. I was doing something wrong and it wasn’t clear to me what, soon enough. Before we get to that, let’s discuss what we have here. We have a try-catch language construct. I know from my AWS experience that most of what’s going on in the try block was done for Windows Server 2012 R2 and newer. I also know that what’s in the catch block is how we ensure UserData is enabled in Windows Server 2016 and later. AWS couldn’t take my UserData and drop it on this instance. Instead, I got this code in its place. Ugh.

This code also includes the finally block. That code is run regardless of whether the try or catch block fired. The code creates a value in the Windows Registry and then restarts the computer using Invoke-Expression—interesting choice. It’s always fun to see vendor code. It closes with an empty While language construct. While $true is $true, this While loop will run—and do absolutely nothing. It will do that successfully, however.

Again, my UserData PowerShell wasn’t getting into this UserScript file. It was, however, available by “navigating” to 169.254.169.254 on the EC2 instance.

Invoke-RestMethod -Uri http://169.254.169.254/latest/user-data
# >> Add function to memory.
Function Set-SystemForNextRun {
...
    Set-SystemForNextRun -CodeSectionComplete 2
} # End If-ElseIf.

The problem was that my code didn’t have the begin and end PowerShell tags. In order for the UserScript.ps1 file to be populated with my code and not this code from Amazon, I needed to ensure I was including everything required by me. It seems like something in the AWS Management Console (the web front end) could’ve notified me after looking at my code, but before moving to the next step in manually building my instance. Or, be even less helpful, and additionally write something else in the UserScript.ps1 file. They could’ve just started their code with a comment to tell me I didn’t follow the directions. I’ve used UserData in CloudFormation; I know these tags are required.

<#
This doesn't look right, does it?
Did you remember to use script or
powershell start and end tags?
#>

Anyway, once I enclosed my PowerShell in the proper tags, it worked, and I moved on. And by moved on I mean, I found another problem to consume me—as is typical in this industry. It should’ve look liked this from the start. Ugh.

Invoke-RestMethod -Uri http://169.254.169.254/latest/user-data
<powershell>
# >> Add function to memory.
Function Set-SystemForNextRun {
...
    Set-SystemForNextRun -CodeSectionComplete 2
} # End If-ElseIf.
</powershell>

Things changed between Windows Server 2012 R2 and 2016, 2019. I’m not exactly sure where the UserData code ends up in the newer OS if it even does end up in a file on disk as it has previously.