Author Posts

October 15, 2016 at 10:02 pm

I have a function that takes an object as a parameter which includes AD info like computer name and last login date, it goes out and finds a bunch of info from WMI, SMB, etc, builds and returns a new object, with a lot more data, including some of the input data.

It works fine, but when I wrap it in a Starrt-Job scriptblock, all I get back is an array, like this.

PS WINDOWS> $item.job| Receive-Job -Keep
@{PSComputerName=localhost; RunspaceId=650db34a-e613-4085-8f26-dfc3504b62e8; PSShowComputerName=False}

I'll post the monster script for inspection if I must, but first, is there anything I should look out for, any common mistakes?

October 18, 2016 at 8:16 pm

No, that's just how serialized objects look, particularly of the PSObject variety. What are you trying for?

October 19, 2016 at 9:43 pm

There was supposed to be a lot more data in the result, but I figured out that the functions I was using had to be defined within the job, since it's a separate Powershell process. But I'm still having output problems, I believe it's related to the way I'm handling the background jobs–which is a skill I'm trying to hone.

First I build an object based on computers found in AD. Since I support a large number of different offices, I can't guarantee the server I'm using will have RSAT installed for the AD module, so I use ADSI.

# Build the AD object with all computer objects found
$adsi = $null
$adsi = [adsisearcher]"objectcategory=computer"
# To return only the enabled computer objects, use '!userAccountControl:1.2.840.113556.1.4.803:=2'
$adsi.filter = "(&(objectClass=Computer)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
$EnabledADComputerADSI = $adsi.FindAll()
$EnabledADComputer = Foreach ($E in $EnabledADComputerADSI){
    $obj = $E.Properties
    $props = @{
        Computer    = [string]$obj.name
        OSName      = [string]$obj.operatingsystem -replace 
                                 'Windows','Win' -replace 
                            'Professional','Pro' -replace 
                                'Standard','Std' -replace 
                                'Ultimate','Ult' -replace 
                              'Enterprise','Ent' -replace 
                                'Business','Biz' -replace 
                                    'with', 'w/' -replace 
                            'Media Center','MedCtr'
        Description = [string]($obj.description)
        AD_OU       = [string]($obj.distinguishedname) -replace 
                              '^CN=[\w\d-_]+,\w\w=','' -replace 
                                            ',OU=','/' -replace ',DC=.*'
        LastLogon   = [datetime]::FromFileTime([string]$obj.lastlogon)
        ADCreated   = [datetime]($obj.whencreated)[0]
    }
    New-Object -TypeName PSObject -Property $props
}

Next, I use a ping function to filter out any offline hosts. The result is a nice table, no issues here.

Then I take that object and run it through a foreach loop to kick off a job per device. This might be overly complicated, but it's the only way I've figured out how to grab data from remote PCs in parallel. Since I support a large number of different offices, I can't guarantee PSRemoting will be turned on (in most cases it's not).

# Set the initial conditions for my makeshift throttle limit. 
$MyCurrentJobCount    = 0     
$MyThrottleLimit      = 16    # Number of concurrent jobs/threads 
$MajorThottleInterval = 750   # Space between job checks when queue is full (in milliseconds) 
$MinorThottleInterval = 10    # Space between new job starts to fill the queue  (in milliseconds) 
$MyJobNamePattern     = 'Job-nmAudit-' # prevents other parallel scripts from slowing down this script. 

$Tasks = New-Object System.Collections.ArrayList # Main Object for Output of live info 

foreach ($C in $OnlineADComputer)
{
    # This is my makeshift throttle limit 
    While ($MyCurrentJobCount -ge $myThrottleLimit){
        # Pause longer for a full queue 
        if ($MyCurrentJobCount -eq $myThrottleLimit){
            sleep -Milliseconds $MajorThottleInterval
        }else{
            sleep -Milliseconds $MinorThottleInterval
        }
        $MyCurrentJobCount = (Get-Job -Name "$MyJobNamePattern*" | 
            where {$_.State -eq 'Running'}).count
    }
    $Item = New-Object -TypeName psobject -Property @{
        Name   = "$($MyJobNamePattern)$($C.Computer)"
        Return = $null
        Job    = Start-Job -Name "$($MyJobNamePattern)$($C.Computer)" -ScriptBlock {
            $C = $using:C 
 
            # Define functions
            #  ...
            # Use functions to get data from remote PCs, store to variables
            # Create psobject as output for all data grabbed 
            #  ...

### sample of data grabbed: 

            # Check out the value for last logon. 
            $prevLogon = if(([datetime]$C.LastLogon) -lt [datetime]'1/1/1990')
                {'Unknown'}else{$C.LastLogon}

            # Build PSObject Property hashtable
            $obj = New-Object -TypeName PSObject -Property @{
                                Device = $C.Computer
                                OSName = $C.OSName
                                 AD_OU = $C.AD_OU
                      LastLoggedOnUser = $LastUser
                     BiggestUserFolder = $BiggestUserFolder
                     SystemDriveLetter = $SysDrvInfo.SystemDrive
                           WUSvcStatus = $WUServiceStatus
                        WUAgentVersion = $WUAgentInfo.Version
                           WUAgentDate = $WUAgentInfo.Date.ToShortDateString()
                             SMB_Admin = $SMBAccess
                      RemoteServiceMgt = $RmtSvcMgt
                       RemoteEvtLogMgt = $RmtEvtLogMgt
                       RemoteWMIAccess = $WMIAccess
                  SystemDriveSpaceInGB = $SysDrvSpaceInGB
            }
            return $obj

        } # Job = ... scriptblock {...

    } # $Item = @{...

    $Tasks.Add($Item) | Out-Null 

} # Foreach ($C in $OnlineADComputer){...

Get-Job -Name ($Tasks.Name) | Wait-Job -Timeout 300 | Out-Null

Foreach ($T in $Tasks)
{
    $T.Return = Get-Job ($T.Name) | Receive-Job 
    Remove-Job ($T.Name)
}

$OnlineTable = $Tasks |select -exp Return 

When I check the array after the foreach loop, I see the jobs, I can wait until they finish, but the output is not there when I receive it. Nothing. Even if the scripts returned nothing or errors, I should still get a table with nulls, the device name, and a handful of other properties from the first AD table like OSName and LastLoggedOnUser.

I'm about to scrap it and start from scratch because this thing is so big I'm losing my place every five mins. So even if you can't figure out what my problem is, based on the snippets I've shared maybe you can point me to a template I can use to do this more efficiently.