Tag Archives: Invoke-Command

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.

Ping IP Range

Last week I came across a post on using PowerShell, or more specifically a .NET Framework class, to ping a range of computers in an IP subnet. The original post by Thomas Maurer is here. I added a comment. And after looking at this again I decided to take the ball and run with it a bit further. I’m a big proponent of PowerShell tools that are object oriented and that can add real value to the pipeline. To that end I wrote Test-Subnet. Continue reading

Get Local Administrators with WMI and PowerShell

Earlier this week I was helping someone out on a problem working with the local administrators group. There are a variety of ways to enumerate the members of a local group. The code he was using involved WMI. I hadn’t really worked with the WMI approach in any great detail so I thought I’d see how this might work in PowerShell. I ended up with a function to enumerate members of the local administrators group on a computer, as well as test if an account belongs to the group. Continue reading

ScriptBlocks On the Fly

I’m always preaching about writing PowerShell scripts and functions with reuse and modularization in mind. You should never have to write the same block of code twice. But what about in the shell during your daily grind? Perhaps today you’re dealing with some issue and periodically need to run a particular block of code. Now, you could run it once, make a note of the command number and then use Invoke-History throughout the day to keep running it. What I like to do in these situations is create a scriptblock which is assigned to a variable. Then I can invoke it anytime I want. Continue reading

Get Your TCP Ports Here!

Once again, the PowerShell forum at ScriptingAnswers.com has undone me. I was answering a question about running Netstat in a PowerShell session on a remote computer which got me thinking about a PowerShell function to turn NETSTAT.EXE output into objects. Once you have an object then you can do all sorts of things.  Needless to say I got hooked on working something up instead of working on what I had planned for the day. The good news is that you get a tool and there’s still a few hours of the day left for me to get something else accomplished. I’m sure there are plenty of variations on this topic already out there, but here’s my contribution: Get-TCP.
[cc lang="Powershell"]
Function Get-TCP {
<#
.Synopsis
Get TCP Netstat information
.Description
This function calls the command line NETSTAT.EXE tool and returns an object representation
of the TCP data.

Protocol   : TCP
Localhost  : 172.16.10.127
LocalPort  : 49259
RemoteHost : 74.201.86.29
RemotePort : https
Status     : ESTABLISHED

The default is the local computer without name resolution. However you can specify a remote
computername, assuming the remote computer is running PowerShell 2.0 and has remoting enabled.
Use -ResolveHost to resolve IP addresses to host names. This is a little slower.

This function will only return IPv4 hosts and addresses
.Parameter Computername
The name of the computer to query. The default is the localhost.
.Parameter ResolveHost
Resolve IP addresses to host names. This is a little slower. The is the equivalent of running
Netstat.exe without any parameters.
.Parameter ResolvePort
Resolve the service name associated with the port. This requires access to the legacy
Services file found at $env:windir\system32\drivers\etc\services.
.Parameter IncludeRaw
Include the raw netstat data.
.Example
PS C:\> get-tcp Return TCP Netstat information for the local computer. .Example PS C:\> get-tcp “Server1″,”Server2″ -resolve

Return TCP Netstat information for computers Server1 and Server2 with resolved IP addresses.
.Example
PS C:\> get-content computers.txt } get-tcp -resolveHost | where {$_.RemotePort -eq 80} | format-table -autosize

Get HTTP connections for every computer in computers.txt and present as a formatted table.
.Example
PS C:\> get-tcp | sort RemotePort | Select RemotePort -unique

Get a sorted list of all remote connections by port.
.Inputs
Strings
.Outputs
Custom object
.Link

http://jdhitsolutions.com/blog

.Link
Invoke-Command

.Notes
NAME:      Get-TCP
VERSION:   1.5
AUTHOR:    Jeffery Hicks
LASTEDIT:  July 29, 2010

Learn more with a copy of Windows PowerShell 2.0: TFM (SAPIEN Press 2010)

#>

[cmdletbinding()]

Param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[string[]]$Computername=$env:computername,
[switch]$ResolveHost,
[switch]$ResolvePort,
[switch]$IncludeRaw
)

Begin {
Write-Verbose “Starting $($myinvocation.mycommand)”
if ($ResolveHost) { Write-Verbose “Resolving host names”}
if ($ResolvePort) {Write-Verbose “Resolving port names”}
#put everything into a script block so that if the computer
#is remote it can be executed using Invoke-Command
$scriptblock={netstat.exe -n | where {$_.Contains(“TCP”) -AND $_ -notmatch “\["}}

Write-Verbose "Caching Services information"
$file="$env:windir\system32\drivers\etc\services"
#get just tcp services
$services=Get-Content -Path $file | Select-String -Pattern "/tcp"

Write-Verbose $($scriptblock.ToString())

#define the name resolution function
Function Resolve-DNS  {

[cmdletbinding()]

Param(
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True,
HelpMessage="Enter an IP Address to resolve")]
[string[]]$Address
)
Begin {
Write-Verbose “Starting Resolve-DNS”
#turn off error pipeline
$errorActionPreference=”SilentlyContinue”
}
Process {
foreach ($IP in $Address) {
Write-Verbose “Resolving $IP”
$dns=[System.net.DNS]::GetHostByAddress(“$IP”)
if (-not $dns) {
Write-Verbose “no record found for $IP”
$h=$IP
$a=$null
}
else {
$h=$dns.hostname
$a=$dns.AddressList
}
New-Object -TypeName PSobject -Property @{
Hostname=$h
Addresses=$a
}
} #foreach
} #process

End {
#turn On error pipeline
$errorActionPreference=”Continue”
}
} #end function

} #begin

Process {
foreach ($computer in $computername) {
Write-Verbose “Getting raw NETSTAT data from $($computer.toUpper())”
if ($computer -eq $env:computername) {
#just run the script block if local computer
$data=&$scriptblock
}
else {
Try {
$data=Invoke-Command -ScriptBlock $scriptblock -computername $computer -errorAction “Stop”
}
Catch {
Write-Warning “Failed to run command on $computer”
Write-Warning $error[0].exception.message
}
} #else
#process data
Write-Verbose “Returned $($data.count) items”
Write-Verbose “Parsing data”
#initialize a hash table for IP names
$hash=@{}
foreach ($tcp in $data) {
Write-Verbose $tcp.trim()
#split the line removing empty spaces
$arr=$tcp.trim().split() | where {$_}
$localData=$arr[1].Split(“:”)
$remoteData=$arr[2].Split(“:”)
$protocol=$arr[0]
$raw=$tcp.trim()
$local=$localData[0]
[int]$localPort=$localData[1]
$remote=$remoteData[0]
[int]$remotePort=$remoteData[1]
$status=$arr[3]

#if resolve host requested build a dictionary or
#ip addresses so we don’t need to resolve one already resolved
#and change the property value accordingly
if ($resolveHost) {
if ($hash.contains(“$local”)) {
$local=$hash.Item(“$local”)
}
else {
#look up name
Write-Verbose “Resolving $local”
$localLookup=Resolve-DNS “$local”
#add to hash
$hash.Add(“$local”,$localLookup.Hostname)
#update property
$local=$localLookup.Hostname
}
#do the same for remote
if ($hash.contains(“$remote”)) {
$remote=$hash.Item(“$remote”)
}
else {
#look up name
Write-Verbose “Resolving $remote”
$remoteLookup=Resolve-DNS “$remote”
#add to hash
$hash.Add(“$remote”,$localLookup.Hostname)
#update property
$remote=$RemoteLookup.Hostname
}
} #if $resolveHost

#resolve ports if specified
if ($resolvePort) {
#this is a mini scriptblock to return the service name
#in lieu of a full-blown function
$getsvc={Param ([string]$port)
Write-Verbose “searching for $port”
$service=$services | Select-String -pattern  “\s$port/tcp”

if ($service) {
$data=$service.ToString().Trim().split() | where {$_}
Write-Output $data[0]
}
else {
Write-Output $port
}

} #end $getSvc

[string]$localPort=&$getsvc $localPort
[string]$remotePort=&$getsvc $remotePort

} #end resolveport

#create a new object and pipe it to Select-Object so that
#the properties are in a nice order
$obj=New-Object -TypeName “PSObject” -Property @{
Protocol=$protocol
Localhost=$local
LocalPort=$localPort
RemoteHost=$remote
RemotePort=$remotePort
Status=$status
}
if ($IncludeRaw) {
$obj | Add-Member -MemberType “Noteproperty” -Name “Raw” -Value $raw -PassThru |
Select-Object -Property Protocol,LocalHost,LocalPort,RemoteHost,RemotePort,Status,Raw
}
else {
$obj | Select-Object -Property Protocol,LocalHost,LocalPort,RemoteHost,RemotePort,Status
}
} #foreach $tcp
} #foreach computer
} #Process

End {
Write-Verbose “Ending $($myinvocation.mycommand)”
}

} #end function[/cc]
This is an advanced Windows PowerShell 2.0 function. By default it converts output from Netstat -n to object. I’ve parsed the output so you get an object like this for each line.

[cc lang=DOS]
Protocol   : TCP
Localhost  : 172.16.10.122
LocalPort  : 60553
RemoteHost : 74.201.86.29
RemotePort : 443
Status     : ESTABLISHED[/cc]

When you run Netstat without any parameters it resolves both host and port names. I’ve separated the two so you can use either -ResolveHost and/or -ResolvePort. The former uses an embedded function to get the host entry name by IP address. If not found, then the IP address is used. This function by the way only returns IPv4 information. Service ports are resolved by finding the associated TCP port in the legacy Services file. What you end up with is something like this:

The function can also accept a remote computername as it uses Invoke-Command to run the Netstat command remotely.  All the results are then processed locally. You can pipe computernames to the function or it accepts arrays.

If you have any questions on the nitty-gritty details, please post a comment.

Download Get-TCP.ps1.

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