Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET
PowerShell

Extending Objects in Windows PowerShell, Part 4

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.

Sponsored Content

Say Goodbye to Traditional PC Lifecycle Management

Traditional IT tools, including Microsoft SCCM, Ghost Solution Suite, and KACE, often require considerable custom configurations by T3 technicians (an expensive and often elusive IT resource) to enable management of a hybrid onsite + remote workforce. In many cases, even with the best resources, organizations are finding that these on-premise tools simply cannot support remote endpoints consistently and reliably due to infrastructure limitations.

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-CORE01\Administrator CHI-CORE01    
LocalAdmin    CHI-CORE01\LocalAdmin    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.

Related Topics:

BECOME A PETRI MEMBER:

Don't have a login but want to join the conversation? Sign up for a Petri Account

Register
Comments (0)

Leave a Reply

Live Webinar: Active Directory Security: What Needs Immediate Priority!Live on Tuesday, October 12th at 1 PM ET

Attacks on Active Directory are at an all-time high. Companies that are not taking heed are being punished, both monetarily and with loss of production.

In this webinar, you will learn:

  • How to prioritize vulnerability management
  • What attackers are leveraging to breach organizations
  • Where Active Directory security needs immediate attention
  • Overall strategy to secure your environment and keep it secured

Sponsored by: