Getting output from background jobs - powershell

I created the following script to reset the password of the local admin account on all machines in a specific host file. This script functions properly and provides a useful output, but it is slow as it only does one machine at a time.
# function to convert a secure string to a standard string
function ConvertTo-String {
param(
[System.Security.SecureString] $secureString
)
$marshal = [System.Runtime.InteropServices.Marshal]
try {
$intPtr = $marshal::SecureStringToBSTR($secureString)
$string = $marshal::PtrToStringAuto($intPtr)
}
finally {
if($intPtr) {
$marshal::ZeroFreeBSTR($intPtr)
}
}
$string
}
$clients = Get-Content -Path C:\scripts\utilities\hostnames_online.txt
$adminUser = "Administrator"
# prompt for password and confirm
do {
$ss1 = Read-Host "Enter new password" -AsSecureString
$ss2 = Read-Host "Enter again to confirm" -AsSecureString
# compare strings - proceed if same - prompt again if different
$ok = (ConvertTo-String $ss1) -ceq (ConvertTo-String $ss2)
Write-Host "Passwords match"
if(-not $ok) {
Write-Host "Passwords do not match"
}
}
until($ok)
# set password variable to string value
$adminPassword = ConvertTo-String $ss1
# setup job to reset password on each client
foreach($client in $clients) {
$status = "OFFLINE"
$isOnline = "OFFLINE"
if((Test-Connection -ComputerName $client -Quiet -Count 1 -Delay 1) -eq $true) {
$isOnline = "ONLINE"
}
# change the password
try {
$localAdminAccount = [adsi]"WinNT://$client/$adminuser,user"
$localAdminAccount.SetPassword($adminPassword)
$localAdminAccount.SetInfo()
Write-Verbose "Password change completed successfully"
}
catch {
$status = "FAILED"
Write-Verbose "Failed to change password"
}
# create psobject with system info
$obj = New-Object -TypeName PSObject -Property #{
ComputerName = $client
Online = $isOnline
ChangeStatus = $status
}
$obj | Select computerName, Online, changeStatus | Out-File -FilePath C:\test.txt -Append
if($status -eq "FAILED" -or $isOnline -eq "OFFLINE") {
$stream.writeline("$client -t $status")
}
}
$adminPassword = " "
Write-Verbose "Complete"
Invoke-Item C:\test.txt
To make the script run faster, I set it up to use background jobs so it could run on multiple clients at once. However, now I get no output in my text files. The new script is below. Lines 43 and 79-89 are the changes. What changes do I need to make to this script provide the output it does in the first version? I've tried a number of other ways to get the output than what I currently have on line 89.
# function to convert a secure string to a standard string
function ConvertTo-String {
param(
[System.Security.SecureString] $secureString
)
$marshal = [System.Runtime.InteropServices.Marshal]
try {
$intPtr = $marshal::SecureStringToBSTR($secureString)
$string = $marshal::PtrToStringAuto($intPtr)
}
finally {
if($intPtr) {
$marshal::ZeroFreeBSTR($intPtr)
}
}
$string
}
$clients = Get-Content -Path C:\scripts\utilities\hostnames_online.txt
$adminUser = "Administrator"
# prompt for password and confirm
do {
$ss1 = Read-Host "Enter new password" -AsSecureString
$ss2 = Read-Host "Enter again to confirm" -AsSecureString
# compare strings - proceed if same - prompt again if different
$ok = (ConvertTo-String $ss1) -ceq (ConvertTo-String $ss2)
Write-Host "Passwords match"
if(-not $ok) {
Write-Host "Passwords do not match"
}
}
until($ok)
# set password variable to string value
$adminPassword = ConvertTo-String $ss1
# setup job to reset password on each client
#-----------------------------------------------------------------------------------------
$scriptBlock = {
#-----------------------------------------------------------------------------------------
foreach($client in $clients) {
$status = "OFFLINE"
$isOnline = "OFFLINE"
if((Test-Connection -ComputerName $client -Quiet -Count 1 -Delay 1) -eq $true) {
$isOnline = "ONLINE"
}
# change the password
try {
$localAdminAccount = [adsi]"WinNT://$client/$adminuser,user"
$localAdminAccount.SetPassword($adminPassword)
$localAdminAccount.SetInfo()
Write-Verbose "Password change completed successfully"
}
catch {
$status = "FAILED"
Write-Verbose "Failed to change password"
}
# create psobject with system info
$obj = New-Object -TypeName PSObject -Property #{
ComputerName = $client
Online = $isOnline
ChangeStatus = $status
}
$obj | Select computerName, Online, changeStatus | Out-File -FilePath C:\test.txt -Append
if($status -eq "FAILED" -or $isOnline -eq "OFFLINE") {
$stream.writeline("$client -t $status")
}
}
}
#-----------------------------------------------------------------------------------------
Get-Job | Remove-Job -Force
Start-Job $scriptBlock -ArgumentList $_ -Name AdminPWReset
Get-Job
While(Get-Job -State "Running") {
Start-Sleep -m 10
}
Receive-Job -name AdminPWReset | Out-File C:\test2.txt
#-----------------------------------------------------------------------------------------
$adminPassword = " "
Write-Verbose "Complete"
Invoke-Item C:\test.txt
Invoke-Item C:\test2.txt

You don't receive output, because you moved your entire loop into the scriptblock:
$scriptBlock = {
foreach ($client in $clients) {
...
}
}
but initialized your client list ($clients) outside the scriptblock, so that the variable $clients inside the scriptblock is a different (empty) variable since it's in a different scope. Because of that your job is iterating over an empty list, and is thus not producing any output.
To be able to use the client list inside the scriptblock you'd have to use the using: scope modifier:
$scriptBlock = {
foreach ($client in $using:clients) {
...
}
}
or pass the client list into the scriptblock as an argument:
$scriptBlock = {
foreach ($client in $args[0]) {
...
}
}
Start-Job -ScriptBlock $scriptBlock -ArgumentList $clients
As I can see from your code you're trying to do the latter:
Start-Job $scriptBlock -ArgumentList $_ -Name AdminPWReset
However, that doesn't work for two reasons:
In the context where you run Start-Job there is no current object, so $_ is empty and nothing is passed into the scriptblock.
The scriptblock neither has a Param() block, nor does it use the automatic variable $args, so even if you passed an argument into the scriptblock it would never be used.
With that said, you don't want to pass $clients in the first place, because even if it worked, it wouldn't speed up anything as the entire client list would still be processed sequentially. What you actually want to do is to process the list in parallel. For that you must put just the tests into the scriptblock and then start one job for each client in a loop:
$scriptBlock = {
Param($client)
$status = "OFFLINE"
$isOnline = "OFFLINE"
if (Test-Connection -Computer $client -Quiet -Count 1 -Delay 1) {
$isOnline = "ONLINE"
}
...
}
foreach ($client in $clients) {
Start-Job -Name AdminPWReset -ScriptBlock $scriptBlock -ArgumentList $client
}
while (Get-Job -State "Running") {
Start-Sleep -Milliseconds 100
}
Receive-Job -Name AdminPWReset | Out-File C:\test2.txt
Remove-Job -Name AdminPWReset
Note that if you have a great number of target hosts you may want to use a job queue, so that you don't have hundreds of jobs running in parallel.

Related

PowerShell Script Issues with Variable Values

I am trying to write this script to restart computers only if they are Offline. The script for getting user infomration works but I cannot get the variable values for the restart portion at the bottom of the script. Does anyone have a suggestion? I am somewhat new to Powershell, but writing code. Example of my script follows:
Function Get-LoggedOnUser
{
Param
(
$ComputerName = $env:COMPUTERNAME,
$Credential
)
Function Test-RemoteRegistry
{
Param
(
[Parameter(Mandatory = $False)]
[switch]$Enable
,
[Parameter(Mandatory = $False)]
[switch]$Disable
,
[Parameter(ValueFromPipeline=$True)]
[String[]]$ComputerName = $env:COMPUTERNAME
)
Begin
{
$PipelineInput = (-not $PSBOUNDPARAMETERS.ContainsKey("ComputerName")) -and (-not $ComputerName)
Function Test ($Computer)
{
Try
{
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $Computer) | Out-Null
#20ms faster than Get-Service per computer! Not sure how to handle/check things like the firewall though...
#If we hit here without error Remote Reg is enabled.
If ($Disable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Stopped -ErrorAction Stop
Return $False
#If we hit here without error Remote Reg is now disabled.
}
Catch
{
Return $True
#If we hit here, we couldn't stop remote registry.
}
}
Else
{
Return $True
}
}
Catch
{
If ($Enable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Running -ErrorAction Stop
Return $True
#If we hit here without error Remote Reg is now enabled.
}
Catch
{
Return $False
#If we hit here, we couldn't start remote registry.
}
}
Else
{
Return $False
#If we hit here remote registry is disabled.
}
}
}
}
Process
{
If ($PipelineInput)
{
Test $_
}
Else
{
$ComputerName | ForEach-Object {
Test $_
}
}
}
}
Foreach ($Computer in $Computername)
{
$Online = $False
$User = $False
$Locked = $False
If (Test-Connection $Computer -Count 2 -Quiet)
{
$Online = $True
If ($Credential)
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer -Credential $Credential | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
Else
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
If (Test-RemoteRegistry -Enable -ComputerName $Computer)
{
If ((Get-Process logonui -ComputerName $Computer -ErrorAction SilentlyContinue) -and ($user))
{
$Locked = $True
}
}
}
$Output = New-Object PSObject
$Output | Add-Member noteproperty ComputerName $Computer
$Output | Add-Member noteproperty Online $Online
$Output | Add-Member noteproperty Username $User
$Output | Add-Member noteproperty Locked $Locked
$Output
}
}
Get-LoggedOnUser
If (($Online) -eq $False)
{Shutdown /r t 0 /m \\$Computername}
ELSE
{Write-host 'HELLO $Online $Computername'}
I just want this for a single user as I am using PDQ Inventory to roll out the script. The variables at the end of the script are $null?
Variables defined in a child scope - in which functions run by default - are never seen by the calling scope. See the conceptual about_Scopes help topic
It's best for functions to communicate values to the caller via their output ("return value"), which you're function is already doing: it outputs objects whose properties contain the values of interest.
Therefore:
Get-LoggedOnUser |
ForEach-Object { # Loop over all output objects
# Refer to the object at hand via the automatic $_ variable.
# Note the use of "..." (expandable strings) so as to support
# expansion (string interpolation).
if (-not $_.Online) { Shutdown /r t 0 /m "\\$($_.ComputerName)" }
else { "HELLO $($_.Online) $($_.ComputerName)" }
}

PowerShell Loop Ping Online Status

I'm trying to modify a Powershell to do the following work, but I got into some errors.
My goal is to monitor multiple servers online status from the the Powershell window. I would need to open a Powershell and run the ps1 script from the Powershell window then monitor the online status result.
1. Ping servers from a list from hostname.txt
2. If server pingable and online return hostname and ip in Green
3. If server timeout return hostname and ip in red
4. Loop back to the first server and ping again
Please help. Thank You !
$name=Get-content "c:\temp\hostname.txt"
foreach($name in $names) {
if(Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue) {
Write-Host "$name is up" -foregroundColor Green
$output+="$name is up,"+"'n"
}
else {
Write-Host "$name is up" -foregroundColor Red
$output+="$name is down,"+"'n"
}
}
Here's an old script I have called pinger that does something similar. It uses a hashtable, so it only reports changes in status, and sleeps a little at the end of the loop. It also beeps. If I output an object instead of text, there would be a delay until 2 objects were output.
# pinger.ps1
# example: pinger comp1
# pinger comp1,comp2,comp3
# $list = cat comps.txt; pinger $list
param ($hostnames)
$pingcmd = 'test-connection'
$sleeptime = 1
$sawup = #{}
$sawdown = #{}
foreach ($hostname in $hostnames) {
$sawup[$hostname] = $false
$sawdown[$hostname] = $false
}
while ($true) {
foreach ($hostname in $hostnames) {
if (& $pingcmd -count 1 $hostname -ea 0) {
if (! $sawup[$hostname]) {
echo "$([console]::beep(500,300))$hostname is up $(get-date)"
$sawup[$hostname] = $true
$sawdown[$hostname] = $false
}
} else {
if (! $sawdown[$hostname]) {
echo "$([console]::beep(500,300))$hostname is down $(get-date)"
$sawdown[$hostname] = $true
$sawup[$hostname] = $false
}
}
}
sleep $sleeptime
}
pinger microsoft.com,yahoo.com
microsoft.com is down 07/22/2021 09:55:15
yahoo.com is up 07/22/2021 09:55:15
If you want it to repeat in perpetuity, wrap the whole thing in a while($true){...} loop:
while($true){
$names = Get-content "c:\temp\hostname.txt"
foreach ($name in $names) {
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue) {
Write-Host "$name is up" -foregroundColor Green
$output += "$name is up," + "'n"
}
else {
Write-Host "$name is up" -foregroundColor Red
$output += "$name is down," + "'n"
}
}
}
If you want to show the resolved IP address, make sure you save the output from Test-Connection to a variable:
if($ping = Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
Write-Host "$name [IP: $($ping.ProtocolAddress)] is up" -ForegroundColor Green
$output += "$name [IP: $($ping.ProtocolAddress)] is up `n"
}

Script won't read the service line by line from txt file, it combline them instead

This is the script I have, I'm trying to have it read a list of services from the text but it instead combines them together. For example, I have two services(RTCRGS and TabletInputService), it will read it as
" RTCRGS TabletInputService " instead of one service at a time.
$Serverstxt = "C:\Scripts\Services\Server.txt"
$Servicestxt = "C:\Scripts\Services\Services.txt"
$ServiceList = get-content "$Servicestxt"
$ServerList = get-content "$Serverstxt"
#Initialize variables:
[string]$WaitForIt = ""
[string]$Verb = ""
[string]$Result = "FAILED"
#
foreach($Server in $ServerList){
foreach($Service in $ServiceList){
$svc = (get-service -computername $Server -name $Service)
Write-host "$Service on $Sever is $($svc.status)"
Switch ($svc.status) {
'Stopped' {
Write-host "Starting $Service..."
$Verb = "start"
$WaitForIt = 'Running'
$svc.Start()}
'Running' {
Write-host "Stopping $Service..."
$Verb = "stop"
$WaitForIt = 'Stopped'
$svc.Stop()}
Default {
Write-host "$Service is $($svc.status). Taking no action."}
}
if ($WaitForIt -ne "") {
Try {
# For some reason, we cannot use -ErrorAction after the next statement:
$svc.WaitForStatus($WaitForIt,'00:02:00')
} Catch {
Write-host "After waiting for 2 minutes, $Service failed to $Verb."
}
$svc = (get-service -computername $Server -name $Service)
if ($svc.status -eq $WaitForIt) {$Result = 'SUCCESS'}
Write-host "$Result`: $Service on $Server is $($svc.status)"
}
The foreach is correct, but you are using the entire list, instead of each element throughout your loop.
$SvrNames and $SvcNames are the contents of the files.
$SvrName and $SvcName are the current loop variables.
So you'd use
$svc = (get-service -computername $SvrName -name $SvcName)
and so on, inside the loop.
To make things a bit clearer, rename the variables
$Serverstxt = "C:\Scripts\Services\Server.txt"
$Servicestxt = "C:\Scripts\Services\Services.txt"
$ServiceList = get-content "$Servicestxt"
$ServerList = get-content "$Serverstxt"
foreach($Server in $ServerList){
foreach($Service in $ServiceList){
# . . .
$svc = (get-service -computername $Server -name $Service)
# . . .
}
}

PSObject bug with specific parameter

I have a powershell script that does some simple auditing of group membership on remote servers. The output is as expected except in the case of one group.
There are two parameters two this script, an OU to check in AD and a Group name to check. The OU parameter returns a list of server names, the Group name is the group to return members on. This all works fine except in one case, Backup Operators.
param([parameter(mandatory=$true)][string]$region,[string]$group)
### Debug flag for viewing output when running the script.
$DEBUG = 1
$self = $myinvocation.mycommand.name
function cmdopts {
if ($DEBUG) {
write-host "$self running with options"
write-host "Region: $region"
write-host "Group: $group"
}
}
### Function to handle custom messages to the user.
function usageRegion {
# Removed for brevity
}
function usageGroups {
# Removed for brevity
}
### Cleanup from previous run of the script.
function cleanup {
# Removed for brevity
}
#### Function to load powershell modules at runtime
function loadmod {
param([string]$name)
if ( -not(get-module -name $name)) {
if (get-module -listavailable| where-object { $_.name -eq $name}) {
import-module -name $name
$true
} else {
$false
}
} else {
$true
}
}
### Main()
cmdopts
#### Validate commandline options
if ( "cnr","nwr","swr","ner","ser","emr","lar","apr" -notcontains $region ) {
usageRegion
exit
}
if ( "Administrators","Backup Operators","Event Log Readers","Hyper-V Administrators","Power Users",
"Print Operators","Remote Desktop Users" -notcontains $group) {
usageGroups
exit
} else {
### We are creating three files for each run, previous runs need to be cleaned up before we start.
cleanup
### The ActiveDirectory module is a dependency for this script, we use it to get a list of machine names from AD for the OU.
if ( loadmod -name "activedirectory" ) {
write-host "Loading ActiveDirectory powershell module..." -foregroundcolor green
} else {
write-host "Sorry, you do not have the ActiveDirectory powershell module installed." -foregroundcolor yellow
write-host "The script cannot contnue." -foregroundcolor yellow
exit
}
### Get the list of servers from AD for the OU specified by the user.
get-adcomputer -f * -searchbase "ou=$region,ou=servers,dc=domain,dc=com" | select name | out-file "c:\scripts\ps\$region.srvtmp.txt" -append
### We need to fix some format issues with the file before continuing
# Removed for brevity, cleans up the file output from get-adcomputer and sets variable $srvlist
$srvlist = gc "c:\scripts\ps\$region.srvlist.txt"
# Store for the return
$store = #()
# Fix the group string for the filename
$filestring = $group
$filestring = $filestring.replace(' ', '')
$filestring = $filestring.tolower()
foreach ( $srv in $srvlist ) {
if ( $srv -eq "bustedserver" ) {
# This box hangs and does not tear down WMI when it can't complete, timeout does not work
write-host "skipping $srv"
} else {
$response = test-connection $srv -count 1 -quiet
### This does not work super well, might have to try a custom function
if ($response -eq $false ) {
write-host "$srv was offline during test" -foregroundcolor darkmagenta
} else {
write-host "Checking $group on " -nonewline; write-host $srv -foregroundcolor cyan
$groupinfo = new-object PSObject
$members = gwmi -computer $srv -query "SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$srv',Name='$group'`""
$members = $members | sort-object -unique
$count = 0
if ($members -ne $null) {
add-member -inputobject $groupinfo -membertype noteproperty -name "Server" -value $srv
add-member -inputobject $groupinfo -membertype noteproperty -name "Group" -value $group
foreach ($member in $members) {
$count += 1
$data = $member.partcomponent -split "\,"
$domain = ($data[0] -split "=")[1]
$name = ($data[1] -split "=")[1]
$line = ("$domain\$name").replace("""","")
add-member -inputobject $groupinfo -membertype noteproperty -name "Member $count" -value $line
}
}
if ($DEBUG) {
write-host $groupinfo
}
$store += $groupinfo
}
}
}
}
#$store | export-csv -path "$HOME\desktop\$region-$filestring-audit.csv" -notype
$store
If I run this script against a group like Administrators, or Remote Desktop Users the output looks like the following.
Server: SERVER1
Group: Remote Desktop Users
Member1: GroupName1
Member2: GroupName2
Member3: GroupName3
If I run this script against the group Backup Operators, I only get the first group even if there are many. In the debug write-host statement, it will show all of the groups. When printing the store, it only shows the first one. Even if there are two or more, it will alsways print...
Server: SERVER1
Group: Backup Operators
Member1: GroupName1
Any ideas on why this is broken specifically for 'Backup Operators' and not others would be appreciated.

Powershell 2.0 - Memory Leaking

So here's the scope of what I'm trying to do:
Get remote computer information for Windows computers in multiple sites and write the information found to the .Description property of each computer object in Active Directory. If the script can't connect to the remote machine, log that information into a text file and don't make any changes to the computer object that can't be connected to.
In order to time how long the script is taking to run, I have a second script that measures the execution time.
I have this setup as a scheduled task to run the second script (which calls the first) that is executed via a batch file on a Windows 7 Pro virtual machine.
My problem is I believe the script may be running into memory problems based on the information I see in my log. Any help on possible diagnosing the root cause would be appreciated to the extreme. Without further adieu, here's my code for both scripts as well as a sample of the strange log output.
Main Script (script 1):
set-location \\myscriptcomputer\c$\somefolder\PSScripts
enter code here`function Measure-Latest {
BEGIN { $latestlogon = $null }
PROCESS {
if (($_ -ne $null) -and (($latestlogon -eq $null) -or ($_ -gt $latestlogon))) {
$latestlogon = $_
}
}
END { $latestlogon }
}
Function CreateLog {
#Create a log file
$global:path = "C:\Somefolder\PSScripts\WriteComputerDescriptions"
$global:LogTime = Get-Date -Format "MM-dd-yyyy_hh-mm-ss"
$global:LogName = 'CompDescriptions'
$global:LogFile = 'C:\Somefolder\PSScripts\WriteComputerDescriptions\'+$LogName+$LogTime+'.txt'
Write-Host "Creating log file" -foregroundcolor yellow
if([IO.Directory]::Exists($global:path))
{
#Do Nothing
}
else
{
New-Item -ItemType directory -Path C:\Somefolder\PSScripts\WriteComputerDescriptions
}
cd C:\Somefolder\PSScripts\WriteComputerDescriptions
echo "WriteComputerDescriptions Script Log" >> $global:logfile
}
Function WriteDescription {
Write-Host "Gathering Computer information..." -foregroundcolor yellow
$UserWorkstations = get-qadcomputer -sizelimit 0 -includeallproperties -searchroot my.domain.com/MyUserWorkstations
$IPv4Regex = "^(\d{1,3}\.){3}\d{1,3}$"
foreach ($computerobject in $UserWorkstations) {
$computerIP = $NULL
$computerIP2 = $NULL
$computerIP3 = $NULL
$computerserial = $NULL
$computerserial2 = $NULL
$findlastuser = $NULL
$findlastuser2 = $NULL
$lastlogontime = $NULL
$findlastuserFname = $NULL
$findlastuserFname2 = $NULL
$findlastuserLname = $NULL
$findlastuserLname2 = $NULL
$fullname = $NULL
$userlogon = $NULL
$computerName = $computerobject.name
$oldcomputerdescription = $computerobject.description
Write-Host " "
Write-Host "Testing connection to $computerName ..."
$testConnection = test-connection -computername $computerName -count 2 -quiet
Write-Host "Connection is $testconnection"
if ($testConnection -eq $True) {
$Connect = $testConnection
#get IP address(es)
try {
$computerIP = get-wmiobject -class win32_networkadapterconfiguration -filter IPEnabled=TRUE -computername $computerName
$computerIP2 = $computerIP.ipaddress[0]
$computerIP3 = $computerIP.ipaddress[1]
Write-Host = $computerIP2
if ($computerIP3 -match $IPv4Regex){
Write-Host = $computerIP3
}
}
catch [system.exception]{
$connect = $False
Write-Host "Could not connect to $computerName. No IP collected."
}
#get computer serial
try {
$computerSerial = gwmi win32_bios -computername $computerName | select serialnumber
$computerserial2 = $computerSerial.serialnumber.tostring()
}
catch [system.exception]{
Write-Host "Could not get serial for $computerName."
$computerSerial = "Unavailable"
$computerSerial2 = "Unavailable"
}
#get username of currently logged in user
try {
$findlastUser = gwmi win32_computersystem -computer $computerName | select username
$findlastuser2 = ($findlastUser.username).replace("mydomain\","")
}
catch [system.exception]{
Write-Host "Could not get username of logged in user on $computerName"
$findlastUser = "Unavailable"
$findlastUser2 = "Unavailable"
}
#get last logon time of user
try {
if($findlastuser2 -ne $NULL -and $findlastuser2 -notlike "Unavailable") {
#ignore domain controllers in a datacenter due to connectivity stuff
$lastlogontime = get-qadcomputer -computerrole domaincontroller | where { $_.name -notmatch "-COLO"} | foreach {(get-qaduser -service $_.name -samaccountname $findlastuser2).LastLogon } | Measure-Latest
}
}
catch {
if ($lastlogontime -eq $NULL -and $findlastuser2 -eq $NULL){
Write-Host "Could not find a last logon time"
Write-Host "No username available to query"
$lastlogontime = "Unavailable"
}
if ($lastlogontime -eq $NULL -and $findlastuser2 -ne $NULL){
Write-Host "Could not find a last logon time for user $findlastuser"
$lastlogontime = "Unavailable"
}
}
#search AD for the user identified, select first name
try {
$findlastuserFname = get-qaduser $findlastuser2 | select firstname
$findlastuserFname2 = $findlastuserFname.firstname.tostring()
}
catch [system.exception]{
if ($findlastuserFname2 -eq $NULL) {
Write-Host "No first name for user found"
}
}
#search AD for the user identified, select last name
try {
$findlastuserLname = get-qaduser $findlastuser2 | select lastname
$findlastuserLname2 = $findlastuserLname.lastname
}
catch [system.exception] {
if ($findlastuserLname2 -eq $NULL) {
Write-Host "No last name for user found"
}
}
#join the first and last names together if both properties are available
if ($findlastuserFname2 -ne $NULL -and $findlastuserLname2 -ne $NULL){
$fullname = "$findlastuserFname2" + " $findlastuserLname2"
}
elseif ($findlastuserFname2 -eq $NULL -and $findlastuserLname -ne $NULL){
$fullname = $findlastuserLname2
}
elseif ($findlastuserFname2 -ne $NULL -and $findlastuserLname -eq $NULL){
$fullname = $findlastuserFname2
}
else {
$fullname = "Unavailable"
}
#Set the description data format
#With only 1 IPv4 Address
if ($computerIP3 -notmatch $IPv4Regex -or $computerIP3 -eq $NULL){
$newcomputerdescription = "$fullname | $computerIP2 | $computerSerial2 | $lastlogontime"
}
#With 2 IPv4 Addresses
if ($computerIP3 -match $IPv4Regex) {
$newcomputerdescription = "$fullname | $computerIP2, $computerIP3 | $computerSerial2 | $lastlogontime"
}
#If the description data is the same, leave it as it is
if ($newcomputerdescription -eq $oldcomputerdescription){
Write-Host " "
Write-Host "Information for $computerName has not" -foregroundcolor yellow
Write-Host "changed. No edits were made on this object." -foregroundcolor yellow
}
if ($newcomputerdescription -ne $oldcomputerdescription -and $Connect -eq $TRUE) {
set-qadcomputer -identity $computerName -Description $newcomputerdescription
Write-Host " "
Write-Host "Computer description updated for object $computerName" -foregroundcolor yellow
Write-Host "New host information:"
Write-Host "$newcomputerdescription"
}
}
else {
Write-Host "Could not connect to computer $computerName"
Write-Host "No changes made to description for $computerName"
$noconnecterror = "Could not connect to computer $computerName"
$noconnecterror | Out-File $global:logfile -Append -Force
}
}
Write-Host "Processing complete!"
}
CreateLog -erroraction silentlycontinue
WriteDescription -erroraction silentlycontinue
start-sleep -s 3
##END OF SCRIPT
Second Script:
set-location \\myscriptcomputer\c$\somefolder\PSScripts
Add-PSSnapin Quest.ActiveRoles.ADManagement -erroraction SilentlyContinue
$timeoutput = Measure-Command {\\myscriptcomputer\c$\Somefolder\PSScripts\WriteComputerDescriptions.ps1}
cd \\myscriptcomputer\c$\Somefolder\PSScripts\WriteComputerDescriptions
$scriptlog = get-childitem | sort creationtime | select -last 1
$logname = $scriptlog.name
Add-Content c:\somefolder\PSScripts\WriteComputerDescriptions\$logname "`nExecution Time: $timeoutput"
Write-Host "Script complete!"
Start-sleep -s 3
exit
In the results in my environments Active Directory, this works effectively for several hundred objects, but here's a sample of what I see in my log file:
Could not connect to computer computer391
Could not connect to computer computer392
Could not connect to computer computer393
Could not connect to computer computer394
䔊數畣楴湯吠浩㩥ㄠ㨱㘰㈺⸱㜵㤵㐰ഷ
The very last line with the garbled text is what made me think there's a memory-related issue perhaps. If I run my scripts against a container/OU with a much smaller amount of computers, the last line in my log is a time, which is what I would normally expect.
If any seasoned Powershell pros could offer some advice here, I'd really appreciate the help.
Thanks!
I don't know why my comments are not getting added. Anyways, let me just post it here.
In order to track the free memory, you just look at its the performance counter.
Here is the powershell command:
Get-Counter -Counter "\Memory\Available MBytes"