PowerShell Remoting Script Help Needed Please

I want a template for powershell remoting so I can run any code against 2000 machines. My goal here is to create a loop so the good results go into a CSV file and then the main list of computers created from AD will then have the computers that I have collected the results for removed from that list and I will have a new computer list to run against. So as I collect more and more good results the list will get shorter. This is what I have so far, please help me refine it. I know it’s not pretty. But it does work. I just like to create advanced functions and make things look professional. I am stuck on this project. Any help or comments are greatly appreciated. Or if you have ideas on how you run things against 2000 computers and collect results please let me know. - Dave

Clear-Host
Write-Host " nGet Computer Info Loopn" -ForegroundColor Blue

# Start Loop
$Counter = 0
$Total = 100
do {
$Counter = $Counter+=1
Write-Host "Executing loop $Counter of $Total" -ForegroundColor Green
##########

function makecomputerlist {
# Get AD Computers and save to a text file.
Get-ADComputer -Filter { (OperatingSystem -like "*Windows 10*") -and (Enabled -eq "True") } -SearchBase "DC=domain,DC=org" -Server "domain.org" | Where-Object { $_.DNSHostName -like "*domain.org" } | Select-Object -ExpandProperty Name | Sort-Object | Out-File -FilePath "C:\Temp\ADComputers-ComputerInfo.txt" -Append
}
makecomputerlist

function makenewcomputerlist {
# Compare the ADComputers.txt and the ComputerInfo.csv and make a new ADComputers.txt
# file for only the computers that are not in the ComputerInfo.csv file.
if (-not (Test-Path -Path "C:\Temp\ComputerInfo.csv") ) { New-Item -ItemType 'File' -Path "C:\Temp\ComputerInfo.csv" }
$Csv = Import-Csv -Path "C:\Temp\ComputerInfo.csv"
$Array = @()
foreach ($Item in (Get-Content -Path "C:\Temp\ADComputers-ComputerInfo.txt")) {
if ($Csv.ComputerName -contains $Item) {
#"$Item info has already been collected"
}
else {
$Array += $Item
}
}
if (Test-Path -Path "C:\Temp\ADComputers-ComputerInfo.txt") { Remove-Item -Path "C:\Temp\ADComputers-ComputerInfo.txt" -Force }
$Array | Select-Object -Unique | Sort-Object | Out-File -FilePath "C:\Temp\ADComputers-ComputerInfo.txt"
}
makenewcomputerlist

# Get the total count from ADComputers.txt and ComputerInfo.csv
Write-Host "ADComputers.txt computer count: $((Get-Content -Path "C:\Temp\ADComputers-ComputerInfo.txt").Count)"
Write-Host "ComputerInfo.csv computer count: $((Import-Csv -Path "C:\Temp\ComputerInfo.csv").Count)"

function runpsremoting {
# PsRemoting
$ComputerName = Get-Content -Path "C:\Temp\ADComputers-ComputerInfo.txt"
$Success = Invoke-Command -ComputerName $ComputerName -ErrorVariable Failed -SessionOption (New-PSSessionOption -NoMachineProfile) -ErrorAction SilentlyContinue -ScriptBlock {

$OS = Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop
$DiskC = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='C:'" -ErrorAction Stop
$Proc = Get-WmiObject -Class Win32_Processor -ErrorAction Stop
$Sys = Get-WmiObject -Class Win32_ComputerSystem -ErrorAction Stop
$Bios = Get-WmiObject -Class Win32_BIOS -ErrorAction Stop
$Bios2 = Get-WmiObject -Class Win32_SystemEnclosure -ErrorAction Stop
$Net = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter 'IpEnabled = True' -ErrorAction Stop

$Process = Get-WmiObject -Class Win32_Process -filter "Name = 'explorer.exe'" -ErrorAction Stop
#$Lenovo = Get-WmiObject Win32_ComputerSystemProduct | Select Vendor, Version, Name, IdentifyingNumber

[PSCustomObject][ordered]@{
ComputerName = $env:COMPUTERNAME
Manufacturer = $sys.Manufacturer
Model = $sys.Model
SerialNumber = $bios.SerialNumber
AssetTag = $bios2.SMBIOSAssetTag
BIOSVersion = $bios.SMBIOSBIOSVersion
OSName = $os.caption
OSArchitecture = $os.OSArchitecture
OSVersion = $os.Version
OSBuild = $os.buildnumber
OSInstallDate = $os.ConvertToDateTime($os.InstallDate)
LastBootTime = $os.ConvertToDateTime($os.LastBootupTime)
LogonTime = ($Process.ConvertToDateTime($Process.CreationDate))
UserName = $Process.GetOwner().User
DomainUserName = $sys.UserName
Domain = $sys.domain
IPAddress = $net.IPAddress[0]
SubnetMask = $net.IPSubnet[0]
DefaultGateway = $net.DefaultIPGateway
DNSServers = $net.DNSServerSearchOrder
MACAddress = $net.MACAddress
NICDescription = $net.Description
Processor = $proc.name
NumberOfProcessors = $sys.NumberofProcessors
NumberOfLogicalProcessors = $sys.NumberofLogicalProcessors
Memory = $sys.TotalPhysicalMemory / 1MB -as [int]
Drive = $DiskC.DeviceID
DriveTotalSize = $DiskC.Size / 1GB -as [int]
DriveFreeSpace = $DiskC.freespace / 1GB -as [int]
DrivePercentFree = $DiskC.FreeSpace / $DiskC.Size * 100 -as [int]
}

} # End ScriptBlock

# Display results to screen and out to file.
$Success | Select-Object -Property * | Export-Csv -Path 'C:\Temp\ComputerInfo.csv' -Append -NoTypeInformation

# Display failures to screen and out to file.
$Failed.TargetObject | Out-File -FilePath 'C:\Temp\Failed.txt' -Append
}
runpsremoting

##########
# End Loop
}
Until ( $Counter -eq $Total )

How are the computers managed? No SCCM or other deployment tool in use?

Maintaining lists of 2000 computers and updating them when things run successfully isn’t easily done with any reliability.

If you don’t have SCCM, I would consider deploying it via Group Policy. You could set it to run via the Run Once registry key.

Alternatively, deploy it as a start up or logon script (depending on use case) so that it runs every time but have the script exit immediately based on some condition, e.g. the presence of a registry key which is set after the script runs successfully.

Thanks for the message. I would run SCCM but it’s not my call. So for collecting information like computer details, defender details, Windows Update info like ones that have pending updates, and other info I collect in CSV files and parse through them. A lot of the computers are laptops and are not all turned on at the same time, that’s why I wanted a loop that would run and collect info from that device when it’s on and then take it off the list so it doesn’t run on it again. I know it’s the long way around this and a management system that would poll each machine and collect all of this data would be great. I’m just stuck right now using my make shift skills.

Here is my $.02 based on a SKIM of what you have.

I would group the subs first, then create your loop and make the calls. You seem to be defining your function, then calling them right after.

As for PSRemoting, I see nothing you are doing that requires that? Am I missing something? Get-WmiObject has a -ComputerName parameter that will greatly simplify your task. Using Invoke-Command on 2000 systems seems like it would be a daunting task to get PSRemoting tested and working on all of them.

 

I am using remoting so it takes 14 minutes to run through the list of computers. If I do it the other way it runs one computer at a time. It takes too long to run though the list.

Understood, thanks for the clarification. Thats what I get for skimming …

Since your script works and you are just looking for advice on how to clean it up or refine it, I offer the following suggestions:

  1. You should not have function definitions inside a loop. You can call functions inside a loop but don't continually define them in each iteration.
  2. I don't understand why you need the Do Until loop.
  3. Functions, in general, should rely on parameter values (arguments) not higher scope variables. None of your function definitions have parameters.
  4. You said you like advanced functions but none of your functions are advanced functions. To make them "advanced" you would need to have a param block inside the function definition and include a [cmdletbinding] attribute.
  5. When using PowerShell remoting for a large block of computers where processing may take a long time consider -asjob
  6. Just cosmetic, but indenting your script blocks and multi-line pipes would make it much easier to read/follow.
Recommend review:

Get-Help about_functions_advanced

Get-Help Invoke-Command

POSH style guide

Thank you. I made the loop so it can run all day without me starting it over. I made the functions while testing so i could keep a part of it from running without commenting out a bunch. I wanted input before i re-do it all. I kind of wanted to see what other ideas are out there that I am missing when it comes to remoting. example catching good results and handling failures so you can re-run it on them again without catching good results twice. Thanks you the advice and I appreciate it.

Here is a short example of what I mean by define your functions outside the loop. The function is also an advanced function. Maybe this will give you some ideas.

function Get-RemoteComputerInfo
{
    [CmdletBinding()]
    Param
    (
        # Path to .txt file of remote machines
        [Parameter(Mandatory=$true)]
        [ValidateScript({ Test-Path -Path $_ })]
        [string]
        $Path
    )

    Process
    {
        $splat = @{
            ComputerName = Get-Content -Path $Path
        } #splat

        Invoke-Command @splat -ScriptBlock {
            [pscustomobject]@{
                OS    = Get-CimInstance -ClassName Win32_OperatingSystem
                DiskC = Get-CimInstance -ClassName Win32_LogicDisk -Filter "DeviceID='C:'"
                Proc  = Get-CimInstance -ClassName Win32_Processor
            } #pscustomobject definition            
        } #Invoke-Command scriptblock
    } #Process 

} #Get-RemoteComputerInfo function

$CompPath = "C:\Temp\ADComputers-ComputerInfo.txt"
$OutputPath = "C:\Temp\ComputerInfo.csv"

#Infinite loop to run all day (every 30 min)
while ($true) {
    Get-RemoteComputerInfo -Path $CompPath |
        Export-Csv -Path $OutputPath -Append -NoTypeInformation

    Start-Sleep -Seconds 1800
} #while loop

 

OMG I can’t thank you enough. That is great. I can use this as an example and learn from it. This is what I was wanting to create.

Honestly I wasn’t sure on how to create an advanced function and incorporate invoke-command and a loop. Usually the adv functions I make are simple that run against one or a few computers. So thank you again.