I have the following PS script that repeats in the two blocks the same amount of registry keys. So I have two questions:
How can I avoid to repeat that long list of reg keys? I've tried adding them to a variable that is assigned #" "#, but when I use the variable instead of the reg keys it does not work
The $faultReport does not get the addition when into the Catch block, how to fix that?
$faultReport = #()
if (Test-Connection $2FQDN -Quiet -count 2) {
Try {
Invoke-Command -ComputerName $1FQDN -ScriptBlock {
Loads of registry keys
}
}
Catch {
$faultReport += $1FQDN}
}
elseif (Test-Connection $2FQDN -Quiet -count 2) {
Try {
Invoke-Command -ComputerName $2FQDN -ScriptBlock {
Loads of registry keys
}
}
Catch {
$faultReport += $2FQDN
}
}
It is possible to assign a scriptblock to a variable
$scriptBlock = { ... }
Also you have an error in your code: You check the connection for $2FQDN (instead of $1FQDN) in your first if.
Additionally, you could simplify your code further: both bodies of the if-else are identical.
My suggestion:
$scriptBlock = {
# Loads of registry keys...
}
$faultReport = #()
$computerName = $null
if (Test-Connection $1FQDN -Quiet -count 2) {
$computerName = $1FQDN
}
elseif (Test-Connection $2FQDN -Quiet -count 2) {
$computerName = $2FQDN
}
if ($computerName) {
try {
Invoke-Command -ComputerName $computerName -ScriptBlock $scriptBlock
}
Catch {
$faultReport += $computerName
}
}
Note: Always use proper indentation. I can never stress that enough. It makes reading and troubleshooting your own code so much easier.
Related
Need some help adjusting this code so that it will only provide IP addresses that are not pinging and will be exported to a CSV. Currently, it outputs both up and down IP's and I want to clean up some of the clutter.
$ping = (Test-Connection -ComputerName $comp.IPAddress -Count 1 -ErrorAction SilentlyContinue)
if($ping)
{
$status = "Up"
}
else
{
$status = "Down"
}
[pscustomobject]#{
Location = $comp.Location
IP = $comp.IPAddress
Status = $status
}
I have tried manipulating the $status variable but haven't had any luck. I am sure it's something very simple, I am just missing it.
# -Quiet means we just return a boolean true / false value
$ping = Test-Connection -ComputerName ($comp.IPAddress) -Count 1 -ErrorAction SilentlyContinue -Quiet
if(-not $ping)
{
# we only care about when it's down, so move the logic to return our object here
[pscustomobject]#{
Location = $comp.Location
IP = $comp.IPAddress
Status = 'Down'
}
}
Aramus, Welcome to SO!
Test-Connection returns a Null if the connection is down so...
$ping = (Test-Connection -ComputerName $($comp.IPAddress) -Count 1 -ErrorAction SilentlyContinue)
if($Null -ne $ping)
{
$status = "Up"
}
else
{
$status = "Down"
}
Also note the $() around $Comp.IPAddress, this is to insure the parser fetches the IPAddress before running the rest of the command.
I'm looking for the easiest way to write the success or failure of a scheduled script to a csv file. I am not looking for a way to log the error or reason of failure just a simple "Succes/Failure". For this reason i'm not trying to fix/log non-terminating errors.
I thought the easiest way to put every script in a
try {
Write-Host "test"
}
catch{
Code to write to csv here
}
Block where I write to a csv file in the Catch part. Is there a better/easier way to do this or is this the right way to go?
Continuing from my comment. . .
Honestly, this really depends on the situation, i.e. what is you're trying to accomplish. So, let's make up a scenario of querying a computer for some basic info:
Function Get-SystemInfo {
Param (
[Parameter(Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME
)
Begin {
$ErrorActionPreference = 'SilentlyContinue'
}
Process {
foreach ($Computer in $ComputerName)
{
try {
# attempt cim session with -EA Stop to not allow it to go
# any further and we can calculate the results.
$CIMSession = New-CimSession -ComputerName $Computer -ErrorAction Stop
$Status = $true
# Perform the actual queries
$CS = Get-CimInstance -ClassName Win32_COmputerSystem -CimSession $CIMSession
$BS = Get-CimInstance -ClassName Win32_BIOS -CimSession $CIMSession
# evaluate against the returned objects
$UserName = if ($CS.UserName -eq $null) { 'No User Logged in' } else { $CS.UserName }
$Manufacturer = if ($CS.Manufacturer -eq $null) { 'N/a' } else { $CS.Manufacturer }
$Model = if ($CS.Model -eq $null) { 'N/a' } else { $CS.Model }
$SerialNumber = if ($BS.SerialNumber -eq $null) { 'N/a' } else { $BS.SerialNumber }
}
catch {
# Set the variables to $null
$Status = $false
$UserName = $null
$Manufacturer = $null
$Model = $null
$SerialNumber = $null
}
finally {
# Output the filled variables
[PSCustomObject] #{
ComputerName = $Computer
Connected = $Status
UserLoggedIn = $UserName
Manufacturer = $Manufacturer
Model = $Model
SerialNumber = $SerialNumber
}
}
}
}
End {
# cleanup
# some people argue this should be in the finally block
# to disconnect any machine, but looking at this from both
# sides, they both have pros/cons.
Get-CimSession | Remove-CimSession
}
}
... the biggest take-away from this quick function, is the -ErrorAction Stop while trying to create a CIM session. This is where looking at the bigger picture comes into play. If you are unable to connect to the computer, why bother continuing? This includes getting an echo reply from a quick ping since that doesn't dictate that you can connect to the remote PC just because you got a reply.
The rest is the if, and else statements that handle the light work evaluating against the returned objects for more control over the output.
Results would be:
PS C:\Users\Abraham> Get-SystemInfo
ComputerName : OER
Connected : True
UserLoggedIn : Abraham
Manufacturer : LENOVO
Model : 22251
SerialNumber : 55555555
PS C:\Users\Abraham> Get-SystemInfo -ComputerName BogusComputerName
ComputerName : BogusComputerName
Connected : False
UserLoggedIn :
Manufacturer :
Model :
SerialNumber :
I have a Function invokes a command that starts a new ps session on a remote server. The invoke command has an Exit clause however this is not exiting?
Function CreateID{
Invoke-Command -Session $Script:sesh -ScriptBlock{
Set-Location c:\
Import-Module ActiveDirectory
Try
{
If (Get-ADGroupMember "$Using:IDGroup" | Where-Object Name -match
"$Using:Computer")
{
Write-Host "Already in $using:IDGroup Exiting Script"
Disconnect-PSSession -Session $Script:sesh
Exit-PSSession
Exit
}
}
Catch
{ }
Write-Host "Did not Exit"
}
}
The Get-AD command works fine so where it should not display "did not exit" it does - how can i exit from a scriptblock in a remote ps session?
I am trying the disconnect session and Exit-pssession to see if they would do the same as simply exit but none of those are working.
I have also tried Break and no luck.
Ok so i figured this out - the ps session and invoke-command are red herrings. The basis of this is that you cannot Exit a Try/Catch statement.
i had to do this to get it to work - now it Exits. I just cannot use a Try/ Catch - if anyone knows how to exit a Try/Catch let me know!
#Try
#{
If (Get-ADGroupMember "$Using:IDGroup" | Where-Object Name -match
"$Using:Computer")
{
Write-Host "Already in $using:IDGroup Exiting Script"
Disconnect-PSSession -Session $Script:sesh
Exit-PSSession
Exit
}
#}
#Catch
#{ }
I don't know if this works for PSSession or not, and I don't have an environment to test it, but you can use this to exit powershell within a try catch
[Environment]::Exit(0)
Try/Catch should work in an invoke-command.
I don't usually invoke-commands to sessions, rather I use -ComputerName.
This worked fine for me:
invoke-command -ComputerName "MyDomainController" -ScriptBlock {
try { get-aduser "ValidUser" } catch { "doh!" }
}
I just tried this as well and it also worked:
$sess1 = New-PSSession -ComputerName MyDomainController
invoke-command -Session $sess1 -ScriptBlock { try { get-aduser "ValidUser" } catch { "doh!" } }
If I change either of those "ValidUser" values to invalid users I see the "doh!" as well.
Perhaps it's because you're trying to end the session from within the session.
You should deal with the output of the invoke-command or the function and then handle the session based on that.
Like using my lame example...
$sess1 = New-PSSession -ComputerName MyDomainController
$DoStuff = invoke-command -Session $sess1 -ScriptBlock { try { get-aduser "ValidUser" } catch { "doh!" } }
If ($DoStuff -eq "doh!") { Remove-PSSession $sess1 } else { $DoStuff }
Hello Guys im having trouble trying to figure out how to make this script to work, im very new on scripting but i do understand most of it but still figuring out some things.
try {
Test-Connection -Computername $_ -count 1 -ErrorAction Stop
} catch {
$_.Exception.ErrorCode -eq 0x800706ba
} `
{
$err = 'Unavailable (Host Offline or Firewall)'
}
try {
Test-UserCredentials -Username testuser -Password (Read-Host -AsSecureString)
} catch {
$_.CategoryInfo.Reason -eq 'UnauthorizedAccessException'
} `
{
$err = 'Access denied (Check User Permissions)'
}
Write-Warning "$computer- $err" | Out-File -FilePath c:\temp\Folder\Errors.txt -Append
What im looking for is for this script to test if the system responds or not. If True then next step would be to test credentials, and last would be to perform a get-wmiobject query. But if the system does not respond to ping then i want to catch the hostname that failed to respond ping, capture it and export it to a txt and do the same if the credential fails.
try..catch is for handling terminating errors. Don't abuse it for status checks by forcing a check to fail hard when it doesn't need to. If you just want to test the availability of a system run Test-Connection with the parameter -Quiet as an if condition:
if (Test-Connection -ComputerName $_ -Count 1 -Quiet) {
...
}
If you need to cascade multiple checks you could do so in a more readable manner by inverting the checks and returning with an appropriate message:
function Test-Multiple {
...
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
return "Host $_ unavailable."
}
$pw = Read-Host -AsSecureString
if (-not (Test-UserCredentials -Username testuser -Password $pw)) {
return 'Login failed for user testuser.'
}
...
}
If you want the information about ping or login failures in log files you can just append it to the respective files:
function Test-Multiple {
...
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
$_ | Add-Content 'C:\path\to\unavailable.log'
return
}
$pw = Read-Host -AsSecureString
if (-not (Test-UserCredentials -Username testuser -Password $pw)) {
$_ | Add-Content 'C:\path\to\login_failure.log'
return
}
...
}
Personally, I can't stand the behavior of Test-Connection. Throwing an exception when it doesn't successfully ping isn't the behavior I want. Like, ever. I understand why they did it that way, but it's not how I ever want a ping to work. Test-Path doesn't throw an exception when the path is invalid. It just returns false. Why is Test-Connection so unfriendly?
WMI allows you to capture the actual status code, and it also allows you to easily control the timeout so it will function much more quickly.
I tend to use this:
$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000";
if ($Ping.StatusCode -eq 0) {
# Success
}
else {
# Failure
}
If I actually want to decode the ping status code:
$StatusCodes = #{
[uint32]0 = 'Success';
[uint32]11001 = 'Buffer Too Small';
[uint32]11002 = 'Destination Net Unreachable';
[uint32]11003 = 'Destination Host Unreachable';
[uint32]11004 = 'Destination Protocol Unreachable';
[uint32]11005 = 'Destination Port Unreachable';
[uint32]11006 = 'No Resources';
[uint32]11007 = 'Bad Option';
[uint32]11008 = 'Hardware Error';
[uint32]11009 = 'Packet Too Big';
[uint32]11010 = 'Request Timed Out';
[uint32]11011 = 'Bad Request';
[uint32]11012 = 'Bad Route';
[uint32]11013 = 'TimeToLive Expired Transit';
[uint32]11014 = 'TimeToLive Expired Reassembly';
[uint32]11015 = 'Parameter Problem';
[uint32]11016 = 'Source Quench';
[uint32]11017 = 'Option Too Big';
[uint32]11018 = 'Bad Destination';
[uint32]11032 = 'Negotiating IPSEC';
[uint32]11050 = 'General Failure'
};
$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000"
$StatusCodes[$Ping.StatusCode];
You could do it like this:
if(Test-Connection -Computername $_ -Count 2 -ErrorAction 0 -Quiet) {
if(-not (Test-UserCredentials -Username testuser -Password (Read-Host -AsSecureString))) {
$err = "Access denied (Check User Permissions)"
}
} else {
$err = "Unavailable (Host Offline or Firewall)"
}
if($err) {
Write-Warning "$computer - $err" | Out-File -FilePath c:\temp\Folder\Errors.txt -Append
}
I believe Test-Connection and Test-Credentials are meant to return $true or $false rather than an exception (if properly used), so you don't really need try/catch here.
i have two loops in my program.First loop for setting up the retry peocess and inner loop for testing a connection status.
for($retry=0;$retry<=3;$retry++)
{
while (!(Test-Connection "mycomputer"))
{
if (time exceed)
{
$status=$false
Write-Host "machine is offline"
break
}
}
if($status)
{
Write-Host "machine is online"
break
}
}
is there any way to eliminate the inner loop without changing the output
Not entirely sure what you mean by "time exceeded" - time to do what?
If you want to wait between Test-Connection attempts, you can introduce an artificial delay with Start-Sleep:
$Computer = "mycomputer"
$TimeoutSeconds = 5
for($retry=0; $retry -lt 3; $retry++)
{
if(Test-Connection -ComputerName $Computer -Count 1 -Quiet){
# Didn't work
Write-Host "Machine is offline"
# Let's wait a few seconds before retry
Start-Sleep -Seconds $TimeoutSeconds
} else {
Write-Host "Machine is online!"
break
}
}
The easiest way however, would be to use the Count and Delay parameters of Test-Connection:
$Status = Test-Connection -ComputerName $Computer -Count 3 -Delay $TimeoutSeconds
You don't have to use a loop as test-connection already have a count parameter
The other answers already go in depth about why you don't really need a loop but I wanted to add a solution to your refactoring question.
You can eliminate the inner loop and if statement by pre-initializing a $Result variable and changing it in your original loop if necessary.
Personally, I find this more readable (subjective) at the expense of an extra assignment up front.
$Result = "machine is online";
for($retry=0;$retry<=3;$retry++) {
while (!(Test-Connection "mycomputer"))
{
if (time exceed)
{
$Result = "machine is offline"
break
}
}
Write-Host $Result
Edit To test for multiple computers in parallel, I use following worflow
workflow Test-WFConnection {
param(
[string[]]$computers
)
foreach -parallel ($computer in $computers) {
Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue
}
}
called as Test-WFConnection #("pc1", "pc2", ... , "pcn")
Not much of a difference when every computer is online but a world of difference when multiple computers are offline.