I was tasked with determining if computers on a list were workstations or servers. I ran this against a list of 700 machines and it works. About 20 machines returned RPC errors so I am capturing that in the “else” statement but I imagine there is a better way to do this. I am a scripting newbie so I imagine there is a better way to script this whole thing LOL! But your critique would be appreciated.

#Retrieves list of machinies by product type to determine if workstation or server. .PropertyType to return a number: 1 = workstation, 2 = DC, 3 = server (member)

$outputworkstations = ".\workstations.csv"
$outputservers = ".\servers.csv"
$outputerrors = ".\errors.csv"

$computers = Get-Content -Path .\hosts.txt
$OutputMessage1 = @()
$OutputMessage2 = @()
$OutputMessageError = @()

foreach($computer in $computers){

    $ProductType = (Get-WmiObject -ClassName Win32_OperatingSystem -ComputerName $computer).ProductType
    
    if ($ProductType -eq 1) {
    $OutputMessage1 += "$computer, $ProductType"
   }
   elseif ($ProductType -eq 2 -or $ProductType -eq 3) {
  $OutputMessage2 +=  "$computer, $ProductType"
   }
   else {
   $OutputMessageError += "$computer, Error"
   }
   
 }
 $OutputMessage1 |   Out-File $outputworkstations -Encoding utf8 -Append 
 $OutputMessage2 |   Out-File $outputservers -Encoding utf8 -Append 
 $OutputMessageError |   Out-File $outputerrors -Encoding utf8 -Append

10 Spice ups

+= is to be avoided

2 Spice ups

since you are checking for numbers, maybe ‘switch’ might be better?

1 Spice up

This script is pretty slow, although I really didn’t know what to expect against 700 objects! So I’ll try to incorporate this into my script. Thanks!

1 Spice up

Okay I will need to research this, as I am really quite the beginner

1 Spice up

I’d do it like so:

$outputworkstations = "workstations.csv"
$outputservers      = "servers.csv"
$outputerrors       = "errors.csv"
$computers          = Get-Content "hosts.txt"
$step               = 1

$computerReport = 
foreach($computer in $computers){
    $progress = @{
        ID               = 1
        Activity         = "Progress" 
        Status           = "$([math]::Round((($step / $($computers|measure-object).count * 100)),0))% Complete"
        PercentComplete  = (($step / $($computers|measure-object).count) * 100)
        CurrentOperation = "Working on '$computer' - $step of $(($computers|measure-object).count)"
    }
    Write-Progress @progress

    if(Test-Connection $computer -Count 1 -Quiet){
        $ProductType = 
        try{
            (Get-ciminstance -ClassName Win32_OperatingSystem -ComputerName $computer -ErrorAction Stop).ProductType
        }
        catch{
            [pscustomobject]@{
                ComputerName = $computer
                ProductType  = "Error-$($error[0])"
            }
        }

        if($ProductType){
            [pscustomobject]@{
                ComputerName = $computer
                ProductType  = $ProductType
            }
        }
    }
    else{
        [pscustomobject]@{
            ComputerName = $computer
            ProductType  = "Error-Computer Unreachable"
        }
    }
    $step++
 }

$computerReport |
Where-Object {$_.ProductType -eq 1} |
export-csv "$outputworkstations" -NoTypeInformation

$computerReport |
Where-Object {$_.ProductType -eq 2 -or $_.ProductType -eq 3} |
export-csv "$outputservers " -NoTypeInformation

$computerReport |
Where-Object {$_.ProductType -like "*error*" } |
export-csv "$outputerrors" -NoTypeInformation
4 Spice ups

Wow, this is a great learning step for me, thank you so much. I will be studying this as I can incorporate some of this into other script projects I have.

1 Spice up

I cannot use get-cim because winrm is not running on the workstations, but I believe I can substitute wmi with the same properties.

1 Spice up

I’m copying this script just to use the progress calculation… that’s nerdy good math stuff there!

Thanks much,

Jeff Cummings

MIS Technician

P.S. I love how he just randomly recommends using a switch statement, then recommends a complete script… without using a switch statement. rofl, still very nice though Neally

WMI is being deprecated

get-wmiobject uses the dcom protocol and get-ciminstance uses the wsman/winrm protocol by default.

you can tell CIM to use DCOM though

e.g.

function Get-BIOS{
param
    (
        $ComputerName = $env:COMPUTERNAME,
        
        [Microsoft.Management.Infrastructure.CimCmdlets.ProtocolType]
        $Protocol = 'DCOM'
    )
    $option = New-CimSessionOption -Protocol $protocol
    $session = New-CimSession -ComputerName $ComputerName -SessionOption $option
    Get-CimInstance -CimSession $session -ClassName Win32_BIOS
}

I just might have to charge… $$$$

I said to think about using a switch statement, not that you have to use it.

My script does not care about producttype until it is done collecting the data, hence no switch statement needed.

You could however throw one in at the end, rather than using the ‘where-object’ statements.

If this is something you are going to be using regularly I highly recommend you enable PS remoting . It enables you to use PowerShell remotely with most commandlets that have a -ComputerName argument in parallel. You could do the collection much faster this way but would have to modify the error handling.

$computers = Get-Content "hosts.txt"

# Commands to be run remotely
$scriptBlock = {
    $os = Get-CimInstance -ClassName Win32_OperatingSystem 
    [PSCustomObject]@{
        Type = switch ($os.ProductType) {
            1 { "Workstation" }
            2 { "Server" }
            default { "N/A" }
        }
    }
}

# Invoke-Command automatically adds a ComputerName property to returned objects, no need to specify it in the scriptblock
# by default does 20 calls at a time
$data = Invoke-Command  -ScriptBlock $scriptBlock -ComputerName $computers

if ($data.count -lt $computers.count ) {
    $missing = (computers | Where-Object $_ -NotIn $data.ComputerName) -join ", "    
    Write-Warning "Could not collect information from $missing"
}

$data | Sort-Object Type | Format-Table
1 Spice up

Thanks. yep I do know the WMI is being deprecated. On our large network all the servers run winrm but not the workstations so a lot of the scripts I see here are still using WMI. But this is a neat trick so thank you.

Very cool, thank yo I will consider this. Although if this requires winrm it will not work with our workstations.

Its very simple to enable it, restrict IPs for the listeners and open the necessary firewall ports via GPO.

you should seriously consider doing so,

1 Spice up

I just got a new position in a huge company. Our division alone handles 14,000 machines. We do not even make those kinds of decisions. The servers all have winrm enabled but not the workstations. In this case I need to pull out workstations from a large list of machines that contain servers and workstations. But most often we work with servers so your help is appreciated.