I have a PowerShell script for Microsoft Teams that looks at a list of Users in a .csv file and returns a few properties of the user, then exports it to another .csv with the results. This works fine except the progress bar is stuck displaying the last entry until I hit enter. How do you make the last entry disappear and only show "Done Executing Script" and return to C:>
Im trying to add the time at the end of the line when each of the lines from the CSV is executed, Please help me with that. Example below:
Processing User Number: 1 [Currently Processing: User1#abc.com] - Policies applied in "5s"
Processing User Number: 2 [Currently Processing: User2#abc.com] - Policies applied in "2s"
Processing User Number: 3 [Currently Processing: User3#abc.com] - Policies applied in "10s"
Processing User Number: 4 [Currently Processing: User4#abc.com] - Policies applied in "8s"
Processing User Number: 5 [Currently Processing: User5#abc.com] - Policies applied in "25s"
CODE FOR POWERSHELL SCRIPT:
Write-host "Connecting to Microsoft Teams....." -ForegroundColor Yellow
Connect-MicrosoftTeams
Write-host "Successfully connected to Microsoft Teams" -ForegroundColor Green
$CsvFilePath = Import-CSV -Path "C:\Users\\Desktop\MicrosoftTeams\precheckbatch2.csv"
$Count=0
$results = foreach ($UPN in $CsvFilePath) {
$user = $UPN.UserPrincipalName
Get-CsOnlineUser $user | Select-Object Displ*, UserPri*, IsSipEnabled, TeamsUpgradeE*,
Enterprise*, #{l="FeatureTypes";e={$_.FeatureTypes -join "; "}}, #{l="AssignedPlan";e=
{$_.AssignedPlan -join "; "}}
Write-Progress -Activity "Processing User: $Count" "Currently Processing: $user"
$Count++
}
$results | Export-Csv C:\Users\\Desktop\MicrosoftTeams\precheckdataresults1.csv
Write-host "Done!"
This seems to be an issue of VSCode only. When I run this simplified script in the console, the progress bar disappears automatically, when the script ends. When I run it from VSCode, the progress bar stays visible, reproducing the issue.
foreach( $count in 1..3 ) {
$user = "User$count"
Write-Progress -Activity "Processing User: $Count" "Currently Processing: $user"
Start-Sleep 1
}
To fix the problem for VSCode, add a Write-Progress -Completed line, to explicitly remove the progress bar:
foreach( $count in 1..3 ) {
$user = "User$count"
Write-Progress -Activity "Processing User: $Count" "Currently Processing: $user"
Start-Sleep 1
}
Write-Progress -Activity "Processing User: $Count" -Completed
Related
this show 0%:
this show 4%:
the realted script:
$iWrapper = [hashtable]::Synchronized(#{ i = 0 })
$srcfile = "C:\Users\wnune\OneDrive\Escritorio\imagenes\cardlist.txt"
$urls = Get-Content $srcfile
$lines = 0
switch -File $srcfile { default { ++$lines } }
Write-Host "Total Urls to process: $lines "
Write-Progress -Activity "Downloading files" -Status "In progress" -PercentComplete $i;
$urls | ForEach-Object -Parallel {
try {
$url = $_
$filename = Split-Path $url -Leaf
$destination = "C:\Users\wnune\OneDrive\Escritorio\imagenes\$filename"
$ProgressPreference = 'SilentlyContinue'
$response = Invoke-WebRequest -Uri $url -ErrorAction SilentlyContinue
if ($response.StatusCode -ne 200) {
Write-Warning "============================================="
Write-Warning "Url $url return Error. "
continue
}
if (Test-Path $destination) {
Write-Warning "============================================="
Write-Warning "File Exist in Destination: $filename "
continue
}
$job = Start-BitsTransfer -Source $url -Destination $destination -Asynchronous
while (($job | Get-BitsTransfer).JobState -eq "Transferring" -or ($job | Get-BitsTransfer).JobState -eq "Connecting")
{
Start-Sleep -m 250
}
Switch(($job | Get-BitsTransfer).JobState)
{
"Transferred" {
Complete-BitsTransfer -BitsJob $job
}
"Error" {
$job | Format-List
}
}
}
catch
{
Write-Warning "============================================="
Write-Warning "There was an error Downloading"
Write-Warning "url: $url"
Write-Warning "file: $filename"
Write-Warning "Exception Message:"
Write-Warning "$($_.Exception.Message)"
}
$j = ++($using:iWrapper).i
$k = $using:lines
$percent = [int](100 * $j / $k)
Write-Host "PercentCalculated: $percent"
Write-Host "Progress bar not Show the %"
Write-Progress -Activity "Downloading files " -Status " In progress $percent" -PercentComplete $percent
}
Write-Progress -Activity "Downloading files" -Status "Completed" -Completed
If I am passing in -PercentComplete $percent which is an integer why does the progress bar not receive it correctly?
I have verified that the script and the environment are correctly configured but I cannot validate because the progress bar is not seen correctly.
Note:
A potential future enhancement has been proposed in GitHub issue #13433, suggesting adding parameter(s) such as -ShowProgressBar to ForEach-Object -Parallel so that it would automatically show a progress bar based on how many parallel threads have completed so far.
Leaving the discussion about whether Start-BitsTransfer alone is sufficient aside:
At least as of PowerShell v7.3.1, it seemingly is possible to call Write-Progress from inside threads created by ForEach-Object -Parallel, based on a running counter of how many threads have exited so far.
However, there are two challenges:
You cannot directly update a counter variable in the caller's runspace (thread), you can only refer to an object that is an instance of a .NET reference type in the caller's runspace...
...and modifying such an object, e.g. a hashtable must be done in a thread-safe manner, such as via System.Threading.Monitor.
Note that I don't know whether calling Write-Progress from different threads is officially supported, but it seems to work in practice, at least when the call is made in a thread-safe manner, as below.
Bug alert, as of PowerShell 7.3.1: Irrespective of whether you use ForEach-Object -Parallel or not, If you call Write-Progress too quickly in succession, only the first in such a sequence of calls takes effect:
See GitHub issue #18848
As a workaround, the code below inserts a Start-Sleep -Milliseconds 200 call after each Write-Progress call.
Not only should this not be necessary, it slows down overall execution, because threads then take longer to exit, which affects not only a given thread, but overall execution time, because it delays when threads "give up their slot" in the context of thread throttling (5 threads are allowed to run concurrently by default; use -ThrottleLimit to change that.
A simple proof of concept:
# Sample pipeline input
$urls = 1..100 | ForEach-Object { "foo$_" }
# Helper hashtable to keep a running count of completed threads.
$completedCount = #{ Value = 0 }
$urls |
ForEach-Object -parallel { # Process the input objects in parallel threads.
# Simulate thread activity of varying duration.
Start-Sleep -Milliseconds (Get-Random -Min 0 -max 3000)
# Produce output.
$_
# Update the count of completed threads in a thread-safe manner
# and update the progress display.
[System.Threading.Monitor]::Enter($using:completedCount) # lock access
($using:completedCount).Value++
# Calculate the percentage completed.
[int] $percentComplete = (($using:completedCount).Value / ($using:urls).Count) * 100
# Update the progress display, *before* releasing the lock.
Write-Progress -Activity Test -Status "$percentComplete% complete" -PercentComplete $percentComplete
# !! Workaround for the bug above - should *not* be needed.
Start-Sleep -Milliseconds 200
[System.Threading.Monitor]::Exit($using:completedCount) # release lock
}
An alternative approach in which the calling thread centrally tracks the progress of all parallel threads:
Doing so requires adding the -AsJob switch to ForEach-Object -Parallel, which, instead of the synchronous execution that happens by default, starts a (thread-based) background job, and returns a [System.Management.Automation.PSTasks.PSTaskJob] instance that represents all parallel threads as PowerShell (thread) jobs in the .ChildJobs property.
A simple proof of concept:
# Sample pipeline input
$urls = 1..100 | ForEach-Object { "foo$_" }
Write-Progress -Activity "Downloading files" -Status "Initializing..."
# Launch the parallel threads *as a background (thread) job*.
$job =
$urls |
ForEach-Object -AsJob -Parallel {
# Simulate thread activity of varying duration.
Start-Sleep -Milliseconds (Get-Random -Min 0 -max 3000)
$_ # Sample output: pass the URL through
}
# Monitor and report the progress of the thread job's
# child jobs, each of which tracks a parallel thread.
do {
# Sleep a bit to allow the threads to run - adjust as desired.
Start-Sleep -Seconds 1
# Determine how many jobs have completed so far.
$completedJobsCount =
$job.ChildJobs.Where({ $_.State -notin 'NotStarted', 'Running' }).Count
# Relay any pending output from the child jobs.
$job | Receive-Job
# Update the progress display.
[int] $percent = ($completedJobsCount / $job.ChildJobs.Count) * 100
Write-Progress -Activity "Downloading files" -Status "$percent% complete" -PercentComplete $percent
} while ($completedJobsCount -lt $job.ChildJobs.Count)
# Clean up the job.
$job | Remove-Job
While this is more work and less efficient due to the polling loop, it has two advantages:
The script blocks running in the parallel threads need not be burdened with progress-reporting code.
The polling loop affords the opportunity to perform other foreground activity while the parallel threads are running in the background.
The bug discussed above needn't be worked around, assuming your Start-Sleep interval in the polling loop is at least 200 msecs.
I use this script to run some jobs:
#========================================================================
#Get User stats with ADInfo < --- need to see if this can be converted to native PowerShell
$XMLConfig = Get-ChildItem c:\ADInfo\XML_Config_Files
$Jobs = #()
#loop through each config file and run ADAudit - there is one file per domain
foreach ($config in $XMLConfig) {
write-host "Starting a background job for $($config.name)"
$Jobs += start-job -ScriptBlock {c:\ADInfoCmd.exe /config $args[0] } -ArgumentList $config.fullname.tostring()
}
write-host "`nJobs are running"
#=======================================================================
#End of script
Some jobs take much longer than others and I would like to be able to send a user friendly update to the console when any one of the started jobs are still running to show the script hasn't stalled.
I tried something like this
do{
write-host "working..."
}
while (wait-job $jobs)
but it writes once and then waits for the jobs to finish
I tried this
$joblist = get-job $jobs | where state -eq running
while ($joblist){
write-host "working..."
}
but I get an error for all the jobs get-job : The command cannot find the job because the job name System.Management.Automation.PSRemotingJob was not found and $joblist is never assigned a value.
Is there a way to do this?
I had passed the entire PS Object to get-job. It worked when I passed only the job ID
This is what I ended up using and provides enough feedback to the user to demonstrate the script is still working.
write-host "`nJobs are running" -ForegroundColor Yellow -NoNewline
$RunningJobs = Get-Job $jobs.id | where state -eq running
while($runningjobs){
write-host "." -NoNewline
$RunningJobs = Get-Job $jobs.id | where state -eq running
Start-Sleep -Seconds 3
}
Write-host "Background Jobs Complete"
Write-Host "Script Ends" -ForegroundColor Yellow
I'm working on a process to start up some services across remote servers, however server 2 can't start up until a message is found in server1 logs, server 3 can't start until same message in server 2, etc.
My question is, is it possible to read the file in a "loop" and not proceed with my initial loop until that message is found (and then move forward)? I was thinking I could do below, however while it does recognize that the string in the log file is found, it just repeats that it found it until the timer built in finishes and then moves forward.
So, the process would look like "read this file, if string is found, move forward. If string is not found, wait 30 seconds and rescan the file. If found, move forward, if not found, wait an additional 30 seconds and rescan (I'm fine with a continuous repeat) < do this until string is found.
Example: enter image description here
Any advice would be much appreciated as I think I might be approaching this from the wrong angle...
-- I left out the majority of the script prior to this script and only included the If/Else statement as this is where it checks the files.
$SEL = Select-String -Path \\$Server\$RootDir\folder\anotherfolder\A-Log-File.log -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
}
else
{
Write-Host **** Waiting 60 seconds for cache to build ****
[int]$Time = 60
$Lenght = $Time / 100
For ($Time; $Time -gt 0; $Time--) {
$min = [int](([string]($Time/60)).split('.')[0])
$text = " " + $min + " minutes " + ($Time % 60) + " seconds left"
Write-Progress -Activity "Waiting for Started Message" -Status $Text -PercentComplete ($Time / $Lenght)
Start-Sleep 1
$SEL = Select-String -Path \\$Server\$RootDir\folder\anotherfolder\A-Log-File.log -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
}
else
{
Write-Host **** A-Log-File.log Log does NOT contain a started message **** -ForegroundColor Red -BackgroundColor Yellow
Write-Host **** Investigate further or increase the int-time time on Line 54 to 180 seconds **** -ForegroundColor Red -BackgroundColor Yellow ##This part goes away once action can be taken based on reading contents of the file
}
}
}
You don't need a loop, just use Get-Content -Wait:
$null = Get-Content "\\$Server\$RootDir\folder\anotherfolder\A-Log-File.log" -Wait |Where-Object { $_ -match 'Switching to status: STARTED' } |Select -First 1
Get-Content -Wait will continue outputting new lines written to the file until it's interrupted - luckily we can use Select -First 1 to stop the pipeline once we observe the string
You said "loop" so why aren't you using a loop?
while ($true) {
# (re)try
$SEL = Select-String -Path "\\$Server\$RootDir\folder\anotherfolder\A-Log-File.log" -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
# exit the loop
break;
}
# wait
Write-Host "**** Waiting 60 seconds for cache to build ****"
Start-Sleep 1
}
#marsze, I meant to come back to this and update. I ended up using your suggestion and also wanted to add in that I was able to build in a timer. I wanted to provide the example for anyone that stumbles upon this.
while ($true) {
# (re)try
$SEL = Select-String -Path "\\$Server\$SomeRoot\A.Folder\b.folder2\ThisFileToParse.log" -Pattern "Message Here"
if ($SEL -ne $null)
{
Write-Host "FOUND: Message Here" -ForegroundColor Yellow -BackgroundColor DarkGreen
# exit the loop
break;
}
# wait
Write-Host "**** File Does NOT contain a Message Here message ****" -ForegroundColor Red -BackgroundColor Yellow
Write-Host "**** Waiting 30 seconds, will loop until message is found ****" -ForegroundColor Red -BackgroundColor Yellow
#Start-Sleep 1
[int]$Time = 30
$Lenght = $Time / 100
For ($Time; $Time -gt 0; $Time--) {
$min = [int](([string]($Time/60)).split('.')[0])
$text = " " + $min + " minutes " + ($Time % 60) + " seconds left"
Write-Progress -Activity "Waiting for ThisFileToParse.log to update showing Message Here... " -Status $Text -PercentComplete ($Time / $Lenght)
Start-Sleep 1
}
I'm using PowerShell 5.1 and I am trying to determine why Write-Information messages do not show in the transcript log created by Start-Transcript unless I set $InformationPreference to SilentlyContinue. I want to both display the messages in the console and have them written to the log file.
I looked here:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-5.1#informationpreference
Then I decided to create this script to test what gets written and when. See the preference section right underneath Testing explicit behavior with transcripts -------------
Clear-Host
$ErrorActionPreference = "Stop"
try {
Write-Host "Starting transcript"
Start-Transcript -Force -Path "$PSScriptRoot\default.txt"
<#
In PowerShell 5.1 the default behavior is as follows:
$DebugPreference = SilentlyContinue
$InformationPreference = SilentlyContinue
$ProgressPreference = Continue
$VerbosePreference = SilentlyContinue
See the following for more information:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-5.1
#>
# I am not testing Write-Output as I am not worried about programmatic/pipeline stuff, just contextual messages for end-users or logging
Write-Host "`nTesting default behavior with transcripts --------------------------------`n"
# Setting these just in case I launch this script in a session where a previous script might have modified the preference variables
$DebugPreference = "SilentlyContinue"
$InformationPreference = "SilentlyContinue"
$ProgressPreference = "Continue"
$VerbosePreference = "SilentlyContinue"
Write-Host "Calling Write-Host"
Write-Debug "Calling Write-Debug"
Write-Error "Calling Write-Error" -ErrorAction "Continue"
Write-Information "Calling Write-Information"
Write-Progress "Calling Write-Progress"
Write-Verbose "Calling Write-Verbose"
Stop-Transcript
Start-Transcript -Force -Path "$PSScriptRoot\everything_continue.txt"
Write-Host "`nTesting explicit behavior with transcripts --------------------------------`n"
# Turn everything on
$DebugPreference = "Continue"
$InformationPreference = "Continue" # Setting this to SilentlyContinue makes it show up in the log but not the console. Setting this to 'Continue' makes it show up in the console but not the log.
$ProgressPreference = "Continue"
$VerbosePreference = "Continue"
Write-Host "Calling Write-Host"
Write-Debug "Calling Write-Debug"
Write-Error "Calling Write-Error" -ErrorAction "Continue"
Write-Information "Calling Write-Information"
Write-Progress "Calling Write-Progress"
Write-Verbose "Calling Write-Verbose"
Stop-Transcript
Write-Host "`nResults -------------------------------------------------------------------`n"
# See what actually gets captured and written by the transcriber
$messageTypes = #("Write-Debug", "Write-Error", "Write-Host", "Write-Information", "Write-Verbose")
Write-Host "Default" -ForegroundColor Cyan
$lines = Get-Content "$PSScriptRoot\default.txt"
foreach ($message in $messageTypes) {
if ($lines -like "*Calling $message*") {
Write-Host " $message PRESENT" -ForegroundColor Green
}
else {
Write-Host " $message MISSING" -ForegroundColor Red
}
}
Write-Host "Everything Continue" -ForegroundColor Cyan
$lines = Get-Content "$PSScriptRoot\everything_continue.txt"
foreach ($message in $messageTypes) {
if ($lines -like "*Calling $message*") {
Write-Host " $message PRESENT" -ForegroundColor Green
}
else {
Write-Host " $message MISSING" -ForegroundColor Red
}
}
}
catch {
Write-Host "----------------------------------------------------------------------------------------------------"
Write-Host $_.Exception
Write-Host $_.ScriptStackTrace
Write-Host "----------------------------------------------------------------------------------------------------"
try { Stop-Transcript } catch { }
throw $_
}
What you're seeing is a bug in Windows PowerShell (as of v5.1.17134.590) that has been fixed in PowerShell Core (as of at least v6.1.0 - though other transcript-related problems persist; see this GitHub issue).
I encourage you to report it in the Windows PowerShell UserVoice forum (note that the PowerShell GitHub-repo issues forum is only for errors also present in PowerShell Core).
Here's how to verify if the bug is present in your PowerShell version:
Create a script with the code below and run it:
'--- Direct output'
$null = Start-Transcript ($tempFile = [io.path]::GetTempFileName())
# Note that 'SilentlyContinue' is also the default value.
$InformationPreference = 'SilentlyContinue'
# Produces no output.
Write-Information '1-information'
# Prints '2-Information' to the console.
Write-Information '2-information' -InformationAction Continue
$null = Stop-Transcript
'--- Write-Information output transcribed:'
Select-String '-information' $tempFile | Select-Object -ExpandProperty Line
Remove-Item $tempFile
With the bug present (Windows PowerShell), you'll see:
--- Direct output
2-information
--- Write-Information output transcribed:
INFO: 1-information
That is, the opposite of the intended behavior occurred: the transcript logged the call it should'nt have (because it produced no output), and it didn't log the one it should have.
Additionally, the logged output is prefixed with INFO: , which is an inconsistency that has also been fixed in PowerShell Core.
There is no full workaround, except that you can use Write-Host calls in cases where do you want the output logged in the transcript - but such calls will be logged unconditionally, irrespective of the value of preference variable $InformationPreference (while Write-Host formally provides an -InformationAction common parameter, it is ignored).
With the bug fixed (PowerShell Core), you'll see:
--- Direct output
2-information
--- Write-Information output transcribed:
2-information
The transcript is now consistent with the direct output.
Is there a script that can log out my disconnected RDP session from any server? This is causing a lot of pain and constant ad account lockouts.
Any help would be awesome.
I have got the answer and I am writing this answer to help someone in need as I had to figure this out myself. I created a script using online resources to find out disconnected RDP sessions on all Windows Server in my AD environment. I run a query on each Windows Server and create a CSV formatted list, I then use that list to log out my ID from those servers, so I don't have any disconnected sessions.
I did this to make sure my AD account doesn't get locked out due to some disconnected RDP sessions when its time to change my password.
You are free to modify this script as per your need.
Script Code is below:
param (
#get current logged on username
[string]$UserName = $env:USERNAME
)
# Import the Active Directory module for the Get-ADComputer CmdLet
Import-Module ActiveDirectory
# Query Active Directory for enabled windows servers computer accounts and sort by name
$Servers = Get-ADComputer -Filter {(OperatingSystem -like "*windows*server*") -and (Enabled -eq "True")} | Sort Name
# Initiating variables
$SessionList = $NULL
$queryResults = $NULL
$SError = $null
$SDown = $null
$z = 0
# Get total number of servers
$count = $Servers.count
# Start looping through each server at a time
ForEach ($Server in $Servers) {
# initiate counter for showing progress
$z = $z + 1
$ServerName = $Server.Name
# Start writing progress
Write-Progress -Activity "Processing Server: $z out of $count servers." -Status " Progress" -PercentComplete ($z/$Servers.count*100)
# check if server is pingable before running the query on the server
if (Test-Connection $Server.Name -Count 1 -Quiet) {
Write-Host "`n`n$ServerName is online!" -BackgroundColor Green -ForegroundColor Black
Write-Host ("`nQuerying Server: `"$ServerName`" for disconnected sessions under UserName: `"" + $UserName.ToUpper() + "`"...") -BackgroundColor Gray -ForegroundColor Black
# Store results in array
[array]$queryResults += (
# Query server for specific username
query user $UserName /server:$ServerName |
foreach {
# Look for lines with Disc string to filter out active sessions
if ($_ -match "Disc") {
# format the output in CSV by replacing more than 2 spaces with a comman
write-output ("`n$ServerName," + (($_.trim() -replace ' {2,}', ',')))
}
}
)
}
# If server is not pingable show error message
else {
# Make list of server that are down.
[array]$SDown += ($ServerName)
Write-Host "`nError: Unable to connect to $ServerName!" -BackgroundColor red -ForegroundColor white
Write-Host "Either the $ServerName is down or check for firewall settings on server $ServerName!" -BackgroundColor Yellow -ForegroundColor black
}
}
# If there are some non pingable server then display the list
if ($SDown -ne $null -and $SDown) {
Write-Host "`nScript was unable to connect to the following server:" -ForegroundColor White -BackgroundColor Red
$SDown
}
# Check if any disconnected session are stored in the array
if ($queryResults -ne $null -and $queryResults) {
# Convert the CSV fromat to table format with headers
$QueryResultsCSV = $queryResults | ConvertFrom-Csv -Delimiter "," -Header "ServerName","UserName","SessionID","CurrentState","IdealTime","LogonTime"
# Show the results on console
$QueryResultsCSV |ft -AutoSize
# Go through each Disconnected session stored in the array
$QueryResultsCSV | foreach {
# Grabb session ID and ServerName
$Sessionl = $_.SessionID
$Serverl = $_.ServerName
# Show message on the console
Write-Host "`nLogging off"$_.username"from $serverl..." -ForegroundColor black -BackgroundColor Gray
sleep 2
# Logout user using session ID
logoff $Sessionl /server:$Serverl /v
}
}
else {
# if array is empty display message that no session were found
Write-Host `n`n`n`n("*" * $LineSize)
Write-Host "You are all good! No ghost sessions found!" -BackgroundColor Green -ForegroundColor Black
Write-Host ("*" * $LineSize)
}
# Pause at the end so you can capture the output
$null = Read-Host "`n`nScript execution finished, press enter to exit!"
Screenshots:
When the script is running on through all server, shows you online and offline servers:
List of servers that Script was unable to connect:
The script lists the servers where it found disconnected RDP sessions.
When script start to log your disconnected sessions off and it pauses at the end.
Thank you for your sample code. I have created a simplified code to logoff all disconnected users in the same server
$hostname = hostname
if (Test-Connection -ComputerName $hostname -Quiet -Count 1){
$result = query session /server:$hostname
$rows = $result -split "`n"
foreach ($row in $rows) {
if ($row -NotMatch "services|console" -and $row -match "Disc") {
$sessionusername = $row.Substring(19,20).Trim()
$sessionid = $row.Substring(39,9).Trim()
Write-Output "Logging Off RDP Disconnected Sessions User $sessionusername"#, $session[2], $session[3]"
logoff $sessionid /server:$hostname
}
}
}