Try Catch not working inside function / functions - powershell

2 functions one calls the other.
The CATCH blocks in the function Export-LoggedOnUser are not being triggered when I leave either the $path parameter or the $ComputerName parameter NULL. The Export-LoggedOnUser function is calling the first function "Get-LoggedOnUser". It too will not trigger the catch block if I leave the $ComputerName Parameter null. I have written these in various ways and they both work as desired except the TRY/CATCH structures do not perform in either function.
The Typical error is some always some variation of a 'ParameterBindingValidationException' which is to be expected except that it is not being handled in the CATCH. I'm flummoxed. Gotta be something simple.
function Get-LoggedOnUser{
[CmdletBinding()]
[Alias()]
Param
(
[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
[ValidateScript({Test-Connection -ComputerName $_ -Quiet -Count 1})]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName
)
Try{
ForEach($computer in $ComputerName){
$output = #{
'ComputerName' = $computer; }#OutputHashTable
$output.UserName = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer -ErrorAction Stop).Username
[PSCustomObject]$output
}
}
Catch{
Write-host 'You must enter a valid computername'
}
}
#New Function
function Export-LoggedOnUser{
[CmdletBinding()]
[Alias()]
Param(
[Parameter(Mandatory=$True)]
[string]$Path,
[Parameter(Mandatory=$True)]
[string[]]$ComputerName
)
try{
$loggedonuser = Get-LoggedOnUser -ComputerName $ComputerName -ErrorAction stop
}
catch{
Write-Host "You need to provide a Computername"
}
Try{
$loggedonuser | Export-Csv -Path $Path -NoTypeInformation -ErrorAction Stop
}
Catch{
Write-Host 'You must enter a valid path'
}
}

Christian,
If you want to test the $Computername parameter and provide an error message I'd drop the parameter validation and do the following:
Function Test {
Param (
[Parameter(Mandatory=$False)]
[String[]] $ComputerName
)
If ($Null -ne $ComputerName) {
ForEach ($Computer in $ComputerName) {
$GCIMArgs = #{Class = 'Win32_ComputerSystem'
ComputerName = "$computer"
ErrorAction = 'Stop'}
Try { $UserName = (Get-CIMInstance #GCIMArgs ).Username }
Catch { "Error: $Computer is an invalid computer name!" }
<#+-----------------------------------------------------------+
| Place your code her to place $username in your PSObject! |
+-----------------------------------------------------------+
#>
} #End ForEach
} #End If ($Null -ne $ComputerName)
Else { "You must supply a valid array of computer names..." }
} #End Function Test
#-------------------- Main Program ---------------------
Test #("DellXPS8920","Dellxps8700","JanetsLaptop")
If run as shown above you get this output:
Error: JanetsLaptop is an invalid computer name!
Which is correct for my lan since that laptop was not turned on.
If you just call TEST with out the array you get this:
You must supply a valid array of computer names...

Related

ForEach Loop failing to continue on the file?

The ForEach loop on this powershell script is failing to run more than one item before dropping out?
Can someone help me on this one?
function Get-RemoteLogonStatus {
[CmdletBinding()]
param(
[string]$ComputerName = ' '
)
ForEach ($line in Get-Content C:\ADComputers.csv)
{
$Computername = $line
if ( Test-Connection -ComputerName $ComputerName -Count 3 -Quiet ) {
try {
Get-WmiObject –ComputerName $ComputerName –Class Win32_ComputerSystem | Select-Object UserName = $lname -ErrorAction Stop | Out-Null
}
catch {
Write-Output 'No user logged in - RESTARTING.'
Shutdown /r /t 0 /M \\$ComputerName
$ComputerName
return
}
Write-Output 'Computer in use.'
$ComputerName
}
else {
Write-Output 'Computer in Use or is Offline.'
$ComputerName
}
}
$error.clear
}
Get-RemoteLogonStatus
Should run more than one item from the file. The file has 4 items for test:
a function is supposed to contain a block of code that can be repeated a number of times. Your function does all in one go, hence I don't see the need for it. Also it has the possibility to take one argument, but you don't pass it.
'return' is not necessary in PowerShell, it will throw the content of a variable without the need for a 'return'.
Select-Object needs a name that is being passed from the pipe, and not an assignment.
inside the try statement you might want to get an output, but if you pipe the line to Out-null you get nothing. and the catch will never grab any error.
the write-output are not clearly positioned, and difficult to understand.
I can infer what you are trying to achieve is: reboot computers in the csv file IF no user is logged in, is that so? In that case it's much simpler:
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
of if you want to stick to a function and see its purpose see this:
function Restart-Node {
param(
[Parameter(Mandatory=$true)][string]$ComputerName
)
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
Restart-Node -ComputerName $ComputerName
}

Output in powershell struglling

function Get-PCinfo {
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
.NOTES
<Zadanie 5>
<Author email>
#>
[CmdletBinding()]
Param(
# Param1 help description
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
Position = 0)]
[string[]]$ComputerName = $env:COMPUTERNAME
## Param2 help description
#[int]
#[switch]$outFile = $false
)
Begin {
$Info = #()
$Info | Format-List
}
Process {
foreach ($computer in $ComputerName) {
$NotReachableComputers = $null
Write-Host "Testing connectivity $computer ..... please wait" -ForegroundColor White
if (Test-Connection $computer -Quiet -Count 1) {
Get-CimInstance win32_UserAccount | ForEach-Object {
$PCInfo = [PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
Name = [string](Get-CimInstance win32_UserAccount).Name
SID = [string](Get-CimInstance win32_UserAccount).SID
Lockout = [string](Get-CimInstance win32_UserAccount).Lockout
Disabled = [string](Get-CimInstance win32_UserAccount).Disabled
LocalAdminMember = $_.LocalAdminMember
}
}
}
else {
$NotReachableComputers += $computer.name
}
}
}
End {
if ($NotReachableComputers -ne $null) {
Write-Host "This system is not available on network" -ForegroundColor Red
Write-Host $NotReachableComputers
}
else {
Write-Host "Code worked on all PCs" -ForegroundColor Green
Write-Output $PCInfo
}
#List of systems that were not available on the network
#List of output from systems that were available
#List where the output file is located
}
}
Output now:
Expected output.... every user may be belowe another and the other things same....
Right now, you keep overwriting the value of $PCInfo inside the Process block, and then at the very end you output whatever value was last assigned.
Remove the assignment to $PCInfo completely, and just output the objects immediately once created. You probably also want to explicitly pass $computer as the remote computer name for Get-CimInstance to query:
function Get-PCinfo {
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
.NOTES
<Zadanie 5>
<Author email>
#>
[CmdletBinding()]
Param(
# Param1 help description
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
Position = 0)]
[string[]]$ComputerName = $env:COMPUTERNAME
## Param2 help description
#[int]
#[switch]$outFile = $false
)
Begin {
$NotReachableComputers = #()
}
Process {
foreach ($computer in $ComputerName) {
Write-Host "Testing connectivity $computer ..... please wait" -ForegroundColor White
if (Test-Connection $computer -Quiet -Count 1) {
Get-CimInstance win32_UserAccount -ComputerName $computer | ForEach-Object {
# Don't assign this object to a variable, just let it "bubble up" to the caller
[PSCustomObject]#{
ComputerName = $computer
Name = $_.Name
SID = $_.SID
Lockout = $_.Lockout
Disabled = $_.Disabled
LocalAdminMember = $_.LocalAdminMember
}
}
}
else {
# Keep track of unreachable machines still
$NotReachableComputers += $computer
}
}
}
End {
if ($NotReachableComputers.Count -ge 1) {
Write-Host "This system is not available on network" -ForegroundColor Red
Write-Host $NotReachableComputers
}
}
}
To write the accounts to a file, simply pipe the output from your function to Export-Csv or similar:
Get-PCinfo -ComputerName pc01,pc02,pc03 |Export-Csv path\to\output.csv -NoTypeInformation

PowerShell to find computers in the domain, user is logged on

I am fairly new to PowerShell. I have this script working for computer names on the domain, thanks to #Adam Bertram https://4sysops.com/archives/how-to-find-a-logged-in-user-remotely-using-powershell/.
I understand it and it works fine on my domain i.e. I can query the computer name and it returns a list of logged users. I have difficulties altering the code so it could return a list of computer names for a given user, instead. I believe the issue could be with the correct Win32 class.
function Get-LoggedOnUser
{
[CmdletBinding()]
param
(
[Parameter()]
[ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1 })]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($comp in $ComputerName)
{
$output = #{ 'ComputerName' = $comp }
$output.UserName = (Get-WmiObject -Class win32_computersystem -ComputerName $comp).UserName
[PSCustomObject]$output
}
}
thanks
Tomasz
Try this:
function Get-LoggedOnUser
{
[CmdletBinding()]
param
(
[ Parameter() ]
$UserName
)
Get-WmiObject -Class win32_computersystem | Where-Object { $_.Username -like "*$UserName*" } | Select-Object Name
}
Get-LoggedOnUser -UserName "vish"

How do you make a function that only takes strings, take variables in Powershell?

I want to run a function in Powershell called Get-OSArchitecture which tells me whether a computer has a 32bit or 64bit system when you give it a domain name. However, it only accepts strings such as "SALES-DENNY" and not variables with stored strings such as $string1. I've played around with something called Out-String but this function is really stubborn with getting strings and nothing to do with variables.
The following code is for getting the global Get-OSArchitecture function:
function global:Get-OSArchitecture {
#Requires -Version 2.0
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$false,
Position=1,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName = $env:COMPUTERNAME
)#End Param
Begin
{
Write-Verbose "Retrieving Computer Info . . ."
}
Process
{
$ComputerName | foreach {
$ErrorActionPreference = 0
$Computer = $_
$Windir,$OSArchitecture,$OSVersion = Get-WmiObject -class Win32_OperatingSystem -ComputerName $_ |
foreach {$_.WindowsDirectory,$_.OSArchitecture,$_.Version}
$SysDrive = ($Windir -split ":")[0] + "$"
# $OSVersion[0]
# $OSArchitecture is only suppored on OSVersion -ge 6
# I was going to test for that, however now I just test if $OSArchitecture -eq $True
Write-Verbose "Operating System version on $Computer is: $OSVersion"
if ($OSArchitecture)
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture=$OSArchitecture
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
else
{
# check the program files directory
write-verbose "System Drive on $Computer is: $SysDrive"
$x64 = "\\$Computer\" + $SysDrive + "\Program Files (x86)"
if (test-path ("\\$Computer\" + $SysDrive))
{
if (test-path $x64)
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture="64-bit"
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
elseif (!(test-path $x64))
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture="32-bit"
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
}
else {"Something wrong determining the System Drive"}
}
} | select Hostname,OSArchitecture,SysDrive,WinDir,OSVersion
}#Process
End
{
}#End
}#Get-OSArchitecture
My problem begins below.
$string1 = "SALES-DENNY"
Get-OSArchitecture $string1
The above fails.
The below works.
Get-OSArchitecture "SALES-DENNY"
I expect the function to give out the correct architecture of the computer with the name "SALES-DENNY" but if I don't put it in as a string I always get a blank result.
Although it should not matter if you give the computername as hardcoded string or as a name or IP in a variable, I do believe you could improve the function by not testing the Program Files (x86) directory.
Instead, there are two other WMI functions you can rely on to get the 'bitness' of the OS:
function Get-OSArchitecture {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string[]]$ComputerName = $env:COMPUTERNAME
)
process {
foreach ($computer in $ComputerName) {
Write-Verbose "Retrieving info for computer '$computer'"
$info = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer |
Select-Object #{Name = 'HostName'; Expression = { $_.PSComputerName}},
OSArchitecture,
#{Name = 'SysDrive'; Expression = { '{0}$' -f ($_.SystemDrive).Substring(0,1) }},
#{Name = 'WinDir'; Expression = { $_.WindowsDirectory}},
#{Name = 'OSVersion'; Expression = { $_.Version }}
if ($info.OSArchitecture) {
$info.OSArchitecture = '{0}-bit' -f ($info.OSArchitecture -replace '\D+','')
}
else {
$info.OSArchitecture = '{0}-bit' -f (Get-WmiObject -Class Win32_Processor -ComputerName $computer).AddressWidth
# or do:
# $info.OSArchitecture = '{0}-bit' -f (((Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer).SystemType -replace '\D+', '') -replace '86', '32')
}
# emit info
$info
}
}
}
Hope that helps

Error Handling Issue with Try/Catch in Powershell

I have been working on the following example from one of Don Jones' powershell books as part of my personal development and am having some serious trouble getting the try/catch construct to work as it should. As you can see, when the catch block executes, it sets a variable called $everything_ok to $false - which should trigger the else block in the following code. Which it does - the logfile is appended as per my expectations.
However it does not stop the script from ALSO executing the code in the if block and spewing out 'The RPC Server is unavailable' errors when it tries to query the made-up machine 'NOTONLINE' (Exception type is System.Runtime.InteropServices.COMException).
What makes this even stranger is that I went through the script with breakpoints, checking the contents of the $everything_ok variable along the way, and it never contained the wrong value at any point. So why on earth is the if block still executing for 'NOTONLINE' when the condition I have specified ( if ($everything_ok = $true) ) has not been met?
Am I doing something wrong here?
function get-systeminfo {
<#
.SYNOPSIS
Retrieves Key Information on 1-10 Computers
#>
[cmdletbinding()]
param (
[parameter(mandatory=$true,valuefrompipeline=$true,valuefrompipelinebypropertyname=$true,helpmessage="computer name or ip address")]
[validatecount(1,10)]
[validatenotnullorempty()]
[alias('hostname')]
[string[]]$computername,
[string]$errorlog = "C:\retry.txt",
[switch]$logerrors
)
BEGIN {
write-verbose "Error log will be $errorlog"
}
PROCESS {
foreach ($computer in $computername) {
try {$everything_ok = $true
gwmi win32_operatingsystem -computername $computer -ea stop
} catch {
$everything_ok = $false
write-verbose "$computer not Contactable"
}
if ($everything_ok = $true) {
write-verbose "Querying $computer"
$os = gwmi win32_operatingsystem -computername $computer
$cs = gwmi win32_computersystem -computername $computer
$bios = gwmi win32_bios -computername $computer
$props = #{'ComputerName' = $cs.__SERVER;
'OSVersion' = $os.version;
'SPVersion' = $os.servicepackmajorversion;
'BiosSerial' = $bios.serialnumber;
'Manufacturer' = $cs.manufacturer;
'Model' = $cs.model}
write-verbose "WMI Queries Complete"
$obj = new-object -type psobject -property $props
write-output $obj
}
elseif ($everything_ok = $false) {
if ($logerrors) {
"$computer $_" | out-file $errorlog -append
}
}
}
}
END {}
}
get-systeminfo -host localhost, NOTONLINE -verbose -logerrors
The equals sign in Powershell is used as the assignment operation. -eq is used to test for equality. So your if statement is assigning $true to $everything_ok, which then tests true.