PSMonday #12: Monday, July 18, 2016

Topic: PowerShell Remoting Continued

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

Last week was our initial discussion of PowerShell Remoting (PS Remoting). As it’s so necessary, I figured we’d keep with it for at least another week. Previously we used Invoke-Command to run a single command on a few remote computers. Let’s start today, by switching this around and sending a few commands, to a single, remote computer.

In this example, we start in section 1 (listed as <#1#>), by echoing the computer’s name, just as we did in last week’s PSMonday. In section 2, we assign the results of two mildly different commands to the $Folders and $FolderCount variables. $Folders will store the names of the directories on the current root drive, separated by commas. $FolderCount will store the number of directories on the current root drive. Once those variables are populated, we then echo their values, as part of two separate strings, in section 3.

Invoke-Command -ComputerName MEMSRV01 -ScriptBlock {
    <#1#>"**** $env:COMPUTERNAME ****"

    <#2#>$Folders = (Get-ChildItem -Path \ -Directory) -join ', '
    $FolderCount = (Get-ChildItem -Path \ -Directory).Count

    <#3#>"Root Drive Folders : $Folders"
    "Root Drive Folder Count : $FolderCount"
}

**** MEMSRV01 ****
Root Drive Folders : inetpub, PerfLogs, Program Files, Program Files (x86), support, Users, Windows
Root Drive Folder Count : 7

There is an option to assign the script block to a variable, and then use the variable as part of the Invoke-Command command. This might make the code a bit more readable. Here’s an example.

$ScriptBlock = {
    "**** $env:COMPUTERNAME ****"

    $Folders = (Get-ChildItem -Path \ -Directory) -join ', '
    $FolderCount = (Get-ChildItem -Path \ -Directory).Count

    "Root Drive Folders : $Folders"
    "Root Drive Folder Count : $FolderCount"
}

Invoke-Command -ComputerName MEMSRV01 -ScriptBlock $ScriptBlock

Either way you do it, these script blocks still have the potential to become rather lengthy. In this case, we might consider the -FilePath parameter. Instead of the -ScriptBlock parameter and a script block, Invoke-Command will read in the contents of a .ps1 file and run that against the remote computer.  Here’s the same code as above, that’s been saved as C:\ICMTestFile.ps1.

psmonday-12-monday-july-18-2016-01

With this file in place, we’ll rerun the previous commands using the -FilePath parameter and the path to the file as the parameter value.

Invoke-Command -ComputerName MEMSRV01 -FilePath C:\ICMTestFile.ps1

**** MEMSRV01 ****
Root Drive Folders : inetpub, PerfLogs, Program Files, Program Files (x86), support, Users, Windows
Root Drive Folder Count : 7

As you begin to use Invoke-Command and want to run more and more commands, keep this option in mind. Now, with that said, I think it’s important to know that I don’t personally see the -FilePath option used much. Consider why. When writing your own scripts and functions, or reading someone else’s, you’ll have to go outside of the script or function file to view what Invoke-Command is doing, and that could become tiresome, and difficult when troubleshooting.

Linux Prompt on Windows – Part III

Maybe, just maybe, there’s someone else using my “Linux” prompt. Just in case there is, I thought to share the most recent additions. Here’s the first two posts in order, if you’re interested in reading those: Quick Learn – Duplicate the Linux Prompt and Quick Learn – An Addition to the Linux PowerShell Prompt.

The changes are extremely minor. It now includes the drive in the WindowTitle. There was never an indication of what drive you were on (C:\, AD:\, WSMan:\, Env:\, etc.). I’ve also wrapped the prompt inside square brackets. I didn’t do this initially because I didn’t want it to interfere with the way the prompt changes when inside an interactive PS Remoting session. I’m over that; it works just fine. Anyway, the code is below for your testing pleasures. Dump it in your profile to keep it around for good.

If you have questions why I’ve done certain things, then you might want to visit the first two posts linked above. Below the prompt code, I’ve included an image, so you can see it for yourself. If you try the prompt, be sure to change your location within the file system into a nested directory or two, so you can see the prompt update accordingly. It’s a thing of beauty; it really is.

Function Prompt {
	(Get-PSProvider -PSProvider FileSystem).Home = $env:USERPROFILE

	# Determine if Admin and set Symbol variable.
	If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
		$Symbol = '#'
	} Else {
		$Symbol = '$'
	}
	 
	# Write Path to Location Variable as /.../...
	If ($PWD.Path -eq $env:USERPROFILE) {
		$Location = '~'
	} ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
		$Location = $PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/'
	} Else {
		$Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
	}

	# Determine Host for WindowTitle.
	Switch ($Host.Name) {
		'ConsoleHost' {$HostName = 'ConsoleHost'; break}
		'Windows PowerShell ISE Host' {$HostName = 'ISE'; break}
		default {}
	}

	# Create and write Prompt; Write WindowTitle.
	$Prompt = "[$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Location $Symbol]"
	$Host.UI.RawUI.WindowTitle = "$HostName $Prompt $((Get-Location).Drive):\"
	"$Prompt "
}

an-addition-to-the-linux-powershell-prompt-II-01

“Get Files” to a Remote Computer

I was a little quiet last week, but vacation can do that. I spent a week in Minneapolis, Minnesota in order to visit my wife’s family. Although away from my computer nearly all that time, PowerShell was still on my mind.

Not long before I left for vacation, I replied to a TechNet forum post. In that post, a user was PS Remoting to a computer, and from that computer trying to connect to another computer to pull down some files. Maybe you’ve been down that path before, or know someone that has.

Typically the user will use Invoke-Command to run commands on a remote computer. One of the commands will attempt to reach out to a network location and download some files. It sounds straight forward enough, but the part they forget, or aren’t aware of, is the inability to delegate your credentials (the user name and password) to the location where the files reside. This inability to delegate credentials to a remote computer, from your already remote connection to a computer, is called the second hop, or double hop, problem. It’s by design, as we don’t want repetitive credential delegation from machine to machine, especially if the credentials are being used maliciously, which is basically what would happen if this delegation were allowed.

When faced with this problem, there are three thoughts that I typically have:

The first though is to copy out the file(s) to the remote computers before the PowerShell Remoting connection. Then, when you need the files on the remote computer, they are already in place. I’ve recommend this several times over the years. Think of this file copy as a prestage for the work your remote command, or commands, will complete using those files.

The second thought is CredSSP, and it should be avoided at all costs whenever possible. Once set up, it allows you to approve the credential delegation from your remote computer to the remote location where you want to get the files. It sounds wonderful, but as of this writing, it is still includes some security concerns.

The third thought is to use an endpoint that runs under a different account. It requires that the endpoint you connect to — that’s the thing that accepts your incoming PowerShell Remoting connection — to run as a user other than the connecting user. When set up, or edited, to do this, the endpoint isn’t running as you, when you connect, and therefore, can delegate its own credentials to the location where your files live. This eliminates the second hop, as the credentials you used to connect to the remote computer aren’t used to get to the location of the file(s) you want to copy down.

Before I left for vacation, I came up with another idea; a fourth thought. When we use Invoke-Command we have the option to take variables with us into the remote session. In a previous post, I showed three examples of how to do this: Read it; it’s short. Now, knowing I can fill a variable with the contents of a file… and knowing I can take a variable into a remote session… did a light just go on?

As I mentioned in the forum post, my fourth thought, was mildly unconventional. It didn’t require someone to prestage (copy the files out, before the remote connection), it wasn’t unsecure (CredSSP), and it didn’t require any knowledge about creating, or editing, endpoints to use a run as account. All it required was reading some files into some variables and taking those variables into the remote PowerShell session. Here’s the example I gave them on TechNet (and here’s the forum thread). I’ll discuss the example, below the example, so you can understand the entire, logical flow.

$ConfigFile01 = Get-Content -Path C:\Config01.txt
$ConfigFile02 = Get-Content -Path C:\Config02.txt

Invoke-Command -ComputerName Server01 -ScriptBlock {
    Add-Content -Path C:\file01.txt -Value $Using:ConfigFile01
    Add-Content -Path C:\file02.txt -Value $Using:ConfigFile02

    $1 = Get-Content -Path C:\file01.txt
    $2 = Get-Content -Path C:\file02.txt

    $1
    $2

    Start-Sleep -Seconds 5

    Remove-Item -Path C:\file01.txt,C:\file02.txt
}

Beginning in the first two lines (1 and 2), I set two variables on my local computer with the content of two different files. The variable $ConfigFile01 stores the content from the file C:\Config01.txt, and $ConfigFile02 stores the content from the file C:\Config02.txt. With these variables set, we run the Invoke-Command command on Server01. Remember, the -ComputerName parameter can accept a comma-separated list of multiple computers.

Inside this script block, we do several things. Keep in mind, as you look this over, that this example isn’t doing much with this code except proving that there’s another way to “get a file to a computer.” The first two lines in the script block (lines 5 and 6), create new files on the remote computer. They create the files C:\file01.txt and C:\file02.txt. The value added to file01.txt comes from the $ConfigFile01 variable, and the value added to C:\file02.txt comes from the $ConfigFile02 variable. At this point, we have our files on the remote computer.

The rest of the script block isn’t necessary, but it helps show some of what we can do. Lines 8 and 9 put the contents of the newly created files on Server01 into variables. Lines 11 and 12 echo the contents of the two variables. On line 14, I pause the script block for five seconds. The reason I did this was so that I could test that the files were created and their contents written before the last line. In that line, line 16, we remove the files we created on the remote computer.

You can test with this example by creating C:\Config01.txt and C:\Config02.txt on your local computer with some text in each. Next, change Server01 in the Invoke-Command to one of your computers. Then, open File Explorer to C$ on the computer to which you’re going to create a PS Remoting session. In my case, I would open it to \\Server01\C$. Having this open will allow you to see the creation, and removal, of file01.txt and file02.txt.

PSMonday #11: Monday, July 11, 2016

Topic: PowerShell Remoting

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

When Windows PowerShell 2.0 was released, it came with some welcoming new features. One of the biggest, was PowerShell Remoting. Let’s discuss PS Remoting in regard to two cmdlets: Enter-PSSession and Invoke-Command.

Enter-PSSession allows for a fully interactive PowerShell remoting session. It’s as though you’re sitting at the PowerShell prompt on the remote computer, with the ability to interactively enter commands and immediately see those results. Using Enter-PSSession is considered one-to-one PS Remoting. Here’s an example of using Enter-PSSession with the explanation beneath that.

Enter-PSSession -ComputerName DC01

[DC01]: PS C:\Users\tommymaynard\Documents> Set-Location -Path \
[DC01]: PS C:\> "**** $env:COMPUTERNAME ****"
**** DC01 ****
[DC01]: PS C:\> Exit-PSSession
PS C:>

Let’s discuss each of the above lines in this example. The first line is the command we used to connect to the DC01 computer. Once connected, we move to the root of the drive using the Set-Location cmdlet. This isn’t a requirement, but a shorter path will offer more area in which to type. Then, we instructed the computer to echo four asterisks, a space, the value stored in the computer name environmental variable, another space, and another four asterisks. This is proof, outside the computer’s name in square brackets at the left of the prompt, that we really are on the remote computer. Finally, we run Exit-PSSession to exit the PS Remoting session on DC01, and return to the local computer.

Invoke-Command allows you to send a command to a remote computer, have it run, and have the results sent back and displayed in your console, on the local computer. While you can do this against a single computer, this is considered one-to-many PS Remoting, and you’ll see why momentarily. The next example does the same thing as we did in the above, Enter-PSSession example.

Invoke-Command -ComputerName DC01 -ScriptBlock {"**** $env:COMPUTERNAME ****"}

**** DC01 ****

Invoke-Command has some great features: One, it can run against multiple computers, as you may have already picked up on, or simply knew, and two, it can do this at the same time. Now, there is a built-in limitation of 32 computers that it will run against at once; however, that can be changed with the -ThrottleLimit parameter and a parameter value of something other than 32.

Invoke-Command -ComputerName DC01,DC02,DC03,DC04 -ScriptBlock {"**** $env:COMPUTERNAME ****"}

**** DC01 ****
**** DC04 ****
**** DC02 ****
**** DC03 ****

Because this last example ran against multiple computers at once, you won’t always get the results back in the order you might expect. This is because it’s up to how quickly each computer can be reached, how quickly each command(s) can be run, and how quickly each computer can send the results back to the local computer. This command above could’ve been written as is shown below.

Invoke-Command -ComputerName (Get-ADDomainController -Filter *) -ScriptBlock {"**** $env:COMPUTERNAME ****"}

**** DC01 ****
**** DC02 ****
**** DC04 ****
**** DC03 ****

Start to consider all those times you use Remote Desktop. Are there times you can skip that somewhat lengthy process, and instead return the results you’re after, using PowerShell Remoting?

PSMonday #10: Monday, July 4, 2016

Topic: Less Used Variable Properties Continued II

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

Last week we discussed the option of making our variables read-only. This means that without the -Force parameter, read-only variables cannot be overwritten, cleared, or deleted. This level of protection for our variable’s values is good, but it might not be the best option in some cases. Today we’ll focus on the constant option for the Options property. Let’s first create a new variable, verify it’s a constant, and then try to change its value.

New-Variable -Name String6 -Value "Here’s a new Monday variable." -Option Constant

(Get-Variable -Name String6).Options
Constant

Set-Variable -Name String6 -Value 'Can we change it?'

Set-Variable : Cannot overwrite variable String6 because it is read-only or constant.

As mentioned, with a read-only variable, we used the -Force parameter to overwrite, clear, and delete, and it worked. Let’s try that with our constant variable.

Set-Variable -Name String6 -Value ‘Can we change it?' -Force

Set-Variable : Cannot overwrite variable String6 because it is read-only or constant.

Even with the -Force parameter we can’t change the variable’s value. Let’s attempt to clear it (remove its value), with the -Force parameter.

Clear-Variable -Name String6 -Force

Clear-Variable : Cannot overwrite variable String6 because it is read-only or constant.

No luck. In both cases we get the same error. Let’s try to remove the variable in its entirety.

Remove-Variable -Name String6 -Force

Remove-Variable : Cannot remove variable String6 because it is constant or read-only. If the variable is read-only, try the operation again specifying the Force option.

Still no luck, although we did get a mildly different message. If you create a variable that’s a constant, you can’t get rid of it, or modify it, until you end the PowerShell session; however, if it’s read-only, you’ll have some protection, and still be able to modify things, if necessary. Keep these options in mind, as you may find yourself wanting to add some protection, to your otherwise do-anything-you-want-to-them-at-any-time variables.

Bonus: Last week we saw that we were able to create a variable and then make it read-only. When we create a constant variable, the option has to be applied when the variable is first created. If you create a variable and then try to make it constant, you’ll receive the below error.

Set-Variable : Existing variable String7 cannot be made constant. Variables can be made constant only at creation time.

PSMonday #9: Monday, June 27, 2016

Topic: Less Used Variable Properties Continued I

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

As was mentioned last week, variables have some properties we don’t often use, and many of us, may not even know about them. Last week we briefly learned about the Description property of a variable, and today, we’ll start to learn about the Options property.

While a variable’s Options property is set to None by default, there’s at least two other possibilities, and one of them, is ReadOnly. The first example uses New-Variable to create a new, read-only variable named String3, and Get-Variable and Select-Object to return all the variable’s properties.

New-Variable -Name String3 -Value 'A new week; another Monday.' -Option ReadOnly

Get-Variable -Name String3 | Select-Object *

Name        : String3
Description :
Value       : A new week; another Monday.
Visibility  : Public
Module      :
ModuleName  :
Options     : ReadOnly
Attributes  : {}

Now, what does it mean that it’s read-only? It means that we can’t change the value that’s stored by the variable, as is indicated below.

Set-Variable -Name String3 -Value 'A new week; another Friday!'

Set-Variable : Cannot overwrite variable String3 because it is read-only or constant.

Notice in the error message that it indicates we can’t overwrite the variable because the variable is read-only or constant. It turns out that we actually can change the value stored in a read-only variable, if we use the -Force parameter.

Set-Variable -Name String3 -Value 'We Want Friday!' -Force

Get-Variable -Name String3 | Select-Object *

Name        : String3
Description :
Value       : We Want Friday!
Visibility  : Public
Module      :
ModuleName  :
Options     : ReadOnly
Attributes  : {}

So, in the end, a read-only variable can be overwritten if absolutely necessary. Keep this read-only option in mind as you create variables in Windows PowerShell. There may be times you create variables and want to mostly protect the original, stored value. In that case, you can choose to make them read-only.

If we already have a variable created, how do we then make it read-only? We have to use Set-Variable, whether we created the variable with New-Variable, or the standard way, as is used below.

$String4 = 'Last variable for today.'
$String4

Last variable for today.

(Get-Variable -Name String4).Options
None

Get-Variable -Name String4 | Set-Variable -Option ReadOnly

(Get-Variable -Name String4).Options
ReadOnly

To clear a variable’s value (to continue to allow a variable to exist, but to remove the value it stored), we have to use the -Force parameter. Same goes for removing the entire variable; that will also require the -Force parameter if it’s a read-only variable.

Clear-Variable -Name String3 -Force

Remove-Variable -Name String4 -Force

Get-PSCallStack Demonstration Using Multiple Functions

I wrote a couple recent posts that used the Get-PSCallStack cmdlet. In my case, Get-PSCallStack helped one function determine what function invoked it, and used the information to further determine if that function was approved to invoke it, or not. In doing this, I wrote a series of small functions I’d like to share to help demonstrate this process.

In the below code example, I’ve defined a series of functions. The first function defined is called Get-Main, and it does a few things. It sets a variable $Index to zero, dumps the Get-PSCallStack commands into a global variable (that will exist after the function completes), and then loops through each of the command properties in the second variable, displaying its correctly assumed index, and the command name.

The following functions are in place to progressively call one another from the bottom up. Working downward, however, Get-OneUpOne invokes Get-Main (discussed above), Get-OneUpTwo invokes Get-OneUpOne, Get-OneUpThree invokes Get-OneUpTwo, etc. These multiple functions end up being listed in Get-PSCallStack, as we’ll soon see.

Function Get-Main {
    $Index = 0
    $Global:Var = (Get-PSCallStack).Command
    $Var | ForEach-Object {
        "Index $Index : $_"
        $Index ++
    }
}

Function Get-OneUpOne {
    Get-Main
}

Function Get-OneUpTwo {
    Get-OneUpOne
}

Function Get-OneUpThree {
    Get-OneUpTwo
}

Function Get-OneUpFour {
    Get-OneUpThree
}

Get-OneUpFour

The last line in the above example, starts the magic by invoking Get-OneUpFour, that invokes Get-OneUpThree, etc., moving upward. Here’s a gif that shows how these are invoked, working up from the bottom.

get-pscallstack-demonstration-using-multiple-functions01

When this chunk of code is dropped into the ISE and run, we get the results listed below. They indicate the index and value of Get-PSCallStack’s recorded commands, or steps, on the way to invoking the Get-Main function. Notice, that it’s backwards, and what I mean is this: The first index 0, is the last command recorded. The last index, 5, is the first command recorded.

Index 0 : Get-Main
Index 1 : Get-OneUpOne
Index 2 : Get-OneUpTwo
Index 3 : Get-OneUpThree
Index 4 : Get-OneUpFour
Index 5 : <ScriptBlock>

Since we created the global variable $Var, we can verify if the above results are correct — if our assumed index is accurate. We’ll check with a single index first, and then run $Var though a For loop to see each index.

$Var[2]

Get-OneUpTwo
For ($i = 0; $i -lt $Var.Length; $i++) {
    "Index $i -- $($Var[$i])"
}

Index 0 -- Get-Main
Index 1 -- Get-OneUpOne
Index 2 -- Get-OneUpTwo
Index 3 -- Get-OneUpThree
Index 4 -- Get-OneUpFour
Index 5 -- Get-PSCallStack_Example.ps1

Take a look at index 5 in the directly above example, and then the further above example. One says, “<ScriptBlock>,” and one says, “Get-PSCallStack_Example.ps1.” Any idea what changed between these two examples? If you guessed that I saved my code example, while writing this post, then you’d be correct.

The highest numbered index* — 5 in this example — is always going to be the starting point — whether it’s called from the prompt, or a saved file, and the lowest index — index 0 — will always be the currently invoked function. This means that index 1 will always be the calling function, of the last function that was invoked.

* The highest numbered index will also always be index -1, such as $Var[-1] would equal “<ScriptBlock>,” or “Get-PSCallStack_Example.ps1.” Knowing this, it’s possible to work backwards through these results, or any other array.

So it’s been said, the other time we see an array in reverse, is the $Error variable. $Error[0] is always the most recent error, not the first error. Thanks for reading, and we’ll do it again sometime soon.

Build a Number Line

A year or two ago, I wrote my one and only game in Windows PowerShell. It’s 1 to 100; maybe you played it as a kid. The computer chooses a random number between 1 to 100. You make guesses and every time you’re wrong, the computer will tell you if you should guess higher, or lower, on your next turn. This continues until you guess the correct number. Once you’re a winner, it’ll display the number of attempts it took, the number of games played, and your average attempts per game. Here’s an image that shows that it only took me three guesses (the average, over time, seems to be around 6 to 7).

build-a-number-line01

In the last few months, my daughter has begun to occasionally ask me to play. She’s a smart, little 4-year-old, but the more she plays, the more I wish I had built in an easier mode, or two. Those may be coming.

As we were sitting there last night, she mentioned that she couldn’t find her number line. I had drawn one a few months ago that she’d use to help determine the range of numbers she should choose from next. Well, it went missing. As I try to solve all my worldly problems with PowerShell, I threw together a quick function that would allow us to draw a quick number line in a separate console. Now, she could easily locate a number in the middle of her closest high number and closest low number. Here’s the function, now.

Function Show-NumberLine {
    Param ([int]$Num1, [int]$Num2)
    $Num1..$Num2 | ForEach-Object {$NumberLine += "$_-"}
    "<--$NumberLine->"
}

Show-NumberLine -Num1 1 -Num2 20
<--1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-->
Show-NumberLine -Num1 1 -Num2 10
<--1-2-3-4-5-6-7-8-9-10-->

One of these days, I’ll add to the 1 to 100 function, and enable an easier mode, or two, to perhaps include the number line for beginner users. In the meantime, you can download the function here: https://gallery.technet.microsoft.com/The-1-to-100-Game-5288e279 and use number line function separately, if you need it. 😉

PSMonday #8: Monday, June 20, 2016

Topic: Less Used Variable Properties

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

When someone sets, or assigns, a variable in Windows PowerShell, we typically expect it to look a certain way, as is demonstrated below. This example also shows how we typically return a variable’s value, after it’s been assigned.

$String = 'Welcome back to Monday.' # Assign a value to variable.
$String # Return a variable’s value.

Welcome back to Monday.

This assigns, or sets, the variable named string, the value of ‘Welcome back to Monday.’ Straightforward, right? Well, this isn’t the only way to assign a variable in PowerShell. There’s a more formal process that offers a few extra options. When we use the *-Variable cmdlets, we don’t use the dollar sign ($). The dollar sign is only used to indicate to the PowerShell parser that what follows it, will be a variable name. The difference here is that these variable cmdlets already know you’re providing a variable name.

New-Variable -Name String2 -Value 'Come on, Friday.'
Get-Variable -Name String2

Name                           Value
----                           -----
String2                        Come on, Friday.

If you choose to use the Get-Variable cmdlet to return just the value, you can use the -ValueOnly parameter, dotted-notation, or Select-Object’s -ExpandProperty parameter. In older versions of PowerShell, dotted-notation may not be an option.

Get-Variable -Name String2 -ValueOnly
(Get-Variable -Name String2).Value
Get-Variable -Name String2 | Select-Object -ExpandProperty Value

Come on, Friday.
Come on, Friday.
Come on, Friday.

I’m not here to suggest variables should always be created with New-Variable, that values should always be returned with Get-Variable, that variables should always be updated with Set-Variable, or even that we should always clear or remove variables with Clear-Variable and Remove-Variable, respectively. What I’m out to do, is tell you about a couple extra properties that are attached to our variables, that you might not know about, and how we might use them.

Let’s modify the command we used to return the value of our $String2 variable, so we return all the properties. Keep in mind, that we can do the same thing with our $String variable that was created without the New-Variable cmdlet.

Get-Variable -Name String2 | Select-Object *

Name        : String2
Description :
Value       : Come on Friday.
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

Notice that we have a Description property and an Options property. The Description property is another way to provide additional meaning to a variable. While you should strive to name your variables in a way that describes their contents, if you’re feeling up to it, you can add additional information about the variable in this property.

Set-Variable -Name String2 -Description 'Demo purposes only'
(Get-Variable -Name String2).Description

Demo purposes only

Let’s talk about the Options property next week, as it’s a bit more useful.

Specify Which Functions Can Invoke Another Function Part II

It wasn’t long after I wrote and posted Part I of this topic — which I didn’t know was a Part I at the time — that I had another idea.

For those that didn’t read the post linked above, I have a function called Send-ErrorMessage. Its purpose is to send an email if the catch block of a try-catch, that doesn’t include an error exception, executes. It’s a safety net, just in case I haven’t properly coded for an unknown error condition. Because I had to export the function, so that it’s available to other functions in other modules, I needed a way to ensure that only specific functions were able to invoke Send-ErrorMessage. I didn’t want a user using the function; I only wanted the functions I wrote, that were approved, to be able to use the Send-ErrorMessage function.

Well today, I took the conditional logic that was taking up space in my Begin block and moved it to the [ValidateScript()] attribute. This also helped clean up some of the Process block, too. The only thing that I’m giving up, is that now the message to someone trying to invoke this function with an unapproved function, or at the prompt, is that it’ll show an error using red text, instead of a warning using orange text (in the ISE).

I may have mentioned this before, but I believe it’s okay to display standard, red text error messages in your tools, depending on the audience. If I’m writing tools for my team, they should be comfortable enough to decipher PowerShell errors. If the tool is being written for end-users, or lower tier employees, then it may be wise to better control the errors and warnings.

The complete, modified code is below. It begins by defining the Send-ErrorMessage function. Then it creates two additional functions — one that is approved to invoke Send-ErrorMessage, and one that’s not. Then, it invokes those two functions — Test-Allowed and Test-NotAllowed, respectively — and displays the results one right after the other. The first function will be permitted and will write the verbose statements in the Process block, but the second function will fail inside the [ValidateScript()] attribute, as it’s not approved to invoke Send-ErrorMessage.

Copy, paste, and take it for a run. After you’ve run the code, enter Send-ErrorMessage manually. It still won’t let you run it from the command line, even if you provide an approved function name, as the value of the -FunctionName parameter. That’s right, you can’t fake it out, as it’s not taking your word, it’s using the value returned by Get-PSCallStack. Thanks for reading!

Function Send-ErrorMessage {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({
            $AcceptableFunctions = 'Get-AJob','Test-Allowed','Get-Real'
            $CallingFunction = (Get-PSCallStack)[1].Command
            If (-Not ($CallingFunction -in $AcceptableFunctions)) {
                Throw "The `"$_`" function or command, does not have approval to invoke this function. Send-ErrorMessage will not work if you use the function manually and enter an approved function name."
            } Else {
                $true
            }
        })]
        [string]$FunctionName
    )

    Begin {
    } # End Begin.

    Process {
        Write-Verbose -Message "The calling function ($($PSBoundParameters.FunctionName)) is approved." -Verbose
        Write-Verbose -Message 'Beginning to send error message ...' -Verbose
    } # End Process.
    
    End {
    } # End End.
} # End Function: Send-ErrorMessage.


Function Test-Allowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage -FunctionName $MyInvocation.MyCommand.Name
    }
} # End Function: Test-Allowed.


Function Test-NotAllowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage -FunctionName $MyInvocation.MyCommand.Name
    }
} # End Function: Test-NotAllowed.


# Invoke Function.
Test-Allowed

# Invoke Function.
Test-NotAllowed