PowerShell Script to Ping Computers Works but Give Errors - windows

The script below works and produces output file but gives errors. I've looked at other questions and answers that addresses the same problem in a similar way but I'm still not able to figure out how to fix this problem. I've done this with a splat before but not quite sure how to make it work in this script. All help would be appreciated. Error is first and script is below that.
Error below
Test-Connection : Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command
again.
At C:\Temp\PowerShellScripts\PingComputers\colorfulPingMultipleComputers.ps1:6 char:37
+ if (Test-Connection -ComputerName $name -Count 1 -ErrorAction Silen ...
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Test-Connection], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand
Script below
$Output= #()
$names = Get-content C:\Temp\Computers.txt
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
$Output+= "$name, Pinging"
Write-Host -ForegroundColor Cyan "$Name, Pinging"
}
else{
$Output+= "$name, Not Pinging"
Write-Host Write-Host -ForegroundColor Red "$Name, Not Pinging"
}
}
$Output | Out-file "C:\Temp\PingComputers.csv"
The script produces this output, computer names changed for privacy. So it does what I want it to do, but it still gives the errors as indicated above for each machine
AAA-L-CWCXRQ2, Pinging
BBB-L-DW2FQQ2, Pinging
CCC-L-JN3RM72, Pinging
DDD-L-34SQRQ2, Pinging
EEE-L-84F3ZM2, Pinging
Write-Host FFF-L-1B1CM72, Not Pinging
GGG-L-94XXRQ2, Pinging
Write-Host FFF-L-D5KXRQ2, Not Pinging
Write-Host GGG-L-75SX4Q2, Not Pinging

My guess is you have a blank line at the end of your text file. See this example.
$tempfile = New-TemporaryFile
#'
google.com
bing.com
'# | Set-Content $tempfile -Encoding UTF8
Get-Content $tempfile | foreach {
Test-Connection -ComputerName $_ -Count 1 -ErrorAction SilentlyContinue
}
I'd recommend just changing this line
$names = Get-content C:\Temp\Computers.txt
to
$names = Get-content C:\Temp\Computers.txt | Where-Object {$_}
which will filter out blank lines

Related

Folder audit Powershell script, nested loop

I am trying to get a script working to audit folder permissions on a Windows server, among other data, and export this data to a CSV file for analysis after a ransomware attack.
I ripped the script from a forum, but it did not run correctly as is. Below is a slightly modified version during my troubleshooting.
I am well versed in batch scripting, and have a decent understanding of loops and pipelining, but this Powershell script has me scratching my head.
It seems like the array is not making it to the nested loop.
I am testing in Windows 10 Pro 21H1, using Powershell version 5.1.19041.1320, build 10.0.19041.1320
##The script:
$ErrorActionPreference = "Continue"
$strComputer = $env:ComputerName
$colDrives = Get-PSDrive -PSProvider Filesystem
ForEach ($DriveLetter in $colDrives) {
$StartPath = "$DriveLetter`:\"
Get-ChildItem -LiteralPath $StartPath -Recurse | ?{ $_.PSIsContainer } |
ForEach ($FullPath = Get-Item -LiteralPath{Get-Item -LiteralPath $_.PSPath}{Get-Item -
LiteralPath $FullPath}.Directoryinfo.GetAccessControl())}
Select #{N='Server Name';E={$strComputer}}
#{N='Full Path';E={$FullPath}}
#{N='Type';E={If($FullPath.PSIsContainer -eq $True) {'D'} Else {'F'}}}
#{N='Owner';E={$_.Owner}}
#{N='Trustee';E={$_.IdentityReference}}
#{N='Inherited';E={$_.IsInherited}}
#{N='Inheritance Flags';E={$_.InheritanceFlags}}
#{N='Ace Flags';E={$_.PropagationFlags}}
#{N='Ace Type';E={$_.AccessControlType}}
#{N='Access Masks';E={$_.FileSystemRights}}
Export-CSV -NoTypeInformation -Delimiter "|" -Path "$strComputer`_$DriveLetter.csv"
##The error I am getting:
You cannot call a method on a null-valued expression.
At C:\Users\user\Documents\fileaudit2.ps1:8 char:13
ForEach ($FullPath = Get-Item -LiteralPath{Get-Item -LiteralPath $ ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : InvokeMethodOnNull
##when I modify the nested loop as follows:
ForEach ($FullPath = Get-Item -LiteralPath{Get-Item -LiteralPath $_.PSPath}{Get-Item -LiteralPath $FullPath}).Directoryinfo.GetAccessControl()}
##I get the error:
Get-Item : Cannot evaluate parameter 'LiteralPath' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without
input.
At C:\Users\user\Documents\fileaudit2.ps1:8 char:46
... Path = Get-Item -LiteralPath{Get-Item -LiteralPath $_.PSPath}{Get-Ite ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : MetadataError: (:) [Get-Item], ParameterBindingException
FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.GetItemCommand
##I'm just wholly struggling to understand what is not working in this loop.
You are mixing a lot of unneeded Get-Item calls in there.
I also would not use Get-PSDrive for this because I assume you don't want to get results for CD drives, USB devices etc in the report.
Try:
# this returns drives WITH a trailing backslash like C:\
$colDrives = ([System.IO.DriveInfo]::GetDrives() | Where-Object { $_.DriveType -eq 'Fixed' }).Name
# or use:
# this returns drives WITHOUT trailing backslash like C:
# $colDrives = (Get-CimInstance -ClassName win32_logicaldisk | Where-Object { $_.DriveType -eq 3 }).DeviceID
$result = foreach ($drive in $colDrives) {
Get-ChildItem -LiteralPath $drive -Directory -Recurse -ErrorAction SilentlyContinue |
ForEach-Object {
$path = $_.FullName
$acl = Get-Acl -Path $path
foreach ($access in $acl.Access) {
[PsCustomObject]#{
Server = $env:COMPUTERNAME
Drive = $drive[0] # just the first character of the drive
Directory = $path
Owner = $acl.Owner
Trustee = $access.IdentityReference
Inherited = $access.IsInherited
InheritanceFlags = $access.InheritanceFlags -join ', '
'Ace Flags' = $access.PropagationFlags -join ', '
'Ace Type' = $access.AccessControlType
'Access Masks' = $access.FileSystemRights -join ', '
}
}
}
}
# now you can save your result as CSV file for instance you can double-click to open in Excel:
$result | Export-Csv -Path 'X:\WhereEver\audit.csv' -NoTypeInformation -UseCulture
To do this on several remote machines, wrap it inside Invoke-Command
# set the credentials for admin access on the servers
$cred = Get-Credential 'Please enter your admin credentials'
# create an array of the servers you need to probe
$servers = 'Server01', 'Server02'
$result = Invoke-Command -ComputerName $servers -Credential $cred -ScriptBlock {
$colDrives = ([System.IO.DriveInfo]::GetDrives() | Where-Object { $_.DriveType -eq 'Fixed' }).Name
foreach ($drive in $colDrives) {
# code inside this loop unchanged as above
}
}
# remove the extra properties PowerShell added
$result = $result | Select-Object * -ExcludeProperty PS*, RunspaceId
# output to csv file
$result | Export-Csv -Path 'X:\WhereEver\audit.csv' -NoTypeInformation -UseCulture

How can I add the device names that were not scanned (offline etc) by a PowerShell script

Very very much a PowerShell newbie here I wanted a script to scan devices on the network and report on Local Admins. Found one out there and made some minor modifications to meet my needs - but I have one mod I cant work out how to do. Hoping someone out there will know a simple way to do it ?
The scrip below will read in a list of device names - scan them and output a dated report for all devices that are live and on-line. If the device is not accessible I get the following error on screen but nothing in the report.
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!"
The code I am using is
$date = Get-Date -Format o | foreach {$_ -replace ":", "."}
ECHO "Starting scan"
$Result = #()
foreach($server in (gc .\servers.txt)){
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$Filename = "c:\" + "LocalAdminAudit" + $date + ".txt"
function getAdmins
{
ECHO "SEARCHING FOR DEVICE"
$members = ($Group.psbase.invoke(”Members”) | %
{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) -
replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/',
'DOMAIN\') -replace ('WinNT://', '')
$members}
ECHO "READY TO WRITE OUTPUT"
$Result += Write-Output "SERVER: $server"
$Result += Write-Output ' '
$Result += ( getAdmins )
$Result += Write-Output '____________________________'
$Result += Write-Output ' '
ECHO "Record written"
}
# Added date run to report
$result += Write-Output "Date Reported: $date"
$Result > $Filename
Invoke-Item $Filename
# replace "DOMAIN" with the domain name.
ECHO "Scan Complete"
And the on screen error when a machine is off line or otherwise doesn't respond is
Exception calling "Find" with "1" argument(s): "The network path was not found.
"
At \server\users\User.Name\Powershell Scripts\Get-Local-AdminsV3.ps1:1
0 char:40
+ $Group = $computer.psbase.children.find <<<< (”Administrators”)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!" - I am pretty sure there must be an easy way of doing this - but I cant work it out so any tips would be greatly appreciated
As Matt, mentioned in the comments. You can use a Try/Catch block inside your function to catch the error.
I also made some other changes. The most major is that I changed the function to contain all of the code necessary to get the local administrator group. Then the loop just calls the function once per computer with the computer name. This function is then reusable.
Secondly rather than output to a text file, I changed to outputting to a CSV as is a more structured format that can be used better later.
Also rather than relying on writing to the console host, I used Write-Progress to report the progress of the loop.
$Servers = Get-Content .\servers.txt
$ExportFileName = "c:\LocalAdminAudit$date.csv"
function Get-LocalAdministrator {
[cmdletbinding()]
Param(
$ComputerName
)
$Group = [ADSI]("WinNT://$computername/Administrators,group")
try {
$Group.Invoke("Members") | ForEach-Object {
$User = ($_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null) -split '/')[-2,-1] -join '\'
[PSCustomObject]#{
"User" = $User
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
catch {
[PSCustomObject]#{
"User" = "Failed to Report"
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
$LocalAdmins = foreach ($Server in $Servers) {
Write-Progress -Activity "Retrieving Local Administrators" -Status "Checking $Server" -PercentComplete (([array]::indexof($Servers,$Server)/($Server.count))*100)
Get-LocalAdministrator $Server
}
$LocalAdmins | Export-CSV $ExportFileName -NoTypeInformation
Invoke-Item $ExportFileName
Lastly, be careful of smart quotes especially when cutting and pasting between Outlook and word.

Check If a program is installed on multiple computers using Powershell

I'm having issue with a script I've written and would love some help.
Please note I'm very new to powershell.
I've written a script that uses a txt file that contains remote computers on a domain, I appears to be working to some degree but in the event of a machine being offline I get errors which then loop the script.
$machines
$pcname
Name = 'Machine'
Expression = { $_.PsComputerName }
}
ForEach ($System in $Machines)
{
#Pings machine's found in text file
if (!(test-Connection -ComputerName $System -BufferSize 16 -Count 1 -ea 0 -Quiet))
{
Write-Output "$System Offline"
}
Else
{
#Providing the machine is reachable
#Checks installed programs for products that contain Kaspersky in the name
gwmi win32_product -Filter {Name like "%Kaspersky%"} -ComputerName $Machines | Select-Object -Property $pcname,Name,Version
}
}
At present this runs and output's like so:
Machine Name Version
UKTEST01 Kaspersky Security Center Network Agent 10.1.249
UKTEST02 Kaspersky Endpoint Security 10 for Windows 10.2.1.23
But in the event of a machine not being reachable the following error is given:
gwmi : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
At C:\Scripts\Powershell\Kaspersky Endpoint Security 10\Script\New folder\Kaspersky Checker working v2.ps1:15 char:9
+ gwmi win32_product -Filter {Name like "%Kaspersky%"} -ComputerName $Mach ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], COMException
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
And then moves to the next machine in the list, and then repeats from the beginning again.
I'd like for this to simply show as:
UKTEST03 Offline
And stop once the last machine in the txt file is done.
Any help or advise would be greatly appreciated.
This is the perfect time to use a Try/Catch/Finally block. The flow is this : Try the block of code here, if you encounter an error, suppress the message and do what is in the Catch block instead.
I've modified your code a bit, so simply copy this whole code block and drop it in, replacing your Else {scriptblock} in your original code.
Else
{
#Providing the machine is reachable
#Checks installed programs for products that contain Kaspersky in the name
Try {Get-WMIObject -Class win32_product -Filter {Name like "%Kaspersky%"} `
-ComputerName $Machines -ErrorAction STOP |
Select-Object -Property $pcname,Name,Version }
Catch {#If an error, do this instead
Write-Output "$system Offline }
}
}
Your completed answer
I've folded in the change you requested, to keep your script from running on every machine in $machines instead of $system, as you likely intended.
ForEach ($System in $Machines){
#Pings machine's found in text file
if (!(test-Connection -ComputerName $System -BufferSize 16 -Count 1 -ea 0 -Quiet))
{
Write-Output "$System Offline"
}
Else
{
#Providing the machine is reachable
#Checks installed programs for products that contain Kaspersky in the name
Try {Get-WMIObject -Class win32_product -Filter {Name like "%Kaspersky%"} `
-ComputerName $System -ErrorAction STOP |
Select-Object -Property $pcname,Name,Version }
Catch {#If an error, do this instead
Write-Output "$system Offline "}
#EndofElse
}
#EndofForEach
}
You could try this:
$machines=... # your machines' names
foreach ($machine in $machines)
{
trap{"$machine`: not reachable or not running WsMan";continue}
if(test-wsman -ComputerName $machine -ea stop){
gcim -Class CIM_Product -Filter 'Name like "%Kaspersky%"' |
select pscomputername,name,version
}
}
I'm using gcim because gwmi is deprecated.
Correction: the correct name is Kaspersky; I corrected it.

'Session' Parameter is null or empty in PowerShell script

I'm very new to PowerShell (forgive my ignorance) and am trying to install a program remotely on multiple computers on my domain. Currently, I'm just trying to get it to work on one computer. The script below was found online and adapted for my needs. Yesterday it worked, but today it's complaining about the session parameter.
I don't fully understand "sessions", but I have ensured on the client machine that the winrm service is running and I have invoked Enable-PSRemoting -force.
Here's the script:
$computers = Get-Content "c:\tmpPS\computers.txt"
$rs = Get-PSSession
Get-PSSession | Get-Member
######
## Functions
################
foreach ($comp in $computers)
{
Write-Host "should work with $comp"
}
PushMSI
RemoteConnect
InstallMSI
Function PushMSI {
Write-Host "------------------------------------------------"
Write-Host "This will copy the MSI file from localhost c:\tmpPS\"
write-Host "------------------------------------------------"
Write-Host ""
Write-Host ""
foreach ($comp in $computers)
{
Copy-Item -path "c:\tmpPS\clientInstall.msi" -Destination \\$comp\c$\tmpPS
}
}
Function RemoteConnect
{
Write-Host "------------------------------------------------"
Write-Host "This will establish a PSSession with all computers in c:\temp\computers.txt"
write-Host "------------------------------------------------"
Write-Host ""
Write-Host ""
Get-Content C:\tmpPS\computers.txt | New-PSSession -ThrottleLimit 50
}
Function InstallMSI
{
Write-Host "------------------------------------------------"
Write-Host "This will Install UPS Update on all computers with an Established PSSession"
write-Host "------------------------------------------------"
Write-Host "After the Install PSSessions will be removed"
Write-Host ""
Invoke-Command -Session $rs -ScriptBlock {invoke-item "c:\tmpPS\ClientInstall.msi"}
}
Get-PSSession | Remove-PSSession
And here's the output:
PS C:\Users\Me> C:\tmpPS\remoteInstall.ps1
Get-Member : No object has been specified to the get-member cmdlet.
At C:\tmpPS\remoteInstall.ps1:3 char:17
+ Get-PSSession | Get-Member
+ ~~~~~~~~~~
+ CategoryInfo : CloseError: (:) [Get-Member], InvalidOperationException
+ FullyQualifiedErrorId : NoObjectInGetMember,Microsoft.PowerShell.Commands.GetMemberCommand
should work with eSignWin81.informa.local
------------------------------------------------
This will copy the MSI file from localhost c:\tmpPS\
------------------------------------------------
------------------------------------------------
This will establish a PSSession with all computers in c:\temp\computers.txt
------------------------------------------------
Id Name ComputerName State ConfigurationName Availability
-- ---- ------------ ----- ----------------- ------------
6 Session6 eSignWin81.i... Opened Microsoft.PowerShell Available
------------------------------------------------
This will Install UPS Update on all computers with an Established PSSession
------------------------------------------------
After the Install PSSessions will be removed
Invoke-Command : Cannot validate argument on parameter 'Session'. The argument is null or empty. Supply an argument that is not null or empty and then try
the command again.
At C:\tmpPS\remoteInstall.ps1:49 char:25
+ Invoke-Command -Session $rs -ScriptBlock {invoke-item "c:\tmpPS\ClientInstall.ms ...
+ ~~~
+ CategoryInfo : InvalidData: (:) [Invoke-Command], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeCommandCommand
Try not to use Write-Host as it brakes the pipeline and kills puppy's.
I've tried to improve your script a little bit, so you understand the logic behind the structure better. It hasn't been tested, but it should do the trick.
# First parameters
[CmdletBinding()]
Param(
[ValidateScript({Test-Path $_ -PathType leaf})]
$ComputerList = "c:\tmpPS\computers.txt",
[ValidateScript({Test-Path $_ -PathType leaf})]
$MSI = "c:\tmpPS\clientInstall.msi"
)
# Then functions
Begin {
Function Copy-MSI {
foreach ($Com in $Computers) {
Copy-Item -path $MSI -Destination "\\$Com\c$\tmpPS"
}
}
Function Install-MSI {
foreach ($Com in $Computers) {
Enter-PSSession -ComputerName $Com
invoke-item "c:\tmpPS\ClientInstall.msi"
Exit-PSSession
}
}
}
# Then the actions
Process {
$Computers = Get-Content $ComputerList
Copy-MSI
Install-MSI
}
If you run this, you'll find the information you're looking for:
Get-Help Enter-PSSession

If Then Else broke my script

Currently I have this code -
Set-ExecutionPolicy Unrestricted
$name = (Get-WmiObject win32_bios).SerialNumber.Trim()
$oldname = (Get-WmiObject win32_computersystem).Name.Trim()
IF ($oldname -eq $name){Exit}
Else{ Rename-computer -ComputerName $oldname -NewName "$name" -force
Start-Sleep -s 5
Restart-Computer}
And I have it set to run as a scheduled task at logon and without the If Else it works perfectly however I don't want it to run every time a user logs in because it will just be a cycle of rebooting. Any help would be greatly appreciated.
I would suggest some changes:
Set-ExecutionPolicy is unnecessary, because if the machine has started processing the script, then the executionpolicy isn't a problem. So remove that, and specify it in the powershell.exe-call instead, like: powershell.exe -executionpolicy unrestricted
Use if($oldname -ne $name) { rename-computer .... } so you can remove the else part. Much cleaner
Try running the modified script below, and report back with the output in the scriptlog.txt-file.
$logpath = "c:\scriptlog.txt"
$name = (Get-WmiObject win32_bios).SerialNumber.Trim()
$oldname = (Get-WmiObject win32_computersystem).Name.Trim()
"NewName is '$name'" | Out-File $logpath -Append
"OldName is '$oldname'" | Out-File $logpath -Append
IF ($oldname -ne $name){
"If-test TRUE" | Out-File $logpath -Append
Rename-computer -ComputerName $oldname -NewName $name -Force
Start-Sleep -s 5
Restart-Computer
} else { #I've added the else-part just because of logging.
"IF-test FALSE" | Out-File $logpath -Append
}

Resources