Unable to parse variable into the ForEach ($Server in $Servers) loop? - powershell

I am trying to correct the below script to query the server logon session using Qwinsta.
The goal of this server is to show who is currently logged on.
The script main section:
$queryResults = (qwinsta /server:$ServerName | Select-Object -Skip 1 |
ForEach-Object { (($_.trim() -replace "\s+", ",")) } | convertfrom-csv
-ErrorAction Stop)
is working fine:
PS C:\WINDOWS\system32> $queryResults
services 0 Disc
-------- - ----
console 1 Conn
Administrator 3 Disc
rdp-tcp 65536 Listen
Script:
$HtmlHead = #"
<style>
body {
font-family: Arial;
}
table {
width: 100%;
border-collapse: collapse;
border: 1px solid;
}
th {
background-color: green;
border: 1px solid;
padding: 1px;
}
td {
border: 1px solid;
padding: 1px;
}
</style>
"#
$Today = Get-Date -Format 'F'
$SessionList = "`n`nRDP Session List - " + $Today + "`n`n"
$CurrentSN = 0
# Get a list of servers from Active Directory
write-progress -activity "Getting list of servers from Active Directory" -status "... please wait ..."
$Servers = Get-ADComputer -Filter { Enabled -eq $True -and OperatingSystem -like "*Server*" } -SearchBase "OU=Servers,DC=Company,DC=com," | Where-Object { Test-Connection $_.Name -Count 1 -Quiet } | Select-Object -ExpandProperty Name
$NumberOfServers = $Servers.Count
# Iterate through the retrieved list to check RDP sessions on each machine
ForEach ($Server in $Servers) {
$ServerName = $Server.Name
Write-Host "Processing $($Server.Name) ..." -ForegroundColor Yellow
Write-progress -activity "Checking RDP Sessions" -status "Querying $ServerName" -percentcomplete (($CurrentSN / $NumberOfServers) * 100)
# Run qwinsta and grab the output
try {
$queryResults = (qwinsta /server:$ServerName | Select-Object -Skip 1 | ForEach-Object { (($_.trim() -replace "\s+", ",")) } | convertfrom-csv -ErrorAction Stop)
# get session info from the instance
ForEach ($QueryResult in $QueryResults) {
$RDPUser = $($QueryResult.substring(19, 22)).trim()
$SessionType = $($QueryResult.substring(1, 18)).trim()
$SessionID = $($QueryResult.substring(41, 5)).trim()
$ReturnedCurrentState = $($QueryResult.substring(48, 8)).trim()
$RDPUser = $QueryResult.USERNAME
$SessionType = $QueryResult.SESSIONNAME
$SessionID = $QueryResult.ID
$ReturnedCurrentState = $QueryResult.State
If ($ReturnedCurrentState -eq $null) { $CurrentState = "Disconnected" } Else { $CurrentState = "Active" }
# filter out the irrelevant information
If (($RDPUser -ne $null) -and ($SessionType -ne "console") -and ($SessionType -ne "services") -and ($SessionType -ne "rdp-tcp") -and ($RDPUser -ne "65536")) {
$SessionList = $SessionList + "`n" + $ServerName + " logged in by " + $RDPUser + " on " + $SessionType + ", session id $SessionID $CurrentState"
}
}
}
catch {
$SessionList = $SessionList + "`n Unable to query " + $ServerName
write-host "Unable to query $ServerName!" -foregroundcolor Red
}
$CurrentSN++
}
# Send the output the screen.
$SessionList + "`n`n"
$sendMailArgs = #{
From = "$env:USERNAME#$env:userdnsdomain"
To = 'SOC#domain.com'
SmtpServer = 'SMTP.domain.com'
Priority = 'High'
BodyAsHtml = $true
Body = ($SessionList | ConvertTo-Html -Head $HtmlHead) -join "`r`n"
Subject = "$($SessionList.Count) Logged On users from $($NumberOfServers) online servers as at $($Today)"
}
Send-MailMessage #sendMailArgs
The problem is that the script loop is always going into:
write-host "Unable to query $ServerName!" -foregroundcolor Red
The email result is always like:
*
424
What is that mean?

Most likely the Substring() method throws an exception because the startIndex parameter values of 41 and 48 seem large. If $QueryResult string ends up shorter, you will get that exception and immediately move to the catch statement.
I would recommend separating error handling for querying the server and building the session list.
UPDATE:
Here's how you can separate error handling in your script example. Basically you need to be aware of methods or cmdlets which can theoretically throw an exception with the input parameters you provide and cover those cases.
ForEach ($Server in $Servers) {
$ServerName = $Server.Name
Write-Host "Processing $ServerName ..." -ForegroundColor Yellow
Write-progress -activity "Checking RDP Sessions" -status "Querying $ServerName" -percentcomplete (($CurrentSN / $NumberOfServers) * 100)
# Run qwinsta and grab the output
try {
$queryResults = (qwinsta /server:$ServerName | Select-Object -Skip 1 | ForEach-Object { (($_.trim() -replace "\s+", ",")) } | convertfrom-csv )
}
catch {
# Error handling for failed server connection here
$SessionList += "`n Unable to query " + $ServerName
write-host "Unable to query $ServerName!" -foregroundcolor Red
continue # Skip to next $Server
}
# get session info from the instance
ForEach ($QueryResult in $QueryResults) {
try {
# Do something about this
$RDPUser = $($QueryResult.substring(19, 22)).trim()
$SessionType = $($QueryResult.substring(1, 18)).trim()
$SessionID = $($QueryResult.substring(41, 5)).trim()
$ReturnedCurrentState = $($QueryResult.substring(48, 8)).trim()
$RDPUser = $QueryResult.USERNAME
$SessionType = $QueryResult.SESSIONNAME
$SessionID = $QueryResult.ID
$ReturnedCurrentState = $QueryResult.State
}
catch {
# Insert your error handling here
# Write-Host "Failed to process query result from $ServerName!" -foregroundcolor Red
Write-Host $Error[0].Exception # If you want to get the exception message. A shorter way to do it below
# Write-Host $_
}
If ($null -eq $ReturnedCurrentState) { $CurrentState = "Disconnected" } Else { $CurrentState = "Active" }
# filter out the irrelevant information
If (($null -ne $RDPUser) -and ($SessionType -ne "console") -and ($SessionType -ne "services") -and ($SessionType -ne "rdp-tcp") -and ($RDPUser -ne "65536")) {
$SessionList = $SessionList + "`n" + $ServerName + " logged in by " + $RDPUser + " on " + $SessionType + ", session id $SessionID $CurrentState"
}
}
$CurrentSN++
}
I am genuinely concerned about the code block where you assign values to variables like $RDPUser, $SessionType, etc. I'll give you the benefit of the doubt that I don't know your environment, but the logic looks very strange. If $QueryResult is an object with several properties, why substring? The object doesn't have this method. If it's just a long string with values at certain character positions, why try to get its property values like $QueryResult.ID - it doesn't have any.

Related

How to display the variable result as table in email body or console screen?

The below script to get the logon users and send as email was working great but only on the console output only.
I am trying to get the result as a table so the result in the console and the email body will be like:
Server, ConnectionType, User, ID, State
PRDSVR16, rdp-tcp#44, SVC-SQL, 4, Active
PRDSVR10, rdp-tcp#27, Admin.domain, 6, Disc
SVR25-VM,console,domain.admin,8,Active
Open in new window
This is the script:
$Today = Get-Date -Format 'F'
$SessionList = "`n`nRDP Session List - " + $Today + "`n`n"
$CurrentSN = 0
# Get a list of servers from Active Directory
write-progress -activity "Getting list of servers from Active Directory" -status "... please wait ..."
$Servers = (Get-ADComputer -Filter { Enabled -eq $True -and OperatingSystem -like "*Server*" } -Properties OperatingSystem -SearchBase "OU=Data Center,DC=Company,DC=com") |
Where-Object { Test-Connection $_.Name -Count 1 -Quiet } |
Select-Object -ExpandProperty Name
$NumberOfServers = $Servers.Count
# Iterate through the retrieved list to check RDP sessions on each machine
ForEach ($Server in $Servers)
{
Write-Host "Processing $Server ..." -ForegroundColor Yellow
Write-progress -activity "Checking RDP Sessions" -status "Querying $Server" -percentcomplete (($CurrentSN / $NumberOfServers) * 100)
try
{
$SessionList += qwinsta /server:$Server |
Select-Object -Skip 1 |
% {
[PSCustomObject] #{
Type = $_.Substring(1, 18).Trim()
User = $_.Substring(19, 20).Trim()
ID = $_.Substring(41, 5).Trim()
State = $_.Substring(48, 6).Trim()
}
} |
? { $_.Type -notin 'console', 'services', 'rdp-tcp' -and $_.User -ne $null -and $_.User -ne 65536 } |
% {
"`n$Server logged in by $($_.User) on $($_.Type), session id $($_.ID) $($_.state)"
}
}
catch
{
$SessionList += "`n Unable to query " + $Server
write-host "Unable to query $Server! `n $($Error[0].Exception)" -foregroundcolor Red
}
$CurrentSN++
}
# Send the output the screen.
$SessionList + "`n`n"
$sendMailArgs = #{
From = "$env:USERNAME#$env:userdnsdomain"
To = 'SOC#domain.com'
SmtpServer = 'SMTP.domain.com'
Priority = 'High'
Body = $SessionList | Select-Object #{ N = 'Server'; E = { $Server } },
#{ N = 'User'; E = { $_.User } },
#{ N = 'LogonType'; E = { $_.Type } },
#{ N = 'ID'; E = { $_.ID } },
#{ N = 'State'; E = { $_.State } }
Subject = "$($SessionList.Count) Logged On users from $($NumberOfServers) online servers as at $($Today)"
}
Send-MailMessage #sendMailArgs
Rendering collected information in different places is way easier if you keep strict separation between data and presentation (or formatting) of said data.
For the $SessionList for example, that means doing less than what you're currently trying to do inside the loop:
$ErrorList = #()
$SessionList = foreach($server in $servers){
try{
qwinsta /server:$Server |Select-Object -Skip 1 |ForEach-Object {
[PSCustomObject] #{
Server = $server
Type = $_.Substring(1, 18).Trim()
User = $_.Substring(19, 20).Trim()
ID = $_.Substring(41, 5).Trim()
State = $_.Substring(48, 6).Trim()
}
} |Where-Object { $_.Type -notin 'console', 'services', 'rdp-tcp' -and $_.User -ne $null -and $_.User -ne 65536 }
}
catch{
$ErrorList += [pscustomobject]#{
Server = $server
ErrorRecord = $_
}
}
}
Notice how I don't construct any strings - I just create the custom objects, filter them - and then leave them as-is.
Now it becomes much easier to format the data as desired for different output media:
# For console output, simply pipe to Format-Table
$SessionList |Format-Table
if($ErrorList.Count -gt 0){
Write-Warning "The following servers had errors, please inspect"
$ErrorList |Format-Table
}
# For email output we can use `ConvertTo-Html`
$Body = $SessionList |ConvertTo-Html -As Table -Fragment
if($ErrorList.Count -gt 0){
$ErrorTable = $ErrorList |ConvertTo-Html -As Table -Fragment
$Body = $Body,$ErrorTable -join '<br />'
}
$sendMailArgs = #{
# ...
Body = ConvertTo-Html -Body $Body -Title "Session list"
BodyAsHtml = $true
# ...
}

How to execute command on remote server using Invoke-Command as Domain Administrator?

I am trying to modify a script that used to work when executed on each of the servers manually to generate the DFS replication then send an email, so it can be executed once from one location then remotely connect to each and every server.
Invoke-Command:
$ComputerName = Get-DfsrMember | Select-Object -ExpandProperty ComputerName -Unique | Sort-Object
$ComputerName | ForEach-Object {
Try {
$session = New-PSSession -ComputerName $_ -ErrorAction Stop
Invoke-Command -ErrorAction Stop -Session $session -ScriptBlock {
## Script body starts here....
Write-Host "Processing on server $($_) `n" -ForegroundColor Yellow
Param (
[String[]]$ReplicationGroupList = ("")
)
....
}
}
Catch [System.UnauthorizedAccessException] {
Write-Warning "You do not have the proper access to this system!"
Break
}
Catch [System.Runtime.InteropServices.COMException] {
Write-Warning "Communications Exception occurred!"
Break
}
}
This is the error I have received:
The term 'Param' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:7 char:9
+ Invoke-Command -ErrorAction Stop -Session $session -ScriptBlo ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Param:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName : PRDDC01-VM
The actual Powershell script that used to be working, but must be manually executed in each of the server RDP session:
Param (
[String[]]$ReplicationGroupList = ("")
)
$RGroups = Get-WmiObject -Namespace "root\MicrosoftDFS" -Query "SELECT * FROM DfsrReplicationGroupConfig"
#If replication groups specified, use only those.
if ($ReplicationGroupList) {
$SelectedRGroups = #()
foreach ($ReplicationGroup IN $ReplicationGroupList) {
$SelectedRGroups += $rgroups | Where-Object { $_.ReplicationGroupName -eq $ReplicationGroup }
}
if ($SelectedRGroups.count -eq 0) {
Write-Error "None of the group names specified were found, exiting"
exit
}
else {
$RGroups = $SelectedRGroups
}
}
$ComputerName = $ENV:ComputerName
$Succ = 0
$Warn = 0
$Err = 0
Start-Transcript -path "$([Environment]::GetFolderPath("Desktop"))\dfsr1.txt"
foreach ($Group in $RGroups) {
$RGFoldersWMIQ = "SELECT * FROM DfsrReplicatedFolderConfig WHERE ReplicationGroupGUID='" + $Group.ReplicationGroupGUID + "'"
$RGFolders = Get-WmiObject -Namespace "root\MicrosoftDFS" -Query $RGFoldersWMIQ
$RGConnectionsWMIQ = "SELECT * FROM DfsrConnectionConfig WHERE ReplicationGroupGUID='" + $Group.ReplicationGroupGUID + "'"
$RGConnections = Get-WmiObject -Namespace "root\MicrosoftDFS" -Query $RGConnectionsWMIQ
foreach ($Connection in $RGConnections) {
$ConnectionName = $Connection.PartnerName#.Trim()
if ($Connection.Enabled -eq $True) {
if (((New-Object System.Net.NetworkInformation.ping).send("$ConnectionName")).Status -eq "Success") {
foreach ($Folder in $RGFolders) {
$RGName = $Group.ReplicationGroupName
$RFName = $Folder.ReplicatedFolderName
if ($Connection.Inbound -eq $True) {
$SendingMember = $ConnectionName
$ReceivingMember = $ComputerName
$Direction = "inbound"
}
else {
$SendingMember = $ComputerName
$ReceivingMember = $ConnectionName
$Direction = "outbound"
}
$BLCommand = "dfsrdiag Backlog /RGName:'" + $RGName + "' /RFName:'" + $RFName + "' /SendingMember:" + $SendingMember + " /ReceivingMember:" + $ReceivingMember
$Backlog = Invoke-Expression -Command $BLCommand
$BackLogFilecount = 0
foreach ($item in $Backlog) {
if ($item -ilike "*Backlog File count*") {
$BacklogFileCount = [int]$Item.Split(":")[1].Trim()
}
}
if ($BacklogFileCount -eq 0) {
$Color = "white"
$Succ = $Succ + 1
}
elseif ($BacklogFilecount -lt 10) {
$Color = "yellow"
$Warn = $Warn + 1
}
else {
$Color = "red"
$Err = $Err + 1
}
Write-Output "$BacklogFileCount files in backlog $SendingMember->$ReceivingMember for $RGName"
} # Closing iterate through all folders
} # Closing If replies to ping
} # Closing If Connection enabled
} # Closing iteration through all connections
} # Closing iteration through all groups
Write-Output "$Succ successful, $Warn warnings and $Err errors from $($Succ+$Warn+$Err) replications."
Stop-Transcript
$file = "$([Environment]::GetFolderPath("Desktop"))\dfsr1.txt"
get-content $file |
Select-Object -Skip 18 |
set-content "$file-temp"
Move-Item "$file-temp" $file -Force
$emailrecipients = "boss#it.com";
$emailbody = Get-Content -Path "$([Environment]::GetFolderPath("Desktop"))\dfsr1.txt" -Raw
Send-MailMessage -to $emailrecipients -smtpserver smtp.domain.COM -from "$env:COMPUTERNAME#$env:userdnsdomain" -subject "DFSR Report for $(get-date -format dd/MM/yyyy) from $env:COMPUTERNAME" -body $emailbody;
Remove-Item "$([Environment]::GetFolderPath("Desktop"))\dfsr1.txt"
Theo is correct, the Param() code needs to be first thing in the script block. You can find more information about script blocks here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks?view=powershell-7

Unable to query remote server with Qwinsta command using Powershell?

The script below is currently configured to perform Qwinsta from multiple online servers, and then send the result as email.
$Today = Get-Date -Format 'F'
$SessionList = "`n`nRDP Session List - " + $Today + "`n`n"
$CurrentSN = 0
# Get a list of servers from Active Directory
write-progress -activity "Getting list of servers from Active Directory" -status "... please wait ..."
$Servers = Get-ADComputer -Filter { Enabled -eq $True -and OperatingSystem -like "*Server*" } -SearchBase "OU=servers,dc=dwlab02,dc=local" | Where-Object { Test-Connection $_.Name -Count 1 -Quiet } | Select-Object -ExpandProperty Name
$NumberOfServers = $Servers.Count
# Iterate through the retrieved list to check RDP sessions on each machine
ForEach ($Server in $Servers) {
$ServerName = $Server.Name
Write-progress -activity "Checking RDP Sessions" -status "Querying $ServerName" -percentcomplete (($CurrentSN / $NumberOfServers) * 100)
# Run qwinsta and grab the output
try {
$queryResults = (qwinsta /server:$ServerName | Select-Object -Skip 1 | ForEach-Object { (($_.trim() -replace "\s+", ",")) } | convertfrom-csv -ErrorAction Stop)
# get session info from the instance
ForEach ($QueryResult in $QueryResults) {
$RDPUser = $($QueryResult.substring(19, 22)).trim()
$SessionType = $($QueryResult.substring(1, 18)).trim()
$SessionID = $($QueryResult.substring(41, 5)).trim()
$ReturnedCurrentState = $($QueryResult.substring(48, 8)).trim()
$RDPUser = $QueryResult.USERNAME
$SessionType = $QueryResult.SESSIONNAME
$SessionID = $QueryResult.ID
$ReturnedCurrentState = $QueryResult.State
If ($ReturnedCurrentState -eq $null) { $CurrentState = "Disconnected" } Else { $CurrentState = "Active" }
# filter out the irrelevant information
If (($RDPUser -ne $NULL) -and ($SessionType -ne "console") -and ($SessionType -ne "services") -and ($SessionType -ne "rdp-tcp") -and ($RDPUser -ne "65536")) {
$SessionList = $SessionList + "`n" + $ServerName + " logged in by " + $RDPUser + " on " + $SessionType + ", session id $SessionID $CurrentState"
}
}
}
catch {
$SessionList = $SessionList + "`n Unable to query " + $ServerName
write-host "Unable to query $ServerName!" -foregroundcolor Red
}
$CurrentSN++
}
# Send the output the screen.
$SessionList + "`n`n"
$sendMailArgs = #{
From = 'sender#domain.com'
To = 'recipient#domain.com'
Subject = "$($SessionList.Count) Logged On users from $($NumberOfServers) online servers as at $($Today)"
SmtpServer = 'smtp.office365.com'
Port = 587
UseSsl = $true
Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, (ConvertTo-SecureString -String $Password)
}
Send-MailMessage #sendMailArgs -Body $SessionList
However, the result is always a blank email with the below content:
RDP Session List - Monday, 17 August 2020 12:28:19 PM
Unable to query Unable to query Unable to query Unable to query
Unable to query Unable to query Unable to query Unable to query
Unable to query
How can I fix the above script?

Nested If Statement first -notlike condition

I am trying to use nested IF statements to check a AD-Computer Attribute and if it is not either VM or VeraCrypt, use Manage-BDE to check the Bitlocker status.
When I use just Get-Adcomputer <name> -Properties extensionAttribute1 | select extensionAttribute1 it returns the expected result VM.
When I do
$Attribute = Get-Adcomputer <name> -Properties extensionAttribute1 | select extensionAttribute1
Write-Host $Attribute
I get #{extensionAttribute1=VM}
I have tried many using -Notlike "*VM* but this did not work.
Here is my code.
# -----------------------
# Define global variables
# -----------------------
$ScanCount = 0
$UnprotectedCount = 0
$Date = Get-Date -Format yyyyMMdd
$StartDate = Get-Date -Format HH:mm:ss
$ReportFile = "C:\BitLocker_Status\BitLocker_Status.csv"
$BackupFile = "C:\BitLocker_Status\BitLocker_Status_Backup$BackupCopy'_$Date.csv"
$OutputArray = #()
$BackupCopy = 0
$SearchBase = "DC=Merallis,DC=net"
# ----------------------------------------
# Checking backing up output file
# ----------------------------------------
if (Test-Path $ReportFile) {
Rename-Item -Path $ReportFile -newname $BackupFile
$BackupCopy = $BackupCopy + 1
}
Else {
$BackupCopy = 0
}
# ----------------------------------------
# Build array from list of computers in AD
# ----------------------------------------
Write-Host -NoNewline "- Gathering a list of Computers from Active Directory..."
Try
{
$Computers = Get-ADComputer -SearchBase $SearchBase -Filter * -Properties Name,Description | Sort-Object
Write-Host -ForegroundColor Green "Success"
}
Catch
{
Write-Host -ForegroundColor Red "Failed ($_)"
}
# -------------------------------------------------
# Use the Manage-BDE command to query each computer
# -------------------------------------------------
Write-Host "- Querying BitLocker status..."
ForEach ($Computer in $Computers)
{
$Name = $Computer.Name
$Description = $Computer.Description
$BDE = Manage-BDE -ComputerName $Computer.Name -Status C:
# -------------------------------------------------
# Use the Get-ADComputer command to query the current attribute for each computer
# -------------------------------------------------
$Attribute = Get-ADComputer $Name -Properties extensionAttribute1 | select extensionAttribute1
Write-Host -nonewline " - $Name ..."
If ($Attribute -notlike "*VM*" -or $Attribute -notlike "*VeraCrypt*") {
If ($BDE -Like "*An error occurred while connecting*") {Write-Host -ForegroundColor Yellow "Unable to connect"; $Status = "Unable to connect"; Set-ADComputer -identity $Name -Replace #{"ExtensionAttribute1"=$Status}}
ElseIf ($BDE -Like "*Protection On*") {Write-Host -ForegroundColor Green "Protected"; $Status = "Protected"; Set-ADComputer -identity $Name -Replace #{"ExtensionAttribute1"=$Status}}
ElseIf ($BDE -Like "*Protection Off*") {Write-Host -ForegroundColor Red $Status; $Status = "Not protected"; $UnprotectedCount = $UnprotectedCount + 1 ; Set-ADComputer -identity $Name -Replace #{"ExtensionAttribute1"=$Status}}
ElseIf ($BDE -Like "*The term 'manage-bde'*") {Write-Host -ForegroundColor Red "error manage-bd!"; $Status = "Not protected"; $UnprotectedCount = $UnprotectedCount + 1}
Else {Set-ADComputer -identity $Name -Replace #{"ExtensionAttribute1"="Unknown"}}
}
$ScanCount = $ScanCount +1
$OutputArray += New-Object PsObject -Property #{
'Computer name' = $Computer.Name
'Description' = $Computer.Description
'BitLocker status' = $Status
}
}
# -----------------
# Generate a report
# -----------------
Write-Host -NoNewline "- Saving report..."
Try
{
$OutputArray | Export-CSV -NoTypeInformation $ReportFile
Write-Host -ForegroundColor Green "Success"
}
Catch
{
Write-Host -ForegroundColor Red "Failed ($_)"
}
# -----------------------------------------
# Display completion message and statistics
# -----------------------------------------
$EndDate = Get-Date -Format HH:mm:ss
$Duration = New-TimeSpan $StartDate $EndDate
Write-Host ""
Write-Host "-------------------------------------------------------------"
Write-Host "Script complete. Start time: $StartDate, End time: $EndDate"
Write-Host "Scanned $ScanCount computers. $UnprotectedCount are unprotected!"
Write-Host "-------------------------------------------------------------"man
Write-Host ""
If you want to filter objects from list based on some property value then you can use Where-Object
i.e. Website list ( without "default" website Name )
$YOURLIST = Get-Website
$YOURLIST | Where-Object { $_.Name -notlike "*Default*" }

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"