PowerShell 'if' condition - powershell

Here is a quick little PowerShell blurp that is supposed to get the device's IP address, Service Tag, and MAC address of any active MAC addresses on the device (active meaning there is a connection).
Anyway, if I don't use an if statement, this works perfectly, but if I do, it only executes the first line:
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}},Ipv4Address
If the device is on... and the last line of the device is off.
Write-Host "Device is offline"
Here is my little PowerShell 'script':
$computername = Read-Host 'Enter Computer Name'
$online = Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet
IF ($online -eq "True") {
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}},Ipv4Address
Get-WmiObject win32_SystemEnclosure -computername $computername | select serialnumber
Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername |Where{$_.IpEnabled -Match "True"}
} Else {
Write-Host "Device is offline"
}
Why does this happen? What might I be doing wrong?

Boolean variables in PowerShell are $true and $false. If should be:
if ($online -eq $true) {
Or
if($online)

Try this:
$computername = Read-Host 'Enter Computer Name'
$online = Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet
IF ($online -eq $true) {
Write-Host "Device is online"
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}},Ipv4Address
try {
[PSObject[]]$systemEnclosures = Get-WmiObject win32_SystemEnclosure -computername $computername -ErrorAction Stop
Write-Host "Found $($systemEnclosures.Count) System Enclosures"
$systemEnclosures | select serialnumber
[PSObject[]]$NetworkAdapterConfiguration = Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername -ErrorAction Stop
Write-Host "Found $($NetworkAdapterConfiguration.Count) Network Adapter Configurations"
$NetworkAdapterConfiguration = $NetworkAdapterConfiguration | Where{$_.IpEnabled}
Write-Host "Found $($NetworkAdapterConfiguration.Count) IP Enabled Network Adapter Configurations"
$NetworkAdapterConfiguration
} catch {
Write-Host "An Error Occurred"
Write-Host $_.ToString() #show exception info
}
} Else {
Write-Host "Device is offline"
}
NB: I'm not suggesting you keep this code in your final script; just use this to understand what's happening behind the scenes.
Per comments; use $true instead of "true", as though both are truthy, using the wrong type will lead to a false understanding of the language / some really odd bugs down the line where you find that lines like if($true -eq "false") {write-output "Well, this is unusual"} will cause some odd behaviour.
Also you may wish to look into replacing Write-Host with Write-Output for any logical return values, or Write-Verbose/Write-Debug for any informative/investigation outputs; then call the code with the appropriate switches / preferences... but that's unrelated to your issue. More on that is in Write-Host Considered Harmful.
Update
Per comments, the issue you're seeing is a bug gotcha: https://github.com/PowerShell/PowerShell/issues/4552
If the data coming out of this code is just to be displayed in the console, you can avoid this issue by explicitly calling the Format-Table command:
$computername = Read-Host 'Enter Computer Name'
IF (Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet) {
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}}, 'Ipv4Address' | Format-Table
Get-WmiObject win32_SystemEnclosure -computername $computername | select serialnumber | Format-Table
Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername | Where{$_.IpEnabled} | Format-Table
} Else {
Write-Host "Device is offline"
}
If you need the output to go to the pipeline for consumption elsewhere, all's good as it is (i.e. without the format-table piece); the objects are being correctly written to the pipeline; the issue is simply that when it comes to displaying all of the results together, the first object causes PowerShell to create columns ComputerName and Ipv4Address, and PowerShell subsequently attempts to display those properties of the following objects, despite those not having such properties. That said, this could be improved by putting the different object types into different properties of a custom object for easy reference. For example,
$computername = Read-Host 'Enter Computer Name'
If (Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet) {
(new-object -TypeName PSObject -Property #{
ConnectionTest = Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}}, 'Ipv4Address'
SystemEnclosures = Get-WmiObject win32_SystemEnclosure -computername $computername | select serialnumber
NetworkAdapterConfigs = Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername | Where{$_.IpEnabled}
})
} Else {
Write-Host "Device is offline"
}

Test-Connection -Quiet returns a Boolean, not a string. Try if ($online) or if ($online -eq $true) if you want to be explicit.
https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Management/Test-Connection?view=powershell-5.1

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
}

Powershell, I do input a list gather data and output that whole list into one CSV

I am creating a script that reads a list of computer names and collects data from security event logs about who is on the computer, how long they have been on for, and how long it has been since the computer has restarted. I have it working except that it does not output all the data into one CSV. I just receive one CSV file with one computer name.
function Get-KioskInfo {
param (
[parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=0)]
[Alias('PSComputerName','DNSHostName','CN','Hostname')]
[string]
$ComputerName = $env:COMPUTERNAME
)
#PARAM
$User = try {(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem | Select-Object -ExpandProperty username).trimstart("NG\")} catch {Write-Output "User not detected";break}
$BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days
#These variables are for the DATE & Time calculation
If ($user -NE $null)
{ Write-Verbose 1
# Do something
$Date1 = Get-date
Write-Verbose 2
$SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{LogName = "Security";ID="5379";Data=$User; StartTime=((Get-Date).AddDays(-1))}
Write-Verbose 3
$Date2 =($SP | select -first 1).timecreated
Write-Verbose 4
$USERLOGTIME = ($Date1-$Date2).hours.tostring("N2")
Write-Verbose 5
}
else{Write-Output "No user";break}
Write-Verbose 6
#Rename-Computer -ComputerName "Srv01" -NewName "Server001" -DomainCredential Domain01\Admin01 -Force ------ Rename script for computers if it is needed.
#$computers = Get-Content C:\Users\jaycbee\Desktop\kiosknames.txt ------ To load kiosk list
#foreach ($c in $computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c} for learning how to do a foreach script
Write "Computer Name: $Computername"
Write "---USER---"
Write "Name: $User"
Write "Log in Time $USERLOGTIME"
Write "Boot start $BootStart days ago"
$ComputerName | ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{
Invoke-Command -ComputerName $ComputerName {
}
} # Offline Check
else
{
Write-Host "Computer is Unreachable or Offline" -ForegroundColor Gray
}
} # Foreach
$Continue = Read-Host "WARNING! This will READ LIST of computers in \\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt Type CONTINUE to proceed."
if ($Continue -eq "CONTINUE")
{
$Computers = Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt'
foreach ($C in $Computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c
}
}
[pscustomobject]#{ Name = $ComputerName ; User = $User ; "User Log in time in hours" = $USERLOGTIME;"BootStart days ago" = $BootStart} | export-csv -path "\\ou\ouor-groups\Desktop Support\SD\Kiosks\test45$ComputerName.csv" -Append
} #Function
#For each-computer | do this at this location,
Continuing from my comment. I too wonder why the use of jobs for this use case. Unless you are doing this on hundreds of computers, thus needing parallel processing.
This refactor/formatting is just my way of making sense of what you posted. I'm old, and crowded code just really hurts my eyes. ;-} Yet, code the way you like of course. ;-}
I do not have an environment to test this, but give it a shot.
function Get-KioskInfo
{
param
(
[parameter(ValueFromPipeline = $True,ValueFromPipelineByPropertyName = $True,Position = 0)]
[Alias(
'PSComputerName',
'DNSHostName',
'CN',
'Hostname'
)]
[string]
$ComputerName = $env:COMPUTERNAME
)
($User = try
{
(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem |
Select-Object -ExpandProperty username).trimstart("NG\")
}
catch
{
'User not detected'
break
}
)
($BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days)
If ($user -NE $null)
{
($Date1 = Get-date)
($SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{
LogName = 'Security'
ID = '5379'
Data = $User
StartTime = ((Get-Date).AddDays(-1))
})
($Date2 = (
$SP |
select -first 1
).timecreated)
($USERLOGTIME = ($Date1-$Date2).hours.tostring('N2'))
}
else
{
'No user'
break
}
"Computer Name: $Computername
---USER---
Name: $User
Log in Time $USERLOGTIME
Boot start $BootStart days ago"
$ComputerName |
ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{Invoke-Command -ComputerName $ComputerName}
else
{Write-Warning -Message 'Computer is Unreachable or Offline'}
}
$UserMessage = '
WARNING!
This will READ LIST of computers in:
\\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt
Type CONTINUE to proceed'
$Continue = Read-Host $UserMessage
if ($Continue -eq 'CONTINUE')
{
Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt' |
foreach {
{start-job -Name $PSItem -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $PSItem}
[pscustomobject]#{
Name = $ComputerName
User = $User
'User Log in time in hours' = $USERLOGTIME
'BootStart days ago' = $BootStart
}
} |
Export-Csv -path "$PWD\$ComputerName.csv" -Append
}
}
These didn't help me with my solution, but you were right about the start-jobs. I have to rework the entire script in order to get the correct info.

Powershell and TPM how to manage bitlocker?

I am trying to script a powershell function manage-bde.exe (bitlocker) to add a key protector to systems without TPM. For some reason GPO is not working. I have not had any luck getting powershell to add the protector remotely. I can log on to the endpoint and use the built in wizard to encrypt and save the key to our repository but for some reason remote automated scripting eludes me. My question is really more of guidance. Can powershell only be used, to remotely manage systems with TPM? I have bitlocker enabled and encrypted on systems without but I have had to do it manually.
Start-Transcript -Path ".\bitlockertranscript.txt" -Force
foreach ($Computer in $List) {
if (test-Connection -ComputerName $Computer -Count 1 -Quiet ) {
Get-ADComputer -Identity $Computer -Property * | Select Name,OperatingSystem
Get-WmiObject -class Win32_Tpm -namespace root\CIMV2\Security\MicrosoftTpm -computername $Computer | fl IsActivated_InitialValue, IsEnabled_InitialValue, IsOwned_InitialValue
$BitLocker = Get-WmiObject -ComputerName $Computer -Namespace Root\cimv2\Security\MicrosoftVolumeEncryption -Class Win32_EncryptableVolume
$id = $BitLocker.GetKeyProtectors(3).volumekeyprotectorid | Select -First 1
manage-bde.exe -cn $Computer -protectors -adbackup c:
manage-bde.exe -on C: -cn $Computer
Invoke-GPUpdate -Target $computer
} else
{"No Connection to $Computer"
}
}
Stop-Transcript

Powershell script to compare AD computers to text file and change registry service on those computer and then write the computers that were offline

I'm trying to filter out computers that have already ran the script (that enables remote registry service) within AD from a list in a text file
$NamesFromFile = Get-Content
C:\scripts\Inventory\offlineRemoteRegStartupWorkstations.txt
$computers = get-adcomputer -Filter * | Where-Object {
$_.Name.SubString(1) -in $NamesFromFile }
foreach ($computer in $computers)
{
if (Test-Connection -count 1 -computer $computer.Name -quiet){
Write-Host "Updating system" $computer.Name "....." -ForegroundColor
Green
Set-Service –Name remoteregistry –Computer $computer.Name -StartupType
Automatic
Get-Service remoteregistry -ComputerName $computer.Name | start-service
}
else
{
Write-Host "System Offline " $computer.Name "....." -ForegroundColor Red
echo $computer.Name >> C:\scripts\Inventory\offlineRemoteRegStartup.txt}
}
no errors just blank
Below a re-write of your script.
Because you test if only the first character of the computer name is in the list of computers, your $computers variable will remain empty, so nothing happens.
Also, I think it would be wise to add a check if the startup type of the RemoteRegistry service is not already set to Automatic, because after all.. the computernames you read in from the file may not be accurate.
To avoid having to use $computer.Name all the time, I use a Select-Object -ExpandProperty Name, so we only have to run through a list of strings.
$NamesFromFile = Get-Content -Path 'C:\scripts\Inventory\offlineRemoteRegStartupWorkstations.txt' | Sort-Object -Unique
Get-ADComputer -Filter * |
Where-Object { $NamesFromFile -contains $_.Name } | # if the computer name is in the list
Select-Object -ExpandProperty Name | # we're only interested in the Name property
ForEach-Object {
# the automatic variable '$_' represents a single computername from the list
if (Test-Connection -Count 1 -ComputerName $_ -Quiet) {
# test if the RemoteRegistry service startup type is not already Automatic
# you can do the same with (Get-WmiObject -Class Win32_Service -Filter "Name='RemoteRegistry'" -ComputerName $_)
# only slower..
if ((Get-CimInstance -Class Win32_Service -Filter "Name='RemoteRegistry'" -ComputerName $_).StartMode -ne 'Auto') {
Write-Host "Updating system '$_'....." -ForegroundColor Green
Set-Service –Name RemoteRegistry –Computer $_ -StartupType Automatic
Get-Service -Name RemoteRegistry –Computer $_ | Start-Service
}
else {
Write-Host "RemoteRegistry service startup type already Automatic on computer '$_'....." -ForegroundColor Yellow
}
}
else {
Write-Host "System Offline '$_'....." -ForegroundColor Red
Add-Content -Path 'C:\scripts\Inventory\offlineRemoteRegStartup.txt' -Value $_
}
}

How do I add multi-threading?

Is there a way of getting the below to run in parallel (multi-threading)? I have about 200 servers that need to run and was wondering if there is a way of checking say 10 servers at once rather then one at a time...WMI is very slow in checking this one at a time.
clear
Write-Host "Script to Check if Server is Alive and Simple WMI Check"
$servers = Get-Content -Path c:\Temp\Servers.txt
foreach($server in $servers)
{
if (Test-Connection -ComputerName $server -Quiet)
{
$wmi = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $server).Name
Write-Host "$server responds: WMI reports the name is: $wmi"
}
else
{
Write-Host "***$server ERROR - Not responding***"
}
}
Use powershell jobs:
$scriptblock = {
Param($server)
IF (Test-Connection $server -Quiet){
$wmi = (gwmi win32_computersystem -ComputerName $server).Name
Write-Host "***$server responds: WMI reports the name is: $wmi"
} ELSE { Write-Host "***$server ERROR -Not responding***" }
}
$servers | % {Start-Job -Scriptblock $scriptblock -ArgumentList $_ | Out-Null}
Get-Job | Wait-Job | Receive-Job