使用 Import-Csv 和 ForEach 循环在 PowerShell 中处理 CSV 数据

你是否曾经不得不多次执行同样的任务?比如,使用图形界面逐个创建多个活动目录用户?或者尝试登录服务器,从选定的文件夹中删除旧日志?如果你的答案是,那么你并不孤单。是时候掌握 PowerShell 的 Import-Csv 和 foreach 循环了。

手动任务没有错,有时是必要的。但是当涉及到读取和处理 CSV 文件时,PowerShell 的命令 Import-CsvForEach 循环就可以派上用场。

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 Loop投入实际操作

在接下来的几节中,将介绍如何使用Import-Csv cmdlet和ForEach循环的几个示例,您可能会在真实场景中遇到。虽然这些导入CSV foreach PowerShell示例针对每个目的具体,但重要的是要理解所使用的概念和技术是相同的。

读取和显示来自CSV的记录

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

例如,下面是名为employee.csv的文件的内容,其中包含三列 – EmployeeIDName,和Birthday,以及四条记录。

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文件后,请将文件保存为servers.csv。这将作为输入列表,将由PowerShell脚本导入。

为了提供一个获取磁盘空间使用情况的示例,请将以下代码复制到您的脚本编辑器中,并将其保存为diskReport.ps1。脚本必须保存在与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'
            }
        }
    }
}

执行脚本后,上述脚本执行以下操作。

  • 导入名为servers.csv的csv文件,并将其存储在$server_list变量中。
  • 循环遍历存储在$server_list变量中的服务器列表。
  • 在foreach循环的每次迭代中,当前行由变量$server表示。
  • 使用Get-WmiObject cmdlet从服务器获取磁盘信息。
  • 选择只显示相关属性。
    服务器名称 – 这是正在查询的系统的名称。
    磁盘字母 – 分配给驱动器的字母。
    大小(GB) – 磁盘的大小,以GB为单位
    剩余空间(GB) – 剩余空间的大小,以GB为单位
    阈值(GB) – 定义的GB阈值
    状态 – 如果剩余空间(GB)的值低于阈值(GB)的值,返回的状态是‘警告’。否则,状态将是‘正常

保存了脚本文件diskReport.ps1后,现在可以通过在PowerShell中调用其名称来运行。

./diskReport.ps1

执行后,下面的截图展示了脚本的输出。

Disk space information gathered from multiple servers

输出也可以导出到CSV文件。导出到CSV文件在需要共享报告时非常有用,因为导出的CSV文件可以通过电子邮件发送,或上传到文件共享、SharePoint站点。请查看Export-Csv: The PowerShell Way to Treat CSV Files as First Class Citizens,如果你想了解更多关于导出到CSV的信息。

创建多个Active Directory用户

到这一步,你应该已经对使用Import-CsvForEach有了坚实的理解。下一个例子通过加入New-ADUserGet-ADUser cmdlets,将你的学习提升到一个新的层次。

假设您收到了一个名为new_employees.csv的CSV文件,其中包含了来自人力资源部门的新员工列表。CSV文件中的每一行代表一个待入职的用户,包括以下列:部门员工ID办公室

用户名必须由员工名字的第一个字母与姓氏连接而成(例如,对于用户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作为参考,下面的脚本将运行导入CSV和foreach以获取与列表中匹配的AD用户对象。

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

为Office 365邮箱添加代理邮箱地址

在Office 365邮箱管理中,添加多个用户的代理地址的请求并不罕见。通常,在这种请求中,管理员会收到一个用户列表和要添加的电子邮件地址,类似于以下CSV示例。

The new_address.csv file contents

注意:在 PowerShell 中运行 Exchange Online cmdlet 之前,必须首先登录到 Exchange Online Management 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')
        # 获取该地区的天气预报图像。将图像保存为<AREA>.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/