Remoting the Implicit Way

Doctor Scripto

Summary: Guest blogger, June Blender, talks about how to use Windows PowerShell implicit remoting.

Today we welcome June Blender, senior programming writer for Windows Azure Active Directory. Take it away, June…

Just about everyone knows how to run Windows PowerShell commands on a remote computer. You can use WMI or Windows PowerShell remoting, and the commands are very similar.

Use the ComputerName parameter of a cmdlet to run commands in a temporary session:

PS C:\> Get-Process -ComputerName Server01

Or, use the Invoke-Command cmdlet to run the command in a temporary session:

PS C:\> Invoke-Command -ComputerName Server01 {Get-PSScheduledJob}

Or, create a session on the remote computer, and then use the Invoke-Command cmdlet to run commands in the session:

PS C:\> $s = New-PSSession -ComputerName Server01

PS C:\> Invoke-Command -Session $s {Get-ScheduledJob}

Or, use the Enter-PSSession cmdlet to start an interactive session and then run the commands in the interactive session:

PS C:\> $s = New-PSSession -ComputerName Server01

PS C:\> Enter-PSSession -Session $s

[SERVER01]: PS C:\> Get-ScheduledJob

In every case, you sit at one computer and run commands on another computer. The commands get information from the remote computer and return the results to the local computer.

But there’s another way to run commands on a remote computer. Look at this command sequence:

PS C:\> $s = New-PSSession -ComputerName Server01

PS C:\> Import-Module -Session $s -Name MS

PS C:\> Get-ScheduledJob

The first command creates a session on the Server01 computer. The second command imports a module from the remote session into the local session. That’s nice.

But the third command simply runs a cmdlet from the imported module. There’s nothing obviously remote in that command. There’s no Session or PSSession parameter. There’s no reference to the session in the $s variable. We haven’t used the Enter-PSSession cmdlet to create an interactive session. It’s simply a local command.

Wrong!

But don’t feel badly. The remoting in this command is not obvious. It happens behind the scenes—implicitly. I’ll show you what’s going on.

Import a local module

To understand, let’s start with a standard Import-Module command. The following command imports a module from the hard drive of the local computer:

PS C:\> Import-Module PSWorkflow

The Import-Module cmdlet finds the module on the local hard drive. It runs the scripts and functions in the module in the local session and adds the cmdlets, providers, workflows, CIM commands, and snippets in the module to the session.

Image of flow diagram

In Windows PowerShell 3.0, you don’t need to run Import-Module commands. The modules are imported automatically when you use a command in the module, but the automatic process works just like the Import-Module cmdlet.

When the Import-Module command completes, the commands in the modules are loaded into your session. When you run a command in the module, it runs on your local computer unless you explicitly run it remotely—by using the ComputerName parameter of a command, or by using the Invoke-Command cmdlet.

Import a remote module (implicit remoting)

Unlike the previous scenario, importing a module from a remote computer does not add the commands in the module to your local session. Instead, what it adds to your session are proxy commands. The proxy commands are functions that look like local cmdlets in the session.

When you run a proxy command, instead of running the command on the local computer, the proxy runs the real command in a session on the remote computer and returns the results to the local session.

Image of flow diagram

There are some subtle differences between a locally imported module and a remotely imported module. If you run Get-Module, you might notice that PSScheduledJob is imported as a script module. If you import it locally, it’s a binary module.

PS C:\> $s = New-PSSession -ComputerName Server01

PS C:\> Import-Module -PSSession $s PSWorkflow

PS C:\> Get-Module

 

ModuleType Name                ExportedCommands

———- —-                —————-

Manifest  Microsoft.PowerShell.Management   {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Content…}

Manifest  Microsoft.PowerShell.Utility    {Add-Member, Add-Type, Clear-Variable, Compare-Object…}

Script   PSScheduledJob           {Add-JobTrigger, Disable-JobTrigger, Disable-ScheduledJob, Enable-Job…

The proxy commands look like the real commands, but they’re functions, not cmdlets.

PS C:\> Get-Command -Module PSScheduledJob

 

CommandType   Name                        ModuleName

———–   —-                        ———-

Function    Add-JobTrigger                   PSScheduledJob

Function    Disable-JobTrigger                 PSScheduledJob

Function    Disable-ScheduledJob                PSScheduledJob

Function    Enable-JobTrigger                 PSScheduledJob

Function    Enable-ScheduledJob                PSScheduledJob

Function    Get-JobTrigger                   PSScheduledJob

Function    Get-ScheduledJob                  PSScheduledJob

Function    Get-ScheduledJobOption               PSScheduledJob

Function    New-JobTrigger                   PSScheduledJob

Function    New-ScheduledJobOption               PSScheduledJob

Function    Register-ScheduledJob               PSScheduledJob

Function    Remove-JobTrigger                 PSScheduledJob

Function    Set-JobTrigger                   PSScheduledJob

Function    Set-ScheduledJob                  PSScheduledJob

Function    Set-ScheduledJobOption               PSScheduledJob

Function    Unregister-ScheduledJob              PSScheduledJob

To see the commands in any of the proxy functions, get the value of the Definition property of the function.

Following is an excerpt of the script in the Definition property of the Get-ScheduledJob proxy. You can see that it’s running an Invoke-Command command, hiding the ComputerName property that is added to all remote commands, and adding the parameters and parameter values that you use to call the proxy. It’s also using comment-based Help to get the Help topics from the remote session. (For more information, see about_Comment_Based_Help.)

PS C:\>(Get-Command Get-ScheduledJob).Definition

<snip/>

      $scriptCmd = { & $script:InvokeCommand `

              @clientSideParameters `

              -HideComputerName `

              -Session (Get-PSImplicitRemotingSession -CommandName ‘Get-ScheduledJob’) `

              -Arg (‘Get-ScheduledJob’, $PSBoundParameters, $positionalArguments) `

              -Script { param($name, $boundParams, $unboundParams) & $name @boundParams @unboundParams }`

             }

 

<snip/>

 

  # .ForwardHelpTargetName Get-ScheduledJob

  # .ForwardHelpCategory Cmdlet

  # .RemoteHelpRunspace PSSession

Implicit remoting in custom sessions

In practice, this “under the covers” remoting is precisely what you want. For example, if you have an Exchange server with the Exchange modules, or any type of specialized or dedicated computer with the modules for that feature, you want the commands to run on the server and get data from the server. You want to cordon off these features from other computers with other purposes. And it’s a great convenience to be able to run the commands from your local administrator computer simply by importing the modules. It saves you the hassle of creating and managing sessions.

There’s a security benefit, too. For example, if you’re interested in a cool new Windows PowerShell module from CodePlex or GitHub, you might download it to your test computer, and then import it into your current session. You can test it from your local computer, but it runs on your test computer.

To make it even easier, you can use session configurations to create sessions that contain particular modules, and then direct users to connect to those sessions and import the modules from them. This is the strategy that the Exchange shell uses very effectively.

The “gotcha” of implicit remoting

To be very clear, you really should not use Import-Module to run commands remotely. It works best as designed—that is, when the module and the data that the module gets are both on a remote computer.

When you try to use it for general remoting, you’ll run into a few easily foreseen issues. If you have imported a module from a computer, you need to remember that the commands run on the remote computer. If you run Get commands, they will get data from the remote computer. If you run Set commands, they change data on the remote computer. The commands look and feel local, but they’re remote commands.

PS C:\> Import-Module -PSSession $s -Name PSScheduledJob

PS C:\> Get-ScheduledJob

 

Id     Name      JobTriggers   Command                 Enabled

—     —-      ———–   ——-                 ——-

1     Update-Help   1        Update-Help               True

If you don’t have an Update-Help scheduled job on the local computer, or if you have many scheduled jobs that are not returned, the result might surprise you.

Another potential “gotcha” is shadowing. If you try to import a module from a remote computer when the commands of that module are already in your session, the command fails. The result looks like a warning, but the remote module is not imported into the local session.

PS C:\> Import-Module -PSSession $s -Name Microsoft.PowerShell.Utility

WARNING: The ‘Microsoft.PowerShell.Utility’ module was not imported because the ‘Microsoft.PowerShell.Utility’ snap-in was already imported.

And the Force parameter will not help you.

PS C:\> Import-module -PSSession $s -Name Microsoft.PowerShell.Utility -Force

WARNING: The ‘Microsoft.PowerShell.Utility’ module was not imported because the ‘Microsoft.PowerShell.Utility’ snap-in was already imported.

You can import a module from a remote computer if the module is installed, but not imported into the session. For example, if the local computer has the PSScheduledJob module, but it’s not in the session, you can import it remotely. When you run commands in the module, they run on the remote computer.

PS C:\> Import-Module -PSSession $s -Name PSScheduledJob

PS C:\> Get-ScheduledJob

 

Id     Name      JobTriggers   Command                 Enabled

—     —-      ———–   ——-                 ——-

1     Update-Help   1        Update-Help               True

You can still import the local version of the same module into your session. The command doesn’t fail, because the cmdlets in the local module don’t override the functions from the remote version of the module.

PS C:\> Import-Module PSScheduledJob

PS C:\>

Now, you have two modules with the same name—one remote script module and one local binary module.

PS C:\ps-test> Get-Module PSScheduledJob

 

ModuleType Name                ExportedCommands

———- —-                —————-

Binary   PSScheduledJob           {Add-JobTrigger, Disable-JobTrigger, Disable-ScheduledJob, Enable-Job…

Script   PSScheduledJob           {Add-JobTrigger, Disable-JobTrigger, Disable-ScheduledJob, Enable-Job…

And, for each command in the module, you have a cmdlet and a (proxy command) function.

PS C:\ps-test> Get-Command Get-ScheduledJob -Module PSScheduledJob

 

CommandType   Name                        ModuleName

———–   —-                        ———-

Function    Get-ScheduledJob                  PSScheduledJob

Cmdlet     Get-ScheduledJob                  PSScheduledJob

Because functions take precedence over cmdlets in Windows PowerShell, if you run the command, the proxy command function from the remote module runs. (For more information, see about_Command_Precedence.)

You can test this premise by running Get-Command.

PS C:\ps-test> Get-Command Get-ScheduledJob

 

CommandType   Name                        ModuleName

———–   —-                        ———-

Function    Get-ScheduledJob                  PSScheduledJob

You can run a command from the local module. The module-qualified name of the cmdlet does not help, because the modules have the same name. But you can use the command type to distinguish the commands.

PS C:\> &(Get-Command Get-ScheduledJob -Module PSScheduledJob -CommandType Cmdlet)

However, this trick does not work when the module exports a function.

Using implicit remoting to manage non-Windows computers

I’ve saved the best part for last. In Windows PowerShell 3.0, you can create a CIM session on a computer that does not have Windows PowerShell or does not have Windows PowerShell remoting enabled. You can even create a CIM session on a computer that is not running Windows if it is standards-based and WMI-compatible.

After you have a CIM session, you can use the CIMSession parameter of Import-Module to import CIM modules from the remote computer to the local computer. When you run the commands from the module in the CIM session, it gets and sets data on the remote computer.

This is really a different topic, but you can see the potential.

~June

Thank you, June!

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

0 comments

Discussion is closed.

Feedback usabilla icon