Category Archives: Quick Learn

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

PSMonday #13: Monday, July 25, 2016

Topic: PowerShell Remoting 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.

We’re not done yet; there’s more to discuss about PowerShell Remoting and specifically the Invoke-Command cmdlet. Again, this is the cmdlet that allows us to run a command, or a series of commands, against a remote computer, or group of remote computers. It does all this without the need for us to interactively type commands on the remote computer(s).

One of the things you may eventually need to accomplish is to get a local variable from your local computer, into a PS Remoting session. Let’s start by creating a local variable. In the below example, we create and assign $LocalVariable and echo its value.

PS > $LocalVariable = $env:COMPUTERNAME
PS > $LocalVariable

TOMMYSPC

We’re going to utilize the Using scope modifier to get this variable to the remote computer. The Using scope modifier was introduced in PowerShell 3.0, and it is the preferred and easiest way to accomplish this task. That version of PowerShell shipped inbox with Windows 8 and Server 2012; however, it could be installed on some down-level versions of Windows (2008 R2 and Windows 7).

Now that we have our local variable set, let’s take it to our remote computer. We reference the local variable in the PS Remoting session, by inserting “Using:” in-between the dollar sign and the variable name.

PS > Invoke-Command -ComputerName MEMSRV01 -ScriptBlock {
    "The local computer is $Using:LocalVariable."
    "The remote computer is $env:COMPUTERNAME."
}

The local computer is TOMMYSPC.
The remote computer is MEMSRV01.

That’s it for now. We’ll continue with this topic next week where we’ll discuss how this was completed prior to PowerShell 3.0, and the Using scope modifier.

Using Out-Variable to Reduce Line Count

I’ve written about Out-Variable before, but I used it today in a way I hadn’t previously, and so I thought I’d take a moment before bed to share it.

I’m in the process of writing some small, reusable code and I want to keep the number of lines to an absolute minimum. In the first section of this reusable code, I want to determine the name of the module that contains the currently running function. Not all functions have a value for the module name, so it’s possible this value might be empty.

The below example is how I might’ve written the command had I not been concerned with keeping the line count low. I first attempt to set the $Module variable to the ModuleName property of the currently running function. Then, I evaluate whether or not the $Module variable contains any value. If it does, and therefore evaluates to true, I set the variable $ModuleNameCode to the value of $Module and a trailing backslash. Pretty straightforward.

$Module = (Get-Command -Name $MyInvocation.MyCommand.Name).ModuleName
If ($Module) {
    $ModuleNameCode = "$Module\"
}

In the second example — which boasts one less line, and is the reason why I’m writing this evening — I evaluate the command as part of the If statement’s conditional section. That’s common enough. What isn’t, is the use of the -OutVariable parameter. This allows me to create my $Module variable on the fly, and then use it as a part of the statement portion in the If statement (the part in curly brackets: { }).

If ((Get-Command -Name $MyInvocation.MyCommand.Name -OutVariable Module).ModuleName) {
    $ModuleNameCode = "$Module\"
}

Well, there it is. It’s a simple, yet possibly unconsidered option for using Out-Variable. It also saved me a line. That’s not a normal concern, but it was, and so this usage paid off. Until next time.

Oh, I should mention that in my little chunk of reusable code, I actually made this If statement only consume a single line, such as I’ve done below. That’s it for real, this time.

If ((Get-Command -Name $MyInvocation.MyCommand.Name -OutVariable Module).ModuleName) {$ModuleNameCode = "$Module\"}

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. 😉