Retrieving Computer Names That Produced an Error From Invoke-Command - powershell

I'm working on speeding the execution of a script and long story short, the core of it would look similar to this (minus Measure-Command):
$devices = Get-Content "list.txt"
Measure-Command{
try{
$Result = Invoke-Command -ComputerName $devices -ScriptBlock {
Get-LocalUser | Select-Object -Property #{N="Computer"; E={$env:COMPUTERNAME}},
Name, Enabled, PasswordChangeableDate, PasswordExpires, UserMayChangePassword,
PasswordRequired, PasswordLastSet, LastLogon,
#{n="Groups"; E={
$user = $_
Get-LocalGroup | Where-Object { $user.SID -in ($_ | Get-LocalGroupMember | Select-Object -ExpandProperty "SID") } | Select-Object -ExpandProperty "Name"
}}
} 2>> "errors.txt"
}catch{
Write-Host "Uh oh..." -ForegroundColor Red
Write-Host $Error[0]
}
}
What I'm trying to figure out is, in the case of an error on one of the devices, I want to store that device name in a separate file. In the past I was doing all of this process via a foreach loop with try/catch, which made this part very easy. I'm looking to avoid that with this solution.
Right now, I'm using 2>> "errors.txt" courtesy of this post, but this records the full error, which I don't want. Example:
[EXAMPLE DEVICE] Connecting to remote server EXAMPLE DEVICE failed with the following error message : WinRM cannot process the
request. The following error occurred while using Kerberos authentication: Cannot find the computer EXAMPLE DEVICE. Verify that
the computer exists on the network and that the name provided is spelled correctly. For more information, see the
about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (EXAMPLE DEVICE:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : NetworkPathNotFound,PSSessionStateBroken
I'd like the only record the name of the device (in this case "EXAMPLE DEVICE") in the file. Is there a way to do this?

One way to handle is to set -ErrorActions SilentlyContinue -ErrorVariable Errs.
This enables the program to flow normally without bleeding red errors all over the screen. Then you can look at the error records stored in $Errs to report on which machines had issues. In your case $Errs.TagrgetObject.
Demo might be something like
Invoke-Command -ComputerName $Computers -ScriptBlock { "Whatever..." } -ErrorAction SilentlyContinue -ErrorVariable Errs
# PostOp Check which machines had errors:
$Errs.TargetObject
This should return:
MrBogus
MrsBogus
Of course, if you want to format that more robustly you can do any arbitrary processing on the error records.

Related

Powershell - MSExchange Set-Contact How to throw an error when cmdlet returns error message

I have the following code that updates contact records in MS exchange. My goal is that if it fails to update the record, it goes into my catch block to log that there was an error. Instead, it's just returning the following in the log and moving on, not going to the catch block. I'm newer to powershell but i'm assuming the error msg is not a ps exception, and is reporting the error on the external cmdlet. Is there any way to check that the cmdlet encountered and error so I can log it/go down an error path?
snippet of code I'm running:
try{
$msxchangeInfo = Get-Contact | Where-Object {$_.WindowsEmailAddress -eq "$user_search_email"}
if ($msxchangeInfo)
{
write-host "$(Get-TimeStamp) $user_search_email found...."
$msxchangeInfo = Get-Contact -Filter "WindowsEmailAddress -eq '$($user_search_email)'" |
Select-Object -Property WindowsEmailAddress, Manager, FirstName, LastName, MobilePhone,
DisplayName, Company, Department, Identity, StreetAddress, StateOrProvince, PostalCode,
CountryOrRegion, City, Phone
$msxchangeInfoCountryOrRegion = $msxchangeInfo | Select -ExpandProperty CountryOrRegion
if($msxchangeInfoCountryOrRegion -ne $officeCountry -and $officeCountry -ne "")
{
/*CODE THAT IS RETURNING ERROR MESSAGE BUT NOT THROWING EXCEPTION!*/
Set-Contact -Identity $msxchangeInfoIdentity -CountryOrRegion $officeCountry}
}
}
catch
{
write-host("$(Get-TimeStamp) *****ERROR*****: ERROR ATTEMPTING TO CREATE NEW CONTACT RECORD:
$($displayName) . CHECK ERROR BELOW")
write-host($_.Exception|format-list -force)
write-host("$(Get-TimeStamp) Adding user to error ticket list")
$errMsg = $error[0].ToString() + $error[0].InvocationInfo.PositionMessage
$errMsg -replace ',','|'
}
However the code is never hitting the catch block. It is not throwing an error, it's just logging the cmdlet error message and continuing. How do I break it on the cmdlet error of set-contact?
This is the output in the log (as you can see, not logging my custom error message noted above):
Account Environment TenantId TenantDomain AccountType
------- ----------- -------- ------------ -----------
gggggggggggggg-gggggg-ggggggg-gg AzureCloud gggggggggggg-ggggg-gggg-gg TestTenant.com ServicePrincipal
Cannot process argument transformation on parameter 'CountryOrRegion'. Cannot convert value "Not Found" to type
"Microsoft.Exchange.Data.Directory.CountryInfo". Error: "The ISO-3166 2-letter country/region code or friendly name
"Not Found" does not represent a country/region."
+ CategoryInfo : InvalidData: (:) [Set-Contact], ParameterBindin...mationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Set-Contact
+ PSComputerName : outlook.office365.com
You can force a specific call to a cmdlet to halt on error via the -ErrorAction common parameter:
Set-Contact -Identity $msxchangeInfoIdentity -CountryOrRegion $officeCountry -ErrorAction Stop
You can also set this as the default behavior for any call in the current scope by modifying the $ErrorActionPreference variable at the start of your script:
$ErrorActionPreference = 'Stop'
... or, alternatively use the $PSDefaultParameterValues variable to make specific cmdlets in the current scope always take on this behavior on error:
$PSDefaultParameterValues['Set-Contact:ErrorAction'] = 'Stop'

Taking input from one PSSession and sending it to another

Like many others, my background is in Linux with no powershell experience. So this object oriented programming is messing me up.
I need to search through VMware Horizon for VMs with users assigned to them, then check if they are disabled in AD. If they are disabled in AD I want to recycle the VM.
At the moment I am pulling the SIDs for the users from VMware Horizon, but when I try to use these in an invoke-command against AD I receive the following error
"Object reference not set to an instance of an object"
The Script so far
function getlist() {
$temp=Invoke-Command -ComputerName $vdiserver -ScriptBlock { add-pssnapin vmware.view.broker; get-desktopvm | select user_sid }
$list=$temp | Select-Object user_sid
#$list
}
$vdi1="server1"
$vdi2="server2"
$test=Test-Connection -ComputerName $vdi1 -Quiet
$test2=Test-Connection -ComputerName $vdi2 -Quiet
if ($test -eq "True"){
$vdiserver=$vdi1
getlist
}
elseif ($test2 -eq "True"){
$vdiserver=$vdi2
getlist
}
else {echo "No servers to connect to"}
ForEach ($user in $list) #{
#echo $user
#sleep 1
#}
{Invoke-Command -ComputerName domaincontroller -ScriptBlock {param($p1) get-aduser -identity $p1 } -argumentlist $user}
So this object oriented programming is messing me up.
So you're trying to revert to shell script, and writing twice as much code to do achieve half as much work.
The most important bit you're missing is to imagine an object as a collection of things - like, imagine you're working with /etc/passwd and each line has a user ID and a group ID and a home directory and a login shell.. and you're passing the entire line around at once, that's your analogous object.
An object has many properties, just like that (but overall more capable).
When you Select user_sid you're choosing that field to stay in the 'line', but the line is still something like :::user_sid:::: with the other fields now empty. (Approximately). But they're still there and in the way. To work with it directly, you have to get it out of the 'line' entirely - throw the container away and just have the user_sid outside of it.
get-desktopvm | select user_sid
->
get-desktopvm | select -expandproperty user_sid
which makes "sid1", "sid2", "sid3", but no containers for each sid.
This
function getlist() {
$temp=Invoke-Command -ComputerName $vdiserver -ScriptBlock { add-pssnapin vmware.view.broker; get-desktopvm | select user_sid }
$list=$temp | Select-Object user_sid
}
is essentially saying
function getlist() {
#do any amount of work here, and throw it all away.
}
Because the function returns nothing, and it doesn't change any data on disk or anything, so when the function finishes, the variables are cleared out of memory, and you can't use them afterwards.
This:
if ($test -eq "True"){
is a bit of a nonsense. It might work, but it's not working how you expect because it's happenstance that "a string with content" compared to a boolean True is True, regardless of the string containing the English word "True" or not. But it's also redundant - $test is itself true or false, you don't need to compare True with anything. if ($test). Or even if (Test-Connection -ComputerName $vdi -Quiet)
But stillll, so much work. Just connect to them all, and let it fail for the ones it can't contact. Maybe add -ErrorAction SilentlyContinue if you don't want to see the error.
$VMs = Invoke-Command -ComputerName Server1,Server2 -ScriptBlock {
Add-PsSnapin vmware.view.broker
Get-DesktopVm
}
Now you have all the VMs, get the user enabled/disabled state
foreach ($VM in $VMs) {
$Sid = $VM.user_sid
$AdEnabled = Invoke-Command -ComputerName domaincontroller -ScriptBlock {
(Get-AdUser -Identity $using:Sid).Enabled
}
$VM| Add-Member -NotePropertyName 'AdEnabled' -NotePropertyValue $AdEnabled
}
Now you should ideally have $VM as an array of objects, each one having all the VM Desktop properties - and also the True/False state of the AD Enabled property for that user account.
$VM | Out-Gridview
or
$VM | Export-Csv Report.csv
or
$VM | Where-Object { -not $_.AdEnabled }

PowerShell WMI query fails to return username in logon script

I'm trying to get the username of domain users in a PowerShell logon script. Any number of different users may log into the computers in question.
A local user account (let's call it 'syscheck') is configured on Win7/Win8 domain clients for the purpose of running a PS script (PS 2.0/3.0); the script resides locally and is launched by Task Scheduler on user logon. The script needs to obtain the username of the domain user that is logging in.
I've attempted to do this with WMI:
Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty UserName
but this does not return anything when the script runs.
If I try this:
$env:USERNAME
The username of the 'syscheck' local account is returned.
Is the domain username not yet available when the script is running on logon?
Perhaps there a way to do this with .NET? Other options?
***** UPDATE August 8 *****
I've tested with the solution provided (thanks Alexander!) but still can NOT retrieve the username of the logged-in user. I believe this is because, as mentioned above, this is a logon script launched by Task Scheduler. The principal for the Task that launches the script is a local account. For some reason, all methods of trying to get the domain username fail.
Here is latest attempt:
First, this is how I call the function:
$indx = 0
do {
$username = GetDomUser
if (($indx -eq 25) -or ($username.Length -ne 0)) {
Write-Output $username
Break
}
else {
Start-Sleep -Seconds 12
}
$indx++
}
while ($indx -lt 25) # 5 minutes is PLENTY of time for boot...
Now, here's the function:
Function GetDomUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"MYDOMAIN",Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } | Select-Object -ExpandProperty Antecedent)
Return ([regex]::Match([string]$antecedent[0],"$pattern(.*$)").Value).Split('=')[1] -replace '"', ""
}
Of course, this works perfectly from the console once the machine has booted.
Is it possible to refresh whatever store the Win32_LoggedOnUser Class gets its data from?
Other options?
Here are previous methods I've tried - all return the username of the principal of the Task that launches the script (or an empty string, which is what D returns).
$usernameA = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
$usernameB = $(whoami)
$usernameC = $($env:USERNAME)
$usernameD = $(Get-WmiObject Win32_ComputerSystem -ComputerName $compname | Select-Object -ExpandProperty UserName)
$usernameE = $([Environment]::UserName)
Here's what you could do to find out what's going on:
$iLOGON32_LOGON_INTERACTIVE = 2
$cLogonSessions = Get-WmiObject -Class "Win32_LogonSession" `
| Where-Object { $_.LogonType -eq $iLOGON32_LOGON_INTERACTIVE }
if ($cLogonSessions -ne $null) {
$cInteractiveLogons = #()
foreach ($oLogonSession in $cLogonSessions) {
$sWmiQuery = ('ASSOCIATORS OF {{Win32_LogonSession.LogonId="{0}"}} ' `
+ 'WHERE AssocClass=Win32_LoggedOnUser') -f $oLogonSession.LogonId
$cInteractiveLogons += Get-WMIObject -Query $sWmiQuery `
| Select-Object -ExpandProperty "Caption"
}
} else {
$ex = New-Object -TypeName System.NullReferenceException(`
'$cInteractiveLogons is null.')
throw $ex
}
$cInteractiveLogons | Select-Object -Unique
When $cInterativeLogons is null exception is thrown, it means that no-one is logged on interactively (yet) in which case you can wait and re-check later.
Note that this code is not reliable because LOGON32_LOGON_INTERACTIVE wasn't limited to local console logons in XP and earlier versions.
As for actual solution, I'd recommend using some kind of explicit notifications. You could for example make use of events. Subscribe for an event and then emit the event from the user's regular logon script.
The problem was not with the WMI code but rather the state of the machine it was being run on. It turns out that when users are VPNed into their machines (almost always thanks to a VPN client's automated reconnect feature), or have some third-party utility installed (e.g. certain cloud backup services), there are multiple Logons and "the" logged on user is ambiguous.
For now this is working pretty well:
Function GetDomainUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"' + $($env:USERDOMAIN) + '"' + ',Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } |
Select-Object -ExpandProperty Antecedent | Select-Object -Unique)
Return $(([regex]::Match([string]$antecedent,$($pattern + '(".+")')).Value).Split('=')[1] -replace '"','')
}
But I had to write addition code to work around cases when the LoggedOnUser cannot be discovered (multiple logons exist), or when no one is logged in.

How to verify whether a windows server has mountpoint or not using WMI

I am generating a report where I need to find which servers has mountpoints configured on it..
can you help how to get that infor using WMI or powershell.
I mean I need to identify the servers, if mountpoints exists in it.. and also their names....
Get a list of all servers from textfile, AD, etc. and run a foreach loop with something like this:
Get-Wmiobject -query “select name,driveletter,freespace from win32_volume where drivetype=3 AND driveletter=NULL” -computer servername
A quick google search for "windows mount point wmi" would return THIS (source).
Then export the results to CSV, HTML or whatever you need. Your question is lacking a lot of details and any sign of effort from your part, so I can't/won't go any further.
UPDATE: Does this help? It lists mount points(folder paths, not driveletters).
$servers = #("server1","server2","server3","server4","server5")
$servers | % {
$mountpoints = #(Get-WmiObject Win32_MountPoint -ComputerName $_ | Select-Object -ExpandProperty Directory | ? { $_ -match 'Win32_Directory.Name="(\w:\\\\.+)"' }) | % { [regex]::Match($_,'Win32_Directory.Name="(\w:\\\\.+)"').Groups[1].Value -replace '\\\\', '\' }
if($mountpoints.Count -gt 0) {
New-Object psobject -Property #{
Server = $_
MountPoints = $mountpoints
}
}
}
Server MountPoints
------ -----------
{server1} {D:\SSD, C:\Test}

Powershell commandline with pipes error using Exchange 2010

Being new to powershell and used to oneliners in unix I find powershell strange. The below code gives me a "too many pipes" type of error. Can anyone tell me what I am doing wrong. My last step will be to add code that adds permission if not found in the else block.
[PS] C:\Windows\system32>(Get-mailbox -identity moh | select alias, distinguishedname) | foreach-object -process { if($_.distinguishedname -match "HK1|VO[1-9]") { $alias = $_.alias; get-mailboxfolderstatistics -identity $alias | Where {$_.FolderType -eq 'Calendar'} | %{ $calpath = $_.folderpath.substring(1); Get-MailboxFolderPermission -Identity $alias":\"$calpath -User iTell | %{ if($_.AccessRights -eq 'Editor') { write-host "Editor!" } else { write-host $_.AccessRights }} } } }
I get the following error.
Pipeline not executed because a pipeline is already executing. Pipelines cannot be executed concurrently.
+ CategoryInfo : OperationStopped: (Microsoft.Power...tHelperRunspace:ExecutionCmdletHelperRunspace) [], PSInvalidOperationException
+ FullyQualifiedErrorId : RemotePipelineExecutionFailed
Got it. Had to encapsulate blocks of code with parenthesis. But I thought the pipe block was just some sort of a write lock. In here I was only getting data and hence ought to be able to read from the streams.