Monthly Archives: July 2016

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.