Last Update: Sep 04, 2024 | Published: Aug 20, 2015
Over the last several articles, I’ve demonstrated how to extend an object in PowerShell and why you might want to do so. In this article, I’ll show you how to take these concepts and use them to manage servers from a PowerShell prompt.
Extending Objects in PowerShell Article Series:
One of the reasons I find this approach appealing is that it forces PowerShell users to start thinking about managing at scale. In the pre-PowerShell days, you probably managed one thing at a time. With PowerShell, I’m trying to get people to think about managing 10 or 100 things at a time. Don’t be in the mindset of managing one server at a time when you can manage 2, 20, or 200.
The premise for my management tool is to create an object for each server I want to manage. The object will have a few properties and methods for reporting and to at least serve as a proof of concept. The function I wrote is by no means complete. You could easily extend it to include additional properties and methods to meet your business needs. In some ways this is like a simpler, console-based version of System Center, except that you can completely customize it.
Here’s my New-ManagedComputer function.
Function New-ManagedComputer { <# .Synopsis Create a custom object for managing a server .Description This command will create a custom object for a given server that can be used to easily manage the server. The custom object has its own type name so you should run this command as well: Update-TypeData -TypeName "my.ManagedComputer" -DefaultDisplayPropertySet Computername,OS,PSVersion,LastBoot,Uptime .Example PS C:> $core01 = New-ManagedComputer -computername chi-core01 This first command creates a management object for CHI-CORE01. PS C:> $core01 Computername : CHI-CORE01 OS : Microsoft Windows Server 2012 R2 Datacenter PSVersion : 4.0 LastBoot : 7/12/2015 12:13:04 PM Uptime : 03:25:06.1554444 PS C:> $core01.GetLocalAdmins() | format-table Name,Caption,PSComputername -AutoSize Please wait... Name Caption PSComputerName ---- ------- -------------- Administrator CHI-CORE01Administrator CHI-CORE01 LocalAdmin CHI-CORE01LocalAdmin CHI-CORE01 #> [cmdletbinding()] Param( [Parameter(Position=0,ValueFromPipeline)] [ValidateNotNullOrEmpty()] [Alias("CN")] [string]$Computername = $env:computername ) Begin { Write-Verbose "Starting $($MyInvocation.Mycommand)" } #begin Process { Write-Verbose "Creating a custom management object for $Computername" $os = Get-Ciminstance -ClassName Win32_operatingsystem -ComputerName $computername $PSVersion = Invoke-Command {$PSVersionTable.PSVersion} -HideComputerName -computername $Computername $managed = [pscustomobject]@{ Computername = $computername.ToUpper() PSVersion = $PSVersion OperatingSystem = $os.Caption ServicePack = $os.CSDVersion LastBoot = $os.LastBootUpTime } #adding properties $managed | Add-Member -MemberType ScriptProperty -Name Uptime -Value { (Get-Date) - (Get-Ciminstance win32_operatingsystem -computername $this.computername).lastBootUptime } $managed | Add-Member -MemberType PropertySet -Name Boot -Value Computername,LastBoot,Uptime -force $managed | Add-Member -MemberType AliasProperty -name OS -Value OperatingSystem $managed | Add-Member -MemberType ScriptProperty -Name Shares -value { Get-SMBShare -CimSession $this.computername} #adding methods $managed | Add-Member -MemberType ScriptMethod -Name GetProcesses -Value {Get-CimInstance -class Win32_process -ComputerName $this.computername} $managed | Add-Member -MemberType ScriptMethod -Name Ping -Value {Param([int]$Count=4) Test-Connection -ComputerName $this.computername -Count $count} $managed | Add-Member -MemberType ScriptMethod -Name GetFreeSpace -Value { Param([string]$Drive="c:") $disk = Get-CimInstance -Classname win32_logicaldisk -filter "deviceid = '$drive'" -ComputerName $this.computername $disk.Freespace } $managed | Add-Member -MemberType ScriptMethod -Name Reboot -value { Restart-Computer -ComputerName $this.computername -force} $managed | Add-Member -MemberType ScriptMethod -Name Shutdown -value { Stop-Computer -ComputerName $this.computername -force} $managed | Add-Member -MemberType ScriptMethod -Name GetLocalAccounts -value { get-ciminstance -class win32_useraccount -computername $this.computername -filter "LocalAccount='True'"} $managed | Add-Member -MemberType ScriptMethod -Name GetLocalAdmins -value { Write-host "Please wait..." -ForegroundColor Cyan Get-CimInstance -class win32_group -filter "name='Administrators'" -computername $this.computername | Get-CimAssociatedInstance -ResultClassName Win32_useraccount } $refresh = { $this.PSVersion = Invoke-Command {$PSVersionTable.PSVersion} -HideComputerName -computername $this.Computername $os = Get-Ciminstance -ClassName Win32_operatingsystem -ComputerName $this.computername $this.OperatingSystem = $os.Caption $this.LastBoot = $os.LastBootUpTime $this.ServicePack = $os.CSDVersion } $managed | Add-Member -MemberType ScriptMethod -Name Refresh -value $refresh Write-Verbose "Inserting a type name" $myType = "my.ManagedComputer" $managed.psobject.TypeNames.Insert(0, $myType) #write object to the pipeline $managed } #process End { Write-Verbose "Ending $($MyInvocation.Mycommand)" } #end } #close function
To use the function, you’ll also need to update type data if you want a more user-friendly default display.
Update-TypeData -TypeName "my.ManagedComputer" -DefaultDisplayPropertySet Computername,OS,PSVersion,LastBoot,Uptime
Although I’m talking about scale, you still might want to create a variable for each computer. This could be helpful in my test domain when it comes to troubleshooting a problem, where I can easily do something like this:
"chi-dc01","chi-test01","chi-srv03" | foreach {
$var = $_.split("-")[1]
New-Variable -Name $var -Value (New-ManagedComputer -Computername $_)
}
Because my server names have a hyphen, I can’t use the name as my variable name, so I have an extra step to split the name on the dash and take the last part, which I know will be unique. The end result is variable that looks like this:
And has these methods:
The script methods are the ones that I added. We’ll look at some of these in a moment.
The other approach is to create a single variable for all your managed computers.
$all = "chi-dc01","chi-test01","chi-srv03" | New-ManagedComputer
And there’s no law that says you can’t do both. With this approach, you can use typical PowerShell commands for reporting purposes.
This example is using the custom property set. Or you can use any of the properties.
If you are running PowerShell 4.0 or later, you can also use the new Where method, which is often faster.
$all.where({ $_.psversion.major -gt 3}) | Select Computername,PSVersion,OS
The results will be the same.
Another interesting technique is that you can run a method for all computers at once.
For a small number of computers, this might be too bad. Although you’ll probably want to do this instead:
But depending on your method, you might not need to do anything special.
I hope you are noticing that for the most part I’m getting complete objects. I probably should have done the same for disk information but I wanted to show you how you could get a single value.
I can easily manage information in aggregate:
$all.processes().where({$_.workingsetsize -ge 25MB}) | sort WorkingsetSize -Descending | Select PSComputername,Name,WorkingSetSize
Or individually:
$all.foreach({$_.processes() | sort WorkingsetSize -Descending | Select -first 5})
And of course I have the computername property, which I can leverage with any cmdlet that takes pipeline binding by property name.
$all | get-service wuauserv | Select Name,Status,Machinename
When you think about objects in the pipeline, you can accomplish a great deal with minimal effort. I recognize that many IT pros think about using PowerShell to script everything, but there is an equally amount of amazing work you can do from an interactive prompt. I hope these last few articles have helped prove that. Questions or comments? Throw ’em in the comments.