How to run Powershell on a list of computers - powershell

I'm trying to run a remote command on a list of computers that available in plain text file (1 computer per line) in a file named 1.txt available under c:\1\1.txt.
What I run the powershell script the variable $comp is being run as $comp instead of being changed to the computer name
$computers = Get-Content c:\1\1.txt
foreach ($comp in $computers){
$LicenseInfo = Get-WmiObject SoftwareLicensingProduct -ComputerName $comp | Where-Object { $_.partialProductKey -and $_.ApplicationID -eq "55c92734-d682-4d71-983e-d6ec3f16059f" } | Select-Object PartialProductKey, Description, ProductKeyChannel, #{ N = "LicenseStatus"; E = { $lstat["$($_.LicenseStatus)"] } }
echo $LicenseInfo, $comp
}
run the powershell command with the Computername $comp - where $comp will be changed everytime in the loop for another name of a computer available in the c:\1\1.txt file

The reason you are getting the Select-Object error is because echo is not a command in Powershell that is for batch. Powershell uses Write-Host There is more information here:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-host?view=powershell-4.0
As far as the code adding a delimiter switch to get-content will separate each computer name
Get-Content -Path c:\1\1.txt -Delimiter `r
The `r stands for a carriage return

Depending on how you want the output, you could try like this:
foreach ($comp in $computers) {
$LicenseInfo = Get-WmiObject SoftwareLicensingProduct -ComputerName $comp | Where-Object { $_.partialProductKey -and $_.ApplicationID -eq "55c92734-d682-4d71-983e-d6ec3f16059f" } | Select-Object PartialProductKey, Description, ProductKeyChannel, #{ N = "LicenseStatus"; E = { $lstat["$($_.LicenseStatus)"] } }
$LicenseInfo
$comp
}
The positional parameter error you're getting in your code is because you're giving echo variables and text, and the text isn't encapsulated in quotes.
You don't need echo or even Write-Host here if you just want to output the contents of your variables.

Related

Store Each Line of PowerShell Output as same variable

So, I'm trying to remotely remove some manually added printer from a separate machine. Problem is, I don't want to remove all of the printers, only ones that have "HJK" or "LSG" anywhere in the name.
So, to get the list of names, I do:
Invoke-Command AaronsComputer -ScriptBlock {get-printer | select name -expandproperty name}
and to delete the printer, I would do:
Invoke-Command AaronsComputer -ScriptBlock {Remove-Printer -Name "Full Printer Name Here"}
I know I can export the results of get-printer as a CSV to work with it better but I'd rather not do that and have it all happen in Powershell.
I thought something along these lines would work but I don't think PowerShell intelligently sees each line as a variable.
$PrinterResults = Invoke-Command $Computer -ScriptBlock {get-printer | select name -expandproperty name}
foreach $PrinterResults
if ($PrinterResults -contains "HJK") {
Invoke-Command $Computer -ScriptBlock {Remove-Printer -Name "$PrinterResults"}
}
if ($PrinterResults -contains "LSG") {
Invoke-Command $Computer -ScriptBlock {Remove-Printer -Name "$PrinterResults"}
}
The end goal is that I can delete all printers that match the criteria in one go.
Managed to get this done in the end with the below:
Invoke-Command $Computer -ScriptBlock {
$Printers = get-printer | Where-Object {($_.name -like '*HJK*') -or ($_.name -like '*LSG*')} | Select Name -expandproperty name
foreach ($PrinterName in $Printers)
{
printui.exe /dl /n $PrinterName
}
}

How can I get psloggedon to pull from Active Directory?

get-content 'C:\assets.txt' | % {
$computer = $_
. 'c:\PSTools\PsLoggedon.exe' -accepteula -l -x \\$Computer 3>$null |
? {$_ -match '^\s{2,}((?<domain>\w+)\\(?<user>\S+))'} |
Select-Object `
#{n='Computer';e={$Computer}},
#{n='Domain';e={$matches.Domain}},
#{n='User';e={$Matches.User}} |
? user -notmatch '^Connecting$|^Users$|^NT$'
}
This is what I am using to get all of the currently logged on computers. Is there a way I can combine this with Get-ADUser so I ca pull straight from AD rather than from a txt document?
• Sorry, but currently there is no way through which you can integrate this ‘Psloggedon.exe’ utility with Active directory commands, i.e., ‘Get-AdUser’. But you can retrieve the details of currently logged on users on different computers in the network remotely by executing the below powershell function: -
‘ 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
}
} ‘
The above script will give you currently logged on users on several computer systems in the network that you pass on in place of ‘COMPUTERNAME’ as below. Please note that you must give a list of computers separated by commas when using the above script for multiple computer systems.
If you have AD in your environment, then you can check the Domain Controller logs to see when an Active Directory user account logs on and it will also tell the machine that the user is logged onto. Refer the below links for more on this: -
http://technet.microsoft.com/en-us/library/cc787176(v=ws.10).aspx
http://technet.microsoft.com/en-us/library/bb742435.aspx
http://www.windowsecurity.com/articles/deciphering-authentication-events-domain-controllers.html
Also, find the below link for more information and reference on the above: -
https://4sysops.com/archives/how-to-find-a-logged-in-user-remotely-using-powershell/
Powershell script to see currently logged in users (domain and machine) + status (active, idle, away)
You would use PowerShell's Get-ADComputer to do this job, not Get-ADUser. Here's a script which does all this work for you. The below mainly lifted from the public domain here and only slightly modified. It pulls and pipes all AD domain computers into C:\Computers.txt, then PS-remotes into each computer in that list to find the logged in, interactive user, and their last login date. Gives you a report file named C:\LoggedOnResults.txt in a nice tabled format.
# Finds and pipes all AD domain computers into Computers.txt, then PS-remotes into each computer in the list to find the logged in, interactive user, and their last login date. Generates a report file named C:\LoggedOnResults.txt, in a nice tabled format.
# Deletes the current file C:\Computers.txt (if it exists)
$FileName = "C:\Computers.txt"
if (Test-Path $FileName) {
Remove-Item $FileName
write-host "$FileName has been deleted"
}
else {
Write-host "$FileName doesn't exist"
}
# 0. Capture all AD computers into a text file named Computers.txt
# importing dependancy, assuming it's already installed.
# Install RSAT for Windows workstation, AD DS role for Windows Server if missing
Import-Module "ActiveDirectory"
Get-ADComputer -Filter {(OperatingSystem -like "*windows*") -and (Enabled -eq "True")} | Select -Expand Name | Out-File "C:\Computers.txt"
# 1. Create scriptblock to target computer will execute
$SB = {
$explorerprocesses = #(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
if ($explorerprocesses.Count -eq 0) {
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME;
Username = [string]::Empty
LoggedOnSince = [string]::Empty
}
} else {
foreach ($i in $explorerprocesses) {
$Username = $i.GetOwner().User
$Domain = $i.GetOwner().Domain
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME ;
Username = '{0}\{1}' -f $Domain,$Username ;
LoggedOnSince = ($i.ConvertToDateTime($i.CreationDate)) ;
}
}
}
} # endof scriptblock
# 2. Create an empty array to store results
$results = #()
# 3. Query target computers using PSRemoting
Get-content "C:\Computers.txt" | ForEach-Object -Process {
$computer = $_
try {
$results += Invoke-Command -ComputerName $Computer -ScriptBlock $SB -ErrorAction Stop
} catch {
Write-Warning -Message "Faild to use PSremoting on $Computer because $($_.Exception.Message)"
}
}
# 4. Display the results
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize
# 5. Send results to a text file
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize | Out-File -FilePath "C:\LoggedOnResults.txt"

Powershell - Export CSV file correctly

I hope someone can help me with this. We want to see which computers have a HDD and SDD. I have an excel.csv of the computers. I import the computers. But when I export them I never see the csv or its incomplete. Can you tell what part of my script is incorrect. Thank you
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers) {
Write-Host "`nPulling Physical Drive(s) for $computer"
if((Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet)){
Invoke-Command -ComputerName $computer -ScriptBlock {
Get-WmiObject -Class MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage | Select-Object sort -Property PSComputerName, Model, SerialNumber, MediaType
Export-Csv C:\Temp\devices.csv
}
}
}
Update: 11/11/2021
Thank you everyone for you help
This script worked for me:
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\Computers.csv"
{} | Select "ComputerName", "Status", "Model", "SerialNumber", "MediaType" | Export-Csv $ExportTo
$data = Import-csv -path $ExportTo
foreach ($computer in $computers) {
$Online = Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer.computer -Quiet
if ($Online) {
Write-Host $computer.computer " is Online"
$OutputMessage = Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer.computer | Select-Object -Property PSComputerName,#{N='Status';E={'Online'}}, Model, SerialNumber, MediaType
$data.ComputerName = $computer.computer
$data.Status = $OutputMessage.Status
$data.Model = $OutputMessage.Model
$data.SerialNumber = $OutputMessage.SerialNumber
$data.MediaType = $OutputMessage.MediaType
$data | Export-Csv -Path $ExportTo -Append -NoTypeInformation
} else {
Write-Host $computer.computer " is Offline"
$data.ComputerName = $computer.computer
$data.Status = "Offline"
$data.Model = ""
$data.SerialNumber = ""
$data.MediaType = ""
$data | Export-Csv -Path $ExportTo -Append -NoTypeInformation
}
}
Continuing from my comment. . . as is, you would be exporting the results to the remote machine. That's if it was piped properly. You're currently missing a pipe (|) before Export-Csv.
Also, there's no need to invoke the command, as Get-WMIObject has a parameter for remote computers: -ComputerName. It's also a deprecated cmdlet that has been replaced by Get-CimInstance.
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers)
{
Write-Host "`nPulling Physical Drive(s) for $computer"
if (Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet) {
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer |
Select-Object -Property PSComputerName, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation
}
}
Side Note: Get-CimInstance accepts an array of strings, meaning you can pass the entirety of $Computers to it. This should allow it to perform the the query in parallel, vs serial (one at a time):
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computers -ErrorAction SilentlyContinue |
Select-Object -Property PSComputerName, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation
Performing queries one at a time doesn't necessarily mean it's bad. You can actually have more control over the control of flow for your script.
EDIT:
Following up on your comment...you're no longer using your if statement to check if the computer is online before connecting. So given that you keep the if statement, and add an else condition, you can create a calculated property to add another property to export of Status. Then, you can pass it a value of Online, or Offline depending on if the machine is online or not:
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers)
{
if (Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet) {
Write-Host -Object "`nPulling Physical Drive(s) for $computer"
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer |
Select-Object -Property PSComputerName,#{N='Status';E={'Online'}}, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation -Force
}
else {
Write-Host -Object "`n$Computer is Offline"
[PSCustomObject]#{PSComputerName=$Computer;Status='Offline'} | Export-Csv -Path $ExportTo -Append -Force
}
}
Also:
Always remember that even if you can ping a machine, it doesn't mean you can connect to it.
This can be mitigated by using a CIM Session, or PSSession depending on the type of commands you're running.
To specifically answer the question:
How do I correctly export a CSV file (use Export-Csv)?
You might want to read about PowerShell pipelines and PowerShell cmdlets.
Basically, a cmdlet is a single command that participates in the pipeline semantics of PowerShell. A well written cmdlet is implemented for the Middle of a Pipeline which means that it processes ("streams") each individual item received from the previous cmdlet and passes it immediately to the next cmdlet (similar to how items are processed in an assembly line where you can compare each assembly station as a cmdlet).
To better show this, I have created an easier minimal, complete and verifiable example (MVCE) and replaced your remote command (Invoke-Command ...) which just an fake [pscustomobject]#{ ... } object.
With that;
I have used Get-Content rather then Import-Csv as your example suggest that Computers.csv is actually a text file which list of computers and not a Csv file which would require a (e.g. Name) header and using this property accordingly (like $Computer.Name).
To enforce the pipeline advantage/understanding, I am also using the ForEach-Object cmdlet rather than the foreach statement which is usually considered faster but this is probably not the case here as for the foreach statement it is required to preload all $Computers into memory where a well written pipeline will immediately start processing each item (which in your case happens on a remote computer) while still retrieving the next computer name from the file.
Now, coming back on the question "How do I correctly export a CSV file" which a better understanding of the pipeline, you might place Export-Csv within the foreach loop::
Get-Content .\Computers.txt |ForEach-Object {
[pscustomobject]#{
PSComputerName = $_
Model = "Model"
SerialNumber = '{0:000000}' -f (Get-Random 999999)
MediaType = "MydiaType"
} |Export-Csv .\Devices.csv -Append
}
As commented by #lit, this would require the -Append switch which might not be desired as every time you rerun your script this would append the results to the .\Devices.csv file.
Instead you might actually want do this:
Get-Content .\Computers.txt |ForEach-Object {
[pscustomobject]#{
PSComputerName = $_
Model = "Model"
SerialNumber = '{0:000000}' -f (Get-Random 999999)
MediaType = "MydiaType"
}
} |Export-Csv .\Devices.csv
Note the differences: the Export-Csv is placed outside the loop and the -Append switch is removed.
Explanation
As with e.g. the ForEach-Object cmdlet, the Export-Csv cmdlet has internally Begin, Process and End blocks.
In the Begin block (which runs when the pipeline is started), the Export-Csv cmdlet prepares the csv file with a header row etc. and overwrites any existing file.
In the Process block (which runs for each item received from the pipeline) it appends each line (data record) to the file.

Delete a list of User Profiles

(I am a newbie to PS who has mostly used VBS and Batch so I am still working on following PS scripts)
I need to delete most (but not all) domain accounts off of all 500 of our systems.
Some of these are from a specific list.
Some follow a generic format *21, *19, etc...
I can find scripts that will let me delete a specific user account but I can't figure out how to pass it the long list or use the wildcards...
This one seems promising if I can figure out how to get the values need in it...
:: This Script taken from https://www.nextofwindows.com/delete-user-profiles-on-a-remote-computer-in-powershell
$Computer = Read-Host "Please Enter Computer Name: "
$user = Read-Host "Enter User ID: "
Invoke-Command -ComputerName $computer -ScriptBlock {
param($user)
$localpath = 'c:\users\' + $user
Get-WmiObject -Class Win32_UserProfile | Where-Object {$_.LocalPath -eq $localpath} |
Remove-WmiObject
} -ArgumentList $user
It sounds like you are most of the way there. Just use some pipelines and a foreach loop or two.
This will attempt to remove all users in the list from all computers in the list:
# define function that takes user list from pipeline
function Remove-User {
[cmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true,Mandatory=$true)]
[string]
$user,
[Parameter(Mandatory=$true)]
[string]
$computer
)
process {
# copied from question
Invoke-Command -ComputerName $computer -ScriptBlock {
Get-WmiObject -Class Win32_UserProfile |
Where-Object { $_.LocalPath -eq "c:\users\${$user}" } |
Remove-WmiObject
} -ArgumentList $user
}
}
# get your lists whatever way makes sense
$userList = Import-Csv -Path "users.csv" -Delimiter ','
$computerList = Import-Csv -Path "computers.csv" -Delimiter ','
# call function to remove all users for each computer
$computerList | ForEach-Object {
$userList | Remove-User -computer $_
}
I'm not sure where you get your list from, but I used a csv just because.
*note: This is going off the assumption that the Invoke-Command portion of code from nextOfWindows does what it says

PowerShell - iterating over computer names in Active Directory

I'm new to PowerShell and I'm attempting to write a script that will query AD for machine names, check which ones are responding and write the output into a file. So far I have this:
$computers = Get-ADComputer -filter {(Name -like "PC*")} | Select-Object -Property Name
foreach ($computer in $computers) {
if((Test-Connection -Cn $computer -BufferSize 16 -Count 1 -Ea 0 -Quiet)) {
"Machine $computer connected." | Out-File "out.txt" -Append
} else {
"Machine $computer not connected." | Out-File "out.txt" -Append
} #end if
} #end foreach
What I get in the output text file looks like the following:
...
Machine #{Name=PC-0649} not connected.
Machine #{Name=PC-1541} not connected.
Machine #{Name=PC-1574} not connected.
...
I think my problem lies with the Select-Object -Property Name part of the first line. Running the debugger, it looks like PowerShell is formatting each iteration of $computer to include the header line.
[DBG]: PS Y:\>> $computer
Name
----
PC-0649
What's the best way for me to strip out everything except the PC-#### part in this situation?
I think your problem is that you still have a list of (truncated) computer objects in $computers. Verify this by doing $computers[0].GetType(). If you don't see String, it's not a string. :) Try this instead:
$computers = Get-ADComputer -filter {(Name -like "PC*")} |
Select-Object -ExpandProperty Name