PSMonday #17: Monday, August 22, 2016

 Topic: PowerShell Remoting Continued VI

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.

If you’re done reading about PowerShell Remoting, then you’ll be glad to know that we’re wrapping it up this week with one final conversation. Today we’ll discuss implicit remoting. I recently read some Microsoft written, PowerShell documentation where they indicated this was an advanced topic. If you’ve been following along over the last several weeks, then as far as I’m concerned, you’re ready. You ought to know a good deal about PS Remoting by now.

Let’s start by considering that we have a PowerShell module called Test on a remote computer called SERVER01. Inside the module are three functions: Write-One, Write-Two, and Write-Three. When invoked, each function will echo the name of the computer where the function is running and then echo either “One!,” “Two!,” or “Three!” Here’s a quick look at the module.

psmonday-17-monday-august-22-2016-01

As we’ve seen before, we can use Enter-PSSession to interactively connect and run any of the functions. We can also use Invoke-Command to run the functions on the remote computer and then return the results. In the implicit remoting option, we’ll create a new PSSession to a remote computer, such as we did last week, and then bring the functions to our local computer.

$Session = New-PSSession -ComputerName SERVER01
Import-PSSession -Session $Session -Module Test

ModuleType Version Name             ExportedCommands
---------- ------- ----             ----------------
Script     1.0     tmp_jfhp0hhd.hk5 {Write-One, Write-Three, Write-Two}

By default, Import-PSSession will return output to include the ModuleType, the version, the local, temporary name it’s using for the module, and the exported commands as seen in the above results. These exported commands are the functions in the module, as you can easily tell. To hide this output, you can pipe the Import-PSSession command to Out-Null, as seen in the below example. It’ll do the same work, but without the output.

Import-PSSession -Session $Session -Module Test | Out-Null

While the temporary module now resides on our computer, it still runs the functions on the remote computer.

Write-One
Write-Two
Write-Three

SERVER01
One!
SERVER01
Two!
SERVER01
Three!

Well, there we go. I think we’ve covered a fair amount of information on PowerShell Remoting. We’ll pick up with something new next week, but don’t be surprised if upcoming topics include PS Remoting in one way, or another. It’s a widely used option, when using PowerShell for remote management.

Windows PowerShell, is PowerShell

In every post I’ve written here, I’ve always made a conscious effort to use the full term “Windows PowerShell,” before I use “PowerShell” later in the post. Well, those times have changed. With the recent news, dropped yesterday (August 18, 2016), PowerShell is open-source and cross-platform. Windows PowerShell is just PowerShell, and it’s available on Linux and Mac OS.

Here’s one of several stories on yesterday’s news: https://blogs.msdn.microsoft.com/powershell/2016/08/18/powershell-on-linux-and-open-source-2. And here, is the story that mentions the name change: https://blogs.msdn.microsoft.com/powershell/2016/08/17/windows-powershell-is-now-powershell-an-open-source-project-with-linux-support-how-did-we-do-it.

I’ve always been pleased by my site’s tagline: “A Windows PowerShell Resource.” Today, I’ve gone ahead and made the change, too, signifying that I’m ready, as well. There’s isn’t a non-Windows post here, but in time, there may be.

Yesterday, and today.

windows-powershell-is-powershell-2016-01

windows-powershell-is-powershell-2016-02

It’s a new era, now. Everything I’ve read, from people I trust in the PowerShell community, says the same thing. The investment we’ve made in PowerShell will be transferable to these other operating systems. We’re better off, than we were just a day ago.

Handle a Parameter Whether it’s Included or Not

I’m in the process of writing a new tool. It’s basically a wrapper function for Get-ADObject that only returns Active Directory (AD) contact objects. While there’s a Get-ADComputer cmdlet to get AD computer objects, and a Get-ADUser cmdlet to get AD user objects, there’s no specific cmdlet for contacts. There’s no surprise here really, and that’s likely why we have a Get-ADObject cmdlet. It’s for those AD objects that didn’t make the cut and get their own.

I’ve seen some discussion on this in the past: How do I handle running a cmdlet within a function when I don’t know if a parameter and parameter value will be included, or not? Let’s consider that my Get-ADContact function will run the Get-ADObject cmdlet, as seen below. In the Begin block, we create a $Parameter variable that will hold a hash table. We’ll populate it in such a way that the key Filter will have a corresponding value of {ObjectClass -eq ‘contact’}. In the Process block, we splat this hash table, currently with a single parameter and parameter value, to the Get-ADObject cmdlet.

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

    Begin {
        # Create parameter hash table; add Filter parameter.
        $Parameters = @{Filter = {ObjectClass -eq 'contact'}}
    } # End Begin.

    Process {
        Get-ADObject @Parameters
    } # End Process.

    End {
    } # End End.
} # End Function: Get-ADContact.

There’s two other parameters included in Get-ADObject that I want to allow my users the ability to include. That’s the -SearchBase and -SearchScope parameters. You can read more by checking the help for Get-ADObject: Get-Help -Name Get-ADObject. There’s actually several AD cmdlets that also include these parameters. They can be quite helpful, so that’s why I’ve decided to include them.

Before we continue, I want to let the audience know that I am familiar with creating proxy functions, and I understand it might’ve been a better option. Maybe I’ll circle back sometime and see about replicating the functionality I’ve created here, in that manner. It might turn out this wasn’t worth writing and posting. No guarantee, but it’s possible.

Okay, back on track. Let’s add the additional lines inside the Param block, that make accepting a -SearchBase and -SearchScope parameter value possible.

Function Get-ADContact {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$SearchBase,

        [Parameter()]
        [ValidateSet(0,'Base',1,'OneLevel',2,'SubTree')]
        $SearchScope
    )

    Begin {
        # Create parameter hash table; add Filter parameter.
        $Parameters = @{Filter = {ObjectClass -eq 'contact'}}
    } # End Begin.

    Process {
        Get-ADObject @Parameters
    } # End Process.

    End {
    } # End End.
} # End Function: Get-ADContact.

Now, our Get-ADContact function will include the two additional parameters. Neither parameter is mandatory, but the -SearchScope parameter does include a ValidateSet parameter attribute to ensure it’ll only accept the values 0, 1, 2, Base, OneLevel, or SubTree. Base and 0 are equivalent, as are 1 and OneLevel, and 2 and SubTree.

The next thing I need to do is include the parameter values assigned to the -SearchBase and -SearchScope parameters to our $Parameters hash table when those are included. I decided to do this using the $PSBoundParameters variable, the ForEach-Object cmdlet, and the switch language construct.

Function Get-ADContact {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$SearchBase,

        [Parameter()]
        [ValidateSet(0,'Base',1,'OneLevel',2,'SubTree')]
        $SearchScope
    )

    Begin {
        # Create parameter hash table; add Filter parameter.
        $Parameters = @{Filter = {ObjectClass -eq 'contact'}}

        $PSBoundParameters.Keys | ForEach-Object {
            Switch ($_) {
                'SearchBase' {$Parameters += @{SearchBase = $SearchBase}; break}
                'SearchScope' {$Parameters += @{SearchScope = $SearchScope}}
            }
        }
    } # End Begin.

    Process {
        Get-ADObject @Parameters
    } # End Process.

    End {
    } # End End.
} # End Function: Get-ADContact.

All done. Now, we have function that only returns AD contact objects. Additionally, we have the option of narrowing down our search by including the often used -SearchBase and -SearchScope parameters. While I don’t doubt there’s a better way, I think this one will work for now.

PSMonday #16: Monday, August 15, 2016

Topic: PowerShell Remoting Continued V

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.

I think back on the last few weeks and there’s a few things we haven’t covered. Let’s say we have four servers and we want to run the same command on each one. We know we can use Invoke-Command and supply four names as the value to the -ComputerName parameter. Jump back to PSMonday #11 on July 11, 2016, for an example.

In today’s PSMonday, we’ll see how to do this another way. We’ll start by creating four PS Remoting sessions. Each session object will hold the session information for an opened session with a remote computer. Displaying the value stored in the variable $Session includes the ConfigurationName property. In the below example, you can see it’s the default endpoint — Microsoft.PowerShell — we discussed last week.

$Session = New-PSSession -Computer DC01,DC02,DC03,DC04
$Session

Id Name           ComputerName    State         ConfigurationName     Availability
-- ----           ------------    -----         -----------------     ------------
4 Session4        DC04            Opened        Microsoft.PowerShell     Available
3 Session3        DC03            Opened        Microsoft.PowerShell     Available
2 Session2        DC02            Opened        Microsoft.PowerShell     Available
1 Session1        DC01            Opened        Microsoft.PowerShell     Available

With these sessions stored in the variable $Session, we can use Invoke-Command and the -Session parameter to run commands against each computer. Here’s a slightly modify version from that previous PSMonday.

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

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

One benefit of using sessions is speed while running the commands against the remote computer. It’ll be marginable, until you have a good number of computers as a part of your session variable. The reason there’s a boost in speed is because you already have currently existing sessions to use. Invoke-Command doesn’t have to create sessions, and then destroy them, when it’s done. I should note that it still takes time to create the sessions prior to using them.

Another benefit is when you need to run multiple commands against the same computers, but don’t want to do it at the same time. You can connect and collect some data and then connect again later without the need to create a new connection. Think about it, you can create a variable in the session and it will continue to exist. This means your second Invoke-Command can use the variable you created with the first Invoke-Command, command. See the below example.

$Session = New-PSSession -ComputerName DC05
Invoke-Command -Session $Session -ScriptBlock {$Var = $env:COMPUTERNAME}
Start-Sleep -Seconds 5
Invoke-Command -Session $Session -ScriptBlock {$Var}

DC05

I think we may be done with PowerShell Remoting. We’ll see.

Quick Learn – PSMonday #15: Monday, August 8, 2016

Topic: PowerShell Remoting Continued IV

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 we use PowerShell Remoting, as we have for the last few weeks, we connect to an endpoint, or a session configuration, on a remote computer. It doesn’t matter whether we use Enter-PSSession or Invoke-Command, an endpoint is still required. So it’s been said, the term endpoint and session configuration can be used interchangeably. The Get-PSSessionConfiguration cmdlet allows you to view the endpoints on a computer, as is displayed in the below example.

Get-PSSessionConfiguration

Name          : microsoft.powershell
PSVersion     : 4.0
StartupScript :
RunAsUser     :
Permission    : BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.powershell.workflow
PSVersion     : 4.0
StartupScript :
RunAsUser     :
Permission    : BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.powershell32
PSVersion     : 4.0
StartupScript :
RunAsUser     :
Permission    : BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.windows.servermanagerworkflows
PSVersion     : 3.0
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed

The default endpoint’s name is Microsoft.PowerShell. This is to say, that if you don’t indicate which endpoint to use when you remotely connect to another computer — and we haven’t in any of these PowerShell Remoting PSMondays — then you’re connecting to this endpoint.

Notice the Permission property on the Microsoft.PowerShell endpoint. This indicates, that by default, a user must have a membership in the local administrators group, or in the Remote Management Users group, to use this endpoint. Simply put, you must have specific permissions to use a PS Remoting endpoint. It’s not something that just anyone can use on your servers by default.

The idea behind constrained endpoints, or JEA (Just Enough Administration) endpoints, as they’re now called, is that you can create your own endpoint, give regular users access to it, have it run as an elevated user (using the RunAsUser property), and limit the cmdlets and functions available in the endpoint. That’s right, we can allow non-administrators the ability to perform tasks as an administrator, without the need to ever elevate the account used to connect to the JEA endpoint. While we won’t go into this any further in a PSMonday — at least I can’t imagine we will — this information may be beneficial to know.

See you next week.

Quick Learn – Format PowerShell Results for Outside of PowerShell

There are times I use PowerShell to help format information I need to send along to others in an email. What I mean is that I return results from PowerShell commands, and format it using PowerShell, so that I can simply send it to the clipboard and paste it into an email.

The below example gathers all the computer-related details about my Lync servers each time I open a new Windows PowerShell session. The time to complete is minimal, so I’m perfectly okay with returning all the properties on each of the servers. The extra milliseconds are worth having the most current information on these servers inside my $Lync variable. By the way, you don’t need to know anything about Lync (or Skype) to make use of the post; it’s not the point.

PS > $Lync = Get-ADComputer -Filter * -SearchBase "OU=Lync,DC=MyDomain,DC=com" -Properties *

With the variable set and assigned, I can do things like the next two, combined examples. This comes in handy all the time, and without the need to think about rewriting the command whenever it’s needed again.

PS > $Lync.Name
L-FE01
L-PC02
L-PC01
L-FE02
L-Ed01
L-FE04
L-FE03
L-Ed02

PS > $Lync | Select-Object Name,Description

Name              Description
----              ----------- 
L-FE01            Lync 2013 Front End
L-PC02            Lync 2013 Persistent Chat
L-PC01            Lync 2013 Persistent Chat
L-FE02            Lync 2013 Front End
L-Ed01            Lync 2013 Edge
L-FE04            Lync 2013 Front End
L-FE03            Lync 2013 Front End
L-Ed02            Lync 2013 Edge

Let’s consider that I need to enter the names of the servers into an email and I want them to be comma separated. Easy, we’ll the use the -join operator to complete this task

PS > $Lync.Name -join ','
L-FE01,L-PC02,L-PC01,L-FE02,L-Ed01,L-FE04,L-FE03,L-Ed02
PS >
PS > # Humm... let's add spaces, too.
PS >
PS > $Lync.Name -join ', ' # <-- Notice the trailing space.
L-FE01, L-PC02, L-PC01, L-FE02, L-Ed01, L-FE04, L-FE03, L-Ed02

Because I’m sold on PowerShell, I’ll always take extra time to use it to its full potential. What I wanted to do was add the word “and” after the last comma and a space, and before the name of the final Lync server. This will make the most sense when my text is dropped into an email and used as, or part of, a sentence. We’ll start this example by determining the location of the last comma by using the .LastIndexOf() method. This returns the location within the string.

PS > $index = (($Lync.Name) -join ', ').LastIndexOf(',')
PS > $index
54

Now that we know the location of the last comma, we can remove it and then insert what we want. The next example uses two methods. First the .Remove() method removes the comma, and then the .Insert() method adds everything the way we want it.

PS > (($Lync.Name) -join ', ').Remove($index,1).Insert($index,', and')
L-FE01, L-PC02, L-PC01, L-FE02, L-Ed01, L-FE04, L-FE03, and L-Ed02
PS >
PS > (($Lync.Name) -join ', ').Remove($index,1).Insert($index,', and') | clip.exe

In the last above line, we reran the command and piped it to clip, so that it’s ready to be pasted into my email. After you do this awhile, you find little ways in PowerShell to handle the exact formatting you want. It’s these little tasks, that will give you an opportunity to continue to practice your PowerShell. And finally, here’s the email where I entered the information I had collected and formatted in PowerShell.

format-powershell-results-for-outside-of-powershell-01

Quick Learn – PSMonday #14: Monday, August 1, 2016

Topic: PowerShell Remoting Continued III

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 how to get a locally created variable into a PS Remoting session, by taking advantage of the Using scope modifier — a feature that was added in Windows PowerShell 3.0. Today, we’ll go over how we did it in earlier versions.

In PowerShell 2.0 the Using scope modifier didn’t yet exist. To get locally declared variables into a PS Remoting session took a bit more work. Below are two examples, beginning with the most preferred, first.

The first below example requires the use of the Param block inside the value provided to Invoke-Command’s -ScriptBlock parameter. It also requires the use of Invoke-Command’s -ArgumentList parameter. The $LocalVariable was set last week with the name of the local computer. This time, we’ll take two variables into the PS Remoting session, right after we define the $Date variable. I’ve added a couple blank lines inside the script block, to make things a bit easier to read.

$Date = Get-Date

Invoke-Command -ComputerName MEMSRV01 -ScriptBlock {
    Param($String1,$String2)

    "The local computer is $String1."
    "The local recorded date and time is $String2."
    "The remote computer is $env:COMPUTERNAME."

} -ArgumentList $LocalVariable,$Date

The local computer is TOMMYSPC.
The local recorded date and time is 07/30/2016 09:30:50.
The remote computer is MEMSRV01.

So we’re sure we know what happened here, $LocalVariable went to the remote session and became the value provided to $String1, and $Date was the value provided to $String2.

Our final example uses the $args variable without the need for the Param block. It still requires the -ArgumentList parameter, however. Although this method requires a little less work, using the $args variable can become confusing and so this option should be saved for last. Realistically, it should never be used, as you can always use the above example when you’re working with PowerShell 2.0.

Invoke-Command -ComputerName MEMSRV01 -ScriptBlock {

    "The local computer is $($args[0])."
    "The local recorded date and time is $($args[1])."
    "The remote computer is $env:COMPUTERNAME."

} -ArgumentList $LocalVariable,$Date

The local computer is TOMMYSPC.
The local recorded date and time is 07/30/2016 09:30:50.
The remote computer is MEMSRV01.

And that’s how we get our local variables into our PS Remoting sessions, if the Using scope modifier isn’t an option.

Quick Learn – 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.

Quick Learn – 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\"}

Quick Learn – 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.