在 PowerShell 中使用 Import-Csv 和 ForEach Loop 處理 CSV 數據

你有没有必须多次执行相同任务的经历?比如,使用图形用户界面逐个创建多个Active Directory用户?或者登陆服务器来删除选定文件夹中的旧日志?如果你的答案是是的,那么你并不孤单。现在是时候掌握PowerShell的Import-Csv和foreach循环了。

手动任务没有什么问题;有时是必要的。但是当涉及到读取和处理CSV文件时,PowerShell的Import-Csv命令和ForEach循环可以帮助你。

PowerShell的Import-Csv命令是从表格化源(如CSV文件)读取数据的一种绝佳方法。然后,你可以使用ForEach循环遍历CSV数据中的每一行。

在本文中,你将学习如何使用这个强大的组合来自动化大批量、单调和重复的任务。

如果你对Import-Csv命令和ForEach循环还不熟悉,或者想要回顾一下你已经了解的内容,你可以访问以下链接了解更多信息。

使用Import-Csv(foreach循环)在PowerShell中管理CSV文件

回归基础:PowerShell的foreach循环

先决条件

本文中有几个示例和演示。为了跟上,你首先需要一些东西。

  • A script editor such as Visual Studio Code, Atom, or Notepad++. Use whichever one you’re comfortable with.
  • Windows PowerShell 5.1或PowerShell Core 6+
  • 访问Exchange Online(如果您将按照与Exchange Online相关的示例进行实践,则可选)。

将Import-Csv和ForEach循环付诸实践

下面是几个示例,演示如何使用Import-Csv cmdlet和ForEach循环,您可能会在实际场景中遇到。虽然这些导入CSV的PowerShell示例针对不同的目的,但重要的是要理解所采用的概念和技术是相同的。

从CSV中读取和显示记录

也许最基本的使用Import-CsvForEach循环的方法是从文件中读取记录并在控制台中显示它们。CSV文件的结构类似于数据库。它有列标题,每一行被视为一个记录

例如,下面是名为employee.csv的文件的内容,它包含三列 – EmployeeIDNameBirthday,以及四个记录。

CSV containing employee records

下面的代码导入employee.csv文件的内容,然后将导入的数据传递给ForEach-Object cmdlet。然后,ForEach-Object将遍历导入的CSV中的每个记录,将连接的值显示在控制台中。复制下面的代码并将其保存为list-employee.ps1

注意:此示例中使用的ForEach循环类型是ForEach-Object cmdlet。请参考本文中的“ForEach-Object cmdlet”部分

Import-Csv .\employee.csv | ForEach-Object {
    Write-Host "$($_.Name), whose Employee ID is $($_.EmployeeID), was born on $($_.Birthday)."
}

脚本保存后,通过在PowerShell中调用其文件名来运行它。运行脚本时,您应该会看到类似下面截图的输出。

Imported CSV records displayed in the console

搜索和显示CSV中的记录

在前面的示例中,您学习了如何读取和显示CSV中的所有记录。在这个示例中,将使用相同的CSV文件employee.csv,但这次您将创建一个PowerShell函数来搜索CSV中的EmployeeID。

下面的代码段是一个名为Find-Employee的PowerShell函数,它只有一个参数。参数名称为EmployeeID,它接受一个要从CSV中搜索的员工ID值。

在使用此函数之前,首先需要将其导入到您的PowerShell会话中。有两种方法可以将Find-Employee函数导入到内存中:

  1. 将下面的代码复制并粘贴到PowerShell中。
  2. 将脚本保存为Find-Employee.ps1,并使用点号引用技术进行导入。

注意:在下面的示例中使用的ForEach循环类型是ForEach语句。请参阅本文中的“foreach语句”部分。

Function Find-Employee {
    param (
        # 参数接受要搜索的员工ID。
        [Parameter(Mandatory)]
        $EmployeeID
    )

    # 导入employee.csv文件的内容并将其存储在$employee_list变量中。
    $employee_list = Import-Csv .\employee.csv

    # 循环遍历CSV中的所有记录
    foreach ($employee in $employee_list) {

        # 检查当前记录的员工ID是否等于EmployeeID参数的值。
        if ($employee.EmployeeID -eq $EmployeeID) {

            # 如果找到EmployeeID,则在控制台上显示记录。
            Write-Host "$($employee.Name), whose Employee ID is $($employee.EmployeeID), was born on $($employee.Birthday)."
        }
    }
}

将函数代码导入到PowerShell会话后,通过键入函数名称来调用它,如下所示。

Find-Employee

如果在不使用EmployeeID参数的情况下运行该函数,它将提示输入要搜索的EmployeeID的值。请参考下面的示例输出。

Function to search a CSV file for a specific record

或者,可以在运行时指定EmployeeID参数值来运行该函数,如下所示。

Find-Employee -EmployeeID 'E-2023'

从多个服务器获取磁盘空间使用情况

系统管理员常见的例行任务之一是监视多个服务器的磁盘空间使用情况。您可以创建一个包含服务器名称、驱动器字母和阈值的CSV列表。然后,使用PowerShell导入CSV文件并循环遍历每一行来运行查询。

要创建一个从远程服务器获取磁盘空间使用情况的脚本,首先创建包含以下标题的CSV列表:

  • servername – 这是要查询的名称服务器。
  • disk – 这是要检索空间使用情况的驱动器的字母。
  • threshold – 这定义了以GB为单位的阈值。如果磁盘的可用空间低于此值,报告将显示警告状态。

下面的示例显示了四个记录。您的CSV文件将根据要读取的服务器或磁盘的数量而不同。

servername,disk,threshold
au-dc01,c,120
au-mail01,c,100
au-mail01,d,6
au-file01,c,120

完成CSV文件后,将文件保存为。 这将作为PowerShell脚本导入的输入列表。

为了提供一个获取磁盘空间使用情况的示例,将下面的代码复制到脚本编辑器中,并将其保存为。脚本必须保存在与CSV路径相同的位置。

$server_list = Import-Csv -Path .\servers.csv

foreach ($server in $server_list) {
    Get-WmiObject -Class Win32_logicaldisk -ComputerName ($server.servername) | `
        Where-Object { $_.DeviceID -match ($server.disk) } | `
        Select-Object `
    @{n = 'Server Name'; e = { $_.SystemName } }, `
    @{n = 'Disk Letter'; e = { $_.DeviceID } }, `
    @{n = 'Size (GB)'; e = { $_.Size / 1gb -as [int] } }, `
    @{n = 'Free (GB)'; e = { $_.FreeSpace / 1gb -as [int] } }, `
    @{n = 'Threshold (GB)'; e = { $server.Threshold } }, `
    @{n = 'Status'; e = {
            if (($_.FreeSpace / 1gb) -lt ($server.Threshold)) {
                return 'Warning'
            }
            else {
                return 'Normal'
            }
        }
    }
}

上述脚本在执行时执行以下操作。

  • 导入名为的csv文件,并将其存储在$server_list变量中。
  • 循环遍历存储在$server_list变量中的服务器列表。
  • 在每次foreach循环的迭代中,当前行由变量$server表示。
  • 使用Get-WmiObject命令获取服务器的磁盘信息。
  • 選擇只顯示相關屬性。
    伺服器名稱 – 這是被查詢系統的名稱。
    磁盤代號 – 指派給驅動器的字母。
    大小 (GB) – 磁盤的大小,以GB為單位
    可用空間 (GB) – 可用空間的大小,以GB為單位
    閾值 (GB) – 定義的GB閾值
    狀態 – 如果 可用空間 (GB) 的值低於 閾值 (GB) 的值,則返回的狀態是 ‘警告‘。否則,狀態將是 ‘正常

儲存了腳本檔案 diskReport.ps1 後,現在可以通過在PowerShell中調用其名稱來運行。

./diskReport.ps1

執行後,下面的截圖顯示了腳本的輸出。

Disk space information gathered from multiple servers

輸出也可以導出到CSV。當需要共享報告時,導出到CSV很有用,因為導出的CSV檔案可以通過電子郵件發送,或上傳到檔案共享或SharePoint站點。如果您想了解更多關於導出到CSV的資訊,請務必查看 Export-Csv: PowerShell處理CSV檔案的方式

創建多個Active Directory用戶

到目前為止,您應該已經對使用 Import-CsvForEach 有了堅實的了解。下一個示例通過加入 New-ADUserGet-ADUser Cmdlet,將您的學習進一步深入。

假設您收到一個名為 new_employees.csv 的 CSV 檔案,其中包含人力資源部門提供的新員工清單。CSV 檔案中的每一行代表一個待入職的使用者,並具有以下欄位:名字姓氏部門州份員工編號辦公室

使用者名稱必須由員工的名字的第一個字母連接姓氏而來(例如,對於使用者Bob Parr,使用者名稱應為bparr)。

CSV file containing new employees information for on-boarding

保存 CSV 檔案後,下面的腳本使用Import-Csv來讀取CSV檔案new_employees.csv。然後,迭代每一行,將值傳遞給New-ADUser的相應參數。

Import-Csv .\new_employees.csv | ForEach-Object {
    New-ADUser `
        -Name $($_.FirstName + " " + $_.LastName) `
        -GivenName $_.FirstName `
        -Surname $_.LastName `
        -Department $_.Department `
        -State $_.State `
        -EmployeeID $_.EmployeeID `
        -DisplayName $($_.FirstName + " " + $_.LastName) `
        -Office $_.Office `
        -UserPrincipalName $_.UserPrincipalName `
        -SamAccountName $_.SamAccountName `
        -AccountPassword $(ConvertTo-SecureString $_.Password -AsPlainText -Force) `
        -Enabled $True
}

執行腳本後,新使用者應已存在於Active Directory中。但是,確認是否真的已創建用戶帳戶是一種良好的做法。

使用相同的CSV檔案new_employees.csv作為參考,下面的腳本將運行import CSV和foreach,以獲取與清單中的對應的ADUser物件。

Import-Csv .\new_employees.csv | ForEach-Object {
	Get-AdUser $_.SamAccountName
}

新增Proxy郵件地址到Office 365郵箱

在Office 365郵箱管理中,經常會收到添加多個使用者的代理地址的請求。通常,在此類請求中,管理員會收到一個使用者列表和要添加的電子郵件地址,類似於以下CSV示例。

The new_address.csv file contents

注意: 在 PowerShell 中运行任何 Exchange Online 命令之前,您必须先登录Exchange Online 管理 shell。

使用 PowerShell 脚本中的 Import-CsvForEach 循环,可以一次处理列表。下面的脚本演示了如何操作。

下面的脚本将导入 new_address.csv 文件的内容并存储到 $user_list 变量中。然后,使用 foreach() 方法,PowerShell 循环遍历整个用户列表,并在每个记录中使用 usernameemail 的值来为每个邮箱添加一个新的电子邮件地址。

$user_list = Import-Csv .\new_address.csv

$user_list.foreach(
    {
        Set-Mailbox -Identity $_.username -EmailAddresses @{add="$($_.email)"}
    }
)

运行脚本后,控制台不会显示任何输出。没有屏幕输出意味着新的电子邮件地址已成功添加。但是,如何确保电子邮件地址已添加?

使用相同的 CSV 文件 new_address.csv 作为参考,可以使用 Import-CsvForEach 来验证新地址是否已添加。

下面的脚本将导入 new_address.csv 文件的内容并存储到 $user_list 变量中。然后,使用 foreach() 方法,PowerShell 循环遍历整个用户列表,检查新的电子邮件地址是否存在于邮箱代理地址列表中。如果找到,则状态将返回 True;否则,结果将是 False

$user_list = Import-Csv .\new_address.csv

$user_list.foreach(
    {
        $emailObj = (Get-Mailbox -Identity $_.username).EmailAddresses
        if ($emailObj -contains $_.email) {
            Write-Host "$($_.username) --> $($_.email) --> TRUE"
        }
        else {
            Write-Host "$($_.username) --> $($_.email) --> FALSE"
        }
    }
)

當驗證腳本運行時,輸出應該與下面的屏幕截圖一樣。你會注意到下面的輸出中,狀態都是 TRUE,這意味著新的郵件地址已成功添加到每個郵箱中。

Running the new email address verification

發送每日天氣預報給郵件列表

在這個例子中,假設你有一個包含訂閱者的郵件地址和他們的地區或位置的 CSV 文件。這些訂閱者希望每天收到一封包含當天特定地區天氣預報的電子郵件。請查看下面的樣本 CSV 文件,文件名為 subscribers.csv

Weather forecast subscribers list

下面的 CSV 文件只包含兩個訂閱者。一個在 洛杉磯,一個在 馬尼拉

目標是創建一個腳本執行以下操作:

  • subscribers.csv 文件中導入郵件和地區信息
  • 對於每個訂閱者:
    – 從 https://wttr.in/ 下載基於訂閱者所在地區的天氣預報圖片
    – 把天氣預報圖片作為電子郵件發送給訂閱者的郵件地址。

下面的腳本執行上述操作。你只需要修改前三個變量 – $senderAddress$smtpServer$smtpPort。然後,複製這段代碼並保存為 Send-WeatherInfo.ps1。請參考腳本各個部分上方的注釋以了解代碼的功能。

# 在此處開始SMTP設置
$senderAddress = '<SENDER EMAIL ADDRESS HERE'
$smtpServer = '<SMTP RELAY SERVER HERE>'
$smtpPort = 25
# 在此處開始SMTP設置

# 導入訂閱者的電子郵件列表
$subscriber_list = Import-Csv .\subscribers.csv

# 獲取天氣預報並發送電子郵件
$subscriber_list.foreach(
    {
				
        # 根據地區構建天氣信息URL
        $uri = ('<https://wttr.in/>' + $_.Area + '_u1F.png')
        # 獲取該地區的天氣預報圖像。將圖像保存為.png
        $imageFile = (((Resolve-Path .\).Path) + "$($_.Area).png")
        Start-BitsTransfer $uri -Destination $imageFile

        # 創建附件對象
        $attachment = New-Object System.Net.Mail.Attachment -ArgumentList $imageFile
        $attachment.ContentDisposition.Inline = $true
        $attachment.ContentDisposition.DispositionType = "Inline"
        $attachment.ContentType.MediaType = "image/png"
        $attachment.ContentId = "weatherImage"

        # 構建郵件內容
        $emailMessage = New-Object System.Net.Mail.MailMessage
        $emailMessage.From = $senderAddress
        $emailMessage.To.Add($_.Email)
        $emailMessage.Subject = ('Weather Forecast - ' + $_.Area)
        $emailMessage.IsBodyHtml = $true
        $emailMessage.Body = '<img src="cid:weatherImage">'
        $emailMessage.Attachments.Add($attachment)

        # 發送郵件
        Write-Output "Sending Weather Info to $($_.Email)"
				$smtp = New-Object Net.Mail.SmtpClient($smtpServer, $smtpPort)
        $smtp.Send($emailMessage)

        # 釋放對象
        $attachment.Dispose()
        $emailMessage.Dispose()
    }
)

運行腳本後,將向每個訂閱者發送類似於下面示例電子郵件截圖的電子郵件。

Weather forecast for Manila sent as an email to the subscriber
Weather forecast for Los Angeles sent as an email to the subscriber

總結

使用Import-CsvForEach循環的任務沒有限制。只要任務涉及具有分隔列的列表,您肯定會使用這個強大的組合。

在本文中,您已經了解了CSV文件與數據庫的相似之處。您還學會了如何使用Import-Csv導入數據,以及如何在ForEach循環迭代期間引用這些值。

I hope that with the many examples provided in this article, you now understand more of the Import-Csv and ForEach. And, that you would be able to use the knowledge you gained in this article in your administration and automation tasks.

進一步閱讀

Source:
https://adamtheautomator.com/import-csv-and-the-foreach-loop/