I want to output all hostnames within a network first with a foreach loop, in order (for example) to be able to ping them.
However with the following code I do not get any output in the console. The CSV file will be saved, but what is written in the loop will not be executed.
Does anyone know what the reason for this is and how I can solve it?
Import-Module activedirectory
Get-ADComputer -Filter * -Property * | Select Name | Export-CSV -Path $env:TEMP\ZZZEXPORTE.csv -NoTypeInformation -Encoding UTF8 | ForEach {
$computerName = $_.Name
Write-Host $computerName
Write-Host "----"
}
This occurs because Export-CSV does not output an object. Sometimes cmdlets like this have a -PassThru parameter which you can use to have an object passed along, but thats not the case with Export-CSV, they simply expect it to always be the last cmdlet in the pipeline.
You should instead do this:
$Computers = Get-ADComputer -Filter * -Property * | Select Name
$Computers | Export-CSV -Path $env:TEMP\ZZZEXPORTE.csv -NoTypeInformation -Encoding UTF8
$Computers | ForEach {
$computerName = $_.Name
Write-Host $computerName
Write-Host "----"
}
You could also do this:
Get-ADComputer -Filter * -Property * | Select Name | ForEach {
$computerName = $_.Name
Write-Host $computerName
Write-Host "----"
$_
} | Export-CSV -Path $env:TEMP\ZZZEXPORTE.csv -NoTypeInformation -Encoding UTF8
Noting that we have to add $_ to our ForEach-Object loop so that it outputs the current item to the pipeline, but that our Write-Host statements don't effect the pipeline because they are writing to the console only. To be honest though, this is a bit harder to follow for anyone else reading your code.
Related
could you please help me to correct this script (gathered by pieces from internet) or better change logic how it should work (not working currently). The goal is to get pc's where only one folder exist (oracle11) and not both (11+12) and export it to csv. Oracle is a real pain in the ....
Thank you in advance for your advice.
Import-Module ActiveDirectory
$computers = Get-ADComputer -Filter * -Properties * | Select -Property Name
$output = #()
#$computers = get-adcomputer -filter * | Select-Object -Expand Name | foreach-object {
Foreach ($Computer in $computers){
if ( (test-path "\\$Computer\C$\oracle\product\11.2.0\" ) -and !( test-path "\\$Computer\C$\oracle\product\12.2.0" )) {
$output += $Computer
}
}
$output | Export-Csv -NoTypeInformation -Path c:\temp\test.csv
The problem is that the path strings you construct inside the loop are not as you expect.
When you pipe the output from Get-ADComputer to Select-Object -Property Name, it creates a new object with a single property Name for each input object.
When you then implicitly convert one of these objects to a string, the resulting value is going to be "#{Name=Computer01}", instead of just "Computer01".
You can observe this yourself, by calling Write-Host instead of Test-Path:
Get-ADComputer -Filter * |Select-Object -Property Name |ForEach-Object {
Write-Host "\\$_\C$"
}
To extract just the value of the Name property from each ADComputer, use ForEach-Object -MemberName instead of Select-Object -Property:
$computerNames = Get-ADComputer -Filter * -Properties * | ForEach-Object -MemberName
$output = #()
foreach($ComputerName in $computerNames){
if ( (Test-Path "\\$ComputerName\C$\oracle\product\11.2.0\" ) -and !( Test-Path "\\$ComputerName\C$\oracle\product\12.2.0" )) {
$output += $ComputerName
}
}
$output | Export-Csv -NoTypeInformation -Path c:\temp\test.csv
Note that passing -Properties * to Get-ADComputer is unnecessary, the object name is always part of the default property set sent back by the Get-AD* cmdlets.
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 have simple csv file with column 'logins'
logins
john
mark
maria
...
Have powershell script to check their last logontime:
Import-Module ActiveDirectory
function Get-ADUserLastLogon([string]$userName)
{
$time = 0
$user = Get-ADUser $userName | Get-ADObject -Properties lastLogon
if($user.LastLogon -gt $time)
{
$time = $user.LastLogon
}
$dt = [DateTime]::FromFileTime($time)
Write-Host $username $dt }
import-csv -Encoding UTF8 -path C:\scripts\loginy.csv | foreach {
Get-ADUserLastLogon -UserName $_.logins
}
This works fine with output
john 2018-05-10 14:11:28
mark 2018-11-29 14:26:58
maria 2018-11-02 11:14:17
...
When I try to export results it to csv file by code
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
$results = #()
foreach ($_.logins in $users) {
$results += Get-ADUserLastLogon -UserName $_.logins
}
$results | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
getting error
At C:\scripts\OstatnieLogowanie.ps1:19 char:12
+ foreach ($_.logins in $users) {
+ ~
Missing 'in' after variable in foreach loop.
At C:\scripts\OstatnieLogowanie.ps1:19 char:29
+ foreach ($_.logins in $users)
}
I can't get it work over 2 hours :/
Edit: I've confused LastLogon and LastLogonTimestamp. LastLogonDate is based on LastLogonTimestamp. The differences between these properties are explained here and here. I will come back and update my answer.
You're using Write-Host to output data:
Write-Host $username $dt
This won't work. Write-Host means "write to the console screen, not to standard output." That will work just fine if you're trying to display data, but calling $x = Get-ADUserLastLogon -UserName $login will print the results to the console screen and nothing would be assigned to the $x variable. For example:
PS C:\> $x = Write-Host 0
0
PS C:\> $x
PS C:\>
See how Write-Host still wrote to the console and $x doesn't have a value?
Your function should look something like $username, $dt or Write-Output $username, $dt or return $username, $dt.
Although that's still not really going to work like you want. I would probably use a custom object (see Get-Help about_Object_Creation -ShowWindow) like this:
Import-Module ActiveDirectory
function Get-ADUserLastLogon([string]$userName) {
$user = Get-ADUser $userName -Properties LastLogonDate
[PSCustomObject]#{'Logins' = $username; 'LastLogonDate' = $user.LastLogonDate}
}
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
$results = foreach ($user in $users) {
Get-ADUserLastLogon -UserName $user.logins
}
$results | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
Frankly, however, if I were doing what you're trying to do here, my actual code would look like this:
Import-Csv -Encoding -Path C:\scripts\loginy.csv |
Select-Object -ExpandProperty logins |
Get-ADUser -Properties LastLogonDate |
Select-Object #{n = 'Logins'; e = {$_.SamAccountName}}, LastLogonDate |
Export-Csv -Path C:\scripts\Eksporty\logowania.csv -Encoding UTF8 -NoTypeInformation
Select-Object -ExpandProperty logins will pass just the bare value of the logins column. Get-ADUser accepts identities from the pipeline, and it fetches the LastLogonDate for each user, as long as the SamAccountName (a default property) which is the logon name.
The next line, Select-Object #{n = 'Logins'; e = {$_.SamAccountName}}, LastLogonDate uses a calculated property (See the examples in Get-Help Select-Object -ShowWindow) to rename the SamAccountName property in a column named Logins. You could use Select-Object SamAccountName, LastLogonDate if you don't care about the column name. And the -NoTypeInformation parameter on Export-Csv just keeps it from adding that annoying "#TYPE System.Management.Automation.PSCustomObject" nonsense on the first line.
$_ is the variable for the current value in pipeline. In your second part of code, since you don't have a pipeline, hence $_ is empty and doesn't have any property/method associated with it.
What you can do is -
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
foreach ($user in $users) {
Get-ADUserLastLogon -UserName $user.logins | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
}
OR
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
foreach ($_ in $users) {
Get-ADUserLastLogon -UserName $_.logins | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
}
Although I would recommend not using the latter since $_ is an automatic variable $PSItem and beside you can have plenty other names for a variable which are not keywords, functions etc.
The use of += to extend an array requires creating a new instance behind the scenes in every iteration.
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
I'm trying to simply get a list of computers and their OU's from a CSV file of computer names
Add-PSSnapin quest.activeroles.admanagement
$results = #()
$computers = Get-Content "computers.csv"
foreach ($computer in $computers)
{
$results += Get-QADComputer $computer | select name, parentcontainer
}
$results = Export-CSV -path "computerswithous.csv"
But it errors out asking me to supply values. How can I simply output this data to a CSV file?
You did not include it in the post but is this what you were getting:
cmdlet Export-Csv at command pipeline position 1
Supply values for the following parameters:
InputObject:
Simple answer is this line
$results = Export-CSV -path "computerswithous.csv"
Should most likely be this
$results | Export-CSV -path "computerswithous.csv"
In your example Export-CSV has no input data which is what your prompt (not error) is requesting. You actually want to pipe the $results to the CSV file.
You also could do away with that construct as well and just use standard pipeline to get what you are looking for.
Add-PSSnapin quest.activeroles.admanagement
Get-Content "computers.csv" | ForEach-Object{Get-QADComputer $_} |
Select Name,ParentContainer | Export-CSV -Path "computerswithous.csv" -NoTypeInformation
You don't need that intermediate collection ($results) at all. Just wrap the foreach loop in a sub-expression, and send it on to the pipeline.
Add-PSSnapin quest.activeroles.admanagement
$computers = Get-Content "computers.csv"
$(foreach ($computer in $computers)
{
Get-QADComputer $computer | select name, parentcontainer
}) | Export-CSV -path "computerswithous.csv"