I am having trouble with powershell reading my servers from a text file. When I declare a variable with get-content but when i call it, powershell doesnt read the server names. Here is the code.
$Server = Get-Content C:\servers.txt
Get-WmiObject -Class Win32_PerfFormattedData_PerfOS_System -ComputerName $Server |
Select-Object #{Name = “ComputerName”; Expression = {$_.__SERVER}},
#{Name = “SystemUpTime”; Expression = {New-TimeSpan -Seconds $_.SystemUpTime}}| Export-CSV C:\test2.txt.
If I change the $server to the list form It will work. Any ideas?
I changed the code so the servers are an array and its working now
Related
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.
I am trying to write a simple script to scan through a list of workstations, pull their local user info, and export it to a CSV. The code works for doing that, but I am having trouble including any identifier of what workstation the information is related to. At the moment I just get a large list of users and their info.
$computerList = gc "C:\Temp\ComputerList.txt"
ForEach ($Computer in $computerList){
Get-LocalUser | Export-Csv C:\temp\passUser.csv -NoTypeInformation -Append }
You need to add the property Computer to the objects that are output from Get-LocalUser.
Also, you are currently running Get-LocalUser on your own local machine in each iteration.
Try
$computerList = Get-Content "C:\Temp\ComputerList.txt"
Invoke-Command -ComputerName $computerList -ScriptBlock {
Get-LocalUser |
Select-Object #{Name = 'Computer'; Expression = {$env:ComputerName}}, *
} | Export-Csv -Path C:\temp\passUser.csv -NoTypeInformation
Invoke-Command can take an array of computernames.
It also has a -Credential parameter with which you can specify the credentials of a user that has permissions to perform the actions inside the scriptblock
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.
So having some good old fashion Powershell frustrations today. What I need to do is this:
Get a list of computers from a file
Query those computers for "CSName" and "InstallDate" from Win32_OperatingSystem
Convert InstallDate into a useable date format.
Export all that to a .Csv
I've tried so many different iterations of my script. I run into 2 major issues. One is that I can't export and append to .Csv even with Export-Csv -Append. It just takes the first value and does nothing with the rest. The 2nd is that I can't get the datetime converter to work when piping |.
Here's a few samples of what I've tried - none of which work.
This sample simply errors a lot. Doesn't seem to carry $_ over from the WMI query in the pipe. It looks like it is trying to use data from the first pipe, but I'm not sure.
Get-Content -Path .\Computernames.txt | Foreach-Object {
gwmi Win32_OperatingSystem -ComputerName $_) |
Select-Object $_.CSName, $_.ConvertToDateTime($OS.InstallDate).ToShortDateString()
} | Export-Csv -Path Filename -Force -Append -NoTypeInformation
}
This one simply exports the first value and gives up on the rest when exporting .Csv
$Computers = Get-Content -Path .\Computernames.txt
foreach ($Computer in $Computers) {
echo $Computer
$OS = gwmi Win32_OperatingSystem -ComputerName $Computer
$OS | Select-Object
$OS.CSName,$OS.ConvertToDateTime($OS.InstallDate).ToShortDateString() |
Export-Csv -Path $Log.FullName -Append
}
This one does get the data, but when I try to select anything, I get null values, but I can echo just fine.
$OS = gwmi Win32_OperatingSystem -ComputerName $Computers
$OS | Foreach-Object {
Select-Object $_.CSName,$_.ConvertToDateTime($OS.InstallDate).ToShortDateString() |
Export-Csv -Path $Log.FullName -Force -Append -NoTypeInformation
}
This feels like it should be ridiculously simple. I can do this in C# with almost no effort, but I just can't get PS to do what I want. Any help would be much appreciated!
Here you go,
$Array = #() ## Create Array to hold the Data
$Computers = Get-Content -Path .\Computernames.txt
foreach ($Computer in $Computers)
{
$Result = "" | Select CSName,InstallDate ## Create Object to hold the data
$OS = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer
$Result.CSName = $OS.CSName ## Add CSName to line1
$Result.InstallDate = $OS.ConvertToDateTime($OS.InstallDate).ToShortDateString() ## Add InstallDate to line2
$Array += $Result ## Add the data to the array
}
$Array = Export-Csv c:\file.csv -NoTypeInformation
The script reads in a .TXT file of computer names. It then checks to see if Windows Update is set to Auto or Manual; the computers name and update type are then exported to a CSV. At first, it was only exporting the final computer and it's type. I did some research and saw that creating an object to hold the name and type and then adding that object to an array would work. However, exporting the array to a CSV results in interesting data which is related to the array instead of the computer name and type. On top of that, I have the array printed out in the console pre-export and it looks to be the last computer and its type listed 9 times, the amount of computers on the one .TXT file I'm reading in. I know export-CSV now has an append parameter that could take care of this but we have much more PS 1.0 and 2.0 in the office and need it to work on those version.
clear-host
$item = new-object PSObject
$list = #()
$c=Get-Credential
$a = gc "c:\Scripts\pcList.txt"
ForEach ($b in $a)
{
$pcName = Get-WmiObject win32_service -filter 'name="wuauserv"' -computer $b -credential $c -ErrorAction SilentlyContinue |
select-object name,
#{N="System Name";E={$_.Systemname}}
$name = $pcName.'System Name'
$item | Add-Member NoteProperty Name $name -Force
$pcType = Get-WmiObject win32_service -filter 'name="wuauserv"' -computer $b - credential $c -ErrorAction SilentlyContinue |
select-object name, #{N="Startup Type";E={$_.StartMode}}
$type = $pcType.'Startup Type'
$item | Add-Member NoteProperty StartupType $type -Force
$list += $item
}
Write-Host $list
export-csv -Path "c:\Scripts\pcListCSV" -inputobject $list -notype
Ok, a couple things. The script looks fairly good really, all things considered, and a couple little tweaks and it should work fine. Let's approach this a tad different and just construct the object all at once instead of making it then adding noteproperties.
Also, we'll just make 1 call to GWMI (short for Get-WMIObject). I think this will work better for you. First off, making the GWMI call once means we have to select more than one thing when we make it. Since you are really making the exact same call twice, and selecting two different things from it each time, this just kind of makes sense. We're going to skip defining things in a select, and just let $pcName store all the data from the GWMI call. Though, first we're going to make sure that there's no old data in $pcName just to make sure we don't get duplicates.
clear-host
$item = new-object PSObject
$list = #()
$c=Get-Credential
$a = gc "c:\Scripts\pcList.txt"
ForEach ($b in $a)
{
if(Get-Variable pcName -ea SilentlyContinue){Remove-Variable pcName}
$pcName = Get-WmiObject win32_service -filter 'name="wuauserv"' -computer $b -credential $c -ErrorAction SilentlyContinue
Ok, now we create the object, and we'll just add it directly to the array. Here's where we define things like Name and StartupType as object properties, so when you display the array later it shows those two properties for each item. After that we'll close the ForEach loop and both display the list to the screen and pipe it to Export-CSV to save the results.
$list += New-Object PSObject -Property #{
Name = $pcName.SystemName
StartupType = $pcName.StartMode
}
}
$list
$list | Export-Csv "C:\Scripts\pcList.CSV" -notype
So, this doesn't check to make sure that the systems are online, or do any error checking like that, but if that's OK with you then I think this should do exactly what you need.