Trying to script profile deletion for remote workstations - powershell

I work in a large company and that is global and I would like to be able to run a script that will allow me to clean up old profile remotely. I found this script but it doesn't seem to do anything. Does anyone have thought to why. I am a novice in powershell so any help would be appreciated. Thanks in advance!
function Remove-ProfileWD {
<#
.SYNOPSIS
Interactive menu that allows a user to connect to a local or remote computer and remove a local profile.
.DESCRIPTION
Presents an interactive menu for user to first make a connection to a remote or local machine. After making connection to the machine,
the user is presented with all of the local profiles and then is asked to make a selection of which profile to delete. This is only valid
on Windows Vista OS and above for clients and Windows 2008 and above for server OS.
.EXAMPLE
Remove-ProfileWF
Description
-----------
Presents a text based menu for the user to interactively remove a local profile on local or remote machine.
#>
#Prompt for a computer to connect to
(
[Parameter(Mandatory=$true)]
$Computer = Read-Host "Please enter a computer name"
)
#Test network connection before making connection
If ($computer -ne $Env:Computername) {
If (!(Test-Connection -comp $computer -count 1 -quiet)) {
Write-Warning "$computer is not accessible, please try a different computer or verify it is powered on."
Break
}
}
Do {
#Gather all of the user profiles on computer
Try {
[array]$users = Get-WmiObject -ComputerName $computer Win32_UserProfile -filter "LocalPath Like 'C:\\Users\\%'" -ea stop
}
Catch {
Write-Warning "$($error[0]) "
Break
}
#Cache the number of users
$num_users = $users.count
Write-Host -ForegroundColor Green "User profiles on $($computer):"
Write-Host -ForegroundColor Green "User profile Last Use ate"
#Begin iterating through all of the accounts to display
For ($i=0;$i -lt $num_users; $i++) {
Write-Host -ForegroundColor Green "$($i): $(($users[$i].localpath).replace('C:\Users\','')) $(get-item \\$computer\C`$\users\$(($users[$i].localpath).replace('C:\Users\',''))| Foreach {$_.LastWriteTime}) "
}
Write-Host -ForegroundColor Green "q: Quit"
#Prompt for user to select a profile to remove from computer
Do {
$account = Read-Host "Select a number to delete local profile or 'q' to quit"
#Find out if user selected to quit, otherwise answer is an integer
If ($account -NotLike "q*") {
$account = $account -as [int]
}
}
#Ensure that the selection is a number and within the valid range
Until (($account -lt $num_users -AND $account -match "\d") -OR $account -Like "q*")
If ($account -Like "q*") {
Break
}
Write-Host -ForegroundColor Yellow "Deleting profile: $(($users[$account].localpath).replace('C:\Users\',''))"
#Remove the local profile
($users[$account]).Delete()
Write-Host -ForegroundColor Green "Profile: $(($users[$account].localpath).replace('C:\Users\','')) has been deleted"
#Configure yes choice
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes","Remove another profile."
#Configure no choice
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No","Quit profile removal"
#Determine Values for Choice
$choice = [System.Management.Automation.Host.ChoiceDescription[]] #($yes,$no)
#Determine Default Selection
[int]$default = 0
#Present choice option to user
$userchoice = $host.ui.PromptforChoice("","Remove Another Profile?",$choice,$default)
}
#If user selects No, then quit the script
Until ($userchoice -eq 1)
}

Related

Computer Type and Asset tag merge using PS

I wrote out a PowerShell Script to rename my computer, add it to specific OU's and join it to the domain. My question is we have two types of computer Desktop (DT) and Laptop (LT) and we give it an asset tag and I would like for it to ask me to select if it is a desktop or laptop and then ask for the asset tag number and then add the DT or LT in front of the asset tag number as the computer name (sorry if confused) example: DT01234 or LT01235. I will post my code below and bold the area that renames the computer. Any and all help would be greatly appreciated.
Write-Host "Select Desktop or Laptop [1-2] [Default 1]:
1. Desktop
2. Laptop"
$computertype = Read-Host
Write-Host "Please Enter Asset Tag"
$NewCompName = Read-Host
$renamecomputer = $true
if ($NewCompName -eq "" -or $NewCompName -eq $env:COMPUTERNAME) {$NewCompName = $env:COMPUTERNAME; $renamecomputer = $false}
Write-Host "Please enter your desired location [1-7] [Default 1]:
1. Test
2. Compliance Stations
3. Controls Stations
4. Processing Stations
5. QC Stations
6. Receiving Stations
7. Shipping Stations"
$ou = Read-Host
#$creds = Get-Credential
function Test-ADCrential{
[CmdletBinding()]
param(
[pscredential]$Credential
)
try {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
if(!$Credential) {
$Credential = Get-Credential -EA Stop
}
if($Credential.username.split("\").count -ne 2) {
throw "You haven't entered credentials in DOMAIN\USERNAME format. Given value : $($Credential.Username)"
}
$DomainName = $Credential.username.Split("\")[0]
$UserName = $Credential.username.Split("\")[1]
$Password = $Credential.GetNetworkCredential().Password
$PC = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName)
if($PC.ValidateCredentials($UserName,$Password)) {
Write-Verbose "Credential validation successful for $($Credential.Username)"
return $True
} else {
throw "Credential validation failed for $($Credential.Username)"
}
} catch {
Write-Verbose "Error occurred while performing credential validation. $_"
return $False
}
}
$mycreds = Get-Credential
Write-Host "Adding $NewCompName to the domain"
Read-Host "Press enter to change computer name"
if ($renamecomputer -eq $true)
{Rename-Computer -NewName $NewCompName -DomainCredential $mycreds}
Read-Host "Press enter to restart computer"
Restart-Computer
Looks like you want something like this
if ($NewCompName -eq "" -or $NewCompName -eq $env:COMPUTERNAME)
{
$NewCompName = $env:COMPUTERNAME; $renamecomputer = $false
}
elseif($NewCompName -inotmatch "^DT|^LT")
{
switch($computertype )
{
1{$NewCompNamePrefix = "DT"}
2{$NewCompNamePrefix = "LT"}
default{}
}
$NewCompName = $NewCompNamePrefix + $NewCompName
}
the -inotmatch is just making sure that if a $newcompname is inputted that already looks like DTwhatever or LTwhatever, it won't append an extra LT or DT
Unrelated (just me being a bit nitpicky):
Look into Read-host -Prompt and nest the prompts into do while loops, so you can validate that the input is what you're expecting.
something like this
$properChoices = #("a","b","c")
do
{
$choice = (Read-Host -Prompt "pick a, b, or c").ToLower()
switch($choice)
{
"a"{"You picked $choice"}
"b"{"You picked $choice"}
"c"{"You picked $choice"}
default{Write-Warning "Invalid Input"}
}
}
while($choice -notin $properChoices)

Delete any user profiles that haven't been logged into in the last 6 months using Cim and Powershell

I want to free up some C Drive space on my servers by removing user profiles that from C:\users who haven't logged into the server in the last 6 months. I connect to the servers using PowerShell Cim commands.
So far I have only found the Get-CimInstance -CimSession $CimSession -ClassName Win32_UserProfile command that will list users profiles but it doesn't list the last logon time for each user. Is there another command that can be used to list UserProfiles with LastLogon? Once I have that list I want to delete any profile that hasn't logged into the server in the last 6 months.
How to delete user profiles older than a specified number of days in Windows
This PowerShell script sample shows how to delete user profiles older than a specified number of days.
Example 1:
C:\Script\RemoveLocalUserProfile.ps1 -ListUnusedDay 1
Example 2:
C:\Script\RemoveLocalUserProfile.ps1 -DeleteUnusedDay 1 -ExcludedUsers “marry”
# Begin Script
If ($ProfileInfo -eq $null)
{
Write-Warning -Message "The item not found."
}
Else
{
Foreach ($RemoveProfile in $ProfileInfo)
{
#Prompt message
$Caption = "Remove Profile"
$Message = "Are you sure you want to remove profile '$($RemoveProfile.LocalPath)'?"
$Choices = [System.Management.Automation.Host.ChoiceDescription[]]`
#("&Yes", "&No")
[Int]$DefaultChoice = 1
$ChoiceRTN = $Host.UI.PromptForChoice($Caption, $Message, $Choices, $DefaultChoice)
Switch ($ChoiceRTN)
{
0
{
Try {$RemoveProfile.Delete(); Write-Host "Delete profile '$($RemoveProfile.LocalPath)' successfully."}
Catch {Write-Host "Delete profile failed." -ForegroundColor Red}
}
1 {break}
}
}
$ProfileInfo|Select-Object #{Expression = {$_.__SERVER}; Label = "ComputerName"}, `
#{Expression = {$_.ConvertToDateTime($_.LastUseTime)}; Label = "LastUseTime"},`
#{Name = "Action"; Expression = {If (Test-Path -Path $_.LocalPath)
{"Not Deleted"}
Else
{"Deleted"}
}
}
}
# End Script
Similar approaches can be see here:
https://community.spiceworks.com/how_to/124316-delete-user-profiles-with-powershell
https://www.business.com/articles/powershell-manage-user-profiles
Take care when deleting profiles, you don't want to hit machine special accounts. The Win32_UserProfile class has a LastUseTime property you can rely on.
$session = New-CimSession -ComputerName $cn
$gcimParams = #{
'CimSession' = $session
'ClassName' = 'Win32_UserProfile'
'Filter' = 'RefCount<1 and Special="false" and Loaded="false"'
}
$profileList = (Get-CimInstance #gcimParams).Where{$PSItem.LastUseTime -lt (Get-Date).AddMonths(-6)}
foreach ($user in $profileList)
{
$user | Remove-CimInstance -CimSession $session
}

PowerShell script fails to authenticate user when running Invoke-Command

I've recently created a little script that allows me to get the disk size and free space of 2 servers at each school site when I provide the script with the schools 4 digit site code.
First it pulls the information on the sites from a .csv file, and then uses that information to put together a string for the DC FQDN hostname, and the .10 server.
Then it requests the password for my elevated access account used to get the information on the disks.
I am having an issue where when the script creates the script block and then uses Invoke-Command and sends the script block to the servers, and provides back the PowerShell object with the information.
The error provided is as per below:
[{ServerName}] Connecting to remote server {ServerName} failed with the
following error message : WinRM cannot process the request. The following
error with errorcode 0x80090311 occurred while using Kerberos authentication:
There are currently no logon servers available to service the logon request.
Possible causes are:
-The user name or password specified are invalid.
-Kerberos is used when no authentication method and no user name are specified.
-Kerberos accepts domain user names, but not local user names.
-The Service Principal Name (SPN) for the remote computer name and port does
not exist.
-The client and remote computers are in different domains and there is no trust
between the two domains.
After checking for the above issues, try the following:
-Check the Event Viewer for events related to authentication.
-Change the authentication method; add the destination computer to the WinRM
TrustedHosts configuration setting or use HTTPS transport.
Note that computers in the TrustedHosts list might not be authenticated.
-For more information about WinRM configuration, run the following command:
winrm help config. For more information, see the about_Remote_Troubleshooting
Help topic.
+ CategoryInfo : OpenError: ({ServerName}:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : AuthenticationFailed,PSSessionStateBroken
Things I've tried:
Resetting my password
Altering the Authentication type to Basic
Getting others to try the same thing - some have the same issue, others do not
Other users on my workstations also have the same issue
I re-imaged my workstation and it worked for a bit, but then stopped again as it appeared to stop after the device installed software updates, so I'm in the middle of uninstalling those updates, however two of them won't allow me to uninstall, I assume they're forced installs by Microsoft and required to be installed (The uninstall button disappears when selected) - KB4019472 and KB4049065.
Device is running Windows 10 1607 v14393.1944, PowerShell v5.1.
There is a one-way trust between the domain I am in and the domains the DC1 and MS10 (.10) are in, the domains trust us, but we don't trust the domains.
The account I use is local admin on the device via a nested AD Group, across all domains.
I'm not very understanding of Kerberos, so any help would be amazing.
The script is below:
Note: I've had to remove some parts, so I've filled the area with what would be there (i.e. {String} where there would just be standard text, and {FQDNServerName} where there would be a FQDN server name written as text, or {Region} where I would have had the region written as text}).
$csvSchoolsLoc = "{FQDNServerName}\SharedReports$\SchoolsExport.csv"
$Schools = Import-Csv $csvSchoolsLoc -Delimiter "`t" -Header LocCode,SchoolName,SchoolAddress,SchoolPhoneNumber,SchoolFaxNumber,SchoolOfficerInCharge,DistrictCode,DistrictNumeric,RegionCode,RegionNumeric,LSD,WANLinkType,RouterName,RouterIP,RouterStatus,OneSchemaGraphUrl,OneSchemaSiteUrl,SCCMSiteID,SiteAdminNetwork,ProxyServerIP,PrimaryDcName,PrimaryDcIP,PrimaryDcOS,PrimaryDcVersion,PrimaryDcPatch,Style
#Gets the users credentials for their GBN ZZ account - this is used throughout the script for authentication
$username = "{Region}\zz-$env:USERNAME"
$mycreds = Get-Credential -UserName $username -Message "Enter your password for {region}\zz-$env:USERNAME"
Clear-Host
Write-Host "What is the schools 4 digit site code?" -ForegroundColor Magenta
$Global:SiteCode = Read-Host
Function Main {
Clear-Host
$SchoolName = $schools | Where-Object {$_.LocCode -eq $SiteCode} | ForEach-Object SchoolName
$Region = $schools | Where-Object {$_.LocCode -eq $SiteCode} | ForEach-Object RegionCode
Write-Host "Getting details for: " -ForegroundColor Gray -NoNewline; Write-Host "$SchoolName - $SiteCode - ($Region)"-ForegroundColor Yellow
$DC1 = "{String}$($Region)$($SiteCode)001.$region.{String}.{String}.{String}"
$MS10 = "{String}$($Region)$($SiteCode)010.$region.{String}.{String}.{String}"
if (Test-Connection -ComputerName $DC1 -Count 2 -Delay 1 -Quiet) {
$DC1Run = $true
} else {
$DC1Run = $false
}
if (Test-Connection -ComputerName $MS10 -Count 2 -Delay 1 -Quiet) {
$MS10Run = $true
} else {
$MS10Run = $false
}
$ScriptBlock = {
$DiskCTotal = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='C:'" -Impersonation 3 | ForEach-Object {$_.size / 1GB}
$DiskCFree = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='C:'" -Impersonation 3 | ForEach-Object {$_.freespace / 1GB}
$DiskZTotal = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='Z:'" -Impersonation 3 | ForEach-Object {$_.size / 1GB}
$DiskZFree = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='Z:'" -Impersonation 3 | ForEach-Object {$_.freespace / 1GB}
return #{
'ZFreeSpace' = $DiskZFree
'CFreeSpace' = $DiskCFree
'ZTotalSize' = $DiskZTotal
'CTotalSize' = $DiskCTotal
}
}
if (($DC1Run -eq $true) -and ($MS10Run -eq $true)) {
$ServerDC1 = Invoke-Command -ComputerName $DC1 -Credential $mycreds -ScriptBlock $ScriptBlock
$ServerMS10 = Invoke-Command -ComputerName $MS10 -Credential $mycreds -ScriptBlock $ScriptBlock
#Clear-Host
Write-Host -ForegroundColor Yellow "$SchoolName - $SiteCode - ($Region)"
Write-Host -ForegroundColor Cyan "Server $DC1 - Domain Controller"
Write-Host "$([math]::round($ServerDC1.CFreeSpace,2)) GB free on C Drive (Total Size $([math]::round($ServerDC1.CTotalSize,2)) GB)"
Write-Host "$([math]::round($ServerDC1.ZFreeSpace,2)) GB free on Z Drive (Total Size $([math]::round($ServerDC1.ZTotalSize,2)) GB)"
Write-Host ""
Write-Host -ForegroundColor Cyan "Server $MS10 - Distribution Point"
Write-Host "$([math]::round($ServerMS10.CFreeSpace,2)) GB free on C Drive (Total Size $([math]::round($ServerMS10.CTotalSize,2)) GB)"
Write-Host "$([math]::round($ServerMS10.ZFreeSpace,2)) GB free on Z Drive (Total Size $([math]::round($ServerMS10.ZTotalSize,2)) GB)"
} else {
#Clear-Host
Write-Host -ForegroundColor Yellow "$SchoolName - $SiteCode - ($Region)"
Write-Host -ForegroundColor Cyan "Server $DC1 - Domain Controller"
if ($DC1Run) {
Write-Host "DC1 connection status is running" -ForegroundColor Green
} else {
Write-Host "DC1 connection status is down" -ForegroundColor Red
}
Write-Host ""
Write-Host -ForegroundColor Cyan "Server $MS10 - Distribution Point"
if ($MS10Run) {
Write-Host "MS10 connection status is running" -ForegroundColor Green
} else {
Write-Host "MS10 connection status is down" -ForegroundColor Red
if ($DC1Run -eq $true) {
$RDP = Read-Host -Prompt "Would you like to RDP to $DC1 'Y'"
if ($RDP -eq "Y") {
Start-Process -FilePath "$env:windir\System32\mstsc.exe" -ArgumentList "/v:$DC1" -Wait -WindowStyle Maximized
}
}
}
}
Write-Host ""
Write-Host "What is the next schools 4 digit site code? -or- Press Enter to retry the above site again" -ForegroundColor Magenta
$Entry = Read-Host
if ($Entry -eq "") {
# Do nothing
} else {
$Global:SiteCode = $Entry
}
}
$x = 0
do {
Main
} until ($x -gt 0)
EDIT: The uninstall of the software updates did not fix the issue, so unless it's something to do with those 2 updates that I can't uninstall it doesn't appear to be Software Updates.
It turns out that the domains I am trying to reach were not in my TrustedHosts config for WinRM.
By using the following command, I was able to add the domains (of which I have numerous) to the TrustedHosts using the '*' wildcard.
NOTE: I have replaced part of the domain with {String} where it would normally have part of the domain name for confidentiality reasons.
winrm set winrm/config/client #{TrustedHosts="<local>,*.{string}.edu.au"}

Upload local users outlook client signature to Office 365 OWA (remotely) - On-prem exchange to Exchange online

We are in the process of migrating user mailboxes from on-prem exchange to office 365. Since we are only doing few users at a time, our requirements were to sync local outlook client signatures into office 365 OWA portal. So user sees the same signature on their OWA portal.
Has anyone done this?
I have figured it out. So I have created a script to help our migration team to upload local outlook client signature to office 365.
You need to have admin access to user PC and exchange online admin privileges such as Global Administrator.
Here is the code:
write-host "`n`n`n`n"
#initializing variables
$username = $null
$computer = $null
$path = $null
$number = $null
$i = 0
$select = $null
$signs = $null
$chkerr = $false
$session = $null
#Run a continous loop
do {
#if powershell is not connected to office 365 then run the connection code below
if ($session.State -ne "opened") {
Write-Host "`nChecking connection to Office 365: " -NoNewline
Write-Host "Inactive!" -ForegroundColor white -BackgroundColor red
Write-Host "`nConnecting to Office 365 account...`n"
#Get user office 365 credentials
$UserCredential = Get-Credential
#create session
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
#start the session to office 365
Import-PSSession $Session
Sleep 4
}
#if the session to 365 is success then run the code below
if ($?) {
Write-Host "`nChecking connection to Office 365: " -NoNewline
Write-Host "Active!" -ForegroundColor Black -BackgroundColor green
#Start a loop to prompt for username and machinename
do {
#this variable is used below and set as false if there is an error. Also, shows a message that user need to re-enter the information
$chkerr = $false
if ($chkerr) {write-host "`nPlease try again..." -BackgroundColor yellow -ForegroundColor black}
#store username and machine name
$username = Read-Host "`n`nPlease enter user name"
$computer = Read-Host "Please enter computer name"
#if machine is not pingable then show an error
if (!(Test-Connection $computer -Count 1) ) {Write-Host "`nError: Computer is offline or name is incorrect" -ForegroundColor white -BackgroundColor red; $chkerr = $true}
else {
#if machine is pingable then check if signature direcotry exists
if (!(Test-Path "\\$computer\c$\Users\$username\AppData\Roaming\Microsoft\Signatures\")) { Write-Host "`nError: Outlook signature directory does not exists `nOR `nUsername is incorrect!" -ForegroundColor white -BackgroundColor red;$chkerr = $true}
}
#show error msg if username or machine name is left blank
if ($username -eq $null -or !$username -or $computer -eq $null -OR !$computer) {Write-Host "`nError: Username or computer name cannot be left empty" -ForegroundColor white -BackgroundColor red;$chkerr = $true }
#repeatedly ask for username and machine name until user gets it right or moves on to next username and machine name
} while ($chkerr)
#set the path to signature directory
$path = "\\$computer\c$\Users\$username\AppData\Roaming\Microsoft\Signatures"
#get a list of signature files and store them in a variable
$number = Get-ChildItem "$path\*.htm"
#check if there are any signature files at all
if ($number -or $number -ne $null) {
#start a loop
do {
#if there are multiple signature files found then show them on screen
Write-Host "`nSelect one for following file:`n"
for ([int]$i=0;$i -lt $number.count; $i++) {
Write-Host ("$i" +' --> ' + $number[$i].name)
}
#if there is more than one signature file then prompt user to select one
if ($number.count -gt 1) {
$select = Read-Host "`nPlease enter your selection"
#show error if user enters a number that is of selection
if ([int]$select+1 -gt [int]$number.Count) {Write-Host "`nError: Please enter a valid selections!" -ForegroundColor white -BackgroundColor red}
} else {
#if only one signature file is found then do not prompt and select that one file as default
$select = "0"
Write-Host "`nOnly one file found, selecting default file -->" $number[$select].name -ForegroundColor black -BackgroundColor yellow
}
#repeat the loop until user selects atleast one signature file
} while ([int]$select+1 -gt [int]$number.Count)
#show the selected signature file
Write-Host "You have selected following signature file:" $number[$select].fullname
sleep 3
#grab the html file and store it into a variable
Write-Host "`nImporting signatures into powershell"
$signs = Get-Content $number[$select].fullname
#show error if the signature import fails
if (!$?) {Write-Host "`nError: Unable to import signature into powershell!" -ForegroundColor white -BackgroundColor red}
else {
Write-Host "`nSuccess!" -ForegroundColor Black -BackgroundColor green
Write-Host "`nImporting signatures to Office 365 account of $username"
#import the signature into users OWA and set it to be used during replies, forwards and message creations.
Get-Mailbox $username | Set-MailboxMessageConfiguration -SignatureHtml $signs -AutoAddSignature:$true -AutoAddSignatureOnReply:$true -AutoAddSignatureOnMobile:$true
if (!$?) {Write-Host "`nError: Unable to import signature into Office 365 account!" -ForegroundColor white -BackgroundColor red}
else {Write-Host "`nSuccess!" -ForegroundColor Black -BackgroundColor green}
}
}
else {
#show error if no signature files are found
Write-Host "`nError: No signature files found!" -ForegroundColor white -BackgroundColor red
}
}
else {
# show error if we are unable to connect to office 365 account.
Write-Host "`nError: Connection to office 365 is required for this script to work" -ForegroundColor white -BackgroundColor red
}
} while ($true)
Successful test of Output:
Making connection:
After successfull connection:
After entering username and password:
After making the selection:
OWA view:
Error checking against user input:
Entering wrong office 365 credentials:
Entering correct computer name but wrong username:
Entering correct username but wrong computer name:
Entering wrong selecting that is out of the provided set of options:
You can also modify the script to grab input from an excel sheet.

Powershell to find out disconnected RDP session and log off at the same time

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
}
}
}