Try method in powershell - powershell

So I want to build a try method into my powershell script below. If I am denied access to a server, I want it to skip that server. Please help..
[code]$Computers = "server1", "server2"
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers | Select-Object `
#{n='Server';e={ $_.__SERVER }}, `
#{n='Physical Memory';e={ "$('{0:N2}' -f ($_.TotalPhysicalMemory / 1024))mb" }}, `
#{n='Virtual Memory';e={ "$('{0:N2}' -f ($_.TotalPageFileSpace / 1024))mb" }} | `
Export-CSV "output.csv"[/code]

Try/catch functionality is built-into PowerShell 2.0 e.g.:
PS> try {$i = 0; 1/$i } catch { Write-Debug $_.Exception.Message }; 'moving on'
Attempted to divide by zero.
moving on
Just wrap you script in a similar try/catch. Note you could totally ignore the error by leaving the catch block empty catch { } but I would recommend at least spitting out the error info if your $DebugPreference is set to 'Continue'.

You can simply suppress errors with the ErrorAction parameter:
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers -ErrorAction SilentlyContinue | ...

You can use trap to replicate Try/Catch, see http://huddledmasses.org/trap-exception-in-powershell/ or http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx for examples.

Use a filter function? Like this tutorial explains.
He passes a list of computers to his pipeline - first it tries to ping each one, and then only passes the ones that respond to the next command (reboot). You could customize this for whatever actual functionality you wanted.

Related

Foreach write output when one or more fail

Currently I have this script:
$AdminSiteURL="https://contoso-admin.sharepoint.com"
$SiteURL=""
$UserID="klaas.hansen#contoso.nl"
$sitecollectios = #("https://contoso.sharepoint.com/sites/Extranet","https://contoso.sharepoint.com/sites/contoso","https://contoso.sharepoint.com/sites/Projecten","https://contoso.sharepoint.com/sites/PFO","https://contoso.sharepoint.com/sites/beheer","https://contoso.sharepoint.com/sites/Intranet")
#Get Credentials to connect
$Cred = Get-Credential
#Connect to SharePoint Online Admin Site
Connect-SPOService -Url $AdminSiteURL -Credential $cred
foreach ($collectie in $sitecollectios)
{
Get-SPOUser -Site $collectie -LoginName $UserID
}
When it can't find the user however the foreach shows an error. which is obvious. Is it possible to when it can't find the user in one or more of the site collections it shows me an error in write output, but not every time it can't find it. so for example it can't find the user in 3 site collections it only has to show me once that it can't find it.
Mathias R. Jessen's solution is effective, but there's a simpler and faster alternative:
The -ErrorVariable common parameter has a rarely seen feature that allows you to append the errors collected during command execution to an existing variable, simply by prepending + to the target variable name, which enables the following solution:
foreach ($collectie in $sitecollectios)
{
# Using built-in alias parameter names, you could shorten to:
# Get-SPOUser -ea SilentlyContinue -ev +errs ...
Get-SPOUser -ErrorAction SilentlyContinue -ErrorVariable +errs -Site $collectie -LoginName $UserID
}
# Print the errors that occurred.
$errs
-ErrorAction SilentlyContinue silences the errors (do not use Ignore, as that would suppress the errors altogether).
-ErrorAction +errs collects any error(s) in variable $errs, by either appending to the existing collection in $errs or by creating one on demand.
Note how the variable name, errs must not be prefixed with $ when passed to -ErrorAction.
Afterwards, you can examine the $errs collection to see for which users the call failed.
$errs (like the automatic $Error variable that collects errors session-wide) will be an array-like object (of type System.Collections.ArrayList) containing System.Management.Automation.ErrorRecord objects.
The simplest way to get the error message (short of simply printing $errs as a whole to the screen) is to call .ToString() on an error record; e.g., $errs[0].ToString(); to get all error messages in the collection, use $errs.ForEach('ToString'). There is the .Exception.Message property, but that can situationally be overruled by .ErrorDetails.Message when the error prints to the display; .ToString() applies this logic automatically.
The .TargetObject property tells you the target object or input that triggered the error; I can't personally verify what Get-SPOUser does, but it would make sense for the -LoginName argument of a non-existing users to be reflected there; this is how it works analogously with Get-Item -Path NoSuch, for instance: in the resulting error record, .TargetObject contains 'NoSuch' resolved to a full path.
Catch the errors inline and then report on number of errors caught at the end:
$FailedCollections = #()
Connect-SPOService -Url $AdminSiteURL -Credential $cred
foreach ($collectie in $sitecollectios)
{
try{
Get-SPOUser -Site $collectie -LoginName $UserID -ErrorAction Stop
}
catch{
$FailedCollections += $collectie
}
}
if($FailedCollections.Count -ge 1){
Write-Error "Errors encounted in $($FailedCollections.Count) collections: [$($FailedCollections -join ', ')]"
}

Can I use -ErrorVariable while using Out-String in Powershell

I'm trying to capture the output of a command into output variable in Powershell. The output of the command is in table format which is why I'm using Out-String. Now I need to suppress the error messages which is why I use an error variable to store the error messages. I tried the following things but none of them suppress my error and error gets displayed on screen.
$output = $esxcli.network.firewall.ruleset.allowedip.list() | Out-String -ErrorVariable myErr
$output = $esxcli.network.firewall.ruleset.allowedip.list() -ErrorVariable myErr | Out-String
$output = $esxcli.network.firewall.ruleset.allowedip.list() | Out-String 2>&1
$output = $esxcli.network.firewall.ruleset.allowedip.list() 2>&1 | Out-String
Is there a way where I can suppress the errors while using Out-String in a simple way (nothing like try-catch)?
If your .list() method is throwing an exception you have two options:
Set your $ErrorActionPreference variable to 'SilentlyContinue' or 'Ignore'
$ErrorActionPreference = 'SilentlyContinue'
Wrap your method call in a try/catch block
try
{
$output = $esxcli.network.firewall.ruleset.allowedip.list() | Out-String
}
catch
{
"Error thrown! $PSItem"
}
Do note if your method call is throwing a terminating error, you do not have a workaround.
To add a bit more detail to your original question about -ErrrorVariable. -ErrorVariable is a common parameter which you get by default when adding the CmdletBinding attribute to the beginning of a function.
The .list() is a .Net method not a PowerShell function and thus -ErrorVariable does not work with it. You can however write a short function to wrap the .Net method in a small PowerShell function if you would like to use it a lot and would like to leverage some of PowerShell's awesomeness with that method.
Example:
function Get-ESXAllowedIPList {
[CmdletBinding()]
param($esxcli)
return $esxcli.network.firewall.ruleset.allowedip.list()
}
You can then use it like this:
Get-ESXAllowedIPList -ErrorAction SilentlyContinue -ErrorVariable ESXAllowedIPListErrors | Out-String

Catch error and restart the if statement

I have a powershell script that adds a computer to a domain. Sometimes, when I run the script I get the following error and when I run it for the second time it works.
How can I make the script to check if I get this error, and if so then to retry adding it to the domain?
I have read that it is hard to try and catch errors like that. Is that correct? Is there a better/different way to catch the error?
Thank you!
Code:
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
Add-Computer -DomainName my-domain.local | out-null
} else {...}
Error:
This command cannot be executed on target computer('computer-name') due to following error: The specified domain either does not exist or could not be contacted.
You can use the built in $Error variable. Clear it before executing code, then test if the count is gt 0 for post error code.
$Error.Clear()
Add-Computer -DomainName my-domain.local | out-null
if($Error.count -gt 0){
Start-Sleep -seconds 5
Add-Computer -DomainName my-domain.local | out-null}
}
You could setup a function to call itself on the Catch. Something like:
function Add-ComputerToAD{
Param([String]$Domain="my-domain.local")
Try{
Add-Computer -DomainName $Domain | out-null
}
Catch{
Add-ComputerToAD
}
}
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
Add-ComputerToAD
} else {...}
I haven't tried it to be honest, but I don't see why it wouldn't work. It is not specific to that error, so it'll infinitely loop on repeating errors (i.e. another computer with the same name exists in AD already, or you specify an invalid domain name).
Otherwise you could use a While loop. Something like
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
While($Error[0].Exception -match "The specified domain either does not exist or could not be contacted"){
Add-Computer -DomainName my-domain.local | out-null
}
}

Need to check for the existence of an account if true skip if false create account

I am trying to create local user on all servers and I want to schedule this as a scheduled task so that it can run continually capturing all new servers that are created.
I want to be able to check for the existence of an account and if true, skip; if false, create account.
I have imported a module called getlocalAccount.psm1 which allows me to return all local accounts on the server and another function called Add-LocaluserAccount
which allows me to add local accounts these work with no problems
when I try and run the script I have created the script runs but does not add accounts
Import-Module "H:\powershell scripts\GetLocalAccount.psm1"
Function Add-LocalUserAccount{
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName=$env:computername,
[parameter(Mandatory=$true)]
[string]$UserName,
[parameter(Mandatory=$true)]
[string]$Password,
[switch]$PasswordNeverExpires,
[string]$Description
)
foreach ($comp in $ComputerName){
[ADSI]$server="WinNT://$comp"
$user=$server.Create("User",$UserName)
$user.SetPassword($Password)
if ($Description){
$user.Put("Description",$Description)
}
if ($PasswordNeverExpires){
$flag=$User.UserFlags.value -bor 0x10000
$user.put("userflags",$flag)
}
$user.SetInfo()
}
}
$usr = "icec"
$rand = New-Object System.Random
$computers = "ServerA.","ServerB","Serverc","ServerD","ServerE"
Foreach ($Comp in $Computers){
if (Test-Connection -CN $comp -Count 1 -BufferSize 16 -Quiet){
$admin = $usr + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122)
Get-OSCLocalAccount -ComputerName $comp | select-Object {$_.name -like "icec*"}
if ($_.name -eq $false) {
Add-LocalUserAccount -ComputerName $comp -username $admin -Password "password" -PasswordNeverExpires
}
Write-Output "$comp online $admin"
} Else {
Write-Output "$comp Offline"
}
}
Why bother checking? You can't create an account that already exists; you will receive an error. And with the ubiquitous -ErrorAction parameter, you can determine how that ought to be dealt with, such as having the script Continue. Going beyond that, you can use a try-catch block to gracefully handle those exceptions and provide better output/logging options.
Regarding your specific script, please provide the actual error you receive. If it returns no error but performs no action check the following:
Event Logs on the target computer
Results of -Verbose or -Debug output from the cmdlets you employ in your script
ProcMon or so to see what system calls, if any, happen.
On a sidenote, please do not tag your post with v2 and v3. If you need a v2 compatible answer, then tag it with v2. Piling on all the tags with the word "powershell" in them will not get the question answered faster or more effectively.
You can do a quick check for a local account like so:
Get-WmiObject Win32_UserAccount -Filter "LocalAccount='true' and Name='Administrator'"
If they already exist, you can either output an error (Write-Error "User $UserName Already exists"), write a warning (Write-Warning "User $UserName Already exists"), or simply silently skip the option.
Please don't use -ErrorAction SilentlyContinue. Ever. It will hide future bugs and frustrate you when you go looking for them.
This can very easily be done in one line:
Get-LocalUser 'username'
Therefore, to do it as an if statement:
if((Get-LocalUser 'username').Enabled) { # do something }
If you're not sure what the local users are, you can list all of them:
Get-LocalUser *
If the user is not in that list, then the user is not a local user and you probably need to look somewhere else (e.g. Local Groups / AD Users / AD Groups
There are similar commands for looking those up, but I will not outline them here

Why are the errors slipping through TRY/CATCH Loop in Powershell

I got a simple loop which gets all the serial Numbers in the Server List;
Start
foreach ($computer in $computers)
{
try
{
Get-WmiObject -computer $computer -Class Win32_OperatingSystem|Select Serial*
}
catch
{
Write-Host "Invalid Server"
}
}
END
But, the output looks all ugly with following errors as well as the correct outputs for few servers.
Get-WmiObject : The RPC server is unavailable
Get-WmiObject : Access Denied etc (Isnt it the purpose of Try/Catch loop to eliminate those?)
Strangely soemtimes the output says "Invalid Server" too , so what exactly is the difference between errors and what are the limitations of Try/Catch loops?
What am i doing wrong here? Please if any questions.
To make the above code throw an exception, you can add -ErrorAction Stop to your Get-WmiObject line, like this:
Get-WmiObject -computer $computer -Class Win32_OperatingSystem -ErrorAction Stop | Select Serial*
See this article by Keith Hill: distinction between "terminating" and "non-terminating" errors.
#Graimer the line $ErrorActionPreference = "Stop" did the trick and the output was clean without errors.