Get Remote PowerShell Session Connections

magnifying-glass During a recent PowerShell training class we naturally covered PowerShell remoting. During the discussion I explained that a remote PSSession is essentially transparent to any currently logged on user. Unless of course you reboot the computer! One way you can identify a remote session is by the presence of the wsmprovhost process. You should see this process whenever there is a remote PSSession to the computer. So then the discussion turned to tracking who might have sessions across multiple computers which is especially helpful when dealing with disconnected sessions since you can only see your own sessions. I don’t have a perfect solution, but let’s see if this helps. Here is Get-PSRemoteSession.

This function takes a computername as a parameter. It does a quick ping to verify the computer is running. I probably should have made that optional, but I needed it at the time. The function then queries the computer using Get-CimInstance for all instances of the wsmprovhost.exe process. I’m using CIM because datetime values are automatically formatted which makes it much easier to add a custom property indicating how long the process, and presumably the remote session, have been running. I also add a custom property to get the process Owner. Due to a quirk (bug?) in the CIM cmdlets, I can even query a remote computer running PowerShell 2.0. When using a filter, the CIM cmdlets work with a v2 computer. I won’t question it but will take advantage of it.

By default, the function writes a summary object to the pipeline.

get-psremotesession-1

The one thing I have yet to figure out is a way to show what computer each session is connected from. Although even if I could make the correlation with an active network connection, I’m not sure that would help in the event of a disconnected session. Nor can I tell the state of the session from the process.

I included an option to get the full process object so you could run commands like this:

get-psremotesession-2

But I suspect for many of you the summary will suffice. Here are some examples.

get-psremotesession-3

get-psremotesession-4

get-psremotesession-5

If you kill the wsmprovhost process, that will break the PSSession so be careful. But at least now you have a way of identifying what sessions might be open. I hope you’ll let me know what you think. Enjoy!

Runspaces, Remoting and Workflow, Oh My!

talkbubbleThe other day on Twitter I saw a message about new script in the Microsoft Script Center on getting remote event logs with WMI. So I took a look at the script. If you take a minute to look at the script you’ll quickly realize this is not a script for beginners. My initial thought was “Why?”. We already have cmdlets in PowerShell for querying event logs on remote computers. I realize the script was trying to avoid some of the issues we run into with WMI and I can’t question the effectiveness of his function. It works and with out a doubt using runspaces like this is faster than cmdlets.

My concern when I see scripts like this is that someone new to PowerShell will see it and run to the hills thinking they’ll never be able to use PowerShell and that is definitely not the case. So I decided to see what I could come up with that used a more IT Pro friendly cmdlet-based approach. I wanted to write something that most of you could have come up with.

My first attempt is a function that uses Invoke-Command to run the Get-Eventlog cmdlets in a remote session. In the function I define a scriptblock that gets all the event logs with records, and then gets all non-information or SuccessAudit events from those logs that have happended since midnight yesterday. My function supports credentials and takes advantage of a few other features from Invoke-Command.

The function works and took a about 1 1/2 min to query 8 machines in my virtual test environment. There is definitely some overhead when using Invoke-Command, but the trade off is a script that is a little easier to develop and maintain.

Then I thought, what about a workflow? I’m querying event logs but there’s no reason I can’t query all of them simultaneously. Here’s my workflow that does essentially the same thing as my function.

Interestingly, in my tests the workflow took about the same amount of time to run. But this is a shorter script to develop because all of the features like remoting, credentials and jobs are automatically part of the workflow. There is a potential downside in that all the remote machines must be running PowerShell 3.0. This workflow is also a great example in that workflows aren’t always the answer. There’s nothing really here, other than potentially the use of parallelism, that makes this a better choice than my function.

My last concern with the gallery script, and I don’t know if this would have an effect on its performance, is that all the event logs are rolled up in a property for each computer. This means you have to take some further steps to expand and format the results. My function and workflow, because they rely on Get-Eventlog, are formatted and ready to go.

What I haven’t tried yet, is how this same task can be done with Get-WinEvent or Get-CimInstance. The latter helps avoid some of the issues with WMI and might perform better. If I have time, I’ll get back to you with my results. But in the meantime, what do you think about all of this?

Using runspaces in PowerShell has a place, but definitely requires an advanced skill set and a willingness to accept a tradeoff of a more complicated script to develop and maintain (especially if someone else has to) with improved performance. I can see where using a runspace approach makes sense when you have 1000s of computers. But I might also argue that if that is your environment, you probably have full-blown management suites. Yes, I know there will always be exceptions. But for the majority of you, are you happy writing scripts that use existing cmdlets or do you feel obligated to learn .NET before you can use PowerShell?

UPDATE:
I tried this using Get-CIMInstance. Yes, this requires PowerShell 3 remotely (unless you take an extra step to setup a DCOM CIM session) and the use of the WSMan protocol, but this performs suprisingly well and only takes a few lines of PowerShell.

I ran this in my test environment and it took about 30 seconds to return 188 event log entries. Because we’re using WSMAN we avoid some of the issues with RPC and DCOM. So here is a solution, at least in this case, that is as fast as using runspaces but only took a few minutes to write and is easy to follow.

Everything we do as IT Pros is a matter of balancing trade-offs and working within a set of limitations.

Get Local Admin Group Members in a New Old Way

Yesterday I posted a quick article on getting the age of the local administrator account password. It seemed appropropriate to follow up on a quick and dirty way to list all members of the local administrator group. Normally, I would turn to WMI (and have written about this in the past). But WMI is relatively slow for this task and even using the new CIM cmdlets in PowerShell 3.0 don’t improve performance. Instead I’m going to return to an old school technique using the NET command.

It is very easy to see members. To query a remote computer all I need to do is wrap this in Invoke-Command and use PowerShell remoting.

Yes, there is some overhead for remoting but overall performance is pretty decent. And if you already have an established PSSession, even better. For quick and dirty one-liner it doesn’t get much better. Well, maybe it can.

I have no problem using legacy tools when they still get the job done and this certainly qualifies. To make it more PowerShell friendly though, let’s clean up the output by filtering out blanks, that last line and skipping the “header” lines.

Boom. Now I only get the member names. Let’s go one more level and write an object to the pipeline and be better at handling output from multiple computers. I came up with a scriptblock like this:

This will create a simple object with a properties for the computername, group name and members. Here’s how I can use it with Invoke-Command.

get-netlocalgroupNow I have objects that I can export to XML, convert to HTML or send to a file. But since I’ve come this far, I might as well take a few more minutes and turn this into a reusable tool.

This function lets me specify a group of computers or PSSessions as well as the local group name. Today I may need to know who belongs to the local administrator’s group but tomorrow it might be Remote Desktop Users.

Sometimes even old school tools can still be a part of your admin toolkit.

 

Using Start-Job as a Scheduled Task

Here’s a technique you might want to use for ad hoc troubleshooting or reporting. Even though it is possible to set up scheduled tasks to run PowerShell commands or scripts, it is cumbersome and time consuming. PowerShell v3 offers a great alternative, but I’ll cover that another day. Suppose I want to do something every 15 minutes such as check the status of a service. Instead of going through the effort of creating a scheduled task, I’ll create a PowerShell job.

If I setup a job, it will run in my PowerShell session for as long as it needs to run or until I close my PowerShell session. Thus the trick is to keep the job running which easy to accomplish with a simple While loop.


While ($True) {
#do something
}

This will loop indefinitely because True is always True. In an interactive session, I can break out of this using Ctrl+C or insert code to break out if some condition is met. If I use this loop in my Start-Job command, the command will run indefinitely until the job terminates. I can always kill the job with the Stop-Job cmdlet. Although I suppose you should be careful with the code you put in the While loop as part of job because if you use Stop-Job, there’s no way of knowing what code might be executing at the time. But for my use of this technique I’m keeping it simple and quick.

The other part is the timing interval. This is easily accomplished using the Start-Sleep cmdlet and specifying a value in milliseconds, or more typically, seconds. Depending on your task you could also take advantage of eventing, but that too is a bit complicated to setup and tear down. So, now the main part of my While loop looks like this:


While ($True) {
#do something
Start-Sleep -seconds 900
}

My code will run every 900 seconds (15 minutes). Here’s a longer example that I put in a script.


$file="c:\work\mylog.txt"
#the number of seconds to pause between checks
$seconds=600
#the service to check
$service="Spooler"
#the computer to check
$computer=$env:computername

while ($True) {
$s=get-service $service -ComputerName $computer
$t="{0} Service {1} on {3} has a status of {2}" -f (Get-Date), $s.name,$s.status,$computer
$t | Out-File -FilePath $file -Append
Start-Sleep -seconds $seconds
}

I could have put all of that into a script block and created the job, or I could use the script.


PS C:\> start-job -FilePath C:\scripts\PollService.ps1

The job will write the service status information to a local text file every 10 minutes. When I’m done I can simply my PowerShell session or run Stop-Job. Here’s where it can really get interesting: how about running this “scheduled task” ON a remote computer? There are two approaches. It depends on where you want the job to live. I could keep the job on my computer:


PS C:\> invoke-command -FilePath C:\scripts\PollService.ps1 -ComputerName quark -asjob
PS C:\> get-job 3 | format-list

HasMoreData : True
StatusMessage :
Location : quark
Command : #requires -version 2.0

$file="c:\work\mylog.txt"
#the number of seconds to pause between checks
$seconds=600
#the service to check
$service="Spooler"
$computer=$env:computername

while ($True) {
$s=get-service $service -ComputerName $computer
$t="{0} Service {1} on {3} has a status of {2}" -f (Get-Date),
$s.name,$s.status,$computer
$t | Out-File -FilePath $file -Append
Start-Sleep -seconds $seconds
}
JobStateInfo : Running
Finished : System.Threading.ManualResetEvent
InstanceId : 03a48cd0-f21a-4b7d-9a78-f148ec784ff8
Id : 3
Name : Job3
ChildJobs : {Job4}
Output : {}
Error : {}
Progress : {}
Verbose : {}
Debug : {}
Warning : {}
State : Running

The job object is on my (local) computer, even though the task is running on the remote computer. The other approach is to put the job ON the remote computer. This is a little trickier since I have to get the code I want to run ON the remote computer. I could copy the script over. Or I might try something like this:

First I want to convert my script into a scriptblock by stripping out the comments and inserting a semi colon at the end of each line. Then I can create a scriptblock from this text on the remote computer.


PS C:\> $text=get-content C:\scripts\PollService.ps1 | where {$_ -notmatch "^#" -AND $_} | foreach {"$_;"}

I can pass this text as a parameter with invoke-command to setup a job on the remote computer. I recommend using a PSSession in case you want to go back later and stop the job.


PS C:\> $quark=new-pssession -comp quark
PS C:\> invoke-command -scriptblock {param ($txt) $sb=$executioncontext.invokecommand.newscriptblock($txt) ; Start-job -scriptblock $sb } -session $quark -ArgumentList ($text | out-string)

The job is created on the remote session and runs indefinitely.


PS C:\> invoke-command {get-job -State Running} -Session $quark

WARNING: 2 columns do not fit into the display and were removed.

Id Name State HasMoreData Location
-- ---- ----- ----------- --------
17 Job17 Running True localhost
PS C:\> invoke-command {get-content C:\work\mylog.txt} -Session $quark
1/5/2012 9:37:41 AM Service Spooler on QUARK has a status of Running
1/5/2012 9:38:46 AM Service Spooler on QUARK has a status of Running
1/5/2012 10:44:54 AM Service Spooler on QUARK has a status of Running

As I mentioned there are probably several ways you could do this. When I am finished I can either terminate the PSSession or stop the remote job.


PS C:\> invoke-command {stop-job -State Running -PassThru} -Session $quark

WARNING: 2 columns do not fit into the display and were removed.

Id Name State HasMoreData Location
-- ---- ----- ----------- --------
17 Job17 Stopped False localhost

So the next time you need some scheduled PowerShell, at least on a temporary basis, take a look at Start-Job.

Remote PowerShell Performance Comparison

Fellow Windows PowerShell MVP Marco Shaw clued me in on a Microsoft blog post that did a terrific job of comparing, from a performance perspective the different PowerShell 2.0 techniques you can use when managing remote computers. The results are pretty much as I would expect.

Continue reading