Output in powershell struglling - powershell

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

Related

PowerShell Script Issues with Variable Values

I am trying to write this script to restart computers only if they are Offline. The script for getting user infomration works but I cannot get the variable values for the restart portion at the bottom of the script. Does anyone have a suggestion? I am somewhat new to Powershell, but writing code. Example of my script follows:
Function Get-LoggedOnUser
{
Param
(
$ComputerName = $env:COMPUTERNAME,
$Credential
)
Function Test-RemoteRegistry
{
Param
(
[Parameter(Mandatory = $False)]
[switch]$Enable
,
[Parameter(Mandatory = $False)]
[switch]$Disable
,
[Parameter(ValueFromPipeline=$True)]
[String[]]$ComputerName = $env:COMPUTERNAME
)
Begin
{
$PipelineInput = (-not $PSBOUNDPARAMETERS.ContainsKey("ComputerName")) -and (-not $ComputerName)
Function Test ($Computer)
{
Try
{
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $Computer) | Out-Null
#20ms faster than Get-Service per computer! Not sure how to handle/check things like the firewall though...
#If we hit here without error Remote Reg is enabled.
If ($Disable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Stopped -ErrorAction Stop
Return $False
#If we hit here without error Remote Reg is now disabled.
}
Catch
{
Return $True
#If we hit here, we couldn't stop remote registry.
}
}
Else
{
Return $True
}
}
Catch
{
If ($Enable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Running -ErrorAction Stop
Return $True
#If we hit here without error Remote Reg is now enabled.
}
Catch
{
Return $False
#If we hit here, we couldn't start remote registry.
}
}
Else
{
Return $False
#If we hit here remote registry is disabled.
}
}
}
}
Process
{
If ($PipelineInput)
{
Test $_
}
Else
{
$ComputerName | ForEach-Object {
Test $_
}
}
}
}
Foreach ($Computer in $Computername)
{
$Online = $False
$User = $False
$Locked = $False
If (Test-Connection $Computer -Count 2 -Quiet)
{
$Online = $True
If ($Credential)
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer -Credential $Credential | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
Else
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
If (Test-RemoteRegistry -Enable -ComputerName $Computer)
{
If ((Get-Process logonui -ComputerName $Computer -ErrorAction SilentlyContinue) -and ($user))
{
$Locked = $True
}
}
}
$Output = New-Object PSObject
$Output | Add-Member noteproperty ComputerName $Computer
$Output | Add-Member noteproperty Online $Online
$Output | Add-Member noteproperty Username $User
$Output | Add-Member noteproperty Locked $Locked
$Output
}
}
Get-LoggedOnUser
If (($Online) -eq $False)
{Shutdown /r t 0 /m \\$Computername}
ELSE
{Write-host 'HELLO $Online $Computername'}
I just want this for a single user as I am using PDQ Inventory to roll out the script. The variables at the end of the script are $null?
Variables defined in a child scope - in which functions run by default - are never seen by the calling scope. See the conceptual about_Scopes help topic
It's best for functions to communicate values to the caller via their output ("return value"), which you're function is already doing: it outputs objects whose properties contain the values of interest.
Therefore:
Get-LoggedOnUser |
ForEach-Object { # Loop over all output objects
# Refer to the object at hand via the automatic $_ variable.
# Note the use of "..." (expandable strings) so as to support
# expansion (string interpolation).
if (-not $_.Online) { Shutdown /r t 0 /m "\\$($_.ComputerName)" }
else { "HELLO $($_.Online) $($_.ComputerName)" }
}

Try Catch not working inside function / functions

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...

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.

Active logged in Users on Remote machine

I am using below script to get logged on user on remote machine . It works fine but I need to get the users those status "active"
How Can I get those active logged in users on remote machine ?
function Global:Get-LoggedOnUser {
#Requires -Version 2.0
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$false,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName = $env:COMPUTERNAME
)#End Param
Begin
{
Write-Host "`n Checking Users . . . "
$i = 0
$MyParams = #{
Class = "Win32_process"
Filter = "Name='Explorer.exe'"
ErrorAction = "Stop"
}
}#Begin
Process
{
$ComputerName | Foreach-object {
$Computer = $_
$MyParams["ComputerName"] = $Computer
try
{
$processinfo = #(Get-WmiObject #MyParams)
if ($Processinfo)
{
$Processinfo | ForEach-Object {
New-Object PSObject -Property #{
ComputerName=$Computer
LoggedOn =$_.GetOwner().User
SID =$_.GetOwnerSid().sid} } |
Select-Object ComputerName,LoggedOn,SID
}#If
}
catch
{
"Cannot find any processes running on $computer" | Out-Host
}
}#Forech-object(ComputerName)
}#Process
End
{
}#End
}#Get-LoggedOnUsers
Add a query for the Win32_ComputerSystem class:
Get-WMIObject -Class Win32_ComputerSystem -Computername $Computer | Select UserName
That'll grab the 'active' user, then you can build an object with an 'Active' boolean value.
Here's my implementation:
function Get-LoggedOnUser
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName
)
Begin
{
$users = $null
$return = #()
}
Process
{
ForEach($Computer in $ComputerName)
{
$activeUser = Get-WMIObject -class Win32_ComputerSystem -ComputerName $Computer -EA stop | select UserName
Try
{
$processinfo = #(Get-WmiObject -class win32_process -ComputerName $Computer -EA "Stop")
If ($processinfo)
{
ForEach($process in $processinfo)
{
[string[]]$users += $process.GetOwner().user| Where{($_ -ne "NETWORK SERVICE") -and ($_ -ne "LOCAL SERVICE") -and ($_ -ne "SYSTEM")}
}
If($Users)
{
ForEach($user in ($Users | Select -unique))
{
If($ActiveUser.username -like "*$user")
{
$Return += New-Object PSObject -Property #{
"User" = $user
"Active" = $true
"Computer" = $Computer
}
}
Else
{
$Return += New-Object PSObject -Property #{
"User" = $user
"Active" = $false
"Computer" = $Computer
}
}
}
}
Else
{
"There are no users logged onto $computer" | Out-Host
}
}
}
Catch
{
"Cannot find any processes running on $computer" | Out-Host
}
}
}
End
{
$Return
}
}
It is worth it to point out that the Win32_ComputerSystem username is only populated if the user is logged in locally, so anyone logged in through remote desktop won't show as 'Active'.