Powershell - 正确导出 CSV 文件

Powershell - Export CSV file correctly

我希望有人能帮助我解决这个问题。我们想看看哪些计算机有 HDD 和 SDD。我有 excel.csv 台计算机。我进口电脑。但是当我导出它们时,我从来没有看到 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)){ 
        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
        }
    }
}

更新:2021 年 11 月 11 日 谢谢大家的帮助 这个脚本对我有用:

$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
}
}

继续我的评论。 . .照原样,您会将结果导出到远程机器。那就是如果它 管道 正确。您目前在 Export-Csv.

之前缺少管道 (|)

此外,不需要调用该命令,因为 Get-WMIObject 有一个用于远程计算机的参数:-ComputerName。它也是一个已弃用的 cmdlet,已被替换为 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

        } 
    }

旁注Get-CimInstance 接受字符串数组,这意味着您可以将整个 $Computers 传递给它。这应该允许它在并行中执行查询,而不是串行一次一个) :

$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

一次执行一个查询并不一定意味着它不好。实际上,您可以更好地控制脚本的 流程控制

编辑: 跟进您的评论...您不再使用 if 语句在连接之前检查计算机是否在线。因此,假设您保留 if 语句,并添加一个 else 条件,您可以创建一个计算的 属性 以添加另一个 属性 以导出 Status 。然后,你可以给它传递一个值Online,或者Offline,这取决于机器是否在线:

$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
        } 
    }

还有:

  1. 永远记住,即使你能ping通一台机器,并不意味着你能连接上它。
  2. 这可以通过使用 CIM 会话PSSession 来缓解,具体取决于您使用的命令类型重新 运行.

具体回答问题:

如何正确导出 CSV 文件(使用 Export-Csv)?

您可能想阅读有关 PowerShell pipelines and PowerShell cmdlets 的内容。
基本上,cmdlet 是参与 PowerShell 管道语义的单个命令。一个写得很好的 cmdlet 是 implemented for the Middle of a Pipeline,这意味着它处理(“streams”)从前一个 cmdlet 接收到的每个单独的项目,并立即将它传递给下一个 cmdlet(类似于如何项目在装配线上处理,您可以将每个装配站作为一个 cmdlet 进行比较)。

为了更好地展示这一点,我创建了一个更简单的 minimal, complete and verifiable example (MVCE) 并替换了您的远程命令 (Invoke-Command ...),它只是一个伪造的 [pscustomobject]@{ ... } object.
有了那个;

  • 我使用了 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,这需要一个(例如 Name)header 并相应地使用这个 属性(例如 $Computer.Name)。
  • 为了执行管道 advantage/understanding,我还使用了 ForEach-Object cmdlet rather than the foreach statement,它 通常 被认为更快,但这可能 不是 这里的情况对于 foreach 语句,需要将所有 $Computers 预加载到内存中,在内存中,编写良好的管道将 立即 开始处理每个项目(在您的情况下发生在远程计算机上),同时仍然从文件中检索下一个计算机名称。

现在,回到问题“如何正确导出 CSV 文件”,这样可以更好地理解管道,您可以将 Export-Csv 放在 foreach 循环中::

Get-Content .\Computers.txt |ForEach-Object {
    [pscustomobject]@{
        PSComputerName = $_
        Model          = "Model"
        SerialNumber   = '{0:000000}' -f (Get-Random 999999)
        MediaType      = "MydiaType"
    } |Export-Csv .\Devices.csv -Append
}

正如 @lit 所评论的那样,这将需要 -Append 开关,这可能不是您每次重新运行脚本时所需要的,因为这会将结果附加到 .\Devices.csv 文件中。
相反,你可能真的想这样做:

Get-Content .\Computers.txt |ForEach-Object {
    [pscustomobject]@{
        PSComputerName = $_
        Model          = "Model"
        SerialNumber   = '{0:000000}' -f (Get-Random 999999)
        MediaType      = "MydiaType"
    }
} |Export-Csv .\Devices.csv

注意区别:Export-Csv 放在循环外,-Append 开关被移除。

说明
与例如ForEach-Object cmdlet, the Export-Csv cmdlet has internally Begin, Process and End blocks.

  • Begin 块(管道启动时运行)中,Export-Csv cmdlet 准备 csv 文件,其中包含 header 行等。覆盖任何现有文件。
  • Process 块(针对从管道接收到的每个项目运行)中,它 将每一行(数据记录)追加 到文件。