Tag Archives: Invoke-Command

Text-To-Speech in PowerShell

If you’re like me, you’ve used the Narrator, built into Windows, to read something to you that you’ve written. If you’re not, then you may find this whole thing, strange. But yes, for me, I’ll occasionally do this to ensure I didn’t miss a word or two, or that I didn’t use the wrong tense. Or maybe, I just wanted to hear how something sounds when it isn’t me saying it out loud, and instead, it’s one of my computer’s robot voices. So yeah, I do that sometimes.

I’m growing tired of opening Narrator, so I’ve decided I should make a PowerShell function to do the text-to-speech for me. My biggest complaint is that the Narrator tells me way too much information before it actually starts doing what I expect it to do, and yes I do understand the Narrator wasn’t included in the operating system for someone such as myself. Still, sometimes, depending on something’s importance, I prefer to have what I’ve written, read to me, to help ensure my accuracy. It’s a final safety (I’d-prefer-to-not-sound-like-an-idiot for this piece of writing) check.

I’ve had some exposure with System.Speech. But, what was that for? Ah yes, I remember. It was for the Convert-TMNatoAlphabet function. I have copied it here from its previous home on the Microsoft TechNet Gallery. This function would do exactly as the examples below indicate. It takes a string made up of numbers and letters and writes it out in the Nato phonetic alphabet. It’s an old function, so I’ve included an old image. You can’t tell in the image examples, but at some point, I added a Speak parameter. If that was included, as -Speak, not only would it display the result on the screen, but it would say them out loud, as well.

The end goal now is to write a function that I’ll keep around to read things back to me without opening the Narrator. I provide some text, and PowerShell provides me a robot voice to help me recognize if I’ve missed any words while having something read to me. I think I’ll pause here for now and go write the code. Yeah, that’s right, that hasn’t actually been done yet. So for you, back in split second, and for me it’ll be some time longer.

This got a touch more confusing than I had hoped it would, but it’s nothing you won’t be able to comprehend. We need to remember that we’re operating in a PowerShell world now — not a Windows PowerShell world. That means that we’re going to assume we’re using something greater than version 5.1, but that 5.1 is available to us (since I’m running Windows 10). We’re going to use powershell.exe (5.1 and earlier) from pwsh.exe (6.0 and later). Let’s take a look at the below-completed function in order to understand what it’s doing and then let’s test it out. One final note before we do that, however. While you may have no interest in having anything spoken by your computer, the concepts in this example may prove to be helpful if you ever need to use Windows PowerShell, from PowerShell.

Function Convert-TextToSpeech {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$Text,
        [Parameter()]
        [ValidateSet(1,2,3,4,5,6,7,8,9,10)]
        [int]$SpeechSpeed = 5
    ) # End Param.

    Begin {
        Function ConvertTextToSpeech {
            [CmdletBinding()]Param (
                [Parameter()]$Text,
                [Parameter()]$SpeechSpeed
            ) # End Param.
            Add-Type -AssemblyName System.Speech
            $VoiceEngine = New-Object System.Speech.Synthesis.SpeechSynthesizer
            $VoiceEngine.Rate = $SpeechSpeed - 2
            $VoiceEngine.Speak($Text)
        } # End Function: ConvertTextToSpeech.
    } # End Begin.

    Process {
        $Session = New-PSSession -Name WinPSCompatSession -UseWindowsPowerShell
        Invoke-Command -Session $Session -ScriptBlock ${Function:ConvertTextToSpeech} -ArgumentList $Text,$SpeechSpeed
    } # End Process.

    End {
        Remove-PSSession -Name WinPSCompatSession
    } # End End.
} # End Function: Convert-TextToSpeech.

The entire code example is within a single function, which is both expected and preferred. Copy, paste, run the code, and the Convert-TextToSpeech function becomes available to be invoked within your current PowerShell session. This function does several things. The first thing to notice is that the function includes a parameter named Text, which expects a value is included for that parameter when the function is invoked. This is the text to be read out loud. It also includes a parameter named SpeechSpeed that accepts a value from 1 through 10. It includes a default value of 5, which is what we’re going to use for the normal/standard/default speed. Therefore, anything less than 5 is slower than normal and anything greater than 5 would be faster.

In the Begin block, the function creates a nested function. It uses the same name as our main function; however, it removes the dash from in between the words “Convert” and “Text,” making it a completely different function. We could have used the same name since these functions would be scoped differently, however, I thought that would probably end up being even more confusing than this already may be.

Notice that the nested function includes the identical Text parameter as our parent function. The parameter value that comes in when the parent function is invoked will be passed along to our nested function’s Text parameter. We will see that shortly. It also includes a SpeechSpeed parameter, as well. This will also be passed from the parent function to this child function. There are not as many rules about what these parameters will accept in the child function. That’s because the validation features and variable type enforcements are already requirements in the parent function. Therefore, they don’t need to be in place for this nested function, too.

That nested function’s purpose is to load what’s necessary to actually do the text-to-speech conversion before doing it. This is code that cannot be executed using .NET Core (cross-platform). It can only be executed by the .NET Framework (Windows). This is why we need Windows PowerShell. If I later determine that’s not entirely true, I’ll be sure to update this post. While we’re looking at this nested function though, do notice where we set the Rate. This is the SpeechSpeed, which I’m beginning to figure out, isn’t easy to say out loud. Meh. As it was mentioned earlier, the values the function allows for are 1 through 10 where 1 is the slowest and 10 is the fastest. You’ll notice that in the nested function we subtract two from that value. That’s because three is actually the normal speed.

The Process block only includes two lines, however, they are both quite important. The first line creates a new PowerShell Remoting session. The remoting session will not be to a remote or different computer, but instead will stay on the same computer and make use of Windows PowerShell, or powershell.exe. It will create a new “remote” Windows PowerShell session where it can run powershell.exe and make use of the .NET Framework. The nested function will be “taken” into the remote session using Invoke-Command along with the two parameter values passed into the parent function. After the text is read in the “remote” Windows PowerShell session, it will return to this function where the code will progress into the End block. From here, our function will remove the PS Remoting session and the function invocation will end.

In order to experience the below example, copy the above function, paste it into the ConsoleHost or VSCode and run it so that the function is added to the session/memory. Then, invoke the function by running your own example, or even the one below. The one below will start out slowly and gradually get faster. I’ve included the audio of the function being invoked beneath the example code. Watch your use of quotes/double quotes in your text. It could break things.

1..10 | ForEach-Object {Convert-TextToSpeech -Text "This is testing $_" -SpeechSpeed $_}

Again, you may have no interest in a function that can read text out loud, but one day you may have a need to run something inside Windows PowerShell from (just) PowerShell.

Edit: If you have a large block of text, you might rather store it in a text file and use a Get-Content command to read in the file’s contents, before it’s used as the parameter value for the Text parameter. Something like the below example.

Convert-TextToSpeech -Text (Get-Content .\Desktop\file.txt)

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

Keep PowerShell Cmdlets Powerful

I’ve seen what I’m about to show you done repeatedly in Windows PowerShell. With that thought in my mind, I’ve decided it’s time to officially write about it. The first time I noticed someone doing it, well, it was me. I did, however, write a correction to my team so they knew I was aware of my error, and as a hope they wouldn’t repeat it.

Those doing this may be new to PowerShell, but if you aren’t careful and don’t fully understand the capabilities of the cmdlets you use, then it’s possible you might make this mistake, too. What am I talking about? I’m talking about Invoke-Command and an improper way that it’s sometimes used.

Invoke-Command‘s purpose is to run a command, or commands, on remote computers and return the results back to the local computer. That said, it can be used on the local computer, as well; however, this is usually only necessary to check if PowerShell Remoting is working on the local computer. While I don’t normally do this in my examples, I’m piping my result of this example to Format-Table -AutoSize once they’re returned from the remote computer. This is so it’ll display better on this webpage.

Invoke-Command -Computer DC01 -ScriptBlock {Get-PSDrive -PSProvider FileSystem} | Format-Table -AutoSize
Name Used (GB) Free (GB) Provider Root              CurrentLocation PSComputerName
---- --------- --------- -------- ----              --------------- --------------
A                                 A:\                               DC01
C        12.91     26.75          C:\  Users\tommymaynard\Documents DC01
D                                 D:\                               DC01

This example of Invoke-Command connected to the remote computer DC01, ran a filtered Get-PSDrive command, returned the results to my computer, and then were formatted by the Format-Table cmdlet.

Now, let’s say we have… 23 computer names stored in the variable $Computers and we want to run the command we used above, against each computer. What so many people seem to do is wrap Invoke-Command in a Foreach construct (think: loop). I’m guessing, because I’ve made the error myself, that it’s because someone learned about Foreach first. Like I said, I discovered my error on my own, and only moments later. It didn’t wake me at 2 a.m. in a cold sweat after two months, which could’ve been possible.

Here’s how not to do this, and again, $Computers is holding the names of 23 computers.

Foreach ($Computer in $Computers) {
    Invoke-Command -ComputerName $Computer -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
}
Name           Used (GB)     Free (GB) Provider      Root                                CurrentLocation PSComputerName
----           ---------     --------- --------      ----                                --------------- --------------
C                  20.47         59.18               C:\                                 ...rd\Documents DC02
D                                                    D:\                                                 DC02
A                                                    A:\                                                 WEB01
C                  50.36         29.30               C:\                                 ...rd\Documents WEB01
D                                                    D:\                                                 WEB01
A                                                    A:\                                                 SQL01
C                  53.59         26.06               C:\                                 ...rd\Documents SQL01
D                                                    D:\                                                 SQL01
... # Not all results included.

What we’ve done here is force Invoke-Command to run against only one computer at a time. During each iteration of the Foreach loop, Invoke-Command runs against the computer name currently stored in $Computer. This variable is updated to the next computer name in the variable at the start of each loop. It’s like this: connect to DC02 and run the command. Now, connect to WEB01 and run the command. Are you done, Invoke-Command? Okay then, now Foreach says to run the same command against the computer SQL01. That’s not how this cmdlet was designed to be used.

Before we go any further, I wrapped this command inside the Measure-Command cmdlet to determine how long it took my system to run this against the computers in $Computers. It took nearly 30 seconds. While that’s not an eternity, we’re only talking about 23 computers, and we’re only talking about a single command.

Measure-Command -Expression {
    Foreach ($Computer in $Computers) {
        Invoke-Command -ComputerName $Computer -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
    }
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 27
Milliseconds : 513
Ticks : 275132689
TotalDays : 0.000318440612268519
TotalHours : 0.00764257469444444
TotalMinutes : 0.458554481666667
TotalSeconds : 27.5132689
TotalMilliseconds : 27513.2689

Now, let’s set this command up the proper way. The ComputerName parameter of Invoke-Command can take a collection of computers. It should be said that many cmdlets can take more than one computer as a value to their ComputerName parameter. The difference is that Invoke-Command can run against all of the 23 computers at the same time. In fact, the default throttle limit—the number of computers in which the command will run against simultaneously—is 32. It can be changed by including the ThrottleLimit parameter, such as -ThrottleLimit 50 or -ThrottleLimit 15.

Now, here’s how this command should’ve been written.

Invoke-Command -ComputerName $Computers -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
Name           Used (GB)     Free (GB) Provider      Root                                CurrentLocation PSComputerName
----           ---------     -------u-- --------      ----                                --------------- --------------
A                                                    A:\                                                 DC02
A                                                    A:\                                                 WEB01
C                  41.72         37.93               C:\                                 ...rd\Documents WEB01
C                  41.13         38.52               C:\                                 ...rd\Documents DC02
D                                                    D:\                                                 DC02
D                                                    D:\                                                 WEB01
A                                                    A:\                                                 SQL01
C                  46.75         32.91               C:\                                 ...rd\Documents SQL01
D                                                    D:\                                                 SQL01
... # Not all results included.

I measured this command five times, and the average of those five runs was only 2.8 seconds. Remember, the Foreach loop took almost 30 seconds to get the same results. This is due to the fact that, again, Invoke-Command will work with the remote computers simultaneously (when it’s not inside a Foreach). There’s never any waiting to run the command on any additional computers unless there’s more computers than the value of ThrottleLimit.

Please keep the topic in this post in mind, as it’s possible to momentarily forget this feature just long enough to wrap the Invoke-Command cmdlet in a Foreach, or other looping construct.

 

Proving PowerShell’s Usefulness to Newbies, Part III

Here we go! It’s the third time doing what we can, to try and prove the usefulness and power in Windows PowerShell. With a little work, those new in PowerShell, get a glimpse into not only what they can do with PowerShell, but also why they’d want to do it with PowerShell. In the end, most of this is about time savings, and tonight’s post is no exception.

Part I can be found here. In that post, we learned that we could use PowerShell to create 10,000 folders in about 10 seconds — that’s 1,000 folders per second, or 1 folder each millisecond.

Part II is here. This post was about reading in a file’s contents, sorting the contents alphabetically, and then writing the sorted results back to the same file. PowerShell took milliseconds, while my manual attempt took minutes.

Part III
As part of my imaginary Windows PowerShell presentation I thought I might give one day, I always pictured a common theme — saving time. That’s a big part of why many of us have taken so fondly to PowerShell. I pictured telling a story about a few different approaches to the same problem, all to be completed by a different admin, each with their own best way of handling the request. Here’s my hypothetical, yet quite possible situation, beginning with the characters.

Admin One: This admin is fairly new to the industry. While they know their way around a desktop version of Windows, they’re still often learning new things about it. While they’re motivated, they haven’t learned enough to often know better.

Admin Two: This admin has a year or two on admin one, and is quickly picking up better ways to speed things up by utilizing certain tools.

Admin Three: This admin is getting there. It’s almost daily that they’re figuring out faster, and more remote ways to complete tasks. While they aren’t scripting and running commands (yet), they do know some tricks to get their results quicker.

Admin Four: This admin has been studying all things PowerShell whenever there’s a moment for it. Every task assigned is another challenge to learn more PowerShell, and to continue to add to their skills. They’re freeing up time, all the time.

One day the boss comes in with a request of the team. It’s not a difficult task. In fact, any one of the team members can get the task done. They need to check 200 of their computers to determine, and then document, which of the computers are running the print spooler service. Maybe it’s just busy work handed down by the boss, or maybe this is a valid concern somehow.

Admin One gets his clipboard and heads down to one of the computer labs. Once the three labs are checked, he’ll head to each of the offices to check those computers manually, too. Here’s the steps this admin takes: (1) Log on to the computer, (2) Open Computer Management, (3) Click on Services, (4) Find the Print Spooler service, noting the status, and (5) Write down the computer name and service’s status.

Let’s assume Admin One takes 5 minutes per computer which includes the time spent walking from computer to computer and office to office. At 200 computers, that’s 1,000 minutes, or approximately 16.6 hours. That’s longer than two full 8 hour days at the office. That kind of time loss equates to zero time spent learning anything new for two days in the week. You can’t stay in the field using this procedure.

Admin Two’s first though is Remote Desktop. While this admin still has to log on, it can be done without physically visiting each computer. In addition, this admin can work with more than one remote computer at a time. This allows Admin Two the ability to complete one computer every 2 1/2 minutes, cutting the time it takes Admin One in half. That’s only a little over one work day — 8.3 hours — lost to this project. While it’s shorter, it’s still too much time.

Admin Three knows enough to know that they can connect to a remote computer from inside Computer Management, which means they can also avoid leaving their desk. This removes the step of interactively logging on to a computer, and loading a desktop environment, too. We’ll assume this admin can do one computer every minute. That’s 200 total minutes, or 3.3 hours. That admin can actually finish before lunch if they start soon enough in the day. This might be reasonable by some boss’ standards, but PowerShell has been designed to do this quicker.

The final admin, Admin Four, knows PowerShell, and they know it well enough to pull back all those results without the need for any GUIs. There’s no wandering around the building, writing things down to a clipboard, or even entering data into a file, as Admin Two and Three do from their desks, as they use RDP and Computer Management, respectively.

Admin Four has a couple approaches and plenty of time to try them both out. While there’s many ways to read in computer names, we’ll assume they have a list of the 200 computers in a text file with each computer name on its own line. They may have had to build this file, but if they’re pulling from Active Directory, then it was probably automated, as well.

The first option is to use the Get-Service cmdlet, as seen below, in the first part of the example. The second part, which requires PowerShell Remoting, uses Invoke-Command. I’ve only include 20 computers in the computers.txt file, and to save space, only included 5 results per command in the example.

PS> # Uses the Get-Service cmdlet with -ComputerName parameter.
PS> # Have to add the MachineName property to see which results goes with which computer.
PS> Get-Service -ComputerName (Get-Content -Path .\computers.txt) -Name spooler | Select-Object MachineName,Status,Name,DisplayName

MachineName                                          Status Name                          DisplayName
-----------                                          ------ ----                          -----------
DC01                                                 Running spooler                       Print Spooler
DC02                                                 Running spooler                       Print Spooler
DC03                                                 Running spooler                       Print Spooler
WEB01                                                Running spooler                       Print Spooler
WEB02                                                Running spooler                       Print Spooler
...

PS> # Uses Invoke-Command which automatically adds the name of the remote computer by default.
PS> Invoke-Command -ComputerName (Get-Content -Path .\computers.txt) -ScriptBlock {Get-Service -Name spooler}

Status   Name               DisplayName                            PSComputerName
------   ----               -----------                            --------------
Running  spooler            Print Spooler                          SQL01
Running  spooler            Print Spooler                          DC02
Running  spooler            Print Spooler                          DC02
Running  spooler            Print Spooler                          WEB02
Running  spooler            Print Spooler                          DC03
...

While I didn’t include the results, each of these two options — Get-Service, and Get-Service inside Invoke-Command — took about the same amount of time when they were measured. Even so, we’ll round up and say it takes 10 second for each set of 20 computers. At 200 computers, that 100 seconds for all of them. At 100 seconds, we’re talking about an approximation of 1 1/2 minutes to check all two hundred computers.

If I was in front of a crowd right now, and had just talked thought this post, I’d take a moment to ask the crowd which admin they thought would be around after budgets cuts. The admin that showed up with the results written down, after two days, or the one who sent the boss the results via email after two minutes? Keep in mind, that either of these cmdlets above can be used in conjunction with Export-Csv — a cmdlet that produces a text-based file that can be opened and edited with Microsoft Excel — something in which the boss probably has on their computer. In addition to automatically writing the results to file, by using the Send-MailMessage cmdlet, Admin Four can also automate sending the results by email.

Get a Locally Declared Function into a Remote Session

A part of why I write, is to have a place to store things I’m probably going to forget the moment I need them. Notice the search feature to the right. I’d be surprised to find out that someone has used it more than me.

That brings us to today’s topic: getting a locally declared function into a remote session. I wrote a recent post about getting variables into a remote sessions — you can read that here: http://tommymaynard.com/quick-learn-getting-local-variables-into-remote-sessions-2015 — and thought that getting a local function into a remote session, would make for a good followup.

First, let’s start with a function. This is a mildly complex function to use for an example, but by using it, I’ll be ensuring I can find it later, too. This function is a wrapper for the command: Dism /online /Get-Features. It takes the text produced by that command and returns the results as objects. I wrote this as part of an answer to a thread on the Microsoft Technet Forums: https://social.technet.microsoft.com/Forums/scriptcenter/en-US/972d87a9-9930-4f64-8592-5406f5fab8f4/dism-online-getfeatures-with-where-clause?forum=ITCG#01aeebe3-01b5-49cd-a8dd-1b1ce0352c8b.

Function Get-DismFeatures {
    [CmdletBinding()]
    Param ()

    Begin {
        $Results = Dism /online /Get-Features
        $Results = $Results[8..($Results.Count - 3)] | Where-Object {$_ -ne ''}

        $Feature = $Results | Select-String 'Feature'
        $State = $Results | Select-String 'State'
        $ResultsCounter = $Results.Count/2
    } # End Begin.

    Process {
        for ($i = 0; $i -lt $ResultsCounter; $i++) { 
            [pscustomobject]@{
                Feature = $Feature[$i].ToString().Split(':')[-1].Trim()
                State = $State[$i].ToString().Split(':')[-1].Trim()
            }
        }
    } # End Process.

    End {
    } # End End.
} # End Function.

Again, we could’ve use a much simpler function. Once a function is declared, we run the actions inside the function by invoking it — entering its name into the console. To run this locally, we’d just enter Get-DismFeatures, press Enter, and it would produce the results below. I’ve greatly shortened the results to save space.

PS C:\> Get-DismFeatures

Feature                                                     State
-------                                                     -----
Microsoft-Hyper-V-All                                       Enabled
Microsoft-Hyper-V-Tools-All                                 Enabled
Microsoft-Hyper-V                                           Enabled
Microsoft-Hyper-V-Management-Clients                        Enabled
Microsoft-Hyper-V-Management-PowerShell                     Enabled
Printing-Foundation-Features                                Enabled
Printing-Foundation-LPRPortMonitor                          Disabled
Printing-Foundation-LPDPrintService                         Disabled
...

To run this on a remote computer, or computers, we would need to ensure PS Remoting is available and working. You can test this with your own computer, or with a remote computer. Yes, you can use your local computer to determine if PS Remoting is working from, and into, your computer. Here’s three different examples of using my own computer, and one example of using a remote one.

PS> Invoke-Command -Computer localhost -ScriptBlock {$env:COMPUTERNAME}
TOMMYSCPU
PS> Invoke-Command -Computer . -ScriptBlock {$env:COMPUTERNAME}
TOMMYSCPU
PS> Invoke-Command -ScriptBlock {$env:COMPUTERNAME}
TOMMYSCPU
PS> Invoke-Command -Computer DC05 -ScriptBlock {$env:COMPUTERNAME}
DC05

Okay, so on to taking the function to a remote computer. Here’s how we do that (also with shorten results). Notice the placement of the dollar sign ($). This is different that we saw when taking locally declared variables to the remote session.

PS> Invoke-Command -ComputerName DC05 -ScriptBlock ${function:Get-DismFeatures}

Feature        : NetFx4ServerFeatures
State          : Enabled
PSComputerName : DC05
RunspaceId     : 72dc5b41-31b8-22b1-b5b1-29eae648123a

Feature        : NetFx4
State          : Enabled
PSComputerName : DC05
RunspaceId     : 72dc5b41-31b8-22b1-b5b1-29eae648123a
...

And, that’s it.

If you’re like me, you’re sitting back now and wondering, “Why not include the ability to run the function against remote computers, inside the function?” That’s an option, and often employed to make a function useful on both local and remote systems. Here’s the thing: You may not always write the tools you use, so having a way to use one that was written without a way to run it remotely, might be helpful one day.

Getting Local Variables into Remote Sessions

There are at least three ways to get your local variables into your remote sessions. I’ve written these in preferential order beginning with the best option. Don’t forget to read about_Remote_Variables (Get-Help -Name about_Remote_Variables) to learn more, and see more examples.

$Word1 = 'PowerShell'
$Word2 = 'on'
$Computer = 'Server1'

# Using Scope Modifier
# (1st Option -- works in PowerShell 3.0 and up)
Invoke-Command -ComputerName $Computer -ScriptBlock {
    Write-Verbose -Message "$Using:Word1 $Using:Word2 $env:COMPUTERNAME (Using Scope Modifer)" -Verbose
}
VERBOSE: PowerShell on Server1 (Using Scope Modifier)


# Param
# (2nd Option -- works in PowerShell 2.0 and up)
Invoke-Command -ComputerName $Computer -ScriptBlock {
    Param($Word1InParam,$Word2InParam)
    Write-Verbose -Message "$Word1InParam $Word2InParam $env:COMPUTERNAME (Param)" -Verbose
} -ArgumentList $Word1, $Word2
VERBOSE: PowerShell on Server1 (Param)


# Args
# (3rd Option -- works in PowerShell 2.0 and up)
Invoke-Command -ComputerName $Computer -ScriptBlock {
    Write-Verbose -Message "$($args[0]) $($args[1]) $env:COMPUTERNAME (Args)" -Verbose
} -ArgumentList $Word1, $Word2
VERBOSE: PowerShell on Server1 (Args)

Out-GridView in a PSRemoting Session

Twitter Reply to:

We can’t use the Out-GridView cmdlet in a remote session. How do we know this? Well for one, the documentation says so (search for ‘You cannot use a remote command’ on that webpage), and two, because if you try, you’ll get a straight forward error message on the topic: “Out-GridView does not work in a remote session.” Okay, but why?

Out-GridView produces a Graphic User Interface (GUI) — something we don’t use in PSRemoting sessions. In fact, everything about a PSRemoting session is text only. This isn’t just about Out-GridView, though. Other graphical elements in Windows PowerShell aren’t going to work either. This includes Get-Help’s -ShowWindow parameter, and the Show-Command cmdlet. It just wasn’t designed to work this way.

Other GUI elements don’t work either. While you won’t get a helpful message, like you do with the PowerShell cmdlets and parameters, launching notepad.exe and calc.exe isn’t going to work like it does on a local computer. Those programs work a little differently though, as they will actually launch on the remote computer. It just won’t be in any useable fashion from your remote session. Bonus: They may lock up your remote session until they are closed.

Note: If you were to try this, one way to rectify the situation would be to open a second PowerShell console and run the following, assuming you launched calc.exe: Invoke-Command -ComputerName computername -ScriptBlock {Get-Process -Name calc.exe | Stop-Process}. This would end the process on the remote computer, and give you back your prompt on the console where you were running your PSRemoting session.

Everything until now, assumed we were talking about an interactive PSRemoting session (using the Enter-PSSession cmdlet). Well, what about Invoke-Command? Invoke-Command is used to run commands on remote systems and return the results to the local computer. As you can see in the examples below, we can run a command on a remote computer and then display the results on our local computer, inside Out-GridView.

Note: Although I didn’t have any problems with the small handful of cmdlets I tried, the same webpage linked above indicates that data returned from a remote computer may not be formatted correctly for use with Out-GridView.

PS C:\> $Services = Invoke-Command -ComputerName dc01 -ScriptBlock {Get-Service}
PS C:\> $Services | Out-GridView
PS C:\> $PSWARules = Invoke-Command -ComputerName PSWAServer01 -ScriptBlock {Get-PSWAAuthorizationRule}
PS C:\> $PSWARules | Out-GridView

Thanks for the inspiration to write a little about this topic, Tim.

Creating Multiple Credential Objects

Download the complete function: https://gist.github.com/tommymaynard/98031ccd5de67005bf3063db06a33851

There are times when you made need to use additional credentials, other than those used to begin the Windows PowerShell session. When I need to PSRemote to another domain’s computer, I quickly run a function I have stored in my $PROFILE to create a variable that contains a credential object for the second domain. It’s a bit more specific for my environment, so I won’t bother sharing that exact function. What I will do, however, is share and explain a function I’ve written to create up to 10 credential objects. You’ll soon see where that can be changed (if for some reason someone would want more than that many). Realistically, 10 seems much too high anyway. Moving on.

Now, it might be important to know a bit more about how this started. The unfortunate thing about that function (the one in the link) is that it is maxed out at three credential sets (and it continually used the word ‘domain’). As well, it wasn’t as flexible as it should’ve been and it didn’t have any comment-based help or any verbose statements. So, a couple of days after publishing that post, I copied the function back into the PowerShell ISE and started working on a “1.1” version. That’s what we’ll discuss in this post.

First, we’ll write some basic, structural code for the advanced function.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param ()

    Begin {
    } #End Begin

    Process {
    } #End Begin
} #End Function

Now, let’s add the parameter that will define how many credential objects the function will create. The variable we’ll use is $Set and we’ll cast it as an integer (Set will, therefore, also be the name of the parameter). In addition, we’ll add code to define the -Set parameter as being mandatory (it must be included when the function is run), and make the parameter positional (the value for -Set can be entered without providing the -Set parameter name). In addition, we’ll add the ValidateRange validation attribute that will require that the integer entered, as the value for the -Set parameter, must be 1 through 10. This can be changed if necessary.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
    } #End Begin
} #End Function

We won’t need to do anything in the Begin block, so we’ll focus on the Process block next. In there, we’ll need to prompt for a user name and password as many times as the value of the parameter -Set indicates. Since we know the number of times we’ll be looping (to prompt for the username and password), I recommend we use a for statement. For statements work this way: set a variable ($i in our case) as a counter variable, add a comparison to determine how many times to loop (while $i is less than or equal to $Set), and finally, include a way to increment the counter variable (that’s what $i++ does), so that we only loop the proper number of times.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
        For ($i = 1; $i -le $Set; $i++) {

        }
    } #End Begin
} #End Function

The final piece is adding the parts necessary to prompt for usernames and passwords, and creating variables to store each of the credential objects. We’ll do this as part of a try-catch.

Line 15 below, first runs the Get-Credential cmdlet. We know this because it is in parenthesis. These parentheses indicate that this cmdlet needs to run before it’s used as the value of the Set-Variable‘s -Value parameter. If for some reason the user presses Cancel, or presses the X in the top-right corner of the prompt dialog, the try portion of the try-catch will fail, and the catch portion will run. It will indicate that no credential was created for that iteration through the loop.

If the user enters, at minimum a username (because a password can be blank), then it will set a variable called $CredSet# (the hash mark (#) indicates a number). If we indicate we want to create two credential objects when we run the function (New-TMMultiCred 2), then $CredSet1 will be the variable that holds the first credential object, and $CredSet2 will hold the second.

Still on line 15, notice that the -Scope parameter is being used with the Set-Variable cmdlet. If we didn’t include this parameter and its value, Global, then the variables created (or modified, if the variable(s) already existed) by this command would not be available after the function was done executing.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
        For ($i = 1; $i -le $Set; $i++) {
            try {
                Set-Variable -Name CredSet$($i) -Value (Get-Credential -Credential $null) -Scope Global
                Write-Output -Verbose "SUCCESS: Credential Set $i stored in `$CredSet$($i)"
            } catch {
                Write-Warning -Verbose "No credential object was created for set $($i)."
            }
        }
    } #End Begin
} #End Function

Here’s a look at the function in progress. The first image shows that three credential objects were requested. The first credential object has already been created and is stored in $CredSet1, the second wasn’t created, since the user pressed Cancel on the second prompt, and the third credential object will be created when the user presses OK. The second image shows the end result.

Function for Creating Multiple Credential Objects-01

Function for Creating Multiple Credential Objects-02

Once this is complete, the user can run cmdlets that have an optional -Credential parameter and supply the fitting credential object, as seen in the example below. You can return all the cmdlets and functions that have the -Credential parameter using Get-Command: Get-Command -ParameterName Credential.

PS C:\> Invoke-Command -ComputerName dc01 -ScriptBlock {Get-Date} -Credential $CredSet3

Download the complete function: https://gist.github.com/tommymaynard/98031ccd5de67005bf3063db06a33851