Tag Archives: PS Remoting

Determine If There’s a Current PowerShell Remoting Session

Ever need to reboot a server and worry someone else may be actively logged on? In this situation, you can either check Task Manager > Users, or use the quser.exe command-line tool. If you’re not familiar with the command-line tool quser.exe, it can be used against a remote computer, as in the example below.

PS> quser.exe /server DC01
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 adminbob                                  2  Disc      2+01:17  1/29/2016 8:58 PM

You can use this command-line tool on a local computer, as well, in order to see if anyone else is logged on. All you need to do is enter quser.exe (drop the /server switch [which indicates the remote server to query]), and the remote computer name. Since quser.exe is located in C:\Windows\System32, you don’t even need to include the .exe. The Path variable, $env:PATH, will assist in correctly determining the executable.

The problem is that this command-line tool, and Task Manager, don’t tell us if anyone has a current PowerShell Remoting session to the server. That’s where the Get-WSManInstance cmdlet can help. Here’s an example of using this cmdlet to check our DC01 server — the same server as in the example above.

PS> Get-WSManInstance -ComputerName DC01 -ResourceURI Shell -Enumerate
PS>

Because it didn’t return any information, we can safely assume there are no active PS Remoting sessions on DC01. Had there been, it would have returned something like the results below.

PS> Get-WSManInstance -ComputerName DC01 -ResourceURI Shell -Enumerate

rsp             : http://schemas.microsoft.com/wbem/wsman/1/windows/shell
lang            : en-US
ShellId         : AB4D6A3B-213B-20F6-A61C-9CCAG41A1C2E
Name            : Session1
ResourceUri     : http://schemas.microsoft.com/powershell/Microsoft.PowerShell
Owner           : MYDOMAIN\adminbill
ClientIP        : 10.10.10.20
ProcessId       : 10192
IdleTimeOut     : PT7200.000S
InputStreams    : stdin pr
OutputStreams   : stdout
MaxIdleTimeOut  : PT2147483.647S
Locale          : en-US
DataLocale      : en-US
CompressionMode : XpressCompression
ProfileLoaded   : Yes
Encoding        : UTF8
BufferMode      : Block
State           : Connected
ShellRunTime    : P0DT0H0M6S
ShellInactivity : P0DT0H0M4S
MemoryUsed      : 70MB
ChildProcesses  : 0

The default properties are much more than I need. Here’s a filtered down version of the cmdlet’s properties using the Select-Object cmdlet. In my opinion, these properties are the ones that include the most desirable information.

PS> Get-WSManInstance -ComputerName DC01 -ResourceURI Shell -Enumerate |
>>> Select-Object Name,Owner,ClientIP,State

Name         Owner                  ClientIP        State
----         -----                  --------        -----
Session1     MYDOMAIN\adminbill     10.10.10.20     Connected

It seems I have a difficult time remembering the required parameters in this command; therefore, I wrapped the whole thing in a function and placed it in my profile. This, in order to make sure that I’ll never remember them. My function also allows me to run this command against multiple remote computers at nearly the same time. The -ComputerName parameter of Get-WSManInstance only accepts a single computer per execution, so wrapping this cmdlet in a Foreach construct (within the function), allows us to work around this behavior. Take a look at the function and the example below, as the function runs against three different computers.

Function Get-PSRemotingSession {
    Param(
        [string[]]$ComputerName
    )

    Foreach ($Computer in $ComputerName) {
        Get-WSManInstance -ComputerName $Computer -ResourceURI Shell -Enumerate |
            Select-Object -Property @{N='ComputerName';E={$Computer}},Name,Owner,ClientIP,State
    }
}
PS> Get-PSRemotingSession -ComputerName DC02,WEB01,DC03

ComputerName : DC02
Name         : Session1
Owner        : MYDOMAIN\admindave
ClientIP     : 10.10.10.22
State        : Connected

ComputerName : DC03
Name         : Session1
Owner        : MYDOMAIN\admindave
ClientIP     : 10.10.10.22
State        : Connected

These results indicate that two of the three computers have active PS Remoting sessions. In this case, the two sessions were initiated by MYDOMAIN\admindave. The IP is not the remote computer’s IP, such as DC02 and DC03, but instead the IP from the connecting computer (Dave’s computer).

Keep this cmdlet in mind when you’re doing anything that will restart the WinRM service, to include restarting the server. Restarting this service on a computer where there’s an active PS Remoting session, will instantly break the remoting session.

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.