PowerShell – Fun With PSCustomObjects

PowerShell is a superb tool for digging directly into the systems and getting the information you need.  Sometimes though, getting exactly what you’re looking for isn’t readily available.  For example, say I want to get the ACLs for my scripting directory, I just:

Get-Acl -Path c:\scripts | Format-Table -AutoSize -Wrap

And I get this:

PSC11

Cool to be sure, but if you look closely, you’ll see that the Access field is concatenated data.  The user, the access control type, and file system rights are all one string.  Not bad for reading, but if I want some kind of report to send to the boss (a la CSV), or use the output at a later time to reapply/compare permissions, it might not be acceptable.  Furthermore, we’ve formatted the data, making it essentially useless at this point if we want to move it through the pipeline.  But if you expand the Access property like so:

$Path = "c:\scripts"
$Directory = Get-Acl -Path $Path
$Directory.Access

We get the data broken down by it’s subproperties:

PSCO1

This is something we can work with!  But maybe I want the path too.  So let’s see if it’s available.

$Directory.Access | Get-Member

And we get:

PSCO2

Not exactly screaming out any winners here.  But we did have the file path in the Get-ACL.  So how do we put this all together in a single return?  How about with a PSCustomObject?

PSCustomObjects are a lot like hash tables.  They use name-value pairs like hash tables, but offer you a little more flexibility like allowing you to control the order in which they’re presented.  This makes them very nice for gathering the data you need, ordering it however you like (as far left as possible), and letting you move it through the pipe to be used or exported.  There’s some good reading to be had regarding PSCustomObjects in about_Object_Creation in PowerShell’s Get-Help or on TechNet.

So for our first example, let’s get the file path, the directory owner, and then the group, the access type, and the folder rights.  To do this, we’ll need to grab data from our Path variable, our Get-ACL cmdlet, and the Access property.  So we modify our original lines of code with a ForEach statement to call out the Access property to get our group list:

$Path = "c:\scripts"
$Directory = Get-Acl -Path $Path
ForEach ($Dir in $Directory.Access){}

And then we’ll create our PSCustomObject table inside the ForEach statement:

$Path = "c:\scripts"
$Directory = Get-Acl -Path $Path
ForEach ($Dir in $Directory.Access){
[PSCustomObject]@{
Path = $Path
Owner = $Directory.Owner
Group = $Dir.IdentityReference
AccessType = $Dir.AccessControlType
Rights = $Dir.FileSystemRights
}#EndPSCustomObject
}#EndForEach

And then we execute…

PSCO3

Success!  So now we can make our script a function and feed it some parameters and we can get our ACLs by group for a directory!

But we’re not done yet!

Oftentimes, I get asked from my colleagues if there’s a quick and easy way to retrieve network adapter information from machines on the network.  Those requests often include IP Address, DNS Settings, driver information and link speed.  Using Get-CIMInstance, we can retrieve the network adapter configuration for a machine by leveraging the Win32_NetworkAdapterConfiguration class.

PSCO4

This class gives us a wealth of information on the adapter config, but lacks the driver info and link speed that I mentioned earlier.  Using the Get-NetAdapter cmdlet, however, will net us that information that we’re missing.

For your information, you could also use the Get-CIMInstance cmdlet and target the MSFT_NetAdapter class in the root\standardcimv2 Namespace, but since we have a cmdlet that does all of the heavy lifting for us, we’ll go ahead and use that.

PSCO5

So, now that we know how to get the data we want, let’s go ahead and put together our list in a PSCustomObject like before:

$Computer = "server01"
$AdapterCfg = (Get-CIMInstance Win32_NetworkAdapterConfiguration -ComputerName $Computer).where({$PSItem.IPEnabled})
$NetAdapter = Get-NetAdapter -CimSession $Computer
[PSCustomObject]@{
System = $AdapterCfg.PSComputerName
Description = $AdapterCfg.Description
IPAddress = $AdapterCfg.IPAddress
SubnetMask = $AdapterCfg.IPSubnet
DefaultGateway = $AdapterCfg.DefaultIPGateway
DNSServers = $AdapterCfg.DNSServerSearchOrder
DNSDomain = $AdapterCfg.DNSDomain
DNSSuffix = $AdapterCfg.DNSDomainSuffixSearchOrder
FullDNSREG = $AdapterCfg.FullDNSRegistrationEnabled
WINSLMHOST = $AdapterCfg.WINSEnableLMHostsLookup
WINSPRI = $AdapterCfg.WINSPrimaryServer
WINSSEC = $AdapterCfg.WINSSecondaryServer
DOMAINDNSREG = $AdapterCfg.DomainDNSRegistrationEnabled
DNSEnabledWINS = $AdapterCfg.DNSEnabledForWINSResolution
TCPNETBIOSOPTION = $AdapterCfg.TcpipNetbiosOptions
IsDHCPEnabled = $AdapterCfg.DHCPEnabled
AdapterName = $NetAdapter.name
Status = $NetAdapter.status
LinkSpeed = $NetAdapter.linkspeed
Driverinformation = $NetAdapter.driverinformation
DriverFilename = $NetAdapter.DriverFileName
MACAddress = $AdapterCfg.MACAddress
}#EndPSCustomObject

PSCO6

Success!  Well…almost…

Once thing you might have noticed is that the data you’ve collected concatenates the data for all network adapters, so if you have multiple ethernet or wireless adapters, the data is all together and you might not want that.  Also, for my purposes, I only want the adapters that are connected to the network.  So:

$Computer = "server01"
$NetAdapter = (Get-NetAdapter -CimSession $Computer).where({$PSItem.LinkSpeed -gt 1})

This will give me only my network adapters that are showing a link speed greater than 1.

PSCO7

Now that I’ve got my adapters, I’ll feed them through a loop to gather the additional data from the Win32_NetworkAdapterConfiguration class.  I’ll use the MAC Address to match up the network adapters.  To do that, I need to modify the MAC Address string because Get-NetAdapter displays the MAC Address with dashes, and Win32_NetworkAdapterConfiguration stores it with colons.:

ForEach ($Net in $NetAdapter){
$NetMAC = $Net.MACAddress -replace "-",":"
$AdapterCfg = (Get-CIMInstance Win32_NetworkAdapterConfiguration -ComputerName $Net.PSComputerName).where({$PSItem.MACAddress -eq $NetMAC})
}

PSCO8So for the one MAC Address, we have two instances in our WMI class.  But looking further, we see that only one is actually IPEnabled.  So let’s go with that one:

(Get-CIMInstance Win32_NetworkAdapterConfiguration -ComputerName $Net.PSComputerName).where({$PSItem.MACAddress -eq $NetMAC}) | Where-Object IPEnabled -EQ $True

PSCO9

Now that looks a bit better!  So now let’s create our PSCustomObject table.

[PSCustomObject]@{
System = $AdapterCfg.PSComputerName
Description = $AdapterCfg.Description
IPAddress = $AdapterCfg.IPAddress
SubnetMask = $AdapterCfg.IPSubnet
DefaultGateway = $AdapterCfg.DefaultIPGateway
DNSServers = $AdapterCfg.DNSServerSearchOrder
DNSDomain = $AdapterCfg.DNSDomain
DNSSuffix = $AdapterCfg.DNSDomainSuffixSearchOrder
FullDNSREG = $AdapterCfg.FullDNSRegistrationEnabled
WINSLMHOST = $AdapterCfg.WINSEnableLMHostsLookup
WINSPRI = $AdapterCfg.WINSPrimaryServer
WINSSEC = $AdapterCfg.WINSSecondaryServer
DOMAINDNSREG = $AdapterCfg.DomainDNSRegistrationEnabled
DNSEnabledWINS = $AdapterCfg.DNSEnabledForWINSResolution
TCPNETBIOSOPTION = $AdapterCfg.TcpipNetbiosOptions
IsDHCPEnabled = $AdapterCfg.DHCPEnabled
AdapterName = $Net.name
Status = $Netr.status
LinkSpeed = $Net.linkspeed
Driverinformation = $Net.driverinformation
DriverFilename = $Net.DriverFileName
MACAddress = $AdapterCfg.MACAddress
InterfaceName = $Net.InterfaceDescription
}#EndPSCustomObject

And our return looks good!

PSC10

Now we can put the finishing touches on our script to make it a full function!

PSCustomObjects can help you create some exciting tools to gather information in your environment, or feed information from different sources into other applications (such as Active Directory or SQL).  So take a little time to get to know the PSCustomObject class and it might save you some time on the back-end!

Go here if you’d like to download a copy of my ACL function.

Go here if you’d like to download a copy of my NetAdapter

Sidenote: You might notice that I’m using Where-Object as a method in my examples (.where), but my downloads leverage Where-Object in the code.  This is something specific to PowerShell 4 and later that I learned from Jeff Hicks’ presentation on PowerShell V4 New Features on Pluralsight.  If you have access to the course, I highly recommend taking a look!  I’ll be talking about the different methods available in PowerShell v4 at a later date, and why I’ve gotten so hooked on them!