You are on page 1of 28

:::Tips and Tricks with POwershell:::

Use PowerShell to Explore Process Threads


in Windows
Networking Diagnostics with PowerShell in
Windows
Discovering Shares
Local Group Membership

Use PowerShell to Explore Process Threads


in Windows
Use WMI to find info about threads
To find information about threads, I use the Win32_Thread WMI class. I found this by using the GetCimClass cmdlet as shown here.
Get-CimClass *thread*
The command and its associated output are shown in the following image.

I can also do the same thing by using the Get-WmiObject cmdlet. This technique is shown here.
Get-wmiobject -list *thread*
So, I decide to query the WMI class. Here is the Windows PowerShell 2.0 version of the command.

Get-WmiObject win32_thread
I can do the same thing with the CIM cmdlets in Windows PowerShell 3.0. This command is shown here.
Get-CimInstance win32_thread
The command and the output from the command are shown here.

Find a specific thread


The easiest way to find a specific thread is to first get the process handle, and then use that handle in a
WMI filter. The following command obtains the handle for a running instance of Notepad, and then obtains
the thread information.
$handle = (Get-Process notepad).handle
Get-WmiObject win32_thread -filter "handle = $handle"
By using the Get-CimInstance Windows PowerShell 3.0 CIM cmdlet, I arrive at the following syntax.
$handle = (Get-Process notepad).handle
Get-CimInstance win32_thread -filter "handle = $handle"
There is very little difference between the two commands. There is a bit of a difference between the output
from the two commands. The output from the Get-CimInstance cmdlet is cleaner. The command and
output from Get-CimInstance is shown here.

To understand the thread state, it is necessary to look up the ThreadState property. I can do this in the
MSDN article,Win32_Thread WMI class. The ThreadState values are shown here.

Value

Meaning

Initialized. It is recognized by the microkernel.

Ready. It is prepared to run on the next available processor.

Running. It is executing.

Standby. It is about to run. Only one thread may be in this state at a time.

Terminated. It is finished executing.

Waiting. It is not ready for the processor. When ready, it will be


rescheduled.

Transition. The thread is waiting for resources other than the processor.

Unknown. The thread state is unknown.

The ThreadWaitReason value codes are shown in the table that follows.

Value

Meaning

Executive

FreePage

PageIn

PoolAllocation

ExecutionDelay

FreePage

PageIn

Executive

FreePage

PageIn

10

PoolAllocation

11

ExecutionDelay

12

FreePage

13

PageIn

14

EventPairHigh

15

EventPairLow

16

LPCReceive

17

LPCReply

18

VirtualMemory

19

PageOut

20

Unknown

BC, that is all there is to using Windows PowerShell and WMI to find information about threads. Join me
tomorrow when I will talk about more cool stuff.Therefore, the Notepad process is waiting and not ready for
the processor. The reason it is waiting is EventPairLow.

Networking Diagnostics with PowerShell in


Windows
Discovering System Settings
Viewing the Current Configuration
Viewing and understanding a systems configuration is the first step in being able to configure the basic
network settings through PowerShell. In this introduction, thats exactly what we hope to teach you by
introducing you to a few PowerShell cmdlets that describe in-depth networking information. We will also
show you a shortcut with a PowerShell cmdlet, Get-NetIPConfiguration and its alias GIP. GIP is a one
stop shop when it comes to your computers networking configuration. GIP combines elements of multiple
PowerShell cmdlets that give detailed information and acts as an orchestrator to give you a
comprehensive view of how your system is setup. To fully understand GIP, we will first go over the
underlying cmdlets that it uses.
The first cmdlet is Get-NetAdapter. This cmdlet retrieves the basic network adapter properties for all of
the physical adapters connected to the system. It details the type of adapter, adapter name, the MAC
address, link speed and its current status. By default only visible adapters are returned. This information
can be useful when trying to trouble shoot a problem on a remote computer. This will tell you the adapters
on the system so you know what youre working with.
PS C:\WINDOWS\system32> Get-NetAdapter

Name

InterfaceDescription

ifIndex Status

MacAddress

LinkSpeed

----

--------------------

------- ------

----------

---------

Ethernet 5

Broadcom BCM5709C Ne...#36

7 Disconnected 00-10-18-44-06-7A

0 bps

Ethernet 2

Broadcom BCM5709C Ne...#35

4 Disconnected 00-10-18-44-06-78

0 bps

Ethernet 4

Broadcom NetXtreme Gigabit

6 Up

B8-AC-6F-82-68-DB

1 Gbps

Ethernet 3

Intel(R) Ethernet Ser...#2

5 Disconnected 00-1B-21-8E-EB-F5

0 bps

Ethernet

Intel(R) Ethernet Serve...

3 Disconnected 00-1B-21-8E-EB-F4

0 bps

If you are running in a Hyper-V environment, there is also a cmdlet to gather information for the VM NICs
on that host. A VM NIC is the equivalent of a network adapter for a VM.
PS C:\Users\Administrator> Get-VMNetworkAdapter -VMName DemoVM1

Name

IsManagementOs VMName

SwitchName MacAddress

Status IPAddresses

----

-------------- ------

Network Adapter False

---------- ----------

------ -----------

DemoVM1 BackToBack 00155D3AE401 {Ok}

{192.168.0.3}

In the example above we choose a specific VM but you can also search for all VM NICs by specifying the
wildcard or * character after the cmdlet. An example of this cmdlet is given below.
PS C:\Users\Administrator> Get-VMNetworkAdapter *

Name

IsManagementOs VMName

SwitchName MacAddress

Status IPAddresses

----

-------------- ------

---------- ----------

------ -----------

Network Adapter False

DemoVM2 BackToBack 00155D3AE403 {Ok}

{192.168.0.4}

Network Adapter False

DemoVM1 BackToBack 00155D3AE401 {Ok}

{192.168.0.3}

Get-NetIPAddress will return the IP addresses that are configured on that system system for both IPv6
and IPv4. This can be run inside a VM or on the host.
PS C:\> Get-NetIPAddress

IPAddress

: fe80::bf7f%7

InterfaceIndex

: 7

InterfaceAlias

: Ethernet 5

AddressFamily

: IPv6

Type

: Unicast

PrefixLength

: 64

PrefixOrigin

: WellKnown

SuffixOrigin

: Link

AddressState

: Deprecated

ValidLifetime

: Infinite ([TimeSpan]::MaxValue)

PreferredLifetime : Infinite ([TimeSpan]::MaxValue)


SkipAsSource

: False

PolicyStore

: ActiveStore

IPAddress

: 192.168.191.127

InterfaceIndex

: 7

InterfaceAlias

: Ethernet 5

AddressFamily

: IPv4

Type

: Unicast

PrefixLength

: 16

PrefixOrigin

: WellKnown

SuffixOrigin

: Link

AddressState

: Tentative

ValidLifetime

: Infinite ([TimeSpan]::MaxValue)

PreferredLifetime : Infinite ([TimeSpan]::MaxValue)


SkipAsSource

: False

PolicyStore

: ActiveStore

These cmdlets provide a wealth of detailed information.


GIP is an alias for Get-NetIPConfiguration and is meant to provide concise output that will give an
overview of the systems network configuration without having to dig through the details of the cmdlets
explained above. GIP provides the IPv4/IPv6 addresses, gateways, interface alias/description/index and
DNS servers of adapters with an up status and only interface information of adapters that are down.
PS C:\> GIP

InterfaceAlias

: Ethernet 4

InterfaceIndex

: 6

InterfaceDescription : Broadcom NetXtreme Gigabit Ethernet


NetProfile.Name

: ntdev.corp.microsoft.com

IPv6Address

: 2001:4898:1a:3:f0d3:dc16:89c6:86eb

IPv4Address

: 192.31.25.52

IPv6DefaultGateway

: fe80::8a75:56ff:fe3d:7380

IPv4DefaultGateway

: 192.31.25.1

DNSServer

: 158.55.16.178

You can use the Detailed flag with GIP to show more information.
PS C:\> GIP -Detailed

ComputerName

: GS-BDESKTOP

InterfaceAlias

: Ethernet 4

InterfaceIndex

: 6

InterfaceDescription

: Broadcom NetXtreme Gigabit Ethernet

NetAdapter.LinkLayerAddress : B8-AC-6F-82-68-DB
NetAdapter.Status

: Up

NetProfile.Name

: work.microsoft.com

NetProfile.NetworkCategory

: DomainAuthenticated

NetProfile.IPv6Connectivity : Internet
NetProfile.IPv4Connectivity : Internet
IPv6Address

: 2001::86eb

IPv6TemporaryAddress

: 2001::e7b4

IPv6LinkLocalAddress

: fe80::86eb%6

IPv4Address

: 192.31.25.52

IPv6DefaultGateway

: fe80::8a75:56ff:fe3d:7380

IPv4DefaultGateway

: 192.31.25.1

NetIPv6Interface.NlMTU

: 1500

NetIPv4Interface.NlMTU

: 1500

NetIPv6Interface.DHCP

: Enabled

NetIPv4Interface.DHCP

: Enabled

DNSServer

: 158.55.16.178

Like most PowerShell cmdlets, Get-NetIPConfiguration returns rich scriptable objects. For example, the
default route is accessible directly.
PS C:\WINDOWS\system32> $Object = GIP 'Ethernet 4'
PS C:\WINDOWS\system32> $Object.IPv4DefaultGateway

ifIndex DestinationPrefix

NextHop

RouteMetric PolicyStore

------- -----------------

-------

----------- -----------

172.31.232.1

0.0.0.0/0

0 ActiveStore

Testing Connectivity
Windows Server 2012 R2 supports a new cmdlet for testing ICMP and TCP connectivity, called TestNetConnection (TNC).

Pinging Servers and Trace Route


Ping, nslookup and tracert are basic tools of the networking trade. For many scenarios however, TNC is a
more scriptable and usable alternative to these tools. It provides much more information in a single cmdlet
to help troubleshoot a connection quickly.
Here is an example of using TNC to test connectivity to www.xbox.com.
PS C:\WINDOWS\system32> tnc www.Xbox.com

ComputerName

: www.xbox.com

RemoteAddress

: 2600:1409::1ac

InterfaceAlias

: Ethernet 4

SourceAddress

: 2001::e7b4

PingSucceeded

: True

PingReplyDetails (RTT) : 2 ms
The output object of TNC includes the relevant WMIv2 objects of the local machine. This means you can
script against them directly. Below is an example of using TNC to rename the network adapter that is used
by default to reach www.xbox.com.
PS C:\WINDOWS\system32> tnc www.xbox.com | Rename-NetAdapter -NewName "ConnectsToXbox"
PS C:\WINDOWS\system32> $Object.NetAdapter

Name

InterfaceDescription

ifIndex Status

MacAddress

LinkSpeed

----

--------------------

------- ------

----------

---------

ConnectsToXbox

Broadcom NetXtreme G...

6 Up

B8-AC-6F-82-68-DB

Using the TraceRoute flag will return the list of hosts on the path to the specified target.
PS C:\WINDOWS\system32> tnc www.xbox.com -TraceRoute

ComputerName

: www.xbox.com

RemoteAddress

: 2600:1409:a:18f::1ac

1 Gbps

InterfaceAlias

: ConnectsToXbox

SourceAddress

: 2001::e7b4

PingSucceeded

: True

PingReplyDetails (RTT) : 2 ms
TraceRoute

: 20013::1
2001::2
2001::6c:1
TimedOut
2001:4898:8000:2:ff::36
TimedOut
TimedOut
TimedOut
TimedOut
2600:1409:a:18f::1ac

Testing TCP Connectivity


While testing ICMP connectivity is a common troubleshooting step, most of the time youre interested in
resolving a TCP connectivity issue. TNC enables you to test connectivity to a specified remote host and
port.
PS C:\WINDOWS\system32> tnc www.bing.com -Port 80

ComputerName

: www.bing.com

RemoteAddress

: 204.79.197.200

RemotePort

: 80

InterfaceAlias

: ConnectsToXbox

SourceAddress

: 192.31.25.5

PingSucceeded

: True

PingReplyDetails (RTT) : 0 ms
TcpTestSucceeded

: True

Testing Remote Desktop Connectivity


TNC has some common keywords to simplify selection of which TCP port to test. For example, to test
remote desktop connectivity, use the RDP keyword. In the example below you can see that the ping
succeeded but the TCP test to remote port 3389 failed.
PS C:\WINDOWS\system32> tnc HomeBase RDP
WARNING: TCP connect to HomeBase:3389 failed

ComputerName

: HomeBase

RemoteAddress

: 192.168.0.5

RemotePort

InterfaceAlias

: ConnectsToXbox

SourceAddress

: 192.31.25.5

PingSucceeded

: True

PingReplyDetails (RTT) : 0 ms
TcpTestSucceeded

: False

IPSec and Network Isolation


In the prior example, I couldnt remote desktop to my home server, HomeBase. Lets use the Detailed flag
to see more information.
PS C:\> tnc HomeBase RDP -InformationLevel Detailed
WARNING: TCP connect to HomeBase:3389 failed

ComputerName

: HomeBase

RemoteAddress

: 10.195.58.228

RemotePort

: 3389

AllNameResolutionResults : 10.195.58.228
MatchingIPsecRules

: Msit-Ipsec-Win8/WS8-Domain-CorpnetIpv4-Authip

NetworkIsolationContext

: Private Network

InterfaceAlias

: ConnectsToXbox

SourceAddress

: 192.31.25.5

NetRoute (NextHop)

: 192.31.25.1

PingSucceeded

: True

PingReplyDetails (RTT)

: 0 ms

TcpTestSucceeded

: False

The MatchingIPSecRules field tells us which IPsec rules are attempting to secure the connection, in this
case the IPsec rules that Microsofts IT department has deployed for DirectAccess. Because this is
PowerShell, the property in fact is a rich WMI object.
The NetworkIsolationContext field tells us which capability Windows store apps need to use to access the
resource. In order for Windows store apps to access HomeBase, they need to declare the Private Network
capability.
If youre wondering why DirectAccess isnt working, its because I havent provided my smartcard
credentials to the system.
PS C:\WINDOWS\system32> Get-DAConnectionStatus
Status : ActionableError
Substatus : StrongAuthorizationCredentialsNeeded

Easy Scripting with TNC


There are many elements of TNC that aid with scripting. Some key features are specified below:
1. If DNS resolution fails to the specified host, an exception is thrown and can be caught.
2. Using the InformationLevel parameter, you can have TNC run in a quiet mode. This means that TNC
simply returns a Boolean indicating if the connection succeeded or not.
if(TNC www.bing.com InformationLevel Quiet) {}
3. When using TNC to test TCP connectivity, if the connection succeeds, you can access the TCP socket
using the TCPClientSocket property. This provides the connected .NET socket for further scripting.
4. The route, network adapter, and source IP address, used for connectivity, are all accessible as WMI
objects embedded in TNCs output.
5. GIP and TNC produce objects that can be serialized into XML files. You can send the rich objects over
emails, instead of simply cutting and pasting the output.
PS C:\WINDOWS\system32> tnc www.xbox.com HTTP | export-clixml C:\Output.xml
PS C:\WINDOWS\system32> Import-Clixml C:\Output.xml

ComputerName

: www.xbox.com

RemoteAddress

: 2600:1409:a:181::1ac

RemotePort

: 80

InterfaceAlias

: ConnectsToXbox

SourceAddress

: 2001::e7b4

PingSucceeded

: True

PingReplyDetails (RTT) : 2 ms
TcpTestSucceeded

: True

Testing VM Connectivity
Windows Server 2012 R2 also helps with diagnosing connectivity issues when access to VMs is not
available like in service provider environments. In service provider environments, it will be common to see
tenant overlays on the physical network using Hyper-V Network Virtualization (HNV). With HNV, customers
can easily move their subnets to the cloud while preserving their existing IP addresses (called a Customer
Address or CA), and topology into the cloud so that existing services continue to work unaware of the
underlying fabrics address space (called a, Provider Address or PA), used by the service provider. As a
result of this isolation and indirection, testing connectivity between VMs is challenging and tools like TNC
may not always suffice.
Diagnosing connectivity for the service provider and tenant networks, however, remains important in order
to validate infrastructure setup or deployment configuration. To help diagnose these issues Windows
Server 2012 R2 introduces two new tools: PA Ping and Test-VMNetworkAdapter.

Provider Address (PA) Ping


PA Pings allows admins to validate connectivity in the physical infrastructure that the tenant networks are
overlaid on. The system injects ICMP packets above physical NIC of the source VM and waits for an echo
response from the physical NIC of the destination VM. This enables the ability of sending an ICMP packet
from one PA to another. This tool is available in the traditional ping tool with the p parameter:
C:\> ping p 1.2.4.15 1.2.3.20

Pinging 1.2.3.20 with 32 bytes of data


Reply from 1.2.3.20: bytes=32 time=1ms TTL=128
Reply from 1.2.3.20: bytes=32 time<1ms TTL=128
Reply from 1.2.3.20: bytes=32 time<1ms TTL=128
Reply from 1.2.3.20: bytes=32 time<1ms TTL=128

Ping statistics for 1.2.3.30:


Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:

Minimum = 0ms, Maximum = 1ms, Average = 0ms

Test-VMNetworkAdapter
Test-VMNetworkAdapter allows admins to diagnose connectivity issues in the tenant network. The
challenge here is that admins may not have direct access to the VMs and therefore cannot run tools like
TNC from within the VM. Test-VMNetworkAdapter injects ICMP packets on the port that the VM is attached
to and awaits an echo response from the receiver VM:
PS C:\> Test-VMNetworkAdapter VMName Helen sender SenderIPAddress 10.20.20.5 ReceiverIPAddress
10.20.20.6 NextHopMacAddress f4-ce-46-2d-98-16 VMNetworkAdapterName HelenNic RoundTripTime :
16 milliseconds
If this command succeeds, this can indicate that the problem is in the VM, and not in the tenant network.
If the NextHopMacAddress is not known, use Select-NetVirtualizationNextHop cmdlet to look up several
parameters like NextHopMACAddress from the HNV database:
PS C:\> Select-NetVirtualizationNextHop DestinationCustomerAddress 10.20.20.6
SourceCustomerAddress 10.20.20.5 SourceVirtualSubnetID 5001

SourceCustomerAddress

: 10.20.20.5

DestinationCustomerAddress

: 10.20.20.6

SourceVirtualSubnetID

: 5001

NextHopAddress

: 0.0.0.0

SourceMACAddress

: fece462d9814

NextHopMACAddress

: fece462d9816

For business reasons you may want to avoid sending ICMP ping packets to their tenant VMs. To do so, use
the receiver parameter to enable an ICMP responder running on the switch port of the VM:
PS C:\> Test-VMNetworkAdapter VMName Helen receiver SenderIPAddress 10.20.20.5
ReceiverIPAddress 10.20.20.6 VMNetworkAdapterName HelenNic
The control channel inspects all packets and echoes back ICMP packets that contain the sequence number
100 in their payload. This number can be configured using the SequenceNumber parameter. Be sure to
use the same sequence number on both VMs so that the receiver may intercept and echo back the correct
ICMP packets.

Putting It Together
Lets take a look at an example scenario that merges the use of these cmdlets: Brian has just deployed
HNV on his network. His topology consists of multiple hosts with multiple VMs on each host. He wants to
validate connectivity between each VM and ensure HNV is deployed correctly.
First, he will use PA Ping (ping p) to validate connectivity over the physical infrastructure between each
VM. Then he will use Test-VMNetworkAdapter with sender and receiver parameters to validate
connectivity between each VM. In doing so he will validate that the indirection introduced by HNV has
resulted in the mapping as configured at the time of deployment. Finally, he can use the same script with
Test-VMNetworkAdapter without the receiver parameter to validate correct firewall configuration in the
operating system on the VMs.
By doing this, Brian can validate the correct configuration of HNV on his network and ensure connectivity
between all VMs. If a customer were to report a connectivity problem, Brian can quickly resolve it back to
the customer saying that he expects the problem is in the customer VM! The customer can now use TNC to
detect further problems.

Discovering Shares
Windows 8 and Windows Server 2012 ship with PowerShell 3.0 and a suite of extra modules. Many of these
modules are created by using the cmdlets-over-objects technology, which means that you take a WMI
class, wrap it in XML, and you get a PowerShell module. Details of how to create your own CDXML modules
are provided in my book, PowerShell and WMI.
You can easily tell if your module has been constructed in this manner because it will have a .cdxml
extension. In many instances, the WMI classes that are used for this process are new to Windows 8 and
Windows Server 2012, and they are not available on earlier versions of Windowseven if you install
Windows PowerShell 3.0.
The cmdlets are in a module called SmbShare. If you use Get-SmbShare, you see this output on a
Windows 8-based computer:
> Get-SmbShare | Format-Table -AutoSize

Name ScopeName Path


---- --------- ---ADMIN$ *

Description

----------C:\Windows Remote Admin

C$

C:\

Default share

D$

D:\

Default share

IPC$ *

Remote IPC

Users *

C:\Users

This is for the local machine, but more importantly, you need to be able to administer your remote servers.
Lets look at the parameters that are available with Get-SmbShare:
> Get-Command Get-SmbShare -Syntax
Get-SmbShare [[-Name] <string[]>] [[-ScopeName] <string[]>] [-Scoped <bool[]>] [-Special <bool[]>]
[-ContinuouslyAvailable <bool[]>] [-ShareState <ShareState[]>] [-FolderEnumerationMode
<FolderEnumerationMode[]>] [-CachingMode <CachingMode[]>] [-ConcurrentUserLimit <uint32[]>] [AvailabilityType <AvailabilityType[]>] [-CaTimeout<uint32[]>] [-EncryptData <bool[]>] [-IncludeHidden] [CimSession <CimSession[]>] [-ThrottleLimit <int>] [-AsJob][<CommonParameters>]
You will notice one glaring omission. There isnt a ComputerName parameter. One of the quirks of
CDXML-based modules is that you need to use CIM sessions to access remote machines (or run the
command over a Windows PowerShell remoting session).
$sess = New-CimSession -ComputerName Win12R2
Get-SmbShare -CimSession $sess
$sess | Remove-CimSession
You will automatically get PSComputerName output as an additional property. If you try to run this
against an earlier version of Windows with Windows PowerShell 2.0, it will fail because you need
WSMAN 3.0 on the remote system to use CIM sessions.
The older versions of Windows cant be left out of this, so you need to look at another way of retrieving the
data. WMI supplies a Win32_Share class that you can use:
> Get-WmiObject -Class Win32_Share | Format-Table -AutoSize
Name Path
---- ----

Description

-----------

ADMIN$ C:\Windows Remote Admin


C$

C:\

Default share

D$

D:\

Default share

IPC$

Remote IPC

Users C:\Users

The next step is to extend this to a remote machine. Get-WmiObject has had a
ComputerName parameter since PowerShell 1.0. Back in those days, it was our only tool for working
remotely!
> Get-WmiObject -Class Win32_Share -ComputerName Win12R2 | Format-Table AutoSize
So, you get to the age-old question, Which do I use?
The drawback with CIM sessions is that you need WSMAN 3.0 on the remote machine, which means that
PowerShell 3.0 or above must be installed. On older machines with Windows PowerShell 2.0, you need to
have WMI allowed through any firewalls you have between you and the remote machine. As the
percentage of machines with WSMAN 3.0 increases, you need to be maximizing the use of CIM sessions.
The answer is to test the version of WSMAN that you have installed and proceed accordingly:
> Test-WSMan -Authentication default -ComputerName win12r2
wsmid

: http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd

ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor : Microsoft Corporation
ProductVersion : OS: 6.3.9600 SP: 0.0 Stack: 3.0
The value you need to test is the Stack property on the ProductVersion. This leads you to a function like
this:
function get-share {
[CmdletBinding()]
param(
[parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$computername
)
PROCESS{
foreach ($computer in $computername){
$stack = ((Test-WSMan -Authentication default -ComputerName $computer -ErrorAction
SilentlyContinue ).ProductVersion -split ": ")[-1]
switch ($stack) {
"2.0" {
Get-WmiObject -Class Win32_Share -ComputerName $computer |
select PSComputername, Name, Path, Description

}
"3.0" {
$sess = New-CimSession -ComputerName Win12R2
Get-SmbShare -CimSession $sess |
select PSComputername, Name, Path, Description
$sess | Remove-CimSession
}
default {Write-Warning -Message "Couldn't determine WSMAN Stack version for $computer" }
} # end switch
} # end foreach
} # End process
}
Take a pipeline input, and test the WSMAN version for a computer that is passed in. This line may look a bit
intimidating:
((Test-WSMan -Authentication default -ComputerName $computer -ErrorAction
SilentlyContinue ).ProductVersion -split ": ")[-1]
Its not that bad when you break it down...
This gets the information:
Test-WSMan -Authentication default -ComputerName $computer -ErrorAction SilentlyContinue
This simply takes the ProductVersion property:
(Test-WSMan -Authentication default -ComputerName $computer -ErrorAction
SilentlyContinue ).ProductVersion
This splits that property on a : :
-split ": "
The result is an array of which you take the last element by using the -1 index. Try building up the
command like this at the Windows PowerShell prompt to see how it all works.
A switch statement is used to determine actions based on the WSMAN stack version. Version 2.0 uses WMI
and version 3.0 uses CIM. The appropriate cmdlet is run and then a select statement is used to determine
the data returned. In this case, the PSComputerName property is used to identify the machine from
which we generate the data.
If the stack version isnt 2.0 or 3.0, the switch statement drops into the default option, which prints a
warning on screen.
What about the situation where you have hundreds or thousands of machines? Do you want to wait for this
to run?

If the answer is, No, you could try turning this into a workflow, but the switch statement doesnt work
very well in workflows. We recommended against using it in PowerShell in Depth. Your choices are to
completely rewrite or to use Windows PowerShell jobs. Get-WmiObject and Get-SmbShare have an
AsJob parameter.
A Windows PowerShell job will create a job object to perform your processing and then immediately
continue with the next line in the script. This works for Get-WmiObject, but there is an issue using GetSmbShare like this because the next line will immediately remove the CIM session the job is trying to use,
and the job will fail.
The answer is to use Wait-Job to test the completion of the job, which will delay the whole script and
negate the reason for using jobs, or to move the whole process into a script block and use Start-Job.
function get-share {
[CmdletBinding()]
param(
[parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$computername
)
PROCESS{
foreach ($computer in $computername){
$stack = ((Test-WSMan -Authentication default -ComputerName $computer -ErrorAction
SilentlyContinue).ProductVersion -split ": ")[-1]
switch ($stack) {
"2.0" {
Get-WmiObject -Class Win32_Share -ComputerName $computer -AsJob
}
"3.0" {
$sb = {
param($comp)
$sess = New-CimSession -ComputerName $comp
Get-SmbShare -CimSession $sess
$sess | Remove-CimSession
}

Start-Job -ScriptBlock $sb -ArgumentList $computer


}
default {Write-Warning -Message "Couldn't determine WSMAN Stack version for $computer" }
} # end switch
} # end foreach
} # End process
}
You can then use the standard job cmdlets to retrieve the data.
Creating your scripts in this way allows you to mix and match new technologies with older ways of doing
things so that your automation will keep working as your server estate changes.
If you want to extend the use of this script you could:

Incorporate it into the server documentation tool that you read about in the first article in this
series: The Admins First Steps: Documenting Servers.

Add process to retrieve data from the jobs.

Extend process to retrieve the permissions granted on the share.

Local Group Membership


Of the three ways to find local group membership, the oldest method uses the WinNT ADSI provider. ADSI
is a scripting interface to directory services. Its normally used for scripting against Active Directory, but
you can also use it against local machines.
$group = [ADSI]"WinNT://./Administrators"
@($group.Invoke("Members")) |
foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
The ADSI scripting interface isnt directly available in Windows PowerShell in the same way it is in VBScript.
Instead, you have to use the System.DirectoryServices.DirectoryEntry .NET Framework class, which
provides a wrapper for ADSI. A shortcut, known as a type accelerator has been provided so you dont have
to type the whole class name. To add confusion, the type accelerator is known as [adsi] or [ADSI]case
doesnt matter.
The WinNT provider is used to access the local machine by using . to represent the system together with
the name of the groupin this case, Administrators. You then have to use the Invoke() method of
the $group object to get the members that you can pipe to Foreach-Object, which uses
the InvokeMember() method to get the member name. The output will look something like this:
Administrator
Richard
Personally, I dont like using . to represent the local machine. Its easy to overlook, and I have seen
problems when using it with WMI. I prefer to use $env:COMPUTERNAME, and pick the machines name
from the environmental variables:
$group =[ADSI]"WinNT://$($env:COMPUTERNAME)/Administrators"
@($group.Invoke("Members")) |
foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
This is OK if your machine is in a workgroup; but in a domain, you might get answers like this:
Administrator
Domain Admins
Richard
Its not easy to separate local users from domain accounts by using this approach. Luckily, you have some
alternatives. One of the alternatives involves using WMI. When Im working with WMI, I find that the CIM
cmdlets introduced in Windows PowerShell 3.0 are the easiest to work with:

$group = Get-CimInstance -ClassName Win32_Group -Filter "Name = 'Administrators'"


Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_UserAccount |
select -ExpandProperty Caption
Use the Win32_Group class to get the WMI object that represents the group. Use that object in GetCimAssociatedInstance to find the Win32_UserAccount instances that are associated with that group.
On a workgroup machine, you get this:
RSLAPTOP01\Administrator
RSLAPTOP01\Richard
And on a domain machine, you get this:
WIN12R2\Administrator
MANTICORE\Richard
Now you can see which of the results are local accounts and which are domain accounts. But (and isnt
there always a but?) you dont get the nested domain groups. You need to find the associated groups,
which simply involves another call toGet-CimAssociatedInstance:
$group = Get-CimInstance -ClassName Win32_Group -Filter "Name = 'Administrators'"
Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_UserAccount |
select -ExpandProperty Caption
Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_Group |
select -ExpandProperty Caption
Because you are displaying the same data, you can output from multiple calls (generally its viewed as a
bad thing), and you get something like this:
WIN12R2\Administrator
MANTICORE\Richard
MANTICORE\Domain Admins
Now you can see the domain and machine name, so you know which entries are local, and you get the
nested groups. If you cant use the CIM cmdlets, you can fall back on the WMI cmdlets.
$query = "ASSOCIATORS OF {Win32_Group.Domain='$($env:COMPUTERNAME)',Name='Administrators'}
WHERE ResultClass = Win32_UserAccount"
Get-WmiObject -Query $query | Select -ExpandProperty Caption

$query = "ASSOCIATORS OF {Win32_Group.Domain='$($env:COMPUTERNAME)',Name='Administrators'}


WHERE ResultClass = Win32_Group"
Get-WmiObject -Query $query | Select -ExpandProperty Caption
This is a bit more complicated than the CIM cmdlet version because you have to create a WMI Query
Language (WQL) query to discover the associated Win32_UserAccount and Win32_Group instances.
The difficult part is getting the contents of the {} correct. The easiest way is to look at
the __RELPATH property of the returned object when you use the following command:
Get-WmiObject -Class Win32_Group -Filter "Name = 'Administrators'" | fl *
Remember to change the double quotes to single quotes to make the query work.
You get the same results as with the CIM cmdlets:
WIN12R2\Administrator
MANTICORE\Richard
MANTICORE\Domain Admins
The third and final way to get this data is to drop back to the .NET Framework class and use
theSystem.DirectoryServices.AccountManagement classes that were introduced in .NET
Framework 3.5.
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext
-ArgumentList $ctype, $env:COMPUTERNAME
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context,
$idtype, 'Administrators')
$group.Members |
select @{N='Domain'; E={$_.Context.Name}}, samaccountName
This may look a bit more complicated, but most of it is concerned with defining the objects that you want
to work with. The script starts by using Add-Type to load
the System.DirectoryServices.AccountManagement .NET Framework classes into Windows
PowerShell. Not all.NET Framework classes are loaded by defaultonly those that are thought to be of the
most use to the most people.

After that, you use the ContextType class to say that you are looking at the local machine rather than the
domain. TheSystem.DirectoryServices.AccountManagement classes arent used much, which is a
shame because they are powerful and they span local and domain level activities.
You can then use the context type and the local machine name to create a context.
The IdentityType defines what you are using to identify objectsin this case, the SamAccountName.
Now you can use all of that data in the FindByIdentity() method of the GroupPrincipal class to find the
group. TheMembers property holds the members as you might expect; but the final piece is that you have
to dig into the Context of the member to discover whether the object comes from the domain or from the
local machine.
Luckily the code runs much faster than I can write about it and you get results like this:
Domain

SamAccountName

------

--------------

WIN12R2

Administrator

Manticore.org Domain Admins


Manticore.org Richard
Working through these three methods illustrates an important point: If there are multiple methods
available, you should investigate them to discover how they work, any issues that occur for you, and which
one works best with your skills and way of working.
The last part of this journey is to turn your code into a function that you can use across multiple remote
machines. Im going to use the System.DirectoryServices.AccountManagement approach so I can
illustrate how these classes can be used against remote machines without the need to rely on Windows
PowerShell remoting.
function get-localgroupmember {
[CmdletBinding()]
param(
[parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$computername = $env:COMPUTERNAME
)
BEGIN {

Add-Type -AssemblyName System.DirectoryServices.AccountManagement


$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}

PROCESS{
foreach ($computer in $computername) {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext
-ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context,
$idtype, 'Administrators')
$group.Members |
select @{N='Server'; E={$computer}}, @{N='Domain'; E={$_.Context.Name}}, samaccountName
} # end foreach
} # end PROCESS
}
Win12R2", "W12SUS" | get-localgroupmember
Start by defining the function and parameter. In this case, youre only interested in the remote computer
name. When you use an array as the type, you can also do this:
get-localgroupmember -computername Win12R2, w12sus
but only if you have the Foreach loop in the PROCESS block. (You dont have to capitalize these blocks
of codeI do it to make my code more readable.)
You only need to load the .NET Framework classes, define the context, and identity types once. So those
steps go into the BEGIN block, which executes once when the first object in the pipeline hits the function.
The PROCESS block loops through the computers and creates a context for each machine. The
Administrators group is found and the membership list is extracted. You are working against remote
machines so the computer is added to the output to identify to which machine the results apply.
You can use this function on the pipeline as shown or interactively by passing one or more computer
names to the function.

If you want to extend the use of this script you could:

Make the group name a variable.

Save the results for comparison against a future examination to track changes.

AK, thats how you use Windows PowerShell to check the membership of your local groups. Next time Ill
have another idea for you to try as you bring more automation into your environment.