Argument Passed into ScriptBlock Doesn't Work on Last Iteration - powershell

I'm using the following script to run through all of the servers in a specified Active Directory OU and log out the specified user. This runs perfectly well for the first 35 servers but always errors out on the very last server it iterates through. The error is:
Program 'quser.exe' failed to run: Object reference not set to an instance of an object. At line:6 char:24
+ $result = (quser $userAccount)
+ ~~~~~~~~~~~~~~~~~~.
At \\path\to\script.ps1:79 char:5
+ invoke-command -ComputerName $server -ScriptBlock $scriptBlock -A ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Program 'quser....~~~~~~~~~~~~~~.:String) [], RuntimeException
+ FullyQualifiedErrorId : Program 'quser.exe' failed to run: Object reference not set to an instance of an object.At line:6 char:24
+ $result = (quser $userAccount)
+ ~~~~~~~~~~~~~~~~~~.
My reading of the error is that it thinks $userAccount has no value. Is that correct and, if so, can anyone point at what I'm missing? Thank you in advance!
Write-Host " "
Write-Host "This script will log out all active sessions for the specified user. Proceed with caution." -ForegroundColor Black -BackgroundColor Yellow
Write-Host " "
$servers = [System.Collections.ArrayList]::new()
foreach ($server in (Get-ADComputer -SearchBase "OU=FictionalDepartment,DC=Company,DC=net" -Filter "OperatingSystem -like 'Windows Server*'" -Properties Name | select -ExpandProperty name))
{
$servers.add($server) | Out-Null
}
$servers.Sort()
$userAccount = Read-Host "Enter account to log out"
Write-Host " "
foreach ($server in $servers)
{
$scriptBlock = {
param($userAccount)
$ErrorActionPreference = 'Stop'
try
{
$result = (quser $userAccount)
$session = ((quser $userAccount)[1] -split "\s+")[2]
logoff $session
Write-Host " The user was logged into session #$session. They have been LOGGED OFF." -ForegroundColor Yellow
}
catch
{
if ($_.Exception.Message -match 'No user exists')
{
Write-Host " User is not logged in." -ForegroundColor Green
}
else
{
throw $_.Exception.Message
}
}
}
Write-Host "$server"
Invoke-Command -ComputerName $server -ScriptBlock $scriptBlock -ArgumentList $userAccount
$session = $null
}

Based on feedback, I modified the script so that it no longer uses Invoke-Command, but parses the active sessions by running quser <user> /SERVER:<server> as follows:
Write-Host "Will log specified user out of all servers..." -ForegroundColor Black -BackgroundColor Yellow
Write-Host " "
$servers = [System.Collections.ArrayList]::new()
foreach ($server in (Get-ADComputer -SearchBase "OU=Fictional,DC=Company,DC=net" -Filter "OperatingSystem -like 'Windows Server*'" -Properties Name | select -ExpandProperty name))
{
$servers.add($server) | Out-Null
}
$servers.Sort()
$userAccount = Read-Host "Enter account to scan for"
Write-Host ""
foreach ($server in $servers)
{
Write-Host "$server"
$ErrorActionPreference = 'Stop'
try
{
$session = ((quser $userAccount /SERVER:$server)[1] -split "\s+")[3]
logoff /SERVER:$server $session
Write-Host " The user was logged into session #$session. They have been LOGGED OFF." -ForegroundColor Yellow
}
catch
{
if ($_.Exception.Message -match 'No user exists')
{
Write-Host " User is not logged in." -ForegroundColor Green
}
}
}

Related

Subexpression printing out same strings? Powershell

I have this code which deletes User Profiles off a remote machine. The removal of profiles work just fine but, the Aesthetic of doing so doesn't. What do i mean?
I'm passing the user display names to an index and making a selection out of it, and that works fine in regards to assigning the proper names to the appropriate Index Number its associated to in C:\users.
The next line of code is it grabbing the selections i made, and running through them displaying the same name i did for the index, and then it goes off to delete the CIM instance.
So my question is, why is it not passing the subexpression $userinfo1 that is already made and not putting it into the next block of code, for example, the following works as in grabbing the proper Display Name and assigning it to the proper Number:
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
but, the next block doesn't associate the display name (that was captured in $userinfo1) with the number i select and it just continues to display the first display name with the rest of the profiles its reiterating through:
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "}
Hopefully this makes sense, and if anyone can point me in the right direction id greatly appreciate it!
Heres the rest of the script, please feel free to use it as it does work for deleting the actual profile off the system and not just the files.
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &PFL-Delete } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist. Delete manually!"} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""}
}
}
#CountPs $cn
12/31/2020 - EDIT:
Here is the finished result:
Function Delete-PFL{
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &Delete-PFL } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - ACCOUNT NOT in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist."
Write-Host "Attempting to delete files, please wait. . ."
Remove-Item -Path "\\$cn\c$\users\$Profile" -Force -WhatIf
Write-Host ""
Start-Sleep -Seconds 2
Write-Host "Checking if Files are still there. . ."
$TestPath = Test-Path -Path "\\$cn\c$\users\$Profile"
If($TestPath -eq $false){ Write-Host "Profile Files have been deleted. `
Continuing. . . ." -ForegroundColor Green
}
} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""
}
}
}
#CountPs $cn
}
Remember to remove the -whatif parameter. Enjoy!
$selection is an array of indices, so in your foreach loop you must refer to the single index at hand, not to $selection as a whole, to get the desired display output.
The conceptually clearest approach is probably to iterate over the indices contained in $selection:
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
EDIPI:$Profile "
# ...
}

Powershell: Retrieving LogonDate in Active Directory

I've a list of computers, I'm checking if they are connected, if they aren't check with AD and "spit out" the one that aren't connecting for more than 3 months.
If it's connected then check if a services is installed.
Here's my code:
Import-Module ActiveDirectory
$datecutoff = (Get-Date).AddDays(-90)
Get-Content "C:\powershell\pc.txt" |
foreach {
if (-not (Test-Connection -comp $_ -quiet)){
Write-host "$_ is down" -ForegroundColor Red
$LastLog = Get-ADComputer -Identity $_ | Select LastLogonDate
if($LastLog -lt $datecutoff){
Write-host "$_ is offline for more than 3 months" -ForegroundColor Yellow
}
} Else {
$service = get-service -name masvc -ComputerName $_ -ErrorAction SilentlyContinue
if ($service ){
write-host "$_ Installed"
} else {
Write-host "$_ Not Installed"
}
}
}
When it finds a disconnected computer it gives me the following error:
Cannot compare "#{LastLogonDate=}" to "2020.04.16 18:49:19" because the objects are not the same type or the object "#{LastLogonDate=}" does not implement "IComparable".
At line:10 char:20
+ if($LastLog -lt $datecutoff){
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], ExtendedTypeSystemException
+ FullyQualifiedErrorId : PSObjectCompareTo
I know the error happens because my variable is saving wrong info, but I cannot find a way to only select the date in the AD.
Is there anyway to do this?
Thanks in advance.
You have a couple of issues. You'll need to request that LastLogonDate is returned with Get-ADComputer as its not by default. You'll need to use the dotted notation method of selecting property LastLogonDate from the $LastLog object so your compare works.
Import-Module ActiveDirectory
$datecutoff = (Get-Date).AddDays(-90)
Get-Content "C:\powershell\pc.txt" |
foreach {
if (-not (Test-Connection -comp $_ -Quiet)) {
Write-Host "$_ is down" -ForegroundColor Red
$LastLog = Get-ADComputer -Identity $_ -Properties LastLogonDate
if ($LastLog.LastLogonDate -lt $datecutoff) {
Write-Host "$_ is offline for more than 3 months" -ForegroundColor Yellow
}
} Else {
$service = Get-Service -Name masvc -ComputerName $_ -ErrorAction SilentlyContinue
if ($service ) {
Write-Host "$_ Installed"
} else {
Write-Host "$_ Not Installed"
}
}
}
Welcome to stackoverflow, please read https://stackoverflow.com/help/someone-answers
Side note. You can filter for aged computers like this Get-ADComputer -Filter 'LastLogonDate -lt $datecutoff'

Not able to convert from PScustomobject to Arraylist

I am trying to get all the list of patches from multiple servers. I am using invoke command with -asjob parameter to get the patch list from all servers. Running the below code.
I am getting the below error. I have tried with
Get-CIMInstance -Class Win32_QuickFixEngineering
and with
Get-WmiObject -Class Win32_QuickFixEngineering
but keep getting same error.
$Servers = Get-Content "C:\Users\Suman.Ghosh\Servers.txt"
[System.Collections.ArrayList]$All_Jobs = #()
[System.Collections.ArrayList]$Updated_Servers_List = #()
[System.Collections.ArrayList]$Jobs_Output = #()
$Patches_to_lookfor = #(
'KB4462926',
'KB4462941'
)
foreach ($S in $Servers) {
if (Test-Connection -ComputerName $S -Count 1 -Quiet) {
$Updated_Servers_List += $S
$All_Jobs += Invoke-Command -ComputerName $S -ScriptBlock {Get-HotFix} -AsJob
} else {
Write-Warning "Computer $S is not running"
}
}
Write-Host "below Jobs are running" -ForegroundColor Cyan
$All_Jobs
Write-Host "waiting for jobs to finish" -ForegroundColor Cyan
$All_Jobs | Wait-Job
$temp = #()
$flag = $false
foreach ($job in $All_Jobs) {
$Jobs_Output += Get-Job $job.Id | Receive-Job | Select HotFixID, CSNAME
}
foreach ($Job_output in $Jobs_Output) {
Write-Host $Job_output -ForegroundColor Green
### DO some stuff
}
Cannot convert value "#{HotFixID=KB4020449; CSName=mit1epxa2}" to type
"System.Collections.ArrayList".
Error: "Cannot convert the "#{HotFixID=KB4020449; CSName=mit1epxa2}" value
of type "Selected.System.Management.Automation.PSCustomObject" to type
"System.Collections.ArrayList"."
At line:1 char:10
+ foreach ($Job_output in $Jobs_Output) {
+ ~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
Ok, let's see if my second answer gets deleted (with no notification) by people who don't know the topic. I believe you have run a command like this on $job_output (no "s"):
[collections.arraylist]$job_output = #()
Then in the loop at the bottom, an exception is created because a pscustomobject can't be cast to a collections.arraylist with the $job_output variable. $jobs_output (with the "s") is an arraylist of [pscustomobjects]'s created by select.
foreach ($Job_output in $Jobs_Output) {
Write-Host $Job_output -ForegroundColor Green
### DO some stuff
}

Delete one local user account if another local adminstrator ID exist

I have few numbers of server where "tempadmin" and "Administrator" are exist as local admin user. I need to delete "tempadmin" id if "Administrator" is also exist. else "tempadmin" needs to rename as "Administrator". Could anyone help me create one PowerShell script?
cls
$strComputer = Get-Content "c:\patch\clientlist.txt"
$password = Read-Host "Enter the password : " -AsSecureString
foreach ($server in $strComputer) {
Write-Host "Working on server $server"
if (Test-Connection $server -Quiet) {
$usertest1 = Get-WMIObject Win32_UserAccount -Filter "LocalAccount=True AND Name='test1'" -ComputerName $server
if ($usertest1.name -ne 'test1') {
$user = Get-WMIObject Win32_UserAccount -Filter "LocalAccount=True AND Name='test'" -ComputerName $server
$result = $user.Rename('test1')
if ($result.name -eq 'test') {
$result
# you may just print a message here
}
Write-host "$server -> ID renamed and resetting password...."
$user1 = Get-WMIObject Win32_UserAccount -Filter "LocalAccount=True AND Name='test1'" -ComputerName $server
([adsi]("WinNT://" + $server + "$user1")).SetPassword("$password")
Write-Host "$server -> Password reset successfully"
} else {
Write-Host "$server -> Test1 ID already exist"
}
} else {
Write-Host "$server -> server is not reachable"
}
}
Here I am user "test" and "test1". I tried the above code and I can successfully rename and reset the password. But I am unable to delete if both ID's are exist.
I would try something like this:
$VerbosePreference = 'Continue'
foreach ($C in $Computer) {
if (Test-Connection $C -Quiet) {
Write-Verbose "$C > Online"
$Users = Get-WMIObject Win32_UserAccount -Filter "LocalAccount=True" -ComputerName $C
if ($Users.Name -contains 'Administrator') {
Write-Verbose "User 'Administrator' found"
if ($Users.Name -contains 'tempadmin') {
Write-Verbose "Delete user 'tempadmin'"
$ADSI = [ADSI]"WinNT://$C"
$ADSI.Delete('User','tempadmin')
}
}
else {
Write-Verbose "User 'Administrator' not found"
# Here you can rename the 'tempadmin' account or create a new 'Administrator'
}
}
else {
Write-Verbose "$C > Offline"
}
}
It's not complete but I'm sure you get the idea here. It's always better to query a client once and then loop through the collection of objects instead of querying multiple times to check for different objects in each query.
I hope this helps you out or gives you some ideas.
More info can be found on Boe Pox's excellent blog.

Check if OU exists not working properly

Wrote this small script to test if an OU exists, if exists write to console and terminate. If not exists create OU and do some other stuff. Though can't seem to understand why i cant get it working.
For some reason the output will always tell me that the OU exists, and I am pretty sure it does not. Am I doing something terribly wrong?
This is the code:
param (
[parameter(mandatory=$true)] [string] $servername
)
Import-Module ActiveDirectory
Function CheckOU {
$script:OUpath = "OU=$servername,OU=Rechtengroepen,OU=danny,dc=Doenoe,DC=com"
$Status = $false
$GetOU = Get-ADOrganizationalUnit -Identity $OUpath -ErrorAction SilentlyContinue
if ($GetOU -eq $null) {
$status = $false
Write-Host -ForegroundColor Green "$OUpath does not exist."
} else {
$Status = $true
Write-Host -ForegroundColor Red "$OUpath exists!"
}
return $Status
}
$OUStatus = CheckOU
if ($OUStatus -eq $true) {
Write-Host "$OUpath exists. Function working."
} else {
Write-Host "$OUpath does not exsist, do something."
}
Output:
Get-ADOrganizationalUnit : Directory object not found
At C:\Scripts\CreateOUgroups\createadgroups_test02.ps1:10 char:14
+ $GetOU = Get-ADOrganizationalUnit -Identity $OUpath -ErrorAction SilentlyCon ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (OU=notexistsing...c=Doenoe,DC=com:ADOrganizationalUnit) [Get-ADOrganizationalUnit], ADIdentityNotFoundException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Management.Commands.GetADOrganizationalUnit
OU=notexistsingOU,OU=Rechtengroepen,OU=danny,dc=Doenoe,DC=com exists!
OU=notexistsingOU,OU=Rechtengroepen,OU=danny,dc=Doenoe,DC=com exists. Function working.
Using the cmdlet with the -Identity parameter causes a terminating error if the object with the given identity doesn't exist. Use -Filter to avoid this issue:
Get-ADOrganizationalUnit -Filter "distinguishedName -eq '$OUPath'"