Category Archives: Quick Learn

Practical examples of PowerShell concepts gathered from specific projects, forum replies, or general PowerShell use.

Change Prompt on Module Import

To me, my PowerShell prompt is quite important. I’ve written about it nine times already. While I’m not going to write about it again, so much, I am going to focus on an updated prompt I’ve created for a recent project. I couldn’t help but take a few things into this project from my prompt, and that’s why that’s been mentioned.

I recently received a screen capture from someone running into a problem using one of the tools I’ve written. Sure, it needs some error checking, I won’t deny that, but it was a very obscure and unforeseen problem. You know, how we often find out about error conditions.

This tool, or function, can be run on an Amazon Web Services EC2 instance within a project, to determine the status of its partner EC2 instance. When it works, it returns the status of the other instance, to include things like running, stopping, stopped, etc. There’s also a couple other companion functions that can start and stop the partner instance. The second of the two machines has very high specifications, and so we ask that our users shut down those secondary instances when they’re not running experiments. They pricey.

The problem is that when I look at this error, the prompt doesn’t tell me enough. I can only tell it’s PowerShell — the PS in the prompt — and the current path — some of which has been hidden in this first image. I want more information without having to ask the user, and so I’ve added that in.  Here’s the default prompt up close.

The new prompt includes the username (to the left of the @), the computer name (to the right of the @), the project name (while it’s not in this example, it’s normally a part of the computer name), the path, and whether the user is an admin (#) or not ($). Now, when I receive a PowerShell screen capture, I already have a few of my first questions answered.

All of the functions, such as the one that was run that generated this error, are a part of the same PowerShell module. There’s somewhere near 20 of them so far, and I keep finding reasons to add new ones. If you don’t know, creating a PowerShell script module that includes a module manifest file (a .psd1), allows one to include scripts that execute just before a module is imported. This, whether the module is imported manually using Import-Module, or by invoking a function from within the module, when the module hasn’t yet been imported.

Let’s take a look at my SetPrompt.ps1 script file that executes when my PowerShell module is imported.

Function prompt {
    # Determine Admin; set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'} Else {$Symbol = '$'
    }

    # Create prompt.
    "[$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $($executionContext.SessionState.Path.CurrentLocation)]$Symbol "
} # End Function: prompt.

With this script in place and a ScriptsToProcess entry in the module’s manifest file, pointing to this file, I can be ensured that the user’s prompt will change the moment my module is imported. From here on out, I can rest assured that if a user — of these machines at least — sends us a screen capture that it’ll include relevant pieces of information I would have had to ask for, had this prompt not been in place.

There’s a final thought here. When the user is done with this PowerShell module, they’re still going to have this prompt. In my case, it’s perfectly suitable because my users won’t be in PowerShell, unless they’re issuing commands from the module. At least, I can’t imagine they will be. The only other thoughts I’ve had about this “problem” would be to (1) teach users to remove the module, and have code in the prompt monitor whether the module is loaded or not, and revert the prompt if the module is removed, or (2) revert the prompt if a command outside of my module is invoked.

That’s it for today. I don’t have it shown here, but it’s neat to see the prompt alter itself when the module is loaded. Something to keep in mind, if you find yourself in a similar situation.

Update: I was annoyed that $HOME, or C:\Users\tommymaynard, was being displayed as the full path, so I made some additional modifications to the prompt that’s being used for this project. It’ll now look like this, when at C:\Users\tommymaynard (or another user’s home directory).

Here’s the new prompt function, to include a better layout.

Function prompt {
    # Determine Admin; set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'
    } Else {
        $Symbol = '$'
    }

    If ((Get-Location).Path -eq $env:USERPROFILE) {
        $Path = '~'
    } Else {
        $Path = (Get-Location).Path
    }

    # Create prompt.
    "[$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Path]$Symbol "
} # End Function: prompt.

Self-Destruction Script

As a part of some work I’m doing, I’ve decided that I need a specific script to do some final configuration for me. More to come on that in time, perhaps. The point here, however, is that a PowerShell script completes my final configuration, and when this final configuration is complete, I have no need for my script any longer. Can a PowerShell script delete itself? Sure it can.

How does a script delete itself? Easy. It’s a single line, really. However, I have a mildly more interesting example. Before you take a look at the included gif — this really does lend itself to a visual — here are the contents of my self-destruction script. It echos an indication that the script will self-destruct. At this point, it begins a countdown in seconds, from 5 to 1, before removing itself — the script file — from the file system. Take a look, and maybe, just maybe, you’ll find yourself in need of something like this sometime, too.

Write-Output -InputObject 'This script will self-destruct in 5 seconds.'

5..1 | ForEach-Object {
    If ($_ -gt 1) {
        "$_ seconds"
    } Else {
        "$_ second"
    } # End If.
    Start-Sleep -Seconds 1
} # End ForEach-Object.

Write-Output -InputObject 'Self destruction.'

# Here's the command to delete itself.
Remove-Item -Path $MyInvocation.MyCommand.Source

And here’s, the visual representation. It’s a bit small, but you should be able to determine what’s happening. The script executes on the left, and once the countdown is over, DeleteSelfScript.ps1 is removed on the right. It’s deleted itself.

Enjoy the week, and learn something new!

Read-Host Prompt inside Hash Table

I’m working with Plaster and in doing so, have a few hash tables in my workspace. Suddenly, and out of nowhere, I had a thought: Can I put a Read-Host command, as the value of a key-value pair inside of a hash table? Yes, but before we get too far into this, here’s a simple hash table example without any Read-Host commands.

$Params = @{
    meal = 'lunch'
    food = 'Ramen'
    drink = 'Tea'
}
$Params

Name                           Value
----                           -----
drink                          Tea
food                           Ramen
meal                           lunch

Okay, that was simple: Choose a variable name, prefix it with a dollar sign, create the hash table structure as @{}, and then add key-value pairs as <key> = <value>.

Now, on to a Read-Host example. Read-Host is a cmdlet that can be used to interactively prompt a user for a value. It’s not always preferred, or the right to do, but there are times when it can be useful, and where its use is acceptable. In the next example, we’ll create a single key-value pair ourselves, then we’ll prompt the user for a value to assign to the Number key. As you will also determine, when prompted, the word “five” is entered, and sure enough, the entry ends up as the value for the Number key in our hash table.

Remove-Variable -Name Params
$Params = @{
    Color = 'red'
    Number = Read-Host -Prompt 'Enter spelled out number'
}
$Params
Enter spelled out number: five

Name                           Value
----                           -----
Number                         five
Color                          red

This example was one of those “can I do this moments?” Sure, I was able to return the $Params hash table with the correct value in the Number key after having been prompted for that value. This occurred, as the hash table was being defined and built. I didn’t even know if that would work, but now we both know that it does.

In the next example, we try something even more obscure, but as suspected, it doesn’t work. We first assign the Random key the value of a Read-Host prompt. As we’ve seen, that does work. Next, while still creating keys and values, we take the value assigned to the Random key in the $Params hash table and attempt to assign it to the Number key, as well. Again, this doesn’t work. As suspected, we can’t use the $Params hash table until it’s done being created. It’s not a shock, but it was worth a try, of course.

Remove-Variable -Name Params
$Params = @{
    DayOfWeek = 'Friday'
    Random = Read-Host -Prompt 'Enter spelled out number'
    Number = $Params.Random
}
$Params
Enter spelled out number: three

Name                           Value                                                                                                                             
----                           -----                                                                                                                             
Random                         three
Number
DayOfWeek                      Friday

Even if this did work, I’m not sure how this would ever really serve a purpose, but it’s always worth trying something even if doesn’t seem plausible. And if it did work, I don’t expect that we’d even need duplicate values in different keys anyway, and that’s all we’d be doing. But all that said, we absolutely can use Read-Host to assign a value to a key in a hash table, as it’s being created.

Alright, that was it. Enjoy the week!

Array (Not Hash Table) Splatting


Welcome to the 298th post on tommymaynard.com. It’s countdown to 300!


I’ve been interested… okay fine, consumed with PowerShell for five, maybe six years. At some point along the way, I learned how to splat. That’s where you create a hash table of keys and values (think parameters and parameter values), and include those when invoking a cmdlet or function. Here’s an example of not splatting, and splatting, a hash table.

Get-Process -Name firefox -FileVersionInfo -OutVariable FirefoxProcess

The above example shows how we might typically write this command. The below example uses splatting. To be more specific, it uses hash table splatting.

$Params = @{
    Name = 'firefox'
    FileVersionInfo = $true
    OutVariable = 'FirefoxProcess'
}
Get-Process @Params

Well today, many years since I started this journey, I find myself suddenly exposed to array splatting. Huh? You might be able to guess how this works. We’re still going to issue a command, and we’re still going to use the at sign (@) in front of our variable name, but this time, our variable is going to contain an array, not a hash table. Let’s take a look at a few more examples.

Get-ChildItem -Path '.\Documents\test\' -Filter '*.rtf'
Get-ChildItem '.\Documents\test\' '*.rtf'

In the first above Get-ChildItem example, you can see how we might typically issue this command. In the example below that one, we’re running the same above command; however, we’re doing so without including the Path and Filter parameter names. Because this still works, we know these two parameters are positional (or because we read the help first). This means they can be used correctly without the need to include their matching parameter names. Because they’re positional, we can use array splatting. Take a look at the final below example.

$Params = '.\Documents\test\','*.rtf'
Get-ChildItem @Params

Just a quick reminder. We should always use parameter names in code we expect to been seen, or used, more than once, such as code inside functions and scripts. It’s good to know about this, but it might be something to avoid. Using hash table splatting may still be the way to go, as it keeps our parameter names around.

Wrapper Function with Pause


Welcome to the 296th post on tommymaynard.com. It’s countdown to 300!


I’ve recently written a wrapper function. Its purpose is to allow a user the ability to invoke four different functions, by only invoking one — the wrapper. As of part of this assignment, I wrote out a quick function to do some testing. I wanted to determine whether or not I’ll allow the wrapper function the ability to pause between the invocation of each wrapped function. At this point, I think that will be included.

So it’s written down somewhere, here’s what I quickly jotted to test this scenario.

Function Test-Pause {
    [CmdletBinding()]
    Param (
        [switch]$Pause
    )

    'First String'
    If ($Pause) {Pause}
    'Second String'
    If ($Pause) {Pause}
    'Third String'
    If ($Pause) {Pause}
    'Fourth String'
}

If the Pause parameter isn’t used, the function never stops. In my test, it just works top to bottom, echoing out each string stored inside the Test-Pause function. That works as expected!

PS > Test-Pause
First String
Second String
Third String
Fourth String

If the Pause parameter is included, however, it will pause after each time a string is written. Once the user presses Enter, it continues until it’s required to pause again, providing it is.

PS > Test-Pause -Pause
First String
Press Enter to continue...:
Second String
Press Enter to continue...:
Third String
Press Enter to continue...:
Fourth String

That was it. I just wanted a place to store this, just in case I find myself in a situation where it might be useful again outside my newest function. Just remember, if you do something like this in an automated fashion, don’t include the Pause parameter: It’s going to require a human that way.

An Array of Hash Tables


Welcome to the 293rd post on tommymaynard.com. It’s countdown to 300!


If you’ve ever looked into Pester and TestCases, then maybe you’ve seen what I’m about to mention. While today’s post has nothing to do with Pester, this feature in Pester uses an array of hash tables. Neat, right? While adding a couple New-PSDrive commands to my PowerShell profile, I moved from this first option,…

$Param = @{
    Name = 'IDT'
    Root = '\\tommymaynard.com\Windows\Internal\Users\tommymaynard'
}
New-PSDrive @Param -PSProvider FileSystem | Out-Null

$Param = @{
    Name = 'IDI'
    Root = '\\tommymaynard.com\Windows\Internal'
}
New-PSDrive @Param -PSProvider FileSystem | Out-Null

…to something else.

While the use of splatting is a bit more grown-up than creating long, multi-line wrapping commands, there’s no reason why we should be executing the same command — New-PSDrive, in this instance — outside of a looping construct. That was my thought today, anyway. If we need to invoke the same cmdlet or function more than once, it should only be entered into our code, once. It is possible there’s an exception here, but for the most, I stick to this belief.

But how do I combine multiple hash tables in to looping construct? Well, my solution had everything to do with my quick remembrance of Pester and TestCases. For fun, we’ll link to a quick example from a Pester issue I recently created in GitHub. This offers us a good example. See the third It block in the Pester example in number 4 (“Current Behavior”). Do you see that? It’s an array of hash tables. Perfect example for what I needed.

As we create our hash tables, we need to ensure they’re being added to an array. Then we can loop through our array elements with ForEach, or ForEach-Object. I’ll include examples for both. First, however, let’s create our array of hash tables.

$HashTableArray = @(
    @{
        Name = 'IDT'
        Root = '\\tommymaynard.com\Windows\Internal\Users\tommymaynard'
    },
    @{
        Name = 'IDI'
        Root = '\\tommymaynard.com\Windows\Internal'
    }
)

And now, let’s ensure what we have, is where we want it, and want we want.

PS > $HashTableArray

Name                           Value
----                           -----
Root                           \\tommymaynard.com\Windows\Internal\Users\tommymaynard
Name                           IDT
Root                           \\tommymaynard.com\Windows\Internal
Name                           IDI

As mentioned, there’s a couple way to loop though these. One is using a Foreach construct — which is the first below example — and one is using the ForEach-Object cmdlet. They’re both below, and listed in the respective order in which they were discussed. So yes, if you’re calling the same command outside of looping construct, you need to consider how to do better, if you can. There’s no reason in my instance that New-PSDrive should be in a our code more than once.

Foreach ($HashTable in $HashTableArray) {
    New-PSDrive @HashTable -PSProvider FileSystem | Out-Null
}

$HashTableArray | ForEach-Object {
    New-PSDrive -Name $_.Name -PSProvider FileSystem -Root $_.Root | Out-Null
}

If you didn’t notice, I’ve piped my New-PSDrive commands to Out-Null. This was done in order to remove the output that would’ve otherwise been produced.

Add a Single Space Between Characters


Welcome to the 292nd post on tommymaynard.com. It’s countdown to 300!


Even though we’ve been doing this PowerShell thing awhile, we don’t always write our own solutions. You, like me, just might go looking for someone else’s solution, before spending a few minutes of your own time, writing a solution. I’m guilty. That’s said, there’s nothing wrong with it. Seeing what other people have done is often beneficial to our own solutions. It’s part of why I’ve been blogging since mid 2014; take from me, what helps for you.

There’s a very small function I’m currently writing that needed a feature. That feature, which I couldn’t locate well enough anywhere else, is something I wrote myself. Now that I have my solution, after a small amount of time yesterday, I thought I share it for someone else some day.

I needed to accept a string with or without spaces in it, and ensure each character in the string had a space between it when I was done. This ‘ a bcd ef 12 34 gh i’ needed to be come this ‘a b c d e f 1 2 3 4 g h i’. I opted first, to remove all the spaces, whether there were any or not, and then add a space between each character in the string. In case it’s helpful, I’ve include the few lines of PowerShell I wrote out to get this to suitably work for me. Have a look, and take it if you want it.

Remove-Variable -Name String,NewString -ErrorAction SilentlyContinue; Clear-Host

$String =  ' a          ll the d                ogs are so l o u d 1     2 3'
$String
Pause

$String = $String.Replace(' ','')
$String
Pause

$String.ToCharArray() | ForEach-Object {
    $NewString += "$_ "
}
$NewString

Now, let’s run it.

 a          ll the d                ogs are so l o u d 1     2 3
Press Enter to continue...:
allthedogsaresoloud123
Press Enter to continue...:
a l l t h e d o g s a r e s o l o u d 1 2 3

Perhaps you can tell what I might’ve been dealing with when I was writing this. I was at home. My wife was hosting a Girl Scout get together, and the dogs were being just a touch annoying.

The ScriptsToProcess and RequiredModules Order

Recently, I wrote this:

Even more recently, I wrote a usable fix.

Before we get there, however, let’s make sure my Tweet makes sense. First off, if you don’t know already, you need to be aware that there’s an optional file called the module manifest file that we can include alongside a script module file (a .psm1). It’s a .psd1 file and its purpose in life is to help define additional information — metadata — about a script module.

In addition to telling us about the module (the author, description, version, etc.), it can do other things for us. This includes requiring a specific PowerShell host program, requiring a specific version of PowerShell, requiring specific modules are imported when our module is imported, and also running PowerShell scripts, before our module is imported. You don’t have to use a module manifest file when you create and a use a module, but there’s so much to gain from doing so (and it’s super easy [see New-ModuleManifest]).

My problem here is that the RequiredModules section — an entry in our module manifest file — is checked before any of the scripts are run that assist to set up the environment before the module is done loading. This means that I was unable to install a PowerShell module via these scripts (called ScriptsToProcess) before RequiredModules inspected the system for the modules in which our module is dependent. Too bad. To me, and in this instance at minimum, these two module manifest entries run in the wrong order. Had ScriptsToProcess run first, I would have been able to install a PowerShell module before the required modules’ dependencies were evaluated.

To get this to work as desired, required a workaround. I thought I’d take a minute and share what I’ve done. One, we have a script module — a .psm1 file — and a module manifest — a .psd1 file. We also, have a second .psd1 file. This is key.

The first .psd1 file does not require any modules; it does not have a dependency on the system already having specific modules in place. Here’s that entry in our first, or initial, module manifest file. Do notice that RequiredModules is commented out, and therefore not read, when this file is parsed.

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

The next section of interest in our first .psd1 file, is ScriptsToProcess. These are standalone scripts that execute prior to our module importing. Do notice that ScriptsToProcess can accept multiple scripts. This means I can run multiple scripts, one right after another, in order that I don’t have all my code in one big script file. If you’re writing functions and not scripts, you get this. Smaller pieces of code are easier on you.

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = '.\ScriptsToProcess\InstallADDelegationModule.ps1','.\ScriptsToProcess\ReplacePsd1File.ps1','.\ScriptsToProcess\ReimportModule.ps1'

Again, we have two module manifest files for our one, script module. The first script in the above list installs the ADDelegation PowerShell module onto our system. Remember, if our first manifest file required this module, we wouldn’t be able to get it installed to our system with ScriptsToProcess. With an initial, only used once, .psd1 file, we can. The second script, copies our second manifest file over the top of the one that currently executing during this first module import. Not to worry, the manifest is only read when the module is initially imported. The updates are not going to matter just yet. That said, finally, the last ScriptsToProcess script simply imports our module again, using the Force parameter. This parameter imports a module even if it’s already been imported. In doing this second import on our module, the second module manifest becomes active.

Before we consider that our module has been imported again with an updated manifest, we need to discuss the last section in our first manifest. It’s the FunctionsToExport section. Notice that during the first import of our module, there aren’t any functions being exported. Exported functions are how functions are added to our PowerShell session for use. This hardly matters, however, since the last ScriptsToProcess script, discussed above, imports our module again. Since we forcibly import our function again with the second manifest file in place, it doesn’t matters what we do or don’t import in the first run; it becomes of no importance almost immediately. Even so, I’m keeping this hold over, because there’s no reason to do any extra work on the first import, such as importing functions that would never be used.

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @()

Hopefully I’ve explained things well enough that you’ve been able to follow along so far. Our second module manifest file, that we copied over the first manifest in the second ScriptsToProcess file, has some changes as you can see below. In this manifest file, we do require a couple modules. Remember, we installed the ADDelegation module when we were using the first manifest file. We also have a dependency on the ActiveDirectory module, but I’m expecting that is already in place by my users (for now).

# Modules that must be imported into the global environment prior to importing this module
RequiredModules = 'ActiveDirectory','ADDelegation'

Next, we have only a single ScriptsToProcess script. While a ScriptsToProcess script isn’t always a necessity, or a requirement, all this script does is verify I have all the CSV files I need for the functions in our module.

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = '.\ScriptsToProcess\TestForCsvFiles.ps1'

And lastly, we include all the functions we need exported into the PowerShell session for those using our module.

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'New-DomainDelegationPrep','New-DeptADGroupAndRole','New-OnPremExchRole','New-O365ExchRole'

Hope you enjoyed the weekend, but it’s time to start the week again. You may have never seen it before, but we do have a way, albeit a workaround, to install modules and make them required. It takes a little extra work, but it’s doable. In my case, it’s worth the extra work.

Write-Verbose and -Warning Template

I’m pretty serious when it comes to ensuring the use of Write-Verbose and Write-Warning to their fullest potential in my functions. With that in mind, I’ve come up with a standard way in which I write these types of messages to the screen in warning conditions. In order that I don’t have to track down this code from an actual, already written function the next time I need it, I’ve opted to drop an example, right here. Perhaps it’ll be useful for you, as well.

The idea is that I assign the message I want to convey to a variable, and then use it in the Write-Verbose and Write-Warning commands. That whole use-a-variable concept isn’t what I’m here to discuss; I kind of expect that you put your strings into variables when you plan to use them more than once. The idea is that you can see the full structure I use to inform my users in these types of situations. Have a look at the below example.

...
If (Test-Path -Path $CsvPath) {
    ...
} Else {
    $Warning = "Unable to locate the ""$CsvPath"" path."
    Write-Verbose -Message "$BL Preparing and displaying a warning message."
    Write-Warning -Message $Warning
    Write-Verbose -Message "$BL WARNING: $Warning"
    Write-Verbose -Message "$BL Breaking out of the current function [$CmdType`: $CmdName]."
    break
} # End If.
...

Don’t mind the $BL, $CmdType, and $CmdName variables, as these have everything to do with the advanced function template I’ve written and use for just about everything. They’re not required for this basic code structure.

This simple Warning/Verbose design ensures that Write-Warning messages are captured by Write-Verbose. Once a warning message hits the PowerShell host program, it’s mostly lost. By using the same variable with Write-Verbose, I’m able to do other things with it. As my advanced function template can send Write-Verbose messages to log files, I can therefore ensure warning messages are logged by my functions. This is key.

Anyway, it’s nothing groundbreaking, but I am done looking for an example of this in something I’ve already written, in order to use it in something I’m writing. The two following images are the same example, both in the ISE (Integrated Scripting Environment) and in Visual Studio Code. You can see that when the Verbose parameter is used, we get the warning information included in a verbose message. Therefore, with the use of my advanced function template, I can ensure my warning message makes its way into a log file, if that’s what’s wanted.

And yes, it should’ve included “to,” as in “Unable to locate the…” Ugh, I’m not going to bother recreating these screen captures, even though I’ve updated the above code example.

Of course, all this got me thinking. In some recent projects, I’ve been doing something a bit differently. I’ve been piping my string straight to ForEach-Object, where both Write-Warning and Write-Verbose are executed. Therefore, I suppose the template may end up like it looks below. I’m going to have to think about potentially making this code example my Write-Verbose and Write-Warning template.

...
If (Test-Path -Path $CsvPath) {
    ...
} Else {
    Write-Verbose -Message "$BL Preparing and displaying a warning message."
    "Unable to locate the ""$CsvPath"" path." | ForEach-Object {
        Write-Warning -Message $_; Write-Verbose -Message "$BL WARNING: $_"
    }
    Write-Verbose -Message "$BL Breaking out of the current function [$CmdType`: $CmdName]."
    break
} # End If.
...

Either way, I’ve got this template written down somewhere now, outside of a function.

Create Function from Variable Value I

If you were here Wednesday, then perhaps you already read Get the Total Parameter Count. If not, I’ll quickly tell you about it. I wanted to know how many parameters a function had whether or not those parameters were used when the function was invoked. I did this by using Get-Help against the function, from within the function. Yeah, I thought it was cleaver too, but the best part, it gave me the results I was after. If a function had three parameters, it was indicated in the function’s output. Same goes for when it had two parameters, but of course in that instance, it indicated two.

In going along with that post I had an idea. I wanted to create a way to dynamically create a function with a random number of parameters between one and ten. Then, I could prove my code was working. Sure, I could’ve used Pester, but I was after making this work — this whole, create a function with a random number of parameters thing. I needed to figure out how to get the code for a function, stored inside a variable, into an actual PowerShell function. That might’ve been confusing, but the example code in this post will probably prove helpful.

I’ll show you what I started experimenting with for discovery purposes, and then we’ll jump into the code that actually creates my function with a random number of parameters in a soon to be released second part to this post.

First, we’ll start with a here-string variable assignment. Using a here-string allows us to include multiple line breaks within our variables. As you should be able to see, the value of $FunctionCode below is the code that makes up a simple function. It includes the function keyword, a function name, an open curly brace, the function’s actual code — there’s three lines of it — and a closing curly brace, as well.

$FunctionCode = @'
Function Show-Info {
    '*****'
    'This is function Show-Info.'
    '-----'
}
'@

As expected, when I echo my variable’s value, I get back exactly what I put into it.

PS > $FunctionCode
Function Show-Info {
    '*****'
    'This is function Show-Info.'
    '-----'
}

Now, for the fun part. I’ll include all my code and then we can discuss it line by line below.

$FunctionCode = $FunctionCode -split '\n'
$FunctionCodeCount = $FunctionCode.Count - 2
$FunctionCode = $FunctionCode[1..$FunctionCodeCount]
$FunctionCode = $FunctionCode | Out-String

The first of the above four lines assigns the variable $FunctionCode the value stored in $FunctionCode after we split it on each new line. The second of the four lines creates a $FunctionCodeCount variable, assigning it the number of lines in $FunctionCode after having subtracted 2 from its value. See if you can figure why it’s two…

The third line reassigns $FunctionCode again. In this line we only return the contents of the function. This means it doesn’t return the first line of the function, which includes the function keyword, the function name, and the open curly brace. It also will not include the closing curly brace at the bottom. Our final line reassigns the $FunctionCode for a third time, taking its current value and piping that to Out-String. This will help us ensure we add back in our line breaks.

Before we create our Show-Info function, let’s take a look at the $FunctionCode variable value now.

PS > $FunctionCode
    '*****'
    'This is function Show-Info.'
    '-----'

Using Set-Item, we’ll create our function called Show-Info regardless of whether or not it already exists. That’s the difference between New-Item and Set-Item (and most New vs. Set commands). New-Item will only work if Show-Info doesn’t already exist, while Set-Item will work even if the function already exists. It if doesn’t, it’ll act like New-Item and create the function.

Set-Item -Path Function:\Show-Info -Value $FunctionCode

And finally, entering Show-Info invokes the newly created function.

PS > Show-Info
*****
This is function Show-Info.
-----

Okay, with all that out of the way, we’re going to hit the pause button. Be sure you get what’s happening here, because we’ll pick up on this post in a very soon to be released follow-up that includes the rest of what we’re after: creating a function with a random number of parameters and testing we can calculate that number correctly. If you see an area in which I can improve this, please let me know!

Before we sign off though, let me include all the above code in a single, below code block. That may end up being helpful to one of us.

Remove-Variable -Name FunctionCode,FunctionCodeCount -ErrorAction SilentlyContinue

$FunctionCode = @'
Function Show-Info {
    '*****'
    'This is function Show-Info.'
    '-----'
}
'@

$FunctionCode = $FunctionCode -split '\n'
$FunctionCodeCount = $FunctionCode.Count - 2
$FunctionCode = $FunctionCode[1..$FunctionCodeCount]
$FunctionCode = $FunctionCode | Out-String

Set-Item -Path Function:\Show-Info -Value $FunctionCode

Show-Info