I'm attempting to write a script that gives me a running list of computers whose name starts with SOU-C128*. I'm assigning the list to a variable and using it as input for the restart-computer cmdlet. However, I'm recieving the error provided:
restart-computer : Computer name #{Name=SOU-C127-04} cannot be resolved with the exception: One or more errors occurred..
At \\nas\user\IT\restart.ps1:2 char:1
+ restart-computer -computername $computers -force -wsmanauthentication ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (#{Name=SOU-C127-04}:String) [Restart-Computer], InvalidOperationException
+ FullyQualifiedErrorId : AddressResolutionException,Microsoft.PowerShell.Commands.RestartComputerCommand
This is the script
$computers=Get-ADComputer -Filter * | Where-Object {$_.Name -like "sou-c127*"} | Select -Property Name | Sort Name
restart-computer -computername $computers -force -wsmanauthentication Kerberos
Please assist!
Thanks.
Your immediate fix would be to use $computers.name or, expand the property in your Get-ADComputer by using Select-Objet -ExpandProperty 'Name'; foreach name would suffice too.
Wanted to suggest something though. In PowerShell when you're filtering, you always want to filter as far left as possible. This can become computationally expensive running a pipeline filter such as Where-Object on a large list. In your case: Get-ADComputer -Filter *, you're filtering for every computer, then filtering again. You can cut that out by filtering using the filter in your Get-ADComputer cmdlet to begin with:
$computers = Get-ADComputer -Filter "Name -like 'sou-c127*'" | Sort-Object -Property 'Name'
Restart-Computer -ComputerName $computers.Name
Related
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
I'm trying to put together an expression that returns a bare value, without a table or name of the expression included.
For example, I have this line from another solution:
gwmi win32_logicaldisk -Filter "DeviceID = 'C:'" |
Format-Table #{n="Size";e={[math]::Round($_.Size/1GB,2)}}
This returns:
Size
----
475.33
How can I grab just the 475.33?
Use Select-Object instead of Format-Table:
$DiskC = gwmi win32_logicaldisk -Filter "DeviceID = 'C:'" | Select-Object #{n="Size";e={[math]::Round($_.Size/1GB,2)}}
$DiskC.Size
Format-* cmdlets return objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
gwmi is the built-in alias for the Get-WmiObject cmdlet, which is obsolete.
The CIM cmdlets (e.g., Get-CimInstance) superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell [Core] v6+, where all future effort will go, doesn't even have them anymore. For more information, see this answer.
If you simply want to calculate a value derived from (each of) your input object(s), use the ForEach-Object cmdlet.
Therefore:
Get-CimInstance win32_logicaldisk -Filter "DeviceID = 'C:'" | ForEach-Object {
[math]::Round($_.Size / 1gb, 2)
}
Or, more simply, using an expression:
[math]::Round(
(Get-CimInstance win32_logicaldisk -Filter "DeviceID = 'C:'").Size / 1gb, 2
)
If you use your first line:
gwmi win32_logicaldisk -Filter "DeviceID = 'C:'" | Format-Table #{n="Size";e={[math]::Round($_.Size/1GB,2)}}
But Pipe it trough Select-object instead of format table then pipe it again to select object expanding the property (or just .size)
gwmi win32_logicaldisk -Filter "DeviceID = 'C:'" | Select #{n="Size";e={[math]::Round($_.Size/1GB,2)}} | select -ExpandProperty Size
I am using the following command in PowerShell 2.0 to check if the particular folder is shared or not but I am getting an error.
[bool](Get-WmiObject -Class Win32_Share -ComputerName ravenPC -Filter "Path='D:\websites\website1'")
Also, I want to store the value of bool in a variable and check it each time if it's true or false. Can someone help me on this.
The error I get is as follows:
Get-WmiObject : Invalid query
At line:1 char:21
+ [bool](Get-WmiObject <<<< -Class Win32_Share -ComputerName ravenPC -Filter "Path='D:\websites\website1'")
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException
+ FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
You need to escape the backslashes in your WMI query:
[bool](Get-WmiObject -Class Win32_Share -ComputerName ravenPC -Filter "Path='D:\\websites\\website1'")
Can we defer a variable initialization untill it is needed ?
What I would like to do is predefine some variables in my profile that will contain a list of AD computer:
let's say I want:
$OU1_workstation to be fill with computers found in OU=workstations,OU=OU1,dc=domain,dc=com
$OU2_workstation fill with computers found in
OU=workstations,OU=OU2,dc=domain,dc=com and so on...
I use the following script to do it but it takes 30sec to compute, so currently I can't put that in my profile...
Get-ADOrganizationalUnit -SearchScope onelevel -Filter "*" -Properties "name","distinguishedname" |%{
set-Variable -Name "$($_.name)_workstation" -value (Get-ADComputer -Searchbase "OU=workstations,$($_.Distinguishedname)" -Filter * )
}
What options are available in powershell ?
Finally, based on #Richard's reply of a previous question of mine, I've chosen the following path to achieve some sort of lazy loading : using a scriptproperty of a PSCustomObject.
So I can put this in my profile
#requires -module activedirectory
$server=New-Object PSCustomObject
Get-ADOrganizationalUnit -SearchScope onelevel -Filter "*" -Properties "name","distinguishedname" |
?{
$_.name -notmatch 'Administrateurs|Administration|Comptes de deploiement|Contacts|Domain Controllers|Groupes|Serveurs|Services'
} |
%{
$OU=$_.name
$s=[scriptblock]::Create("Get-ADComputer -SearchBase ""OU=servers,OU=$OU,DC=domain,DC=com"" -Filter 'name -notlike "" *old""' |select -expand name")
$server| Add-Member -MemberType ScriptProperty -name $OU -value $s -Force
}
then when needed I can call $server.OU1 to get all server under this OU, $server.OU2 etc...
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.