Powershell6-Linux-管理秘籍-全-
Powershell6 Linux 管理秘籍(全)
原文:
annas-archive.org/md5/a5cb80b4aff4cb55f7129a083399fafe译者:飞龙
序言
《PowerShell 6.0 Linux 管理实战指南》将带领你全面了解 .NET Core、PowerShell 的基础知识,以及如何在 Linux 上使用 PowerShell 进行管理和自动化。本书还将介绍如何使用 PowerShell 管理 Docker 容器、云平台、VMware 和 SQL 数据库的高级概念。
第一章:安装、参考和帮助
在本章中,我们将介绍以下操作步骤:
-
安装 PowerShell
-
在 PowerShell 中获取帮助信息
-
获取特定 cmdlet 的帮助信息
-
更新帮助
-
搜索关键字的帮助信息
-
查找特定参数的帮助信息
-
探索
about_主题 -
发现 cmdlet
-
查找模块
-
从存储库安装模块
-
列出 PowerShell 中的各种提供者
简介
一切都从在您的系统上安装 PowerShell 开始。安装 PowerShell 简单而直接。由于 PowerShell 是开源的,其源代码可以在可能是最大的开源项目平台 GitHub 上获得。目前,Windows、Debian(和 Ubuntu)、RedHat Linux(和 CentOS)、Fedora 和 macOS 是PowerShell 项目的官方支持系统。Arch Linux 和 Kali Linux 得到社区的支持。还有适用于大多数现代 Linux 发行版的 PowerShell AppImage 版本的社区支持。
AppImage 是将应用程序打包为在 Linux 发行版上运行的一种方式。每个应用程序都有其依赖关系。在 Linux 上,包管理器管理安装依赖项时会安装软件包。另一方面,AppImage 软件包会将所有必要的依赖项打包到其内部。AppImage 可以以便携模式运行,也可以根据用户的喜好安装在系统上。
至撰写本章时,PowerShell 已试验性地支持 Windows on ARM 和 Raspbian Stretch。
安装 PowerShell
基本上,在 Linux 中,应该很容易获取源代码,并使用 make 构建应用程序。这种方法对 PowerShell 也适用。但是,如上所述,官方支持和社区支持仅适用于少数几种发行版。我们将重点放在在 Ubuntu 和 CentOS 上安装 PowerShell,因为它们是最流行的两种 Linux 发行版之一。
PowerShell 有两个版本:稳定版和预览版。稳定版适用于生产环境,因为它们更可靠。预览版适用于测试环境,管理员可以在其中稍作尝试,并愿意报告他们遇到的 bug,同时提供关于功能的反馈。
准备工作
准备在计算机上安装 PowerShell 是简单的。您只需拥有一个可以运行 Linux 的计算机,并具有管理员权限。根据您选择安装 PowerShell 的模式,您可能需要或不需要包管理器。您的 Linux 发行版很可能已经有可用的包管理器。
如何操作...
如前所述,我们将讨论在 Ubuntu(及其衍生版本)以及 CentOS(及其衍生版本)上安装 PowerShell 的步骤。
在 Ubuntu 上安装
确实有很多方法可以在你的计算机上安装 PowerShell。由于我们正在 Ubuntu 上安装 PowerShell,我们将介绍两种方法。第一种是通过添加微软的密钥并注册仓库,然后使用高级包工具(APT)安装 PowerShell,第二种是直接使用 GitHub 上的.deb 包。
通过仓库安装
PowerShell 的官方包支持的最后一个 Ubuntu 版本是 Ubuntu 17.04。如果你使用的是 Ubuntu 17.04,请使用以下步骤安装 PowerShell。否则,建议使用直接下载方式。
- 第一步是导入公共仓库的 GPG 密钥:
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
- 现在,密钥已添加,注册仓库——这个仓库由微软维护。
sudo curl -o /etc/apt/sources.list.d/microsoft.list https://packages.microsoft.com/config/ubuntu/17.04/prod.list
- 使用以下命令更新包列表。
sudo apt update
- 接下来,安装 PowerShell。
sudo apt install -y powershell
通过直接下载安装
按照以下步骤在 Ubuntu 上安装 PowerShell:
-
向下滚动,找到包含 PowerShell 团队正式支持的 Linux 发行版列表的表格。
-
点击相关链接:在下载(稳定版)下的
.deb—我们将使用稳定版。 -
如果需要更多信息,请阅读安装说明。
-
使用
gdebi、eddy或类似的包管理工具运行.deb 文件来完成安装。
如果你没有运行 PowerShell 团队或社区正式支持的发行版,请使用 AppImage。按照上述步骤 1 和 2,然后:
-
点击 Many Linux distributions | AppImage 以下载 AppImage 文件。AppImage 文件是一个应用程序,包含运行该应用程序所需的所有依赖项。
-
将 AppImage 文件保存到一个方便的位置。
-
下载完成后,导航到下载位置并运行 AppImage。
-
如果你收到提示询问是否安装 PowerShell,请选择适合你环境的选项。
在 CentOS 上安装
在 CentOS 7(或 RedHat 7)上安装 PowerShell 也有两种方法:仓库和直接下载。过程与在 Ubuntu Linux 上的安装类似。
通过仓库安装
开始安装:
- 首先,注册微软仓库。
curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo
- 接下来,使用
yum安装 PowerShell。
sudo yum install -y powershell
通过直接下载安装
按照以下步骤在 CentOS 上安装 PowerShell:
-
向下滚动,找到包含 PowerShell 团队正式支持的 Linux 发行版列表的表格。
-
点击相关链接:在下载(稳定版)下的
.rpm—我们将使用稳定版。 -
如果需要更多信息,请阅读安装说明。
-
使用以下命令安装 RPM 包(假设你的下载文件在
~/Downloads,当前工作目录是~/Downloads)。
sudo yum install <the-downloaded-file.rpm>
使用 AppImage 包
使用 AppImage 要简单得多。
-
向下滚动至 "Get PowerShell" 部分,并在社区支持的分发版本下找到 AppImage 链接。
-
下载 AppImage 并将其放置在一个方便的位置。
-
运行
chmod a+x PowerShell-<version>-<architecture>.AppImage以使 AppImage 可执行。 -
调用 AppImage 以运行 PowerShell。您可能会被询问是否希望将 AppImage 安装到计算机上。如果希望安装,请选择“是”。
如何操作...
包管理器会安装该包及其所有依赖,并使相关命令可用。
对于 AppImage 包,所有依赖项都已捆绑在包中(包括 .NET Core),且该包可以在您的计算机上以便携模式运行,或者可以安装并调用。通过运行 pwsh 启动 PowerShell 来查看。
另见
- 示例 2.5:比较 Windows PowerShell 和 PowerShell
在 PowerShell 中获取帮助信息
PowerShell 中的命令被称为 cmdlet(发音:command-lets)。Cmdlets 预先包装了所有必要的帮助信息,类似于 Linux 命令提供的帮助。不同之处在于,PowerShell 中有一个单独的 cmdlet 来获取所需的帮助;在 PowerShell 中,帮助不是一个开关。
在我们开始之前,先启动终端(启动您计算机上任何可用的终端模拟器)。现在输入 pwsh 调用 PowerShell。一旦看到 PS 提示符后,按照以下步骤操作。
要在 PowerShell 中获取帮助信息:
在提示符下输入 Get-Help,以获取 PowerShell 中帮助系统的概述。您将看到类似如下的输出:

PowerShell cmdlet 是内置了帮助信息的命令。运行 Get-Help 不带任何参数时,您只会看到有关 PowerShell 中可用帮助的基本信息,这并没有什么特别之处。让我们来看一下另一个示例,如何更好地利用 Get-Help。
获取特定 cmdlet 的帮助信息
当 Get-Help 显示关于帮助本身的信息时,它也可以接受其他 cmdlet 作为参数,并显示它们的帮助信息。让我们以 Get-Command 为例,获取该 cmdlet 的帮助。
如何操作...
- 在提示符下,输入以下内容:
Get-Help Get-Command
- 你将看到类似如下的输出:

- 你甚至可以获得关于 cmdlet 的完整帮助,包括关于每个可以与 cmdlet 一起使用的参数的信息;这些信息包括该参数是否是位置参数、是否是必需的等等。我们将在后续的 PowerShell 管理中详细了解每个内容。要获取 cmdlet 的完整帮助,请输入:
PS> Get-Help Get-Command -Full
- 要查看如何使用 cmdlet 的示例,请输入:
PS> Get-Help Get-Command -Examples
- 要在线阅读帮助信息,请输入:
PS> Get-Help Get-Command -Online
注意,参数在输出中按 SYNTAX 组进行分类。在这种情况下,我们可以看到两个这样的组。这些组称为 参数集。参数集告诉我们哪些参数可以组合在一起。两个不在同一参数集中的参数不能一起使用。
它是如何工作的...
当你运行 Get-Help cmdlet,并传入另一个 cmdlet 作为参数时,Get-Help 会获取传入的 cmdlet(在本例中是 Get-Command)的帮助信息。我们将在后续章节中看到参数是如何处理的。目前,我们只是调用一个任意 cmdlet 的帮助。
如果你查看输出,你会看到 cmdlet 的名称、使用该 cmdlet 的语法、可用的 cmdlet 别名以及如果需要的话,更多的在线帮助。Full 和 Examples 是用来告诉 PowerShell 你需要哪种级别的帮助的开关。这两个选项是互斥的;你不能同时使用 -Full 和 -Examples。
另见
-
方案 2.6:列出别名并将其用作 cmdlet 的替代
-
方案 1.4:更新帮助
-
方案 1.3:获取特定 cmdlet 的帮助信息(关于参数集的信息)
更新帮助
PowerShell 中的帮助信息是动态的。在大多数情况下,PowerShell 会在线获取最新的帮助。然而,也可以将帮助文件离线存储。可以使用 Update-Help cmdlet 完成此操作。
如何操作...
要下载本地安装的 PowerShell 模块的帮助文件:
-
输入
exit退出 PowerShell。这是为了你可以重新启动 PowerShell,并以提升的权限运行。 -
输入
sudo pwsh以超级用户身份启动 PowerShell。 -
在
PS提示符下,运行Update-Help。 -
等待更新进度条显示。进度条会随着帮助文件下载到你的计算机而填充。

它是如何工作的...
每次运行 Get-Help cmdlet 时,PowerShell 都会在线获取帮助信息——帮助文件不会离线存储。这样做的主要目的是确保始终引用最新的帮助信息。此外,在线查看帮助也不会占用本地计算机的存储空间。
然而,在某些情况下,需要将帮助信息存储以供离线访问。在这种情况下,下载并存储帮助文件是有意义的。
这个操作需要提升权限的原因之一是,帮助信息存储在 shell 中。因此,非管理员用户可能无法在没有管理员干预的情况下更新帮助。
在帮助中搜索关键词
Linux 管理员非常习惯使用 grep 来搜索任何文本输出中的特定文本模式。正如我们所知,PowerShell 只返回 对象——PowerShell 也将文本视为对象。
虽然将对象转换为文本会削弱 PowerShell 的功能,但在某些场景下仍然是必要的。在这种情况下,我们将查找帮助输出中的关键词“common”。
如何操作...
让我们首先通过获取 cmdlet 的帮助信息来开始搜索过程:
-
在终端输入
pwsh启动 PowerShell。 -
在提示符下,输入:
PS> Get-Help Get-Command
这应该能给你提供有关指定 cmdlet 的有用信息。
- 使用
Out-Stringcmdlet 并结合-Stream参数将输出转换为文本,这样可以一次返回一个字符串,而不是将整个帮助信息作为单一字符串返回。这样,我们能够更高效地执行字符串匹配,并获得更简短的搜索结果。
PS> Get-Help Get-Command | Out-String -Stream
- 目前不会有明显的变化。添加
Select-Stringcmdlet 来执行类似grep的操作。
PS> Get-Help Get-Command | Out-String -Stream | Select-String 'common'
- 这将输出包含字符串 'common' 的确切行。

它是如何工作的...
在第一步中,我们简单地获取 Get-Command cmdlet 的帮助信息。这个输出是一个对象——当输出为文本时,执行文本查找操作效果最佳。Out-String 将此输出转换为纯文本字符串;唯一的问题是整个文本块是一个单一的字符串。以这种方式进行文本查找可能并不十分有用。我们使用 -String 参数将大字符串拆分成更小的部分(在这种情况下是段落)。接下来,我们使用 Select-String 结合关键字来选择包含我们关键字的字符串。
查找特定于某个参数的帮助信息
正如我们在前面章节看到的,PowerShell 的帮助输出可能会有点让人应接不暇。当我们需要获取 cmdlet 中参数的信息时,我们可以运行 Get-Help 并使用 -Full 参数。然而,这会列出所有的参数。如果我们只想获取某一个参数的信息,该怎么办呢?
发现 Cmdlets
到目前为止,我们已经学习了如何获取 cmdlet 的帮助信息。我们以 Get-Command cmdlet 作为示例参数。现在我们将使用 Get-Command 来发现 PowerShell 中的 cmdlet。因此,Get-Help 和 Get-Command 成为在 PowerShell 中获取帮助信息时最重要的 cmdlet。
PowerShell 就像纯英语——PowerShell 遵循动词-名词格式来命名 cmdlet,这使得 cmdlet 听起来像普通的英语命令。PowerShell 甚至使用被称为 批准动词 的规范,确保动词在命名 cmdlet 时遵循约定。另一方面,名词可以是任意的。
大多数程序员建议学习以语言思考
。结合肌肉记忆(帮助你记住键盘上的按键),这使得编程和脚本编写更高效。PowerShell 的结构类似英语,在这方面帮助很大;用 PowerShell 思考非常容易。例如,如果你想知道当前日期,你只需输入 Get-Date,PowerShell 就会在屏幕上显示日期和时间。
如何操作...
让我们来看一下如何仅获取 Get-Help 中 Noun 参数的信息。
-
通过在终端窗口中运行
pwsh启动 PowerShell。 -
在提示符下,输入
Get-Help Get-Command以查找有关 cmdlet 的基本帮助信息。 -
记下你想要了解更多信息的参数。我们以
Noun参数为例。 -
在提示符下输入以下命令:
PS> Get-Help Get-Command -Parameter Noun
输出包含与Noun参数相关的信息:

它是如何工作的...
这是一个典型的对象筛选示例。由于大多数 PowerShell cmdlet 的输出是对象,因此很容易选择必要的对象并从输出中丢弃其余部分。
当我们使用需要帮助的 cmdlet 和我们正在查找的特定参数运行Get-Help时,我们会得到一个与该 cmdlet 相关的过滤帮助。在我们的案例中,我们选择了Get-Command的 Noun 参数。
探索 About_ 主题
文档是 PowerShell 的一大优势。虽然并非所有模块都有完整的文档,但所有官方模块都有完整文档,第三方模块中的一些也做得很好。默认情况下,PowerShell 本身内置了详尽的文档。
在本教程中,我们将查找about_主题,从输出中选择一个主题并深入阅读我们选择的内容。
如何操作...
让我们从列出所有about_*帮助文件开始。
-
使用命令
pwsh启动 PowerShell。 -
输入
Get-Help about_*来列出所有的about_*帮助文件。
Name Category Module Synopsis
---- -------- ------ --------
about_Aliases HelpFile
about_Arithmetic_Operators HelpFile
about_Arrays HelpFile
about_Assignment_Operators HelpFile
about_Automatic_Variables HelpFile
.
.
.
about_Wildcards HelpFile
-
选择你想阅读的主题——我们选择了
about_Modules。 -
输入
Get-Help about_Modules来查看 PowerShell 模块的文档。
ABOUT MODULES
Short Description
Explains how to install, import, and use PowerShell modules.
Long Description
A module is a package that contains PowerShell commands, such as cmdlets,
providers, functions, workflows, variables, and aliases.
People who write commands can use modules to organize their commands and
share them with others. People who receive modules can add the commands in
the modules to their PowerShell sessions and use them just like the
built-in commands.
.
.
.
How to Find the Commands in a Module
Use the Get-Command cmdlet to find all available commands. You can use the
parameters of the Get-Command cmdlet to filter commands such as by module,
name, and noun.
.
.
.
The following modules (or snap-ins) are installed with PowerShell. *
CimCmdlets * Microsoft.PowerShell.Archive * Microsoft.PowerShell.Core *
Microsoft.PowerShell.Diagnostics * Microsoft.PowerShell.Host *
Microsoft.PowerShell.Management...
它是如何工作的...
Get-Help会提供有关 cmdlet 的信息,如果将 cmdlet 作为参数传递。当你只知道 cmdlet 名称的一部分时,你可以使用字符组合和通配符列出与搜索字符串匹配的 cmdlet(在我们的案例中是about_*)。
现在,我们选择想要阅读的主题并进入该特定的about_主题。此时,Get-Help会显示有关该主题的完整文档。
如何操作...
Get-Command可以帮助确定执行任务的最佳 cmdlet。要查找 cmdlet,请按照以下步骤操作:
-
在终端中,输入
pwsh以启动 PowerShell。 -
输入
Get-Command以列出 PowerShell 中所有可用的 cmdlet。返回的 cmdlet 数量会根据你上次更新 PowerShell 和加载的模块有所不同。
PS> Get-Command
这可能并不特别有用——如果你正在寻找列出当前正在运行的进程的命令,那么看到一个命令列表又有什么意义呢?
- 进程是一个名词。我们想要一个处理进程的 cmdlet 列表。在提示符下,输入:
PS> Get-Command -Noun Process
请注意,在 PowerShell 中,名词是单数形式。因此,它是 Process,而不是 Processes。
- 如果你想进一步缩小返回的 cmdlet 列表,可以同时添加动词。
PS> Get-Command -Verb Get -Noun Process
- 如果你感到懒得查找,或者不确定确切的约定,甚至可以使用通配符。
PS> Get-Command -Noun Proc*
-
如果你不想写太多代码,而且几乎确定你知道部分 cmdlet 名称,可以直接使用通配符搜索。
-
如果你知道包含 cmdlet 的模块名称,甚至可以在调用
Get-Command时使用 Module 参数。
PS> Get-Command -Noun Process -Module Microsoft.PowerShell.Management
它是如何工作的...
PowerShell 能够基于连字符的首次出现识别 cmdlet 中的动词和名词。出现在第一个连字符之前的是动词,之后的是名词。当 cmdlet 从模块中加载时,PowerShell 会识别其中的动词和名词。搜索会根据搜索规格迅速返回 cmdlet 的结果。基于动词、名词或甚至模块名称的过滤器会相应地限制搜索范围。
查找模块
松散耦合组件是框架成功的关键之一,PowerShell 也遵循这一原则。所有 cmdlet 都被打包在模块中。这些模块可以是第一方提供的,也可以是你自己创建的,甚至可以是第三方创建的。
模块的安装,过去可能是个麻烦事,但如今已经变得更加简化。PowerShell 现在预装了一个名为PowerShellGet的包管理器,它可以连接到 PowerShell 库(www.powershellgallery.com)。PowerShell 库是一个在线仓库,包含模块、脚本和其他实用工具,管理员可以下载并安装这些内容,以扩展 PowerShell 的功能。
虽然也可以从第三方网站下载 PowerShell 模块,但本书的重点将是 PowerShell 仓库。
如何操作...
要在 PowerShell 仓库中查找模块,请按照以下步骤操作:
-
通过在终端中运行
pwsh来启动 PowerShell。 -
查找与模块相关的命令。

请注意,有关模块操作的可用命令列表。其中一些是 cmdlet 类型的,另一些是函数。
- 使用动词进一步过滤输出结果。
PS> Get-Command -Noun Module -Verb Find
你将获得Find-Module作为输出结果。
- 输入以下内容以列出所有模块。
PS> Find-Module
列表开始出现。搜索结果太多了。按Ctrl + C中止执行。
- 搜索一个可以帮助你处理 Docker 容器的模块。

它是如何工作的...
数百个模块、脚本和所需状态配置资源已经在PSGallery仓库中注册。微软现在推荐使用该仓库来管理模块。通过使用Find-Module cmdlet,PowerShell 与仓库建立连接,并获取所有可用模块的列表。然后,它会根据你提供的标准对返回的结果进行搜索。
你也可以类似地找到执行重复任务的脚本。要查找脚本,使用Find-Script cmdlet。它的工作方式与Find-Module cmdlet 类似,只不过它查找的是单个脚本,而不是模块。
从仓库安装模块
现在我们知道了如何找到模块,我们可以继续获取并安装模块。所有与PowerShellGet相关的命令都打包在名为PowerShellGet的模块中。
如何操作...
我们现在知道了可以帮助我们管理 Docker 基础设施的模块名称。现在我们尝试安装该模块。
- 安装模块可能需要提升权限。使用
sudo打开 PowerShell。
$ sudo pwsh
- 现在运行以下命令从仓库安装 Docker 模块。
PS> Install-Module Docker

- 在某些情况下,你可能希望在安装模块之前先将其保存在本地。
PS> Save-Module Docker ~/PsModules
这样,你可以在便携模式下简单地导入模块并运行命令,而无需将模块安装到任何系统目录中。
- 要导入下载的模块,运行:
PS> Import-Module ~/PsModules/Docker/1.3.2/Docker.psm1
- 要更新已安装的模块,运行:
PS> Update-Module Docker
- 要删除已安装的模块,运行:
PS> Uninstall-Module Docker
这些功能中的大多数也适用于发布到 PowerShellGet 仓库的脚本。只需在命令中将 Module 替换为 Script 即可,操作方法与模块一样——你可以像处理模块一样查找、保存、安装、更新和卸载脚本。
下载的脚本不一定要安装;它们可以直接调用。我们将在后续章节中看到这一点。
它是如何工作的...
PowerShell 在运行Install-Module时实际上做的是,将模块文件保存到 PowerShell 默认查找模块的某个位置。
另见
-
配方:PowerShell 中的模块发现路径(创建自定义模块)
-
配方 3.11:调用 PowerShell 脚本
列出 PowerShell 中的各种提供程序
在开始准备使用 PowerShell 进行管理之前,还有一个概念需要理解,那就是提供程序。
PowerShell 中的提供程序是面向对象编程中重载概念的一个很好的例子。实际上,提供程序是一个程序,它将非文件系统驱动器以逻辑方式表示为驱动器。例如,在 Windows 上,注册表是一个配置数据库。在 PowerShell 中,注册表是一个提供程序;这样,管理员可以像操作文件一样使用 PowerShell 浏览和操作注册表键。此功能在 Linux 上也可用,不过提供程序的数量没有 Windows 上那么多。
如何操作...
要列出 PowerShell 中的提供程序,请按照以下步骤操作:
-
运行
pwsh以在终端中加载 PowerShell。 -
运行命令:
PS> Get-PsProvider

注意 PowerShell 中可用的提供程序,以及在这些提供程序内部找到的驱动器和功能。
- 导航到 Alias:驱动器。
PS> Set-Location Alias:

注意驱动器名称后面的冒号(Alias: 而不是 Alias)。这很重要,用于告诉 PowerShell 你正在切换驱动器。如果没有冒号,PowerShell 将只会尝试在你当前的工作目录中查找名为 Alias 的目录。
它是如何工作的...
截至目前,PowerShell 提供程序似乎在 Linux 上无法按预期工作。然而,随着时间的推移,这个问题应该会得到修复。
提供程序将类似非文件系统的结构封装到自身,并将它们呈现给 PowerShell,就好像它们是文件和目录一样。这使你能够像管理文件和目录那样浏览复杂的结构,而这些结构可以使用文件系统上的命令(如Get-ChildItem)进行更简单的管理。
输出中的 Capabilities 列显示了每个提供程序的功能,如 Credentials、ShouldProcess 和 Filter。这意味着提供程序支持将凭据传递给核心,支持如 -WhatIf 和 -Confirm 这样的参数(它们是 ShouldProcess 的一部分),并且可以使用 -Filter 参数过滤输出。我们将在接下来的章节中了解这些内容。
另见
-
配方:
ShouldProcess功能,如-Confirm和-WhatIf(函数) -
配方:使用
-Filter参数
第二章:介绍 Core 及其功能
在本章中,我们将覆盖以下主题:
-
解构.NET Core 对象
-
将输出分解为不同的对象
-
解析输入从文本到对象
-
比较 Bash 和 PowerShell 的输出
-
比较 Windows PowerShell 和 PowerShell
-
列出别名并使用它们替代 cmdlet
-
创建自定义别名
-
导入/导出自定义别名以供未来使用
-
列出执行策略并设置合适的策略
介绍
微软在 2014 年宣布“开源” .NET 几乎引发了一场风暴。许多人赶忙涌上台(打个比方)去了解这令人难以置信的消息——微软怎么可能将操作系统的核心开源?一些人持怀疑态度,另一些人则感到高兴。然后宣布更加明确——.NET Core 是开源的,而不是.NET Framework。许多人认为.NET Core 是.NET Framework 的一个子集。
.NET 首次在 2000 年宣布,作为一个基于互联网标准的新平台。年底时,微软发布了公共语言基础设施作为标准,使得任何人都可以基于这些标准编写自己的.NET 框架。.NET Framework 自 2000 年代初以来一直是 Windows 的基础。
Windows PowerShell 于 2006 年发布,作为.NET Framework 的一个实现,专注于系统管理员(或称为 sysadmins),以帮助他们更好地管理 Windows 工作负载并自动化任务。
2016 年 6 月,微软发布了经过协作重构、更现代、更高效的.NET。.NET Core 正式诞生。虽然.NET Framework 继续主宰 Windows 领域,但.NET Core 作为开源且跨平台的框架,获得了巨大的推动力,并持续增长。而.NET Core 似乎是未来的发展方向。
PowerShell(不是Windows PowerShell)基于.NET Core,因此它是开源的,并且具有与.NET Core 相同的愿景,即跨平台。
在本章中,我们将研究一个非常简单的.NET Core 实现,并与 PowerShell 的输出进行比较,以证明 PowerShell 不过是封装了的.NET Core 代码。同时,我们也将观察 PowerShell 的一般行为。
解构.NET Core 对象
.NET Core 在跨平台的标准公共语言基础设施(Common Language Infrastructure)上运行。因此,使用.NET Core 封装 Linux 的内部工作变得可能。正如我们在未来的章节中会看到的,PowerShell 是面向对象的,就像.NET Core 一样。为了演示,我们将选择一个简单的系统类,System.IO.DirectoryInfo,来展示某个目录的信息。我们还将比较.NET Core 对象的输出与 PowerShell cmdlet 的输出,这两者都展示了某个目录的信息。
你不需要记住.NET Core 类、方法或它们的语法来与 PowerShell 一起工作;这正是 PowerShell 存在的意义所在。
准备好了吗
如果你按照上一章的步骤操作,应该已经在你的 Linux 计算机上安装了 PowerShell;打开终端窗口并键入 pwsh 来启动 PowerShell。
每个 对象 都有成员——属性 和 方法。如果你是面向对象编程的新手,属性是对象的特性(对象有什么),而方法是对象的能力(对象能做什么)。因此,引用(可以说是)最常被使用的属性和方法的例子:如果马是一个对象,它的高度、颜色等就是它的属性;奔跑、吃饭等就是该对象支持的方法。
如何操作...
- 使用 PowerShell 时,.NET Core 也作为一个依赖项被安装。让我们在 PowerShell 中创建一个对象,它将调用 .NET 类及其默认构造函数。这个构造函数需要一个参数。
PS> New-Object -TypeName System.IO.DirectoryInfo -ArgumentList '/home/ram'
- 这将为我们提供关于指定目录的信息,如下所示:
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 5/16/18 11:03 AM ram
- 在 PowerShell 中,有一个名为
Get-Item的 cmdlet,它可以提供关于目录的详细信息。让我们使用与之前相同的参数来调用这个 cmdlet,看看我们能得到什么。
PS> Get-Item '/home/ram'
Directory: /home
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 5/16/18 11:03 AM ram
- 很接近了!现在让我们使用
Get-Membercmdlet 查看刚才收到的输出对象的 详细信息。
PS> Get-Item '/home/ram' | Get-Member
Get-Member 显示输出对象中所有可用的成员(大多数 PowerShell cmdlet 返回的是对象输出,而非纯文本)。欲了解更多信息,请运行 Get-Help Get-Member。
这将列出作为输出的一系列成员。我们目前主要关注第一行。
PS> Get-Item '/home/ram' | Get-Member
TypeName: System.IO.DirectoryInfo
Name MemberType Definition
---- ---------- ----------
LinkType CodeProperty System.String LinkType{get=GetLinkType;}
Mode CodeProperty System.String Mode{get=Mode;}
.
.
.
它是如何工作的...
注意输出的第一行,TypeName: System.IO.DirectoryInfo。这就是我们在创建 .NET 对象时使用的精确类型名称。

这证明了显示当前工作目录信息的相同任务可以通过调用 .NET 构造函数,或者运行 PowerShell cmdlet 来实现。因此,我们推测 PowerShell cmdlet 仅仅是封装的 .NET 代码,简化后使管理员能够与计算机进行交互,而不必担心底层的 .NET 代码。
本质上,Get-Item 在幕后调用了 System.IO.DirectoryInfo 类,并传递了与 cmdlet 一起传入的参数。
Get-Item 可以与文件系统中的任何位置一起使用。只要你有权访问该位置,PowerShell 就会返回你作为参数传递的位置信息。
正如他们所说:
如果 C# 大家都能做到,你也可以。
还有更多...
阅读 Get-Item | Get-Member 命令的输出,以了解你可以获得关于指定目录的更多信息。此外,当我们熟悉使用 Select-Object cmdlet 后,应该能够从返回的对象中调用特定的字段。
另见
- .NET 类,System.IO.DirectoryInfo(Microsoft 开发者网络)
将输出分解为不同的对象
在上一节中,我们看到一个对象可以有属性和方法。这些属性和方法被称为成员。在面向对象的编程方法中(以及通过 PowerShell 管理扩展),可以使用成员访问操作符,即单个点(.),来引用对象的属性和方法。
准备工作
理想情况下,这个食谱应该是对前面的扩展。如果你没有运行前面的命令,建议先运行它们,然后再继续以下步骤。
如何操作...
- 看一下之前食谱中命令的输出:
PS> Get-Item '/home/ram' | Get-Member
输出表包含成员的名称、类型和定义。看看 MemberType 列;你会看到 Method 和不同种类的属性,如 CodeProperty 和 Property。
- 假设我们想查看我的主目录最后一次写入的时间。我们可以使用成员访问操作符来引用这个属性。为此,只需将
Get-Item命令及其参数括在括号中,并使用点操作符引用属性。
PS> (Get-Item /home/ram).LastWriteTime
Wednesday, 18 May 2018 11:01:02
- 接下来,让我们选择这个对象的属性
Parent。这将为我们提供我的主目录所在目录的详细信息。
PS> (Get-Item /home/ram).Parent
Mode LastWriteTime Length Name
---- ------------- ------ ----
d-r--- 03/05/2018 17:07 home
这个输出本身是一个对象,这意味着我们可以像处理 /home/ram 一样获取这个返回对象的最后写入时间和其他详细信息。我们如何查看父文件夹(/home)的创建时间?
- 首先,让我们在这个对象上使用
Get-Membercmdlet,并查看返回对象的TypeName。
PS> (Get-Item /home/ram).Parent | Get-Member
TypeName: System.IO.DirectoryInfo
它与 Get-Item 本身相同。因此,Get-Item /home/ram 的任何成员也适用于 (Get-Item /home/ram).Parent。
- 现在在父对象上调用
CreationTime属性。只需在Parent后添加一个点,然后调用CreationTime属性。
PS> (Get-Item /home/ram).Parent.CreationTime
Thursday, 3 May 2018 17:07:38
CreationTime 属性本身就是一个对象,类型为 DateTime。因此,你可以在这个对象上执行日期和时间操作。
PowerShell 在大多数情况下不区分大小写。然而,建议我们遵循约定,以减少错误。特别是因为大小写敏感性在 Linux 中是一个约定。属性可能有不同的数据类型。数据类型可以在Definition列中看到。
- 让我们查看
CreationTime对象的成员,看看是否可以进一步过滤输出。
PS> (Get-Item /home/ram).Parent.CreationTime | Get-Member
TypeName: System.DateTime
- 让我们仅选择这个对象的年份属性。
PS> (Get-Item /home/ram).Parent.CreationTime.Year
2018
这就是从返回的对象中选择属性的全部内容。接下来,让我们使用返回对象中的方法,在 /home/ram 下创建一个子目录。
- 首先,列出该目录下的现有目录。可以使用 cmdlet
Get-ChildItem完成这项操作。
PS> Get-ChildItem /home/ram
Directory: /home/ram
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 06/04/2018 13:05 Desktop
d----- 18/05/2018 16:01 Documents
d----- 18/05/2018 16:01 Downloads
d----- 06/04/2018 13:05 Music
d----- 19/05/2018 11:07 Pictures
d----- 06/04/2018 13:05 Public
d----- 06/04/2018 13:05 Templates
d----- 10/04/2018 03:41 Videos
Get-Item 会提供关于目录本身的详细信息。本质上,这个 cmdlet 处理当前的项目。因此,你可以访问与该意图相关的属性和方法。而子项则意味着在该项内的文件和目录。
- 这是用户配置文件中标准目录的列表。现在让我们在该配置文件文件夹内创建一个子目录,使用
Get-Item中的方法。
PS> (Get-Item /home/ram).CreateSubdirectory('test-directory')
要知道某个方法接受什么参数,请查看 Get-Member 输出中的 Definition 列。对于 CreateSubdirectory,定义是 System.IO.DirectoryInfo CreateSubdirectory(string path)。
你现在应该会看到一个确认信息。

- 很好。现在,列出主目录下的所有目录。
PS> Get-ChildItem /home/ram
Directory: /home/ram
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 06/04/2018 13:05 Desktop
d----- 18/05/2018 16:01 Documents
d----- 18/05/2018 16:01 Downloads
d----- 06/04/2018 13:05 Music
d----- 19/05/2018 11:07 Pictures
d----- 06/04/2018 13:05 Public
d----- 06/04/2018 13:05 Templates
d----- 19/05/2018 13:31 test-directory
d----- 10/04/2018 03:41 Videos
现在我们可以看到我们使用 CreateSubdirectory 方法创建的新目录。
运行 Get-ChildItem 时,正如你可能注意到的,它类似于运行 Get-Directories 和 GetFiles 方法,这些方法来自 Get-Item 返回的对象。这是 .NET Core 中封装的另一个例子。
它是如何工作的……
使用返回对象中的属性和方法非常简单。可以直接在命令(包括参数的 cmdlet)上使用成员访问运算符来调用 cmdlet 返回输出中的对象。
在成员访问运算符后调用属性会获取该属性中保存的数据。在我们的例子中,它是 LastWriteTime。
方法是函数。它们可能需要,也可能不需要参数。CreateSubdirectory 方法需要一个字符串参数,它是我们希望创建的子目录的名称(或路径)——我们在括号中输入的内容基本上构成了我们希望创建的路径。对于那些不需要参数的可以运行的方法,它们需要在方法名后跟一个空括号进行调用,例如 ToString()。
当我们将字符串参数传递给 CreateSubdirectory 时,该方法会运行一个 .NET Core 例程,并在我们通过 Get-Item 指定的目录内创建一个子目录。 .NET Core 的内部工作超出了本书的范围。
另见
- 食谱:通过管道选择对象
从文本到对象的解析
从文本转换到对象模型最初可能看起来有些令人生畏。然而,在 PowerShell 中,切换到新模型并不难,尤其是考虑到 PowerShell 可以根据合适的工具将文本转换为对象。在本食谱中,我们将查看 PowerShell 将文本数据转换为对象的两种方法。
准备就绪
在我们深入食谱之前,先给自己做一个简短的介绍,讲解文本到对象解析是如何处理的。一种方法是使用 .NET 内置的功能,另一种方法则是使用 cmdlet,根据分隔符来执行转换,这是我们将要讨论的内容。
这个配方的基本要求很简单:你只需要在计算机上安装 PowerShell。我们将在 PowerShell 中编辑文件。如果你更喜欢使用文本编辑器,也可以。这大多数 Linux 发行版都自带文本编辑器。如果没有,你可以通过包管理器安装 Vim、Nano、Gedit、Visual Studio Code、Atom 或其他任何文本/代码编辑器。
如何做到这一点...
我们首先来看如何从终端的纯文本输入将文本转换为对象。这涉及到使用所谓的PowerShell 类型加速器。PowerShell 类型加速器是.NET 类的别名。通过使用这些加速器,我们可以调用.NET 类并在 PowerShell 中使用它们的许多功能。
- 让我们以纯文本为输入,并将其转换为日期对象。要检查输入的对象类型,可以使用
Get-Membercmdlet。
PS> '21 June 2018' | Get-Member
将任何文本用单引号括起来,可以将该文本定义为非扩展的文字字符串。在 PowerShell 中,这种情况下不需要显式定义。
TypeName显示为System.String。这确认了我们输入的确实是纯文本。现在让我们使用类型加速器,将该文本转换为DateTime对象。这个加速器是[DateTime];将这个加速器放在文字字符串前面。
PS> [DateTime]'21 June 2018'
Thursday, 21 June 2018 00:00:00
- 接下来,找到返回的对象的
TypeName。
PS> [DateTime]'21 June 2018' | Get-Member
TypeName: System.DateTime
Voilà,字符串已经成功解析为日期和时间!
- 通过使用
Get-Datecmdlet,并传入文本参数,同样也可以实现相同的结果。
PS> Get-Date '21 June 2018'
Thursday, 21 June 2018 00:00:00
- 类似地,
TypeName将是:
PS> Get-Date '21 June 2018' | Get-Member
TypeName: System.DateTime
- 就像我们在前面所做的那样,我们现在可以操作对象,以更有意义的方式展示信息。例如,如果你只关心年份,你可以写:
PS> (Get-Date '21 June 2018').Year
2018
另一种将文本转换为对象的方法是使用执行此类任务的 cmdlet。PowerShell 提供了一些转换 cmdlet,其中之一就是Import-Csv。你可能注意到,PowerShell 通常以表格格式输出结果。这是对象的简单表示形式。而Import-Csv将以分隔符行列结构的数据转换为对象,每一行是对象的一个实例,每一列是对象的一个属性。
- 为了演示这一点,让我们创建一个 CSV 文件,并在其中输入以下内容。在 PowerShell 提示符下,输入:
PS> @'
- 这会让你进入下一行;PowerShell 正在等待输入。请在提示符处粘贴以下示例内容。
WS,CPU,Id,SI,ProcessName
161226752,23.42,1914,1566,io.elementary.a
199598080,77.84,1050,1040,gnome-shell
216113152,0.67,19250,1566,atom
474685440,619.05,1568,1566,Xorg
1387864064,1890.29,15720,1566,firefox
- 转到下一行,在
>>提示符下输入以下内容:
'@ | Out-File sample.csv
你也可以使用touch命令和你选择的文本编辑器执行相同的操作。目标是将内容写入示例文件中。
- 接下来,使用 PowerShell 读取文件的内容。
PS> Get-Content ./sample.csv
WS,CPU,Id,SI,ProcessName
161226752,23.42,1914,1566,io.elementary.a
199598080,77.84,1050,1040,gnome-shell
216113152,0.67,19250,1566,atom
474685440,619.05,1568,1566,Xorg
1387864064,1890.29,15720,1566,firefox
- 看起来像是简单的文本。让我们查看对象的类型名称,以确认这确实是纯文本。输入:
PS> Get-Content ./sample.csv | Get-Member
TypeName: System.String
- 那只是一个简单的字符串。现在让我们将内容转换为一个简单的对象。这可以通过
Import-Csv来完成。
Import-Csv ./sample.csv
这应该会给你一个类似列表的输出。

- 要确认输出为对象,请列出其成员。

总的来说,内容是一个自定义对象,如PSCustomObject所示。我们在 CSV 中拥有的列是NoteProperty类型,如MemberType所示。
NoteProperty是一种通用属性,其特性类似于字符串。虽然大多数属性都是从.NET 继承而来,但NoteProperty是在 PowerShell 中自定义创建的,作为名称-值对。
- 如果你更愿意将内容视为表格,请将内容格式化为表格。
PS> Import-Csv ./sample.csv | Format-Table

这就是本配方的结尾。我们已成功将文本转换为对象。但请注意,这只是一个简单的转换,而Import-Csv的输出仍然类似于字符串。尽管如此,所有内容现在都是对象,这在 PowerShell 中更易于处理。
工作原理...
类型加速器是在 PowerShell 中封装.NET 代码的另一种形式。请记住本章的第一个配方,在其中我们在 PowerShell 中创建了一个.NET 对象。我们使用了 PowerShell 命令New-Object -TypeName System.IO.DirectoryInfo -ArgumentList '/home/ram'来获取主目录的信息:我们创建了System.IO.DirectoryInfo的一个新实例,并向其传递了一个参数。那是一大堆要写的代码。为了加快这个过程,我们可以使用[IO.DirectoryInfo]'/home/ram'(System是默认命名空间;PowerShell 在调用加速器时不需要我们显式提及它,也能理解它),它输出与前一个命令相同的对象。
另一方面,使用Import-Csv的过程是将文本数据简单转换为名称-值对。这类似于使用带有Delimiter参数的ConvertFrom-Text。通过这种方式,我们指示 PowerShell 将每行文本转换为对象的实例:行列结构中的第一行被视为属性名称,其余行是数据。单元格使用逗号分隔符分隔,这在 CSV 文件中是一样的。
还有更多...
寻找更多内置于 PowerShell 的转换命令。可以使用命令Get-Command -Verb ConvertFrom来实现。
另请参阅
-
关于类型加速器的更多信息,请参阅Hey, Scripting Guy! Blog。
-
配方:使用 Here 字符串。
-
PowerShell 中的不同类型的成员。
-
最佳实践汇总:仅在最后进行格式化。
比较 Bash 和 PowerShell 的输出
PowerShell 和 Bash 都是 Shell,都能够与内核进行交互。就像 Bash 可以在 Windows 上运行一样,PowerShell 现在也可以在 Linux 上运行。虽然关于哪个 Shell 更好的几乎所有方面都存在争议,今天选择哪种 Shell 纯粹是个人偏好的问题,但毫无疑问,PowerShell 与.NET Core 的强大能力是相匹配的。
两个 Shell 的主要区别是,正如我们之前所看到的,PowerShell 输出的是对象,而 Bash 返回的是文本。在 Bash 中,操作输出涉及首先处理文本,然后在处理后的文本上运行进一步的命令以获取所需的输出。而 PowerShell 则将内容处理为对象,按设计需要的文本操作较少。
结构化数据,正如 Windows PowerShell 的发明者 Jeffrey Snover 所指出的,随着时间的推移越来越受欢迎,而结构化数据正是 PowerShell 最为出色的地方。
准备工作
在本节中,我们将选择一个例子,展示如何使用 PowerShell 处理文件元数据既简单又高效,主要是因为输出是对象。我们将列出我们的主目录中的文件和文件夹,并使用ls(在 Bash 中)和Get-ChildItem(在 PowerShell 中)显示修改的日期和时间。
如果你愿意,也可以打开两个终端实例:在其中一个上启动pwsh。
如何操作...
- 在 Bash 提示符下,输入
ls -l来列出所有文件及该命令默认显示的元数据。

- 打开运行 PowerShell 的终端,在提示符下输入
Get-ChildItem。

- 现在,让我们只选择文件夹的名称以及最后修改的日期和时间。这可以通过在 Bash 中将
ls -l的输出传递给awk来完成。
ls -l | awk '{print $6, $7, $8, $9}'

- 接下来,让我们在 PowerShell 中也获取相同的信息。
Get-ChildItem | select LastWriteTime, Name

如果你注意到的话,在这两种情况下,输出是非常相似的,然而在 PowerShell 中,你还可以看到列的名称,这意味着你不需要再查找额外的文档。而且,在 PowerShell 中选择列的操作更简单,无需文本操作。另一方面,在 Bash 中,我们使用awk命令来操作文本输出。
- 让我们进一步操作,创建一个名字中带空格的子目录。
$ mkdir 'test subdirectory'
$ ls -l | awk '{print $6, $7, $8, $9}'

注意,原本应该是test subdirectory的部分,显示为test。
如何运作...
PowerShell 从文件系统中读取内容时是作为对象处理,而不是作为文本。因此,你可以直接选择所需的列(或者稍后我们将看到的属性)。而 Bash 则输出文本,其中的列通过分隔符进行操作。
为了证明这一点,我们创建了一个新子目录,目录名中带有空格,我们进行了与之前相同的列选择操作,只不过这次我们没有得到新子目录的完整名称,因为目录名中包含了空格,而空格在awk中是一个分隔符。
比较 Bash 和 PowerShell 就像比较苹果和橘子——不仅仅是在一个方面。然而,理解它们的差异有助于我们充分利用每个工具。
另请参见
- 配方:通过管道选择对象。
比较 Windows PowerShell 和 PowerShell
PowerShell 和 Windows PowerShell 是两个不同的实现。前者基于更大的框架——.NET Framework;后者则是基于更现代的框架——.NET Core。由于 PowerShell 的父框架是跨平台的,因此 PowerShell 也是跨平台的;而 Windows PowerShell 仅限于 Windows,但截至本章节撰写时,它的功能比 PowerShell 更强大。
本书所讨论的 PowerShell 是跨平台的 PowerShell Core,简称PowerShell。而特定于 Windows 的 PowerShell 称为Windows PowerShell。
Windows PowerShell 利用 Windows 的内部组件和架构模型,其能力通过 WinRM 和 Windows 管理工具增强。事实上,大多数差异的存在是因为 Windows 和类 Unix 操作系统之间的固有差异。
对 snap-ins 的支持
PowerShell 不支持传统的模块版本,称为Snap-ins。许多旧版的 snap-ins 已被重新打包为二进制模块,因此这不应成为大问题,因为这些模块的未来开发理论上应该可以在 PowerShell 上运行,只要二进制文件中的系统调用能够在其运行的系统上工作。例如,即使 Windows Active Directory 模块被重新打包为二进制 PowerShell 模块,它也能在 Windows PowerShell 和 Windows 上的 PowerShell 中运行,但无法在 Linux 上的 PowerShell 中运行,因为 Windows Active Directory 无法在 Linux 上运行。
便捷的别名
一个需要注意的重要点是,像 ls 和 mkdir 这样的命令在 Windows PowerShell 中是别名,这意味着在 Windows PowerShell 中运行 ls 会在后台执行 Get-ChildItem(Windows 上的 PowerShell 也是如此)。然而,在 Linux 中,在 PowerShell 中运行 ls 会运行实际的 ls 命令;在 Linux 上的 PowerShell 中,ls 不是别名,而是命令本身,其输出将是纯文本。你可以通过在 Linux 上的 PowerShell 中运行 ls | Get-Member 来验证这一点,并与 Windows 上的 PowerShell 以及 Windows PowerShell 进行对比。(因此,最好遵循不在脚本中使用别名的最佳实践。)

PowerShell 通过自动变量 IsLinux、IsWindows 和 IsMacOS 的值来判断自己运行在哪个操作系统上。在任何系统中,这些变量只有一个是 True。当 PowerShell 发现 IsLinux 为 True 时,它会运行 Linux 命令,而不是最初为了方便 Linux 管理员创建的别名。有关这些自动变量的更多信息,请阅读配方 配置内建变量。
PowerShell 工作流
习惯于 Windows PowerShell 工作流的 Windows 管理员需要注意,PowerShell 中没有这些功能。PowerShell 工作流稍微复杂(可以这么说),用于特定场景,如需要并行运行多个 cmdlet 或活动需要在重启后继续执行。工作流是基于 Windows 工作流基础(Windows Workflow Foundation)的,而这并不跨平台。因此,PowerShell 工作流无法在 PowerShell 中运行。但请理解,这根本不是什么损失。
PowerShell 期望状态配置
所谓的期望状态配置(Desired State Configuration,DSC)目前仍在开发中。到目前为止,DSC 资源有两个代码库:一个是由 Microsoft 的 Unix 团队管理的 Linux 版 LCM,另一个是由 PowerShell 团队编写的 Windows PowerShell 版 DSC 资源。DSC 代码库跨平台化还需要一段时间。
列出所有别名并用它们替代 cmdlet
别名,顾名思义,是 cmdlet 的替代名称。它们有两个目的:
-
减少击键次数
-
使过渡到 PowerShell 更加顺畅
传统上,别名是在 PowerShell 中创建的,以便让 Windows 和 Linux 管理员不觉得新的框架难以使用。然而,别名最好只在命令行中使用,而不是脚本中,因为有些别名在 Linux 中并不存在,并且一般来说,别名会影响可读性。(例如,要意识到 gbp 代表的是 Get-PSBreakPoint,需要有意识的努力。)
如何实现...
现在我们已经看过最佳实践,接下来让我们看看如何列出系统中的所有别名。正如之前所提到的,在 PowerShell 中思考非常简单。当我们知道获取任何信息的动词是 Get,而名词在这个例子中是 Alias,那么相应的 cmdlet 就是 Get-Alias。
- 运行一个简单的
Get-Command来查询Get-Alias,我们就能知道是否确实有这个 cmdlet。
Get-Command Get-Alias
- 现在让我们运行
Get-Help来了解如何使用该 cmdlet。
Get-Help Get-Alias
如果你不确定任何命令,或者想在不使用别名的情况下减少击键次数,可以使用 tab 补全。输入部分 cmdlet 或参数,然后按 Tab 键。PowerShell 会根据你所在的平台,为你完成命令或提供建议。
- 根据帮助文档,
Get-Alias的所有参数都是可选的(它们都被[]括起来)。因此,仅运行Get-Alias将列出当前 PowerShell 实例中所有可用的别名。

- 现在让我们尝试解析别名
gbp,找到它实际运行的 PowerShell cmdlet。
PS> Get-Alias gbp
CommandType Name Version Source
----------- ---- ------- ------
Alias gbp -> Get-PSBreakpoint
- 现在让我们看看如何做相反的操作:获取某个 cmdlet 的别名。如果你阅读此 cmdlet 的帮助文档,你会看到第二个参数集中有一个名为
Definition的参数。这就是调用别名时运行的实际 PowerShell cmdlet。
PS /home/ram> Get-Alias -Definition Get-ChildItem
CommandType Name Version Source
----------- ---- ------- ------
Alias dir -> Get-ChildItem
Alias gci -> Get-ChildItem
- 我们可以看到两个别名作为输出,它们都在幕后运行
Get-ChildItem。现在我们运行dir和Get-ChildItem,并比较它们的输出。
PS /home/ram> dir
Directory: /home/ram
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 06/04/2018 13:05 Desktop
d----- 18/05/2018 16:01 Documents
d----- 18/05/2018 16:01 Downloads
d----- 06/04/2018 13:05 Music
d----- 20/05/2018 02:17 Pictures
d----- 06/04/2018 13:05 Public
d----- 06/04/2018 13:05 Templates
d----- 10/04/2018 03:41 Videos
PS /home/ram> Get-ChildItem
Directory: /home/ram
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 06/04/2018 13:05 Desktop
d----- 18/05/2018 16:01 Documents
d----- 18/05/2018 16:01 Downloads
d----- 06/04/2018 13:05 Music
d----- 20/05/2018 02:20 Pictures
d----- 06/04/2018 13:05 Public
d----- 06/04/2018 13:05 Templates
d----- 10/04/2018 03:41 Videos
- 这两个输出是相同的。现在让我们看看这些命令返回的是哪种类型的对象。
PS /home/ram> dir | Get-Member
TypeName: System.IO.DirectoryInfo
Name MemberType Definition
---- ---------- ----------
LinkType CodeProperty System.String LinkType{get=GetLinkType;}
Mode CodeProperty System.String Mode{get=Mode;}
...
PS /home/ram> Get-ChildItem | Get-Member
TypeName: System.IO.DirectoryInfo
Name MemberType Definition
---- ---------- ----------
LinkType CodeProperty System.String LinkType{get=GetLinkType;}
Mode CodeProperty System.String Mode{get=Mode;}
...
它们也返回了相同的对象。
它是如何工作的...
别名只是在 PowerShell 中的映射。短单词被映射到 PowerShell cmdlet,在每个别名的 Definition 属性中识别。因此,你可以使用别名代替完整的 cmdlet。别名还支持与 cmdlet 相同的参数,因为别名只是指向正确 cmdlet 的指针。
另见
- 最佳实践总结
创建自定义别名
如前一节所见,别名只是指向实际 PowerShell cmdlet 的指针,因此,创建自定义别名只是涉及确定你希望使用的别名的单词,并将其映射到你想要调用的 PowerShell cmdlet。
如何操作...
-
首先,确定你想用作别名的单词。例如,让我们考虑
listdir。 -
在 PowerShell 上运行
listdir,确保不存在此类 cmdlet(或 Linux 命令)。 -
通过运行以下命令列出处理别名的 cmdlet:
Get-Command -Noun Alias
请记住,PowerShell 中的名词是单数形式。因此,不会有包含 Aliases 的第一方 cmdlet。如果第三方模块将 Aliases 作为名词使用,那么它们没有遵循 PowerShell 的最佳实践,即仅使用单数名词。
-
New-Alias是我们要找的 cmdlet,因为它用于创建一个新的别名。(Set-Alias用于修改已存在的别名。) -
通过运行以下命令,查看
New-Alias的帮助文档:
Get-Help New-Alias
帮助文档指出,只有 Name 和 Value 参数是必需的。我们将仅使用这两个来创建这个简单的别名。
- 运行以下命令以创建自定义别名:
New-Alias listdir Get-ChildItem
- 查看别名是否按预期创建。
PS> Get-Alias listdir
CommandType Name Version Source
----------- ---- ------- ------
Alias listdir -> Get-ChildItem
- 另外,运行别名以查看它输出的内容。
PS /home/ram> listdir
Directory: /home/ram
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 4/20/18 6:36 AM Desktop
d----- 5/10/18 1:05 PM Documents
d----- 5/16/18 11:03 AM Downloads
d----- 4/20/18 6:36 AM Music
d----- 5/1/18 2:19 PM Pictures
d----- 4/20/18 6:36 AM Public
d----- 4/20/18 6:36 AM Templates
d----- 4/20/18 6:36 AM Videos
这就是我们熟悉的输出——Get-ChildItem 的输出。
默认情况下,别名是临时的。它们只在 PowerShell 会话存在时有效。为了避免每次都需要重新创建它们,你可以将这些别名导出(下一节将介绍操作步骤),并通过 PowerShell 配置文件导入它们。我们将在后面的章节中了解配置文件。
它是如何工作的……
如前所述,别名是指向 cmdlet 的指针。使用 New-Alias,你可以创建一个带有自定义名称的指针,指向你想要的 PowerShell cmdlet。这实际上是一个名称-值对。
当你在 PowerShell 中运行任何命令时,PowerShell 会检查它的 cmdlet 和别名列表(以及其他定义),以理解你在请求什么。当 PowerShell 遇到一个别名时,它会查找该别名指向的 cmdlet 并执行该 cmdlet。
还有更多……
你可以为别名添加更多内容,比如描述。请参考Get-Alias的帮助文档,看看你可以对别名做些什么。
另请参见
-
配方 3.8:理解 cmdlet 和参数
-
配方 2.8:导入/导出自定义别名以便未来使用
-
配方 3.4:启用每次加载时自动执行命令
导出/导入自定义别名以便未来使用
尽管别名具有优势且被设计用来临时使用,但它们的临时性可能会带来不便。为了使别名可以重复使用,需要将其导出到文件,并在需要时再导入。这个配方将展示如何导出和导入你可能已经创建的别名。
准备工作
你需要先创建自定义别名,才能使此过程生效。如果你没有创建自定义别名,导出操作将仅导出默认别名,这些别名本来就在 PowerShell 中加载。
请返回上一节,创建至少一个自定义别名。
如何操作……
- 确保你的自定义别名存在并正常运行。实现这一点的一种方法是获取你创建的别名的详细信息。
Get-Alias listdir
- 将所有加载到当前会话中的别名导出到文件。
Export-Alias aliases.csv
CSV 是默认的文件类型。我们在接下来的步骤中看到的导入操作理解 CSV 并做出必要的关联。
- 也可以将别名导出为脚本。这样,PowerShell 将创建一个脚本,其中包含每个导出别名的
New-Aliascmdlet。
Export-Alias aliases.ps1 -As Script
- 查看每个文件的内容;首先是 CSV 文件,然后是脚本。
Get-Content ./aliases.csv

Get-Content ./aliases.ps1

- 可选地,编辑文件以删除除你创建的别名之外的所有别名。自定义别名可以在列表的底部找到。
本章到此为止,已完成别名导出部分。
现在,让我们将别名导入到 PowerShell 会话中。
-
重启 PowerShell。
-
查看你创建的别名
listdir是否存在。
Get-Alias listdir
- 现在,导入别名。
Import-Alias ./aliases.csv
你可能会收到多个错误,提示新别名无法创建,因为该别名已存在。处理方法有两种:第一种是从导出文件中移除默认别名(推荐),第二种是使用-Force参数(这可能仍会导致错误,但错误会显著减少)。
如果你将别名导出为脚本,只需调用该脚本即可。
./aliases.ps1
它是如何工作的...
脚本方式很简单:命令按顺序运行,别名像你手动创建它们一样在系统中创建。
使用 CSV 导入时,PowerShell 会将输入解析为名称-值对(并根据导出的内容添加其他参数),并将其添加到当前进程的别名引用中。
另请参见
- 配方 3.4:为每次加载启用自动执行命令
列出执行策略并设置合适的策略
曾几何时,在 Windows 计算机上运行脚本是一件轻松的事。Windows 计算机极易受到远程脚本执行的攻击。随着 PowerShell 的出现,微软为用户提供了一种安全带,使用户能够控制 PowerShell 脚本的加载方式。某些特定的脚本执行模型被限制,弥补了系统中的一些漏洞。
重要的是要记住,执行策略不是一种安全功能。存在绕过此限制并运行脚本的方法。执行策略的目的是确保用户不会在不知情的情况下意外运行脚本。
Windows 上的 PowerShell 和 Windows PowerShell 包含此配置。默认情况下,Windows 上的 PowerShell 脚本仍然是受限制的。而在 Linux 上的 PowerShell,目前不支持此功能,并且根据社区中的互动,尚不确定该功能是否会引入到 Linux 版 PowerShell 中。
执行策略决定了允许执行脚本的类型。以下是六种执行策略(不包括默认策略):
-
AllSigned
-
RemoteSigned
-
Restricted
-
Unrestricted
-
Bypass
-
Undefined
还有三个作用域:
-
进程
-
CurrentUser
-
LocalMachine
执行策略和作用域的组合决定了脚本可以在哪些条件下加载。微软已经详细记录了每种策略的含义。一般来说,AllSigned 要求所有在计算机上运行的脚本都必须由受信任的证书颁发机构使用代码签名证书进行签名。如果设置了此策略,即使是你自己创建的脚本,PowerShell 也不会运行未签名的脚本。
Restricted是默认策略:可以运行命令,但不能运行脚本。RemoteSigned允许在本地计算机上创建的脚本运行。来自互联网的下载脚本无法运行。
Bypass类似于 Unrestricted,但用于特定场景,例如当 PowerShell 构成某个应用程序的基础时,该应用程序有其自己的安全实现。
Unrestricted 表示所有脚本和命令在简单确认后可以运行。Undefined 表示没有为特定范围定义策略。让我们通过以下教程来理解这些概念。
准备工作
此教程需要 Windows 环境才能运行。如果你正在运行纯 Linux 环境,无法使用此教程。你可以运行命令,但会看到在所有级别上都设置了 Unrestricted 策略。
如果你能使用一台 Windows 电脑,那么无论它是否安装了 PowerShell 或 Windows PowerShell,都可以继续进行这个教程。
如何操作...
通过运行 pwsh 或 powershell 打开 PowerShell 窗口。pwsh 命令调用 PowerShell,而 powershell 调用 Windows PowerShell。
Windows PowerShell 在所有现代 Windows 产品中都已预安装;而 PowerShell 则需要单独安装。请注意,当前的所有 PowerShell cmdlet 也可以在 Windows PowerShell 上运行。
- 首先,运行 Get-Command cmdlet 以了解如何处理执行策略。
Get-Command -Noun Execution*

- 现在让我们获取有关运行 cmdlet 的帮助。
Get-Help Get-ExecutionPolicy
- 我们想知道机器上设置的执行策略,因此,我们将运行:
PS> Get-ExecutionPolicy

这显示当前 PowerShell 会话中生效的执行策略。
- 要列出在不同范围内设置的策略,请运行以下命令:
PS> Get-ExecutionPolicy -List

我们看到策略仅在 LocalMachine 层级设置,并且设置为 RemoteSigned,这与前一步中显示的相同。LocalUser 和 Process 范围的策略为 Undefined,这使得会话从 LocalMachine 获取执行策略。
现在让我们将本地计算机的执行策略设置为 Undefined,并查看我们的会话会如何处理。
-
为了使其生效,关闭当前 PowerShell 会话并以管理员身份打开一个新会话。
-
接下来,运行以下命令:
PS> Get-Help Set-ExecutionPolicy

- 帮助文档显示,ExecutionPolicy 参数的值是必需的,且 Undefined 是它所接受的有效值之一。我们希望在 LocalMachine 范围内设置策略,因此,
PS> Set-ExecutionPolicy Undefined -Scope LocalMachine
- 现在,列出不同范围内的执行策略:
PS> Get-ExecutionPolicy -List

- 现在,让我们检查当前生效的执行策略:
PS> Get-ExecutionPolicy

- 现在让我们将执行策略恢复到开始教程前的状态。你可能需要根据自己在计算机上的权限调整策略。
PS> Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
它是如何工作的...
执行策略只是为避免意外执行脚本而在系统上设置的条件。它们在不同的范围内生效。
正如之前所提到的,PowerShell 中有三种作用域。LocalSystem 作用域位于链条的末端。它上面是 CurrentUser 作用域。最上面的是 Process 作用域。优先级的顺序是:Process > CurrentUser > LocalMachine。因此,如果在 Process 作用域设置了除 Undefined 以外的策略,当前会话将使用在进程上设置的策略。如果是 Undefined,则会查找在 CurrentUser 作用域上设置的策略。如果 CurrentUser 也标记为 Undefined,那么会话将应用在 LocalMachine 级别上设置的策略。如果 LocalMachine 设置为 Undefined,则会话将选择默认策略,这个默认策略是基于 PowerShell 所定义的策略,具体内容可能会根据操作系统版本而有所不同。例如,在 Windows 2016 中,默认策略是 RemoteSigned。
在 CurrentUser 和 LocalMachine 级别上设置的策略会存储在 Windows 注册表中。Process 作用域上设置的策略则存储在临时环境变量 $env:PSExecutionPolicyPreference 中。
另见
- 执行策略简介 (Microsoft)
第三章:使用 PowerShell 为管理做好准备
本章内容包括以下主题:
-
安装 Visual Studio Code
-
配置自动变量
-
使用变量更改 Shell 行为
-
启用每次加载时自动执行命令
-
自定义终端提示符
-
理解 PowerShell 中的标准重定向
-
从 PowerShell 调用原生 Linux 命令
-
理解 cmdlet 和参数
-
使用最少的按键运行 cmdlet
-
查找参数别名
-
调用 PowerShell 脚本
-
Dot-sourcing PowerShell 脚本
-
从 PowerShell 外部调用 PowerShell cmdlet
-
记录在 PowerShell 控制台上运行的 cmdlet
介绍
有一个普遍的观念,认为越多使用终端(而非 GUI),效率就越高。输入命令比点击屏幕更轻松、更快捷。然而,对于刚刚开始使用终端的人来说,情况可能并非如此。随着时间的推移,管理员会越来越熟悉终端,并学会像训练有素的马一样配置终端,以提高速度和效率。此外,大多数高效的管理员喜欢自动化他们工作流的多个部分——自定义 .bashrc 和 Vim 脚本就是其中的例子。在本章中,我们将熟悉与 PowerShell 配合使用的不同控制台和工具,并探讨一些简单的技巧,帮助我们自定义工作空间,从而提高效率。
安装 Visual Studio Code
脚本可以直接在控制台上使用 Vim 进行编写。也可以使用其他编辑器如 Gedit 或 Atom 来编写 PowerShell 脚本。然而,推荐使用微软的开源代码编辑器——Visual Studio Code(或 vscode)。在本教程中,我们将介绍如何安装 Visual Studio Code,并将其配置为与 PowerShell 一起使用。
准备工作
我们将介绍在 Ubuntu 上安装 vscode 的步骤。如今,大多数软件库中都包含 Visual Studio Code。您可以在您的发行版的软件下载商店中查找并安装 vscode。如果没有,安装 vscode 最简单的方法是下载 .deb(或根据您的发行版下载 .rpm 包),并运行它以在计算机上安装该软件包。
如何操作...
安装 Visual Studio Code 非常简单。
-
如果您的 Linux 发行版有软件商店,请在商店中搜索 Visual Studio Code。
-
如果找到 Visual Studio Code,请从那里安装该软件包。如果没有,请继续执行下一步。
-
Visual Studio Code 的软件包名称是
code。使用您的包管理器在软件库中搜索该软件包。在 Ubuntu 上,命令是:
$ sudo apt-cache pkgnames code
- 如果您能够在您的软件库中找到该软件包,请像安装任何其他软件包一样安装 vscode。
$ sudo apt install code
-
如果您无法找到该软件包,请访问
code.visualstudio.com/Download并下载适用于您发行版的正确code软件包。 -
要安装 VS Code,调用你的包管理器并指定下载的包路径。
$ sudo apt install install code_version_arch.deb
- 如果你更愿意以便携模式安装 VS Code,可以下载 VS Code 的 tar 包,并将其内容提取到一个方便的位置来运行 VS Code。但请记住,在这种情况下,VS Code 的更新将由 VS Code 自身处理。
Visual Studio Code 本身是一个强大的代码编辑器。然而,它可能无法直接完美支持 PowerShell。你需要安装一个扩展,它可以提供运行和编写 PowerShell 脚本的功能。
-
启动 Visual Studio Code。
-
点击扩展图标或按
Ctrl+Shift+X进入扩展面板。 -
在搜索框中输入
powershell publisher:Microsoft,然后按回车键搜索 PowerShell 包。 -
点击安装,结果包中应该会显示 PowerShell 包,它会排在最上面。
-
安装完成后,点击重新加载,以使 Visual Studio Code 加载 PowerShell 功能。
现在你已经准备好使用一个友好的编辑器来开发 PowerShell 脚本,它支持 Windows PowerShell 集成脚本环境的几乎所有功能,甚至更多!
工作原理...
Windows PowerShell ISE 曾是开发 PowerShell 脚本甚至 PowerShell 编写的应用程序的事实标准环境。后来,Adam Driscoll 的 PowerShell 扩展将 PowerShell 集成到了 Microsoft Visual Studio 中,成为了集成开发环境的一部分。
在 PowerShell 开发的同时,.NET 基金会成立,微软开始开发一个名为 Visual Studio Code 的轻量级代码编辑器,它包含了 Visual Studio 的许多强大功能,但没有语言库的负担。这足以构建 PowerShell 脚本;大多数 PowerShell 脚本编写者 主要使用 IntelliSense 功能,而 Visual Studio Code 提供了这些功能。
使用包管理器来安装 VS Code 可以确保满足所有依赖项。而且,这种安装方式确保了签名密钥被添加到系统中。这样,VS Code 的更新可以通过系统来安装,例如运行 sudo apt upgrade。
另请参阅
- 在 Linux 上安装 Visual Studio Code(Microsoft 文档)
配置自动变量
也许没有什么比可配置性更能提高效率了。配置一个系统是将其塑造为符合你个人口味的一种方式。你是唯一知道什么对你最有效的人。因此,一个系统越可配置,它就越能根据你的需求进行调整。PowerShell 中的自动变量是 PowerShell 自定义的第一步(配置文件是另一种方法;我们稍后会讲到它们)。在本教程中,我们将列出所有的自动变量,并将其中一些配置为满足我们的需求。
准备就绪
阅读 PowerShell 中列出各种提供者 部分,了解如何在 PowerShell 中使用各种提供者。
如何操作...
首先,让我们列出我们所拥有的变量。这可以通过两种方式完成:
-
使用 cmdlet
-
使用提供程序
让我们先看看使用 cmdlet 列出内置于 PowerShell 的变量。
-
打开一个终端窗口。如果已经打开了一个,重启 PowerShell。
-
查找与变量配合使用的 cmdlet。
PS> Get-Command -Noun Variable
请记住,cmdlet 中的名词总是单数形式。因此,应该是 Variable,而不是 Variables。
- 有五个 cmdlet 处理变量。我们希望获取一个新 PowerShell 会话中所有已存在变量的列表。让我们选择
Get-Variable,并获取它的帮助信息。
PS> Get-Help Get-Variable
- 这是我们需要用来列出当前作用域中所有预定义变量的 cmdlet。
PS> Get-Variable
这样应该可以列出当前作用域中所有预定义的变量。
你定义的任何变量都会列在这里。因此,重要的是你要启动一个新的 PowerShell 会话,看看有哪些变量已经预定义。
现在,让我们使用 PowerShell 提供程序列出当前作用域中定义的变量。
- 列出 PowerShell 提供程序。我们在前一章中已经看过提供程序。
PS> Get-PsProvider
- 将位置更改为
Variable:驱动器,这属于Variable提供程序。可以使用Set-Location完成此操作。
PS> Set-Location Variable:
- 现在,让我们列出
Variable:驱动器下所有可用的子项。
PS> Get-ChildItem .
这的输出与没有参数调用的 Get-Variable 完全相同。
它是如何工作的...
PowerShell 内建了一些控制其行为的变量,管理员可以修改其中一些以满足需求。然而,一些变量是不能修改的;它们是上下文相关的,提供了一定的灵活性(或者说模块化,根据具体情况而言)给 shell。
一个这样的例子是 $PWD,它包含当前目录的路径。这个变量会根据 Set-Location 的执行自动变化。不能显式地为这样的变量分配值;显式设置值不会对 shell 的行为产生任何影响。
另一方面,一些变量接受值,并让我们控制命令和脚本的执行。我们将在下一个食谱中看到一个示例。
使用变量改变 shell 行为
在前一个食谱中,我们查看了现有的变量。在这个食谱中,我们将更改其中一个变量的值,以控制 PowerShell 的行为。再次提醒,值的更改是暂时的;一旦 PowerShell 进程重启,值会被重置。
准备工作
阅读前一个食谱以了解哪些自动变量是预定义的。同时,启动 Visual Studio Code。按照下面的步骤启动 VS Code。
-
打开应用程序(我使用的是 Gnome DE,使用
Super + A可以显示所有应用程序)。 -
输入
code。 -
按
Ctrl + `启动终端。 -
在欢迎界面点击新建文件。(或者按
Ctrl + N。) -
在 VS Code 窗口的右下角,你会看到文件类型设置为“纯文本”。点击它,你会被带到顶部的命令栏。
-
在命令栏中输入
powershell。PowerShell 集成控制台会在底部打开。

我们现在已经准备好进行配方操作了。
怎么做…
- 让我们运行一个会导致错误的命令。暂时,我们不关注命令的语法;我们现在的唯一目标是生成一个错误。在脚本窗口的第一行,输入:
Get-ChildItem /home/ram/random-directory
Write-Host "Hello world!"

- 使用
F5键运行这段两行脚本。

PowerShell 会提示显示错误。它还会显示 Hello world! 字符串。
- 现在,让我们使用
ErrorActionPreference变量设置错误操作偏好。我们从前面的配方中知道有这个变量。不过,首先,重新启动 PowerShell。最简单的方法是点击集成控制台窗口顶部的小垃圾桶图标。当 VS Code 提示是否要重新启动会话时,点击“是”。

- 现在,设置
ErrorActionPreference变量。在 PowerShell 集成控制台中,输入:
PS> Set-Variable ErrorActionPreference SilentlyContinue
- 再次运行这段两行的脚本。
这次没有错误。我们在控制台看到 Hello world!。
如果你不希望整个脚本出现在提示符中,可以让脚本的第一行是 Clear-Host;这样就会在显示输出之前清除屏幕。
- 如果你想检查是否生成了错误怎么办?我们检查自动变量
Error的值。
PS> Get-Variable Error

Value列包含一些文本。让我们选择并展开Value的内容。
Get-Variable Error | Select-Object -ExpandProperty Value

输出文本与我们设置错误操作偏好之前收到的结果相同。
你也可以直接调用 Error 变量来读取当前会话中发生的所有错误。
它是如何工作的…
默认情况下,错误操作偏好为 Continue,这意味着 PowerShell 会显示错误,并继续执行脚本的其余部分(在运行单个命令时,这种效果不明显,因此才创建了两行或三行的脚本,假如你在顶部加了 Clear-Host)。通过将 ErrorActionPreference 设置为 SilentlyContinue,我们指示 PowerShell 记录错误,但不在屏幕上显示,同时继续执行脚本的其余部分。
另请参阅
-
about_Preference_Variables, $ErrorActionPreference(Microsoft 文档)
-
配方:PowerShell 中的错误处理
启用每次加载时自动执行命令
就像我们在之前的示例中看到的,这些更改是暂时性的;它们只会在会话处于活动状态时有效。可能会出现管理员需要运行一些命令或加载模块以提高工作效率的情况。例如,我通常会加载一系列帮助我管理 Microsoft Exchange、Active Directory、VMware vSphere 基础设施、Citrix XenApp、Microsoft System Center 以及其他环境的模块,所有这些操作都使用 PowerShell 完成。
如果你观察,你会发现这些产品加载模块、snap-in 和脚本的方式不同,且许多产品每次加载模块时都需要特定的配置(比如以管理员身份连接到虚拟机服务器)。这些都可以通过 PowerShell 配置文件来完成。
准备工作
PowerShell 在安装时默认不会创建配置文件。它只会使用默认配置运行。要覆盖此配置,你需要创建并修改配置文件。
-
打开一个 PowerShell 控制台。(你可以在终端中运行
pwsh或使用 VS Code 控制台。这个示例使用的是终端。) -
显示你的配置文件路径。为此,只需调用自动变量。
PS> $PROFILE
- 检查你的配置文件是否存在。
PS> Test-Path $PROFILE
- 如果你得到的响应是 True,你可以继续执行该示例。如果响应是 False(这通常是默认情况),请运行以下命令:
PS> New-Item $PROFILE -ItemType File
- 你可能会收到一个错误,提示未找到路径的一部分。这是因为你的
~/.config/目录默认情况下不包含powershell目录。添加-Force参数会创建这个目录,并在该位置创建你的配置文件。
PS> New-Item $PROFILE -ItemType File -Force

- 现在我们将在 VS Code 中编辑配置文件。在终端中输入:
PS> code $PROFILE
如何操作...
现在你的配置文件应该已经打开,当前为空。接下来,我们将自定义 PowerShell 的错误操作行为。记住,我们之前在终端中将 ErrorActionPreference 设置为 SilentlyContinue 是临时的。现在,我们将确保每次启动 PowerShell 时,ErrorActionPreference 都会永久设置为 SilentlyContinue。
-
切换到终端窗口。如果你自上一个示例以来仍然保持窗口打开,请重启 PowerShell。
-
让我们看看当前的错误操作首选项是什么。
PS> Get-Variable ErrorActionPreference
Continue
-
切换到 VS Code。配置文件应该已经打开,可以进行编辑。
-
在第一行,输入:
PS> Set-Variable ErrorAction SilentlyContinue
-
保存配置文件并关闭该文件。
-
在终端中,输入
exit退出 PowerShell。然后重新启动 PowerShell。 -
现在让我们检查一下
ErrorActionPreference的值。在提示符下,输入:
PS> $ErrorActionPreference
SilentlyContinue
- 为确保设置已生效,请输入:
PS> Get-ChildItem /home/ram/random-directory

光标简单地返回到下一行的提示符,而没有抛出任何错误。
原理...
长话短说,PowerShell 个人资料在每次加载 PowerShell 会话时都会执行。这个个人资料是一个 PowerShell 脚本文件,可以包含一系列命令和函数,这些命令和函数会像其他脚本一样执行。
需要记住的一个重要点是,每个主机都有不同的个人资料。例如,PowerShell 在终端上加载时有一个单独的个人资料,而 VS Code 中的集成终端则有一个不同的个人资料。这是因为每个终端的性质不同。
PowerShell 在 Linux 上尚未实现执行策略。如果 PowerShell 在未来在 Linux 上获得了安全性设置,则应设置执行策略为允许执行脚本,以便加载个人资料。
还有更多……
设置全局操作首选项是不好的做法。清空个人资料以移除错误操作首选项。由于个人资料中没有其他内容,你甚至可以使用以下命令删除个人资料:
Remove-Item $PROFILE
阅读最佳实践以获取更多信息。
自定义终端提示符
在上一个示例中,我们使用个人资料自定义了错误操作首选项。我们使用了已经演示过的命令,展示了可以在 PowerShell 控制台中运行的命令同样也可以添加到个人资料中,这也是一种自动化执行一组命令的方式,从而提高生产力。
现在,我们将进行下一步,定制我们的控制台提示符。理论上,选项是无穷无尽的;这个示例只是 PowerShell 灵活性如何的另一个演示。
准备工作
本示例需要 VS Code。如果你没有按照上一个示例进行,请按照上一个示例中 准备工作 部分的步骤创建 PowerShell 个人资料,这次在 VS Code 的 PowerShell 集成控制台中运行命令。
如何做……
确保个人资料是空的。如果你刚刚创建了个人资料,可以直接继续。如果不是,请清空个人资料中的所有内容。如果个人资料中设置了错误操作首选项,将来你创建的脚本会很难进行故障排除。
- 在主窗口中输入以下内容。
function prompt {
$Location = (Get-Location).Path.ToString()
switch -Wildcard ($Location) {
"/home/$env:USERNAME" { $Location = '~'; break }
"/home/$env:USERNAME/Documents" { $Location = 'Documents'; break }
"/home/$env:USERNAME/Downloads" { $Location = 'Downloads'; break }
"/home/$env:USERNAME/Pictures" { $Location = 'Pictures'; break }
"/home/$env:USERNAME/Videos" { $Location = 'Videos'; break }
"/home/$env:USERNAME/Music" { $Location = 'Music'; break }
"/home/$env:USERNAME/Documents/code" { $Location = 'Code'; break }
"/home/$env:USERNAME/*" { $Location = $Location.Replace("/home/$env:USERNAME/", '~/'); break }
Default { }
}
Write-Host "PS " -NoNewline
Write-Host `
($($env:USERNAME) + "@" + "$([System.Net.Dns]::GetHostByName((hostname)).HostName) ") `
-NoNewLine -ForegroundColor Cyan
Write-Host "$Location" -NoNewline -ForegroundColor Green
Write-Host ("`n> ") -NoNewline
return " "
}
-
保存个人资料脚本文件。
-
点击集成控制台顶部的小垃圾桶图标以结束当前的 PowerShell 会话。
-
当 VS Code 提示你是否想要重新启动会话时,点击“是”。
你的提示应该如下图所示。

它是如何工作的……
提示由一个名为prompt的函数控制。为了演示这一点,我们可以使用:
Get-Command prompt
输出显示确实有这样的命令,其类型为Function。要查看prompt函数的内容,请输入:
PS> (Get-Command prompt).ScriptBlock
输出显示了你刚刚编写的整个函数。但这其实是覆盖了默认 prompt 函数的那个函数。如果你清除配置文件,重新启动 PowerShell 会话,并输入上述命令,你将看到一个简单的三行输出,那就是默认的 prompt 函数。
现在来解析我们编写的函数:
首先,声明你即将编写的函数。使用关键字 function,后跟函数的名称。由于我们想要处理提示符,所以使用现有的函数名称以覆盖默认的功能。接下来的内容是脚本块,它以 { 开始。
我们希望位置能出现在提示符中。显而易见,这是必须包含的信息。
如果你注意到,PowerShell 在提示符处显示了完整的 Home 路径。虽然这样可以工作,但我们习惯于用波浪符(~)表示 Home。此外,我知道 Documents、Music 和其他文件夹位于我的 Home 文件夹中,我更愿意在提示符处只显示文件夹的名称。这就需要一些文本操作。因此,我将当前路径赋值给一个变量,$Location。
接下来,我们执行一个 switch-case 操作,并得到要在提示符中显示的值。现在先不担心语法问题,我们将在后续章节中详细讲解每一个。
然后,我们使用几个 Write-Host 语句来构造提示符文本。-NoNewLine 参数确保每个语句的内容不会跳到下一行。当我们需要换行时,会显式添加 `n。
如果你想在脚本中断开一行较长的代码,请在希望换行的地方使用反引号(`)字符,并按回车键进行换行。PowerShell 会将含有反引号的那一行与紧随其后的那一行视为同一行。同样,如果你希望在同一行编写两条语句,可以在第一条语句末尾使用分号(;),然后用空格继续写第二条语句。PowerShell 会将这两条语句视为独立的语句,就像普通英语中的两个句子一样。
由于 Write-Host 只是将文本发送到主机,并不会返回任何内容,因此我们向函数中添加一个返回语句,仅返回一个空格。
当我们重新加载 PowerShell 集成控制台时,配置文件被加载,这时,配置文件中的自定义 prompt 函数会覆盖默认的 prompt 函数,并呈现我们定义的漂亮提示符。
还有更多内容…
现在,先尝试修改 Write-Host 语句的序列和内容,以自定义你的提示符。等我们对 PowerShell 的语法熟悉后,应该能够进一步自定义配置文件。
另见
- about_Prompts(Microsoft 文档)
理解 PowerShell 中的标准重定向
当我第一次开始使用命令行界面时,<、> 和 >> 让我感到很困惑。然后,我完全停止使用命令行界面,转而回到了 Windows 管理员的方式,尽管在用键盘快捷键的频率上,达到了非同寻常的程度(这全是为了速度和效率!)。
当 PowerShell 出现时,我已经忘记了那些操作符;我直接去理解对象和管道的概念,并且这样工作了多年。转到 Linux 后,我想了解在终端中使用“Linux 方式”。这些操作符再次回到了我的面前。我干脆在 Linux 上安装了 PowerShell。
PowerShell 中的重定向主要依赖于流。流的概念会在其他章节中讨论。现在,我们坚持使用默认流,即 Success。
本操作涵盖了不同的、简单的重定向方法,帮助完成基本的管理任务。我们将在理解错误处理的概念时再回到流的部分。
在我们开始之前,首先要理解,PowerShell 在重定向方面与 Bash 非常不同,尽管它有一些小的相似性;这些相似性足以让你不会离开,而是欣赏对象模型的灵活性和使用的一致性。
如何做……
在这个操作中,我们将执行四个活动:
-
将输出重定向到文件
-
将另一个输出追加到同一个文件中
-
将命令的输出发送到控制台和文件中
-
将一个命令的输入接受到另一个命令中
除了我们在前两个活动中使用的操作符外,我们还将查看这些操作符的 cmdlet 等价物。
- 列出当前在你计算机上运行的所有进程。
PS> Get-Process
- 输出显示在控制台上。现在,让我们将内容重定向到文件中。
PS> Get-Process > processes.txt
- 列出文件的内容。
PS> Get-Content ./processes.txt
- 现在,让我们将日期和时间戳追加到文件中。
PS> Get-Date >> ./processes.txt
- 现在读取文件的内容。
PS> Get-Content ./processes.txt
你会看到文件现在包含了系统中所有正在运行的进程的列表,以及时间戳。时间戳被追加到文件中了。
使用PowerShell 方式时,可以通过 Out-File cmdlet 实现相同的结果。(Out-File 具有更多功能,如设置编码和换行控制。)让我们使用 Out-File cmdlet 完成相同的任务。在继续之前,你可能需要删除 processes.txt 文件。
- 现在让我们再次列出当前运行的进程,并使用
Out-File将输出发送到文件中。
PS> Get-Process | Out-File processes.txt
- 现在,让我们将时间戳追加到文件中。
PS> Get-Date | Out-File processes.txt -Append
如果你注意到,Get-Process 和 Get-Date 的输出直接进入了文件;没有任何内容显示在主机上。
如果我们想同时在控制台上显示输出并将内容发送到文件,我们只需使用 Tee-Object 替代 Out-File。如果你愿意,可以再次删除文件 processes.txt。
- 运行以下命令。
PS> Get-Process | Tee-Object ./processes.txt
运行的进程列表应该会显示在终端上。
- 检查文件
processes.txt的内容。
PS /home/ram> Get-Content ./processes.txt
正如你所看到的,进程列表也出现在了文本文件中。
现在,让我们继续学习如何让 cmdlet 接受来自文件的输入。Linux 管理员习惯于让命令接受来自文件的输入,如下所示:
$ command < input_file.txt
该命令接受来自input_file.txt的输入,并对输入内容执行操作。
在 PowerShell 中,这些操作通过Get-Content和管道符(|)来处理。PowerShell 中接受来自文件输入的命令等价物是:
PS> Get-Content input_file.txt | command
对于那些不熟悉 PowerShell 的人来说,这可能看起来反过来了。让我们将过程分解成几部分,并试着更好地理解它。例如,假设你有一个名为input.txt的文本文件,其中列出了多个文件。
- 展示文本文件的内容。
PS> Get-Content input.txt

- 列出当前目录中的内容。
$ ls
我们在当前目录下有五个测试文件,其中四个在列表中(即输入文件)。假设你希望从目录中删除输入文件中列出的文件。
- 通过管道将
Get-Content的输出传递给命令Remove-Item。
PS> Get-Content input.txt | Remove-Item

- 列出当前目录中存在的文件。
PS> Get-ChildItem .
看!如果你比较ls(之前)和Get-ChildItem(之后)的输出,你会发现文本文件中列出的文件不再存在了。
它是如何工作的...
在这个教程中,我们旨在理解 Bash 和 PowerShell 之间的相似之处。尽管 PowerShell 在本质上与 Bash 不同,但它确实有一些与 Bash 相似的地方。正如我们看到的那样,这些相似之处包括将内容传递到文件和向文件追加内容。
我们在本教程中介绍了三个 cmdlet,其中一个被使用了两次。
Out-File
熟悉 Bash 的人通常使用>将输出发送到文件,使用>>将输出追加到已有文件。在 PowerShell 中,我们使用Out-File cmdlet。我们运行一个将输出发送到标准输出的命令,并通过管道将输出重定向到Out-File,由它负责将输出写入文件。通常,Out-File用于将内容发送到文本文件。
当需要将内容追加到文件时,我们使用-Append开关与Out-File配合使用。这样,如果正在写入的文件已经包含内容,新的内容就不会覆盖原有内容(覆盖内容是Out-File的默认行为)。
Tee-Object
有时候你需要将内容发送到文件,并同时在控制台上显示该内容。这可以通过简单地调用Tee-Object来实现。Tee-Object像字母 T 一样,除了将内容发送到文件或变量,它还将内容传递到管道中。如果Tee-Object是语句中的最后一个 cmdlet,那么管道的输出会发送到标准输出,这通常是主机(或者换句话说,默认是控制台)。
在我们的配方中,我们将第一部分输出发送到文件 processes.txt,第二部分输出没有通过管道传递。因此,Tee-Object 捕捉到了标准输出。
接受来自文件的输入
在 PowerShell 中,这个过程与 Bash 有显著的不同。在 Bash 中,我们先调用命令,然后让它接受来自文件的输入。在 PowerShell 中,我们先让 PowerShell 读取输入文件的内容,然后通过管道将输出发送到接受输入的命令。
在我们的配方中,我们读取了文件 input.txt 的内容,它包含了四个文件名的列表。我们使用 Get-Content 来读取文件内容。Get-Content 最初将输出发送到标准输出,显示文件的内容。然后我们加了一个管道,告诉 PowerShell 需要进一步处理,然后在命令链中加入了 Remove-Item。(Remove-Item 删除项,可以是目录、文件或链接。)
正如我们在本章后面将看到的,Remove-Item 的第一个参数(位置参数,第 1 位)是 Path,它也是通过管道接受输入的参数。有关更多信息,请运行以下命令并阅读 Remove-Item 的 Path 参数。
PS> Get-Help Remove-Item -Parameter Path

还有更多…
如果你是喜欢保持目录整洁的人,请清理我们为此配方创建的内容!如果以后需要更多的文件或目录,我们会根据需要创建它们。
另请参见
-
配方 3.8:理解 cmdlet 和参数
-
配方 1.6:查找特定于参数的帮助信息
从 PowerShell 调用本地 Linux 命令
在本章中,介绍核心及其功能,我们看到本地 Linux 命令在 Linux 上的 PowerShell 中并不是方便的别名,而是命令本身。在这个配方中,我们将演示如何在 PowerShell 提示符下使用 Linux 命令。记得我们在配方 比较 Bash 和 PowerShell 输出 中,如何使用 Bash 终端运行命令 ls -l 和 awk 来列出目录的内容,并将输出中的列分开吗?我们将不使用任何 PowerShell cmdlet,在 PowerShell 中执行同样的操作,操作对象是主目录。
开始使用
建议你使用一台安装了 PowerShell 的 Windows PC(Windows PowerShell 也可以),以便比较输出并查看是否遇到任何错误。
如何实现...
- 在 PowerShell 提示符下,键入以下命令以列出目录内容。
PS> ls -l
你会看到熟悉的输出(如果你的终端模拟器为文件名使用颜色,输出将没有颜色)。
- 现在让我们看看输出的 .NET 类型名称。为此,我们需要使用
Get-Membercmdlet。
PS> ls -l | Get-Member
PowerShell 显示 TypeName: System.String,这与我们在前述配方中看到的结果一致。
- 如果你有一台带有 PowerShell(或 Windows PowerShell)的 Windows PC,运行相同的命令。
PS> ls -l | Get-Member
注意这里的 .NET 类型名称;它是 System.IO.DirectoryInfo,如果你稍微滚动控制台,你还会看到 System.IO.FileInfo。
- 接下来,在 Windows 上的 PowerShell(或 Windows PowerShell)提示符下,键入:
PS> ls -l
你会收到一个错误,提示没有为参数 LiteralPath 提供值。

- 在 Windows PC 的 PowerShell 提示符下,键入以下内容并按下 Tab 键,而不是 Enter 键。
PS> ls -l
你会看到参数名称被补全为 LiteralPath。
-
按下 Esc 键清除命令行。
-
回到 Linux,在 PowerShell 提示符下,键入以下内容并按下 Tab 键:
PS> ls -l
- 什么也没有发生。现在,输入以下内容并按下 Tab 键:
PS> Get-ChildItem -l
参数名称被补全为 -LiteralPath。让我们再进一步,完成这个过程。
- 在 Windows 上的 PowerShell 提示符下,运行以下命令。
PS> Get-Alias ls

- 切换回 Linux 并运行相同的命令。
PS> Get-Alias ls
你会收到一个错误,提示没有这样的别名。

它是如何工作的...
当我们在 Linux 上的 PowerShell 上运行任何 Linux 命令时,PowerShell 不会调用为 Linux 管理员创建的便利别名,这些别名是在 Windows PowerShell 启动时为 Linux 管理员的便利而创建的;这些便利别名没有包含在 Linux 上的 PowerShell 中。PowerShell 会运行实际的 Linux 命令,并在控制台上显示输出。将输出管道传输到其他 Linux 命令的工作方式与在 Bash 上运行时相同。
第一点需要注意的是,ls -l 是 Linux 中的一个实际命令,它会以表格格式返回当前目录中的文件和目录列表。当在 Windows 的 PowerShell 上运行相同的命令时,我们会收到一个错误,因为 PowerShell 在 Windows 上将 ls 解释为 Get-ChildItem,并将 -l 解释为不完整但确定的 -LiteralPath 调用,返回一个错误,说明没有指定字面路径。
当我们在两个操作系统上对 ls 运行 Get-Alias 时,Linux 上的 PowerShell 会返回一个错误,而 Windows 上的 PowerShell 会显示底层的 PowerShell cmdlet。
另一个证实这一事实的点是,ls 的输出在 Windows 上的 PowerShell 中是字符串,而不是系统对象。当在 Windows 上的 PowerShell 中运行相同的 ls 时,PowerShell 会在后台调用 Get-ChildItem,显示的输出就是 Get-ChildItem 的输出。这可以通过 Get-Member 输出中的类型名称得到支持,该名称来自 System.IO 命名空间。另一方面,在 Linux 上,运行 Get-Member 获取 ls 输出时,返回的仅是 System.String。
另见
- 关于别名(Microsoft 文档)
理解 cmdlet 和参数
我们的大部分脚本编写和管理工作将围绕着运行 cmdlet 并将它们串联起来进行。在某些情况下,我们运行一个 cmdlet 时,期待它按某种方式工作,但却发现它抛出了错误,或者更糟的是,做了某些我们不希望它做的事情。
获取 cmdlet 按预期工作的方法是消除歧义。在本教程中,我们将学习如何在特定上下文中有效构建命令。
准备工作
阅读第一章的帮助部分,安装、参考和帮助。让我们理解Get-Help显示的帮助信息中的通知内容。
尽管这可能不是一本全面的帮助使用指南,但它应该涵盖你日常阅读帮助文档的大部分需求。其目的是向你展示符号。这些符号可能会以多种组合形式出现(例如,参数值用大括号括起来,并用方括号包围)。
| 符号 | 含义 |
|---|---|
-Parameter <DataType> |
无方括号:必需的命名参数。必须通过名称调用该参数,并指定一个 DataType 类型的值。 |
[-Parameter <DataType>] |
参数-数据类型对周围有方括号:可选参数,尽管必须通过名称调用该参数,并且必须传递一个 DataType 类型的值。 |
[-Parameter] <DataType> |
参数名称周围有方括号:位置参数。你只需将一个 DataType 类型的值传递给 cmdlet,只要该值位于帮助文本中所示的参数位置。只要位置正确,参数不必按名称调用。 |
[[-Parameter] <DataType>] |
参数名称周围有方括号,参数-数据类型对周围有另一对方括号:位置参数,是可选的。 |
-Parameter <DataType[]> |
DataType 后面有方括号:多值参数。该参数接受多个值作为输入,每对值之间用逗号分隔。 |
-Parameter |
无数据类型:开关参数。调用该参数时开关为\(true,不调用时使用开关的默认值。要禁用该开关,将其设置为 false,如-Parameter:\)false。 |
-Parameter {Value1 | Value2 | Value3} |
值用大括号括起来:接受预定义值作为输入的参数。在这种情况下,你可以像-Parameter Value1或-Parameter Value2一样调用该参数。换句话说,该参数不接受任意值。 |
Bash 高手注意:PowerShell 需要用逗号分隔多值参数中的值。因此,如果你想对三个文件使用Remove-Item,你应该输入Remove-Item file1, file2, file3。如果只有空格分隔这些值(如 Bash 的输入),PowerShell 会将这三个值视为三个位置参数的值,并可能会抛出错误,或者根据你调用的 cmdlet 做出你不希望的操作。
一般来说,方括号括起来的内容是可选的。大括号括起来的内容表示预定义的参数值(管道符分隔每个值)。数据类型后面跟着一对空的方括号表示该数据类型的数组。参数的位置也需要注意。
注意这个组合:Required为假,下面参数名称的文本中显示了默认值,位置为1。这意味着你只需要调用 cmdlet,它就会根据第一个参数的默认值自动运行。此处使用的例子是Get-ChildItem。
如何操作...
那是一个漫长的准备过程。现在,让我们将这些知识付诸实践。
- 运行命令以获取当前路径下的文件和目录列表。
Get-ChildItem
- 现在,让我们添加一个
.来表示当前目录。
Get-ChildItem .
-
比较最后两个命令的输出。
-
接下来,让我们继续执行以下操作:
Get-ChildItem -Path .
输出是否与最后两个命令的输出相同?
- 运行以下命令,并记录每个键的值:
PS> Get-Help Get-ChildItem -Parameter Path
-Path <String[]>
Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (`.`).
Required? false
Position? 1
Default value Current directory
Accept pipeline input? True (ByPropertyName, ByValue)
Accept wildcard characters? true
- 接下来,在当前路径下创建一个名为
file1的文件,按名称调用该参数。
Get-Help New-Item
.
.
.
SYNTAX
New-Item [[-Path] <String[]>] [-Confirm] [-Credential <PSCredential>] [-Force] [-ItemType <String>] -Name <String> [-UseTransaction] [-Value <Object>] [-WhatIf] [<CommonParameters>]
New-Item [-Path] <String[]> [-Confirm] [-Credential <PSCredential>] [-Force] [-ItemType <String>] [-UseTransaction] [-Value <Object>] [-WhatIf] [<CommonParameters>]
- 我们有两个选择:
Path和Name。查找有关Path的信息。
PS> Get-Help New-Item -Parameter Path
-Path <String[]>
Specifies the path of the location of the new item. Wildcard characters are permitted.
You can specify the name of the new item in Name , or include it in Path .
Required? true
Position? 0
Default value None
Accept pipeline input? True (ByPropertyName)
Accept wildcard characters? false
如果我们使用Name,必须按名称调用它(没有恶搞的意思)。如果不这样做,我们可以将名称作为Path的一部分来指定(也没有故意重复的意思)。
- 让我们首先使用
Path。不需要写“-Path”,因为它是位置参数。
New-Item file1
- 尝试使用
Name进行相同的操作。这次,指定参数名称。
New-Item -Name file2
- 如果需要,可以创建一个第三个文件,通过指定
Path的名称来创建。
New-Item -Path file3
- 列出当前路径的内容。
Get-ChildItem -Path .
文件已存在。
- 现在让我们删除文件。
Get-Help Remove-Item
- 将文件名作为路径提及。
Remove-Item file1
- 列出目录内容。
Get-ChildItem -Path .
- 现在,让我们一次删除多个文件。此次,我们按名称调用参数。
Remove-Item -Path file1, file2
- 现在,让我们创建一个目录。我们需要使用一个名为 ItemType 的参数,该参数具有预定义的值,具体取决于提供者(我们使用的是
FileSystem)。
New-Item -Path test-dir -ItemType Directory
- 这样创建三个新文件:
New-Item test-dir/file1, test-dir/file2 -ItemType File
New-Item test-dir/child-dir -ItemType Directory
New-Item test-dir/child-dir/file3 -ItemType File
- 现在,让我们删除内容。在运行命令后,等待出现确认提示。
Remove-Item -Path test-dir
-
阅读提示信息。它提到
Recurse参数的一些内容。 -
选择
L并按回车键以中止该过程。 -
现在,输入以下命令:
Remove-Item -Path test-dir -Recurse
- 安静了一会儿!列出当前目录的内容,确保
test-dir已经删除。
Get-ChildItem .
目录确实已删除。
它是如何工作的...
使用 cmdlets 非常简单。参数有两种类型:命名参数和位置参数。
位置参数基于其位置工作。它们的编程方式使得 PowerShell 可以理解它们的逻辑顺序并执行相应操作。例如,在移动项目时,通常的操作流程是先调用命令,传入源路径,然后再传入目标路径。
因此:
Move-Item /home/ram/Documents/GitHub /home/ram/Documents/Code/
这意味着你想要将目录GitHub移动到Code。许多 PowerShell cmdlet 都是按照这种方式编程的,能够理解这一操作。
命名参数则需要通过名称来调用。帮助文本显示时不会有任何括号包围它们。通过名称调用位置参数是可选的——你可以直接传递值。不过,请小心你在调用时提到的位置或顺序。
最佳实践是在编写脚本时始终传递参数值,通过名称调用它们。另一方面,当运行快速命令时,可以为了速度而省略通过名称调用位置参数。
有些参数已添加预定义的值验证。这些参数只接受已经定义的值。例如,ItemType 只接受 File、Directory、SymbolicLink、Junction 和 HardLink 作为值(截至本节撰写时)。
然后,还有开关参数。Recurse 就是一个例子。当你调用这些参数且没有传值时,在大多数情况下,参数默认为 True。当你需要将它们设置为 False 时,可以这样写:-Parameter:$false(例如,-Confirm:$false)。如果你没有调用开关参数,则参数会使用 cmdlet 中指定的默认值。
还有更多内容
如果你想重新创建文件和目录,不需要运行四个命令。运行以下两个命令即可。
New-Item test-dir/child-dir -ItemType Directory -Force
New-Item ./test-dir/file1, ./test-dir/file2, ./test-dir/child-dir/file3
参数 Force 在创建 child-dir 时会创建 test-dir。
如果你想的话,继续删除整个目录,不需要 Recurse 参数。在确认提示时,按 Enter(Y 是默认的响应)。
使用最少按键运行 cmdlet
命令历史上被设计得很简短。然而,随着时间的推移,情况变成了一个两难的境地,因为简短的命令意味着它们需要记住,而更长的命令则意味着更多的按键操作。
PowerShell 有很长的命令,但它通过两种方式来处理它们:
-
别名,通常比较短。
-
制表符自动完成,虽然比别名需要更多按键,但不需要记住太多内容。
第一种方式需要我们利用记忆来回忆命令名称。而第二种方式则有效地解决了按键次数问题。
Bash 用户习惯于在制表符匹配到多个字符串时,看到以整齐的表格形式列出的匹配项。而在 Windows 中,匹配项则会在光标处循环显示(大多数 Bash 用户认为这是“奇怪”的)。
尽管如此,制表符自动完成仍然是一个福音,这个技巧充分利用了制表符自动完成和简单的字符串匹配,大大减少了使用 PowerShell cmdlet 时的按键次数。
准备工作
我们在本教程中使用 Gnome Terminal 终端仿真器,PowerShell 在 Gnome Terminal 上的制表符自动完成功能表现与 Bash 在 Gnome Terminal 上的完全相同:
-
如果只有一个单词与制表符前的字符串匹配,则该单词会被完成。
-
如果多个单词与制表符前的字符串匹配,所有可能的选项都会列出。
如果你使用的是 VS Code 或其他终端模拟器,它的行为可能会有所不同。
如何操作……
让我们直接开始吧!
比如,我们希望获取当前目录中的文件和目录列表。
- 按照最佳实践,正确的做法应该是:
Get-ChildItem -Path .
- 然而,正如我们之前所看到的,做这件事的简便方法是:
gci
在将 cmdlet 包含到脚本中时,我们会使用前一种方式。这可以避免在大多数脚本运行的环境中产生歧义,通常意味着最小的 bug。后一种方式,则是通过利用其用户友好的功能,并结合作为智能人的环境意识,运行相同 cmdlet 的快捷方式。这种方式显著减少了击键次数——仅三个字符,而不是二十一个。
虽然,如果你正在编写脚本并希望减少击键次数,你仍然可以这么做,而不需要记住诸如 gpv 代表 Get-ItemPropertyValue 之类的内容。
输入:Tab 补全。
- 请按照以下击键操作:
get-ch<Tab><Space>.
那是十次击键,包括
可能会有一些情况,你需要调用命名参数。而命名参数可能会很长。
- 找到一个命令,其参数包含
ComputerName。
get-comm<Tab><Space>-param<Tab><Space>computername<Enter>
这将完成为:
Get-Command -Parameter computername

但是它抛出了一个错误。错误信息显示:Possible matches include: -ParameterName -ParameterType。这是 Linux 上 PowerShell 的一个特殊问题。让我们再试一次:
get-comm<Tab><Space>-param<Tab><Tab>

- 阅读可能的选项并选择
ParameterName。
get-comm<Tab><Space>-param<Tab>n<Tab><Space>computername<Enter>
- 完整的解析结果是:
Get-Command -ParameterName computername
然后它成功了。
接下来,让我们暂停会话中的活动,例如暂停五秒钟。实现这一点的 cmdlet 是 Start-Sleep。
- 让我们首先查看 cmdlet 的帮助。
Get-Help Start-Sleep
帮助文本显示,位置为一的参数(不必命名)是 Seconds(第二组参数中的 Seconds 周围有方括号),并且它接受整数值。
- 因此,要暂停会话五秒钟,我们将使用:
Start-Sleep 5
- 如果我们希望会话(或脚本)暂停 100 毫秒,我们需要使用命名的
Milliseconds参数。通过 Tab 补全,代码将会是:
start-s<Tab><Space>-mi<Tab>100
这将解析为:
Start-Sleep -Milliseconds 100
- 事实上,在使用参数名称时,你可以减少 Tab 键的次数。只需要输入足够的字符,PowerShell 就能唯一识别该参数名称:
start-s<Tab><Space>-m<Space>100
这将解析为:
Start-Sleep -m 100
如果延迟不明显,可以适当增加数字(例如,3000)。
它的工作原理是……
我们已经看到如何使用别名。别名的工作方式与正常的 cmdlet 相同,包括它们的参数语法。唯一需要注意的是,我们必须记住这些别名。自定义别名,如我们将在最佳实践部分中看到的,由于别名必须在我们运行包含自定义别名的脚本的地方导入,因此它们并不推荐使用。
而另一方面,tab 完成减少了按键次数,但需要肌肉记忆。只要有一定的练习,这会显著提高生产力。
Tab 完成在编写 cmdlet、编写参数名称以及向参数传递预定义值时有效,例如:
set-exec<Tab><Space>-exec<Tab><Space>unre<Tab>
这会完成为:
Set-ExecutionPolicy -ExecutionPolicy Unrestricted
在许多情况下,根本不需要使用 tab 完成,例如在 Start-Sleep 的情况下。此 cmdlet 没有任何以 m 开头的参数。因此,使用 -m 已足够让 PowerShell 唯一识别 -Milliseconds。这样也省去了 <Tab> 的按键。
在 PowerShell 中编写脚本的生产力是一项随着练习而提高的技能。虽然别名确实是加速的捷径,但也有其危险性。另一方面,使用键盘编写脚本有助于肌肉记忆,这不仅能帮助我们以 PowerShell 的方式思考,还能加速 tab 完成,无论是在控制台运行命令还是编写脚本时,它都同样有效。
在编写脚本时,一般不建议使用短参数名;使用时应像别名一样谨慎。短参数名影响可读性,而且在未来某个时刻可能会导致脚本出错。例如,如果你在脚本中调用某个 cmdlet,使用了一个短参数名 -comp,而该参数名在脚本创建时仅代表 ComputerName。后来,假设该 cmdlet 更新并添加了一个新的参数 -CompatibilityMode,这就会导致你写的脚本无法运行。
还有更多...
尝试输入最常用的 cmdlet 和参数,来练习 tab 完成。
通过在脚本窗格中输入 cmdlet 来熟悉 VS Code。注意,VS Code 中 cmdlet、参数和参数值的完成是如何工作的。如果你希望完成方式与控制台一样,请参考我在本书 GitHub 仓库中的自定义设置 JSON。
查找参数别名
我们处理了 cmdlet 的别名,看到如何唯一识别参数名,而不必输入完整的参数名,我们还探讨了如何利用 tab 完成的强大功能。
为了完整性,我们也来看看参数别名。
正如你可能已经猜到的,参数别名的工作方式与 cmdlet 别名非常相似。这些别名的主要目标是减少键盘输入。
参数别名没有以友好的方式记录,但得益于 PowerShell 的面向对象模型,仍然可以轻松找到。在本食谱中,我们将看看如何获取参数别名。
准备就绪
查找所有使用 ComputerName 作为参数的命令,最少的按键输入。
get-comm<Tab>-parametern<Tab>Cn
这会解析为:
Get-Command -ParameterName Cn

输出与我们之前运行的相同:
Get-Command -ParameterName ComputerName
PowerShell 是如何知道 Cn 代表 ComputerName 的?
如何做...
Parameters 是 cmdlet 的一部分,而 Get-Command 是获取关于 cmdlet 信息的 cmdlet。
- 对于这个例子,让我们选择 cmdlet
Invoke-Command。
Get-Command Invoke-Command
- 检查该命令输出的对象。
Get-Command Invoke-Command | Get-Member
- 输出中显示了一个名为 Parameters 的成员。使用成员访问操作符来选择这个成员。
(Get-Command Invoke-Command).Parameters
- 查看这个成员包含哪些成员。
(Get-Command Invoke-Command).Parameters | Get-Member
Values会展示我们所需的信息吗?
(Get-Command Invoke-Command).Parameters.Values
- 是的,这确实给了我们一些东西,但输出太多了。我们只需要参数名称及其别名。
(Get-Command Invoke-Command).Parameters.Values | select Name, Aliases
就这样。
它是如何工作的……
我们在阅读 Core 及其功能时看到,PowerShell 大多数时候会返回对象。而每个对象内部都可以有其他对象。沿着 .NET 的路径,成员访问操作符可以用来选择成员(这些成员可以是属性或方法)。属性通过直接使用属性名称来访问,而方法则需要传递参数(如果不传递参数,依然需要使用一对空括号)。
cmdlet 的参数是 Get-Command cmdlet 返回的输出对象。因此,调用 Get-Command 并指定 cmdlet Invoke-Command,它会返回关于 Invoke-Command 的数据作为输出对象。这可以进一步分解成几个其他对象(成员),其中就包括 Parameters。
Parameters 本身可以进一步分解为其他成员,其中 Values 就是其中之一——Values 包含了参数的名称,运行 Get-Member 时可以看到这一点。
我们从 Values 中选择了两个对象,分别是 Name 和 Aliases。
这些参数别名可以替代参数本身使用。
参数别名有两个需要注意的地方:
-
它们是区分大小写的,这意味着需要按下 Shift 键。
-
它们需要记住,尽管它们有一定的模式,就像 cmdlet 的别名一样(例如,
ip代表Import,g代表Get)。
调用 PowerShell 脚本
PowerShell 脚本不过是一系列 PowerShell cmdlet,每个 cmdlet 都位于 ps1 文件的一行。这些指令一个接一个地执行,类似于传统的 shell 脚本。使用 VS Code 使得运行 PowerShell 脚本变得更简单,你只需要运行脚本,脚本就会发挥它的作用。
然而,在 VS Code 中运行 PowerShell 脚本并不是一种常见的自动化方式,原因显而易见。此外,运行 PowerShell 脚本的方法有很多种。我们将在这个教程中介绍一种非常简单的脚本运行方式;随着书中的进展,我们也会为我们的脚本添加更多功能,进一步将它们打包成模块以供将来使用。
准备工作
本教程使用 VS Code 编写脚本。虽然任何文本编辑器或代码编辑器都可以用来编写脚本,但我们选择 VS Code,因为根据我的经验,VS Code 是 PowerShell 脚本编写中最友好的编辑器。
-
打开 VS Code,创建一个新文件。
-
在一个方便的位置创建一个新目录(例如
~/Code?)。 -
将空文件保存为
hello-world.ps1,保存在你刚刚创建的目录中。 -
查看 VS Code 的右下角,应该现在显示为
PowerShell。 -
按
Ctrl + `关闭底部的控制台,以减少干扰。
如果一切如截图所示,你就准备好开始了。(暂时忽略底部状态栏的颜色以及左下角的 Git 状态。)

如何做到……
- 在脚本面板中输入:
Write-Host "Hello, World!"
-
保存(文件 > 保存或
Ctrl + S)脚本。 -
点击左侧的调试。或者,按下
Ctrl + Shift + D。这将打开调试面板。 -
按下绿色的类似播放按钮的开始调试按钮。
控制台应该会弹出并显示Hello, World!。
- 让我们再运行一次脚本。这次,我们不打开调试面板,也不按屏幕上的按钮。简单地,按下
F5。
Hello, World!应该再次出现在控制台中。

现在让我们不使用调试控件来调用脚本。
-
关闭文件(Ctrl + W)。
-
在控制台的提示符下,输入:
./hello-world.ps1
你应该能看到Hello, World!出现在控制台中。
-
现在,导航到主目录。
-
接下来,输入
&,加上空格,再输入./,然后开始输入存储脚本的路径。像在 Bash 中一样使用 Tab 补全功能。 -
当你到达脚本文件的位置时,按回车键来调用该文件。

它是如何工作的……
运行 PowerShell 脚本的最简单方法是在集成脚本环境中调试它。在这种情况下,我们选择使用 VS Code 作为我们的 ISE。本文中描述的调用脚本的另外两种方式是通过 PowerShell 控制台。你可以使用 VS Code 中的集成控制台,或者在终端中调用 PowerShell 控制台来实现。
当脚本位于当前工作目录时,PowerShell 会直接调用ps1文件并执行它。调用 PowerShell 脚本的另一种方式是使用&(或调用操作符):输入&,然后空格,接着是脚本文件的路径。通过这种方式调用可以很好地处理路径中的空格。
如果脚本的路径包含空格,则该路径需要用引号括起来。这样,PowerShell 会认为你只是给它一个字符串值。然后,PowerShell 会在你按下回车键时,将路径作为文本显示。
当你使用&调用操作符时,你是在告诉 PowerShell 你想要运行一个脚本(或一个命令)。
调用脚本的另一种方式是使用.(或点操作符),我们将在下一个食谱中讨论这一点。
点源 PowerShell 脚本
在上一篇中,我们展示了如何在 ISE 外部调用 PowerShell 脚本。我们给 PowerShell 提供了路径,并明确表示希望它通过调用操作符运行脚本。
如果你只希望脚本执行任务而不留下任何东西,例如变量值等,这种方式是理想的。然而,也有一些情况,我们希望运行脚本,并且保留我们在脚本中声明和赋值的变量,或者使用我们在脚本中声明的函数。
在我们希望函数、变量甚至别名保留在当前会话中的情况下,我们使用点源。
如何操作...
如果你在上一实例之后删除了文件,按照上一实例中的步骤恢复它或重新创建它。然后,
- 打开你在上一实例中创建的脚本文件。在第二行,输入以下内容。
$Message = "I was dot-sourced!"
-
保存脚本。不要运行它。
-
将光标放置在集成控制台,并使用调用操作符调用脚本。
& ./hello-world
- 我们声明了
$Message并给它赋了一个字符串值。调用该变量查看它包含的值。
$Message
没有任何反应。
- 现在,使用点源(dot-source)脚本。(这里有两个点;一个是操作符,另一个是空格后指向当前目录。)
. ./hello-world.ps1
- 再次调用
$Message变量。
$Message

它是如何工作的...
当使用调用操作符调用脚本或命令时,脚本或命令会简单地运行,而不会修改当前会话(或者从技术上讲,是作用域)。命令或脚本运行并退出,而不会更改与会话相关的任何内容,包括对内建/自动变量的更改。
当需要更改作用域时,脚本或命令必须通过点源来调用。这样,脚本中定义的任何变量、函数或别名都会保留在当前作用域中。
还有更多内容...
在 hello-world.ps1 脚本中使用 New-Alias 创建一个新的别名。尝试在第一次调用脚本后(使用调试控制以及使用调用操作符)获取别名的值,然后,通过点源脚本来获取别名的值。观察结果。
从 PowerShell 外部调用 PowerShell cmdlet
到目前为止,我们已经学会了从控制台调用 cmdlet、在 ISE 中运行脚本,并以两种模式调用脚本。在这个非常简短的实例中,我们将学习如何从 PowerShell 外部调用 PowerShell cmdlet。
如何操作...
-
打开一个终端窗口。
-
在提示符下,输入:
pwsh -h
-
阅读命令的语法。
-
在提示符下,输入:
pwsh -c Get-ChildItem

- 现在我们来运行
hello-world.ps1脚本。
pwsh -f ./Documents/code/github/powershell/chapter-3/hello-world.ps1

它是如何工作的...
PowerShell 不是一个运行在 Bash 之上的应用程序。PowerShell 本身就是一个 Shell。然而,它可以像其他应用程序一样从 Bash 中调用。像应用程序一样,pwsh 命令接受参数,然后由 PowerShell 处理。
我们可能想要在 Bash 中使用 PowerShell 的两种主要方式是运行单个命令,或者调用一个脚本文件。你甚至可以调用一个脚本块,然而,这必须在 PowerShell 内部执行。在大多数情况下,运行单个 cmdlet 或调用脚本就足够了。
通常,在调用 pwsh 并使用 cmdlets 时,应将 -Command(或 -c)作为最后一个参数,因为在 cmdlet 本身之后的任何内容都被视为 cmdlet 参数。-File 也是一样。
当一个脚本被设计为接受参数时,脚本参数可以在脚本名称后面传递,就像使用 cmdlet 一样。
记录在 PowerShell 控制台上运行的 cmdlet。
通常,在 PowerShell 控制台上,你会执行一系列任务,在经过一番反复试探后,找到了解决方案。然后,你希望你能记录下所有在控制台上的操作。你仍然可以从控制台复制内容,所以你尝试向上滚动。但你只能滚动这么远。你的命令历史(有点像 Bash 历史)可以帮到你,但有时候,这也感觉有限。
几个月前,我们正在排查两套本应同步工作的软件更新分发系统之间的同步问题。在我们中的一些人已经厌倦了图形界面(GUI)之后,(当然)我们决定使用 PowerShell 来解决问题。我们运行了一系列命令,经过几个小时与系统的斗争,系统终于屈服,我们重新开始工作。
我们的经理要求我们记录所有的步骤,以便将来可以参考。当然,我不能告诉你我们所采取的所有步骤——因为:a) 本书的范围,b) 我们与客户的协议——但我可以告诉你,在这种情况下,有哪些方法可能会有所帮助。
如何操作...
在终端启动 PowerShell,或使用 VS Code 上的 PowerShell 集成控制台。
- 运行以下命令:
Start-Transcript -Path ./command-transcript.txt
- 你也可以直接运行没有任何参数的 cmdlet;它会自动创建一个带有自动生成文件名的文本文件。获取当前系统的日期和时间。
Get-Date
- 列出当前目录中的所有文件和目录。
Get-ChildItem .
- 创建一个新目录。
New-Item test-transcript -ItemType Directory
- 在目录中创建一个新文件。
New-Item -Path test-transcript/testing-transcript.txt -ItemType File
- 向文件添加内容。
@'
In publishing and graphic design, lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document without relying on meaningful content (also called greeking).
Replacing the actual content with placeholder text allows designers to design the form of the content before the content itself has been produced.
—Wikipedia
'@ | Out-File ./test-transcript/testing-transcript.txt -Append
- 删除目录。
Remove-Item -Path ./test-transcript -Recurse
- 停止记录你所做的事情。
Stop-Transcript
- 你现在应该已经收到了转录文件的位置。读取文件内容。
Get-Content -Path ./command-transcript.txt

它是如何工作的...
通过运行Start-Transcript创建的转录文件会将你所有的操作和所有命令的控制台输出存储在一个文本文件中。转录文件还包含一些与命令执行时的上下文相关的其他有用信息。
转录文件比历史文件稍微多一点,因为前者包含了命令的输出,而不仅仅是命令本身。
Start-Transcript cmdlet 完全不需要任何参数;它可以在用户的主目录下创建一个文本文件,并赋予唯一名称,以确保不会覆盖其他转录文件。换句话说,Path 是一个可选参数。
本章内容已经讲完,关于如何使用 PowerShell 为管理做准备。现在是时候活动一下手指,重新倒杯咖啡了。我要告诉你,咖啡能加速 PowerShell 中的思维吗?你说这是安慰剂效应?我们现在可别争论这个。
第四章:使用 PowerShell 的管理基础
在本章中,我们将涵盖以下主题:
-
使用日期属性
-
使用日期和时间函数
-
使用当前运行的进程来测量资源消耗
-
启动和停止进程
-
查找进程的所有者
-
根据文件类型调用应用程序
-
安装 CronTab 模块
-
在 PowerShell 中调度任务
-
在 PowerShell 中删除调度任务
介绍
根据《哈佛商业评论》所说,一种有效的学习方法是循环进行 信息盛宴 和 信息禁食。鉴于我们已经熟悉了(并且可能被上一章的内容量压得有些不知所措),我们将在使用 PowerShell 进行管理的第一步中采取轻松的方法。
到目前为止,我们已经了解了如何运行 cmdlet、它们的参数是什么、如何设置和使用别名等。在本章中,我们将学习如何使用一些基本的工具、处理进程以及调用应用程序。
此外,尽管书名上写着 Linux,但书中的大多数配方也应该适用于 Windows(特别是 Windows 上的 PowerShell 6.0);可能需要做一些小的修改,比如在路径中使用反斜杠。
使用日期属性
PowerShell 最好的学习方式是从简单的 cmdlet 开始。Get-Date 是 PowerShell 中最简单却未完全发挥作用的 cmdlet 之一。在本食谱中,我们将玩转日期,并查看它们如何在不同的场景中使用。像往常一样,PowerShell 的可能性远远超过一本书所能涵盖的范围。为了简洁起见,我们只会讨论足够的内容,以便你优雅地进入 PowerShell 自动化的世界;其余的,你可以自己处理。
准备工作
如果你跟着前面的章节完成了所有的练习,应该已经准备好继续了。如果没有,去章节 安装、参考和帮助,然后获取一份 PowerShell 的副本。回来继续这里的内容。本食谱只使用终端,所以不需要其他任何东西。
如何做到这一点……
在开始处理日期之前,让我们先进行一次简单的日期调用。
- 显示当前的日期和时间。
PS> Get-Date
Saturday, 16 June 2018 12:14:07
- 查找这个返回对象有哪些成员。
PS> Get-Date | Get-Member

一大堆属性和方法。
- 尝试以 16/06/2018 的格式显示日期。
PS> $Date = Get-Date
PS> "$Date.Day/$Date.Month/$Date.Year"

颜色看起来不对。
- 按照如下修改命令。根据你的终端主题,语法现在应该看起来更好。(暂时不用担心为什么这个改动有效,我们将在稍后的变量处理部分进行讲解。)
PS> "$($Date.Day)/$($Date.Month)/$($Date.Year)"
16/6/2018
但这需要做很多工作。有没有更简单的方法?
- 查看帮助以查看你可以获得的参数。
PS> Get-Help Get-Date
有一个 Format 参数。
- 输入以下内容以获取短日期格式。
PS> Get-Date -Format d
尝试以下作为 Format 的值:g、U、yyyy/MM/dd 和 yyyyMMddhhmmss。

- 如果你更习惯使用 UNIX 格式的日期,使用
Uformat参数。
PS> Get-Date -Uformat %d/%m/%Y
让我们再走一步,将自定义日期传递给系统,并利用Get-Date输出的对象成员从中获取一些信息。
- 查找 2018 年万圣节是哪一天。
PS> (Get-Date -Day 31 -Month 10 -Year 2018).DayOfWeek
Wednesday
- 如果你知道本地日期的格式,可以按以下方式传递日期并获取相同的信息。
PS> (Get-Date 31/10/2018).DayOfWeek
- 如果你记得类型加速器的概念,可以利用它获取你需要的信息。
记得在调用DateTime加速器时使用 YMD 或 MDY 格式,以避免错误。
PS> ([datetime]'10/31/2018').DayOfWeek
为避免歧义,拼写出月份。
PS> ([datetime]'31 October 2018').DayOfWeek
它是如何工作的…
.NET 中的System.DateTime类包含足够的属性和方法。Get-Date cmdlet 通过封装来利用这些属性和方法。Get-Date cmdlet 默认提取系统的当前日期和时间,并允许你从中选择子对象,或快速将它们组合成格式。
除了这些操作外,cmdlet 还允许你传递类似日期和时间的简单字符串,并将输入的字符串转换为DateTime对象。
虽然Get-Date考虑了你的当前区域设置,但类型加速器DateTime似乎只适用于 YMD 或 MDY 格式的日期时间。
另见
-
DateTimeFormatInfo 类 | 备注(Microsoft 开发者网络)
-
食谱 2.3:从文本解析输入到对象(类型加速器)
使用日期和时间函数
在前面的食谱《使用日期属性》中,我们专注于DateTime对象的属性。我们看到的Get-Date参数也作用于这些属性。在本食谱中,我们将查看DateTime对象中的方法,并学习如何利用它们。这个食谱的主要思想是让你能够熟练使用作为输出对象一部分的方法。
如何实现…
首先,让我们从将本地时间转换为 UTC 开始。
- 在提示符下,输入
Get-Date并列出输出对象的成员。
PS> Get-Date | Get-Member
- 有一个方法叫做
ToUniversalTime。调用这个方法。
通过使用自动完成减少敲击键盘的次数。
PS> (Get-Date).ToUniversalTime()
将输出与本地时间进行比较。
- 接下来,让我们看看从今天起 35 天后是星期几。
PS> (Get-Date).AddDays(35)
- 如果你想查看三小时十八分钟后的时间:
PS> (Get-Date).AddHours(3).AddMinutes(18)
- 接下来,找出自 2016 年世界环境日以来已经过去了多少天。
PS> (Get-Date).Subtract((Get-Date '5 June 2016'))

它是如何工作的…
Get-Date返回的对象有多个方法,就像它有属性一样。在本食谱中,我们使用这些方法来根据需要操作日期。方法通过传递参数调用。如果你不想指定任何参数,确保仍然调用方法并使用空的圆括号。
方法可以像我们在添加小时和分钟时那样链接。只要输出对象不发生变化,在大多数情况下,你应该能够通过链接方法来调用与对象相关的方法。例如,如果你在第一个方法中将日期对象转换为字符串,那么第二个方法应该是一个接受字符串输入的方法。当不确定时,可以使用Get-Member cmdlet 来查看输出对象支持哪些方法。
当你查看Get-Member命令在DateTime对象上运行的结果时,会看到Subtract方法接受DateTime作为参数。因此,使用Get-Date作为参数的一部分。将 cmdlet 的输出作为方法的参数传递时,cmdlet 和传递的参数需要被额外的括号包围–例如Subtract((Get-Date '5 June 2016')),而不是Subtract(Get-Date '5 June 2016')–这样内部命令先执行,获取一个值,再将其传递给方法。
处理当前正在运行的进程以测量资源消耗。
管理工作的一个重要部分是处理计算机上的进程。PowerShell 包含了可以让你处理计算机上进程的 cmdlet。在这个食谱中,我们将列出系统中所有正在运行的进程,并获取这些进程总共消耗的资源量。
在接下来的章节中,通过管道传递数据,我们将会了解其他选项,如过滤等,利用在本食谱中学到的知识。
如何操作...
正如我们之前讨论的,我们在这里的目标是操作进程。
- 列出帮助你处理进程的 cmdlet。
PS> Get-Command -Noun Process
请记住,PowerShell 仅使用单数名词。
我们得到了五个 cmdlet。我们最初需要的是 Get-Process。
- 运行 cmdlet,查看它的输出。
PS> Get-Process
输出是一张长表,包含进程、它们所消耗的内存、它们使用的 CPU 时间等。
- 获取当前系统中正在运行的进程的总数。
PS> (Get-Process).Count
- 现在,获取进程使用的内存页面的平均值。这个术语称为工作集(Working Set),用 WS 表示。
PS> Get-Process | Measure-Object -Property WS -Average
- 哈希表中还有其他字段。让我们获取所有这些信息。
PS> Get-Process | Measure-Object -Property WS -Average -Sum -Minimum -Maximum
- 获取工作集和 CPU 的平均值、总和、最小值和最大值。
PS> Get-Process | Measure-Object -Property WS, CPU -Average -Sum -Minimum -Maximum

如何工作...
cmdlet Get-Process 会输出一张包含所有正在运行进程的完整表格。可以将每一行视为一个单独的对象,整个表格是这些行的数组。计数(或者说长度)逻辑上是数组的一个属性。因此,我们调用 cmdlet,并对输出的对象运行计数操作。这会告诉我们系统当前运行的进程数量。
我们看到的下一个 cmdlet 也是一个重要的命令:Measure-Object。此 cmdlet 旨在对输出对象执行度量。在我们的案例中,我们选择属性 WS 进行度量。如果没有指定属性,PowerShell 会根据输出对象中的定义选择一个属性。
Measure-Object 能够对输出对象执行一些巧妙的算术计算。我们利用这个 cmdlet 的功能,首先获取平均值,然后是总和、最小值和最大值。Property 参数接受一个字符串数组作为输入(Get-Help Measure-Object -Parameter Property)。因此,我们在 cmdlet 中指定了 WS 和 CPU(用逗号分隔),然后执行度量操作。
启动和停止进程
所有专业终端管理员都经常从终端启动和停止进程。这通常发生在 Bash 的终端提示符下。使用 PowerShell 时,过程并没有太大不同。本节将向你展示如何操作进程。顺便提一下,本节还将带你进行一次全面的信息禁食。
准备就绪
打开 VS Code。如果底部的状态栏是蓝色的,说明可能有一个文件夹在 VS Code 中打开。VS Code 会保存它打开的路径,因此即使在重新启动后重新启动 VS Code,会看到仍然有文件夹打开。我们需要关闭这个文件夹。为此,按 Ctrl + Shift + N(或转到 文件 > 新建窗口)。关闭打开文件夹的窗口。
如何操作…
如果你列出了上一节中的命令,请向上滚动查看哪些 cmdlet 可能对本节有所帮助。
- 在提示符下,输入以下内容以启动 Visual Studio Code。
PS> Start-Process code
确保底部的状态栏不是蓝色的。如果是蓝色的,请阅读本节的 准备就绪 部分。按 Ctrl + Shift + E 或点击 VS Code 左侧边栏中的资源管理器图标。那里不应有任何目录打开。
- 在你创建
hello-world脚本的目录下打开 VS Code。
PS> Start-Process code -ArgumentList /home/ram/Documents/code/github/powershell/
按 Ctrl + Shift + E 或点击 VS Code 左侧边栏中的资源管理器图标。你看到目录打开了吗?
现在,让我们停止 VS Code 进程。
- 列出系统中正在运行的进程,看看是否有任何进程与
code匹配。
PS> Get-Process | grep code
- 如果你想用 PowerShell 的方式来做,运行以下命令,并注意
ProcessName列中的名称:
PS> Get-Process *code*
- PowerShell 处理对象;
grep输出文本。因此,既然我们知道进程的确切名称是code,我们可以直接获取该进程的详细信息。
PS> Get-Process code
这将为我们提供有效的输出。
- 停止所有
code进程。
PS> Stop-Process code
这样做是行不通的;该 cmdlet 接受一个 System.Diagnostics.Process 对象作为输入。
- 将
Get-Process放在括号内,并将输入传递给Stop-Process。
PS> Stop-Process (Get-Process code)
- 现在,查看是否还有
code进程正在运行。
PS> Get-Process code

停止进程的最佳方法是使用其 ID。
- 我现在在我的电脑上运行
dconf-editor,并且想要关闭它。你可以选择停止任何一个进程,但还是建议小心操作。
PS> Get-Process dconf-editor
PS> Stop-Process -Id 20608
它是如何工作的…
cmdlet Start-Process 与路径一起工作。但是,对于code,Linux 知道该软件包的安装路径。因此,只需要通过“命令”调用该软件包就足够了。
VS Code 进程接受目录/文件位置作为输入。因此,我们添加了ArgumentList参数,并将路径作为参数传递给进程。这将使该目录在 VS Code 中打开。
停止进程默认需要一个.NET 对象作为输入;Stop-Process不会接受字符串作为默认输入。因此,我们将Get-Process cmdlet 放在括号内,这样它首先执行并输出一个System.Diagnostics.Process对象,然后由Stop-Process处理。
为了准确确定我们想要停止的进程,我们使用进程 ID,并将其作为参数传递给Stop-Process。
查找进程的所有者
在配方中,处理当前正在运行的进程…,我们列出了系统中正在运行的进程。表格没有显示进程的所有者。在本配方中,我们将讨论如何处理由特定所有者启动的进程。
如何操作…
Get-Process cmdlet 可以为我们获取这个信息。
- 在提示符下,键入以下内容以获取
Get-Processcmdlet 接受的所有参数。
PS> Get-Help Get-Process
- 我们看到有一个参数
IncludeUserName(名称表明它是一个开关参数)。了解更多信息。
PS> Get-Help Get-Process -Parameter IncludeUserName

- 运行命令:
PS> Get-Process -IncludeUserName
- 筛选出由你启动的进程。我们将使用
grep来完成这个操作,直到我们学会如何在 PowerShell 中进行筛选。
PS> Get-Process -IncludeUserName | grep ram
- 计算以你的名字运行的进程数量。
PS> (Get-Process -IncludeUserName | grep ram).Count
- 如果你想使用
Measure-Objectcmdlet,也可以。
PS> Get-Process -IncludeUserName | grep ram | Measure-Object
- 现在,让我们看看你启动的所有进程消耗了多少工作集。
PS> Get-Process -IncludeUserName | grep ram | Measure-Object -Property WS -Sum

出现错误。
它是如何工作的…
默认情况下,Get-Process不会显示进程所有者信息。在某些情况下,可能需要这些信息。在这个简单的配方中,我们使用帮助文档获取所有参数,并使用IncludeUserName开关参数来获取进程所有者信息。
在下一步中,我们筛选出由特定用户启动的进程,然后统计进程数量。我们也使用Measure-Object做了同样的操作,结果是相同的。Measure-Object名字中有"object",但它处理的是grep输出的文本。同时,下一步提到Property的值是无效的。
Measure-Object 在计数时有效的原因在于其前面的步骤。我们知道如果输出是一个数组,PowerShell 可以计数数组中的元素个数。Measure-Object 也能够做到这一点:衡量字符串数组中可以衡量的内容——Count 对字符串数组有效。
然而,当我们尝试获取总工作集时,Measure-Object 无法给出结果。原因是,正如我们之前所看到的,grep 的输出是纯文本。
PS> Get-Process -IncludeUserName | grep ram | Get-Member
还有更多内容...
这或许有点超前,但如果我们想从 grep 的输出中获取工作集信息呢?简单的挑战,接受!
PS> Get-Process -IncludeUserName | grep ram | awk '{print $1}' | ForEach-Object {[Double]$_} | Measure-Object -Sum

暂时不用过于担心这如何工作。一旦我们阅读并理解了如何遍历可用元素,我们就能更好地理解这个是如何工作的。现在,理解我们通过 Double 类型加速器将每个返回的字符串转换成 double 值。然后,我们做了一个 Measure-Object,并得到了总和。
当然,单靠 PowerShell,这一过程会更加简便。
另见
- 食谱 2.4:比较 Bash 和 PowerShell 的输出。
基于文件类型调用应用程序
到目前为止,我们已经看过如何使用Start-Process启动一个应用程序。这通常包括启动应用程序,并通过应用程序打开你想要处理的文件。在本食谱中,我们将利用文件关联的概念,并使用适当的调用 cmdlet 来启动一个应用程序。实际上,我们将走得更远一些。
如何操作...
从网络上下载几张图片,用来处理这个食谱。它们可以是任何类型的,只要确保所有图片都是相同的文件类型。
-
导航到你保存图像文件的位置。
-
在命令提示符下,输入以下命令来打开你的图像查看器,并查看其中的文件。
PS> Invoke-Item -Path *.png
- 现在,在相同位置创建一个文本文件。
PS> New-Item file-1.txt -ItemType File
- 现在,调用所有文件并让它们在各自的应用程序中打开。
PS> Invoke-Item *
它是如何工作的...
在食谱中,从外部调用 PowerShell cmdlet,我们通过先调用 PowerShell,然后将脚本作为参数传递给 pwsh 命令,从 Bash 调用了一个 PowerShell 脚本。PowerShell 中的过程也非常相似,正如在 启动和停止进程 中所看到的。
Invoke-Item cmdlet 依赖于内部文件关联来打开文件。Invoke-Item 的真正用途是在打开多个文件时,无论是相同类型的文件,还是同一路径下不同类型的文件,都能通过各自的应用程序来处理它们。
安装 Crontab PowerShell 模块
在本食谱中,我们将讨论如何安装一个 PowerShell 模块,用于在 Linux 机器上使用 PowerShell 管理 Cron 作业。
有时需要执行管理任务或安排脚本在指定时间自动执行。Linux 发行版默认提供了一个调度工具,名为 Crontab,它允许任何任务在指定的时间自动在后台运行。Cron 是一个基于时间的调度程序,它根据 crontab 文件中的定义生成事件。
如何操作...
PowerShell crontab 包装模块目前尚未在 PowerShell 仓库中提供。你必须手动下载文件来安装该模块。最简单的方法是克隆 PowerShell 仓库,并从其 demos 目录中安装模块。
如果你只希望单独安装 CronTab 模块,可以手动下载 <repo>/demos/crontab 目录。然后,以超级用户身份启动 PowerShell,导航到 crontab。从第 4 步继续。
- 将 PowerShell 仓库克隆到你的计算机上。
$ mkdir ~/code
$ git clone https://github.com/PowerShell/PowerShell.git
-
以超级用户身份启动
pwsh。 -
转到仓库中的
demos/crontab/CronTab目录。该目录中放置了模块清单。
PS> Set-Location ~/code/PowerShell/demos/crontab
- 使用
Import-Modulecmdlet 导入模块清单以加载该模块。
PS> Import-Module -Name ./CronTab/CronTab.psd1
- 要列出 CronTab 模块的组件,请使用
Get-Modulecmdlet。
PS> Get-Module -Name CronTab | Format-List
Name : CronTab
Path : /home/PacktPub/CronTab.psm1
Description :
ModuleType : Script
Version : 0.0
NestedModules : {}
ExportedFunctions : {ConvertTo-CronJob, Get-CronJob, Get-CronTab, Get-CronTabUser...}
ExportedCmdlets :
ExportedVariables :
ExportedAliases :
- 要列出
CronTab模块的可用命令类型,请使用Get-Commandcmdlet。
PS> Get-Command -Module CronTab
CommandType Name Version Source
----------- ---- ------- ------
Function ConvertTo-CronJob 0.0 CronTab
Function Get-CronJob 0.0 CronTab
Function Get-CronTab 0.0 CronTab
Function Get-CronTabUser 0.0 CronTab
Function Import-CronTab 0.0 CronTab
Function Invoke-CronTab 0.0 CronTab
Function New-CronJob 0.0 CronTab
Function Remove-CronJob 0.0 CronTab
它是如何工作的...
当你下载 CronTab 模块时,以下文件将从食谱中提到的网址复制到 CronTab 文件夹中,这些文件包含模块数据。
-
CronTab.ps1xml
-
CronTab.psd1
-
CronTab.psm1
Import-Module cmdlet 会将模块中可用的 cmdlet 注册到 PowerShell 会话中,并使该模块准备好使用。Import-Module cmdlet 会根据模块清单(即 psd1 文件)执行。
当你运行 Get-Command cmdlet 时,你将得到该模块中所有可用 cmdlet 的列表。
在 PowerShell 中安排任务
在本食谱中,我们将展示如何使用 PowerShell cmdlet 安排任务。
准备工作
-
使用 SU 权限登录终端。
-
使用
pwsh命令打开 PowerShell 控制台。 -
导入模块(请参考食谱:安装 Crontab PowerShell 模块)。
如何操作...
- 首先,检查你要在终端中安排的命令或脚本是否存在。
$ pwsh -f "/tmp/DataLoading.PS1;"
- 要根据计划运行脚本,请使用
New-CronJobcmdlet。
PS> New-CronJob -Command 'pwsh -f "/tmp/DataLoading.PS1;"' -Minute 0,15,30,45 | Out-Host
PS> New-CronJob -Command 'pwsh -f "/tmp/DataLoading.PS1;"' -Minute */15 | Out-Host
PS> New-CronJob -Command 'pwsh -f "/tmp/DataLoading.PS1;"' -Minute */15 -Hour 10-12 | Out-Host
PS> New-CronJob -Command 'rm -rf /tmp/clr*' -Minute 15 -Hour 1 | Out-Host
PS> New-CronJob -Command 'pwsh -f "/tmp/DataLoading.PS1;"' -Minute */15 -Hour 10-12 -DayOfWeek sun,tue,fri -Month Jan,Mar,Jun,Sep,Dec | Out-Host
- 要获取当前计划的任务列表,请运行
Get-CronJobcmdlet。
PS /home/PacktPub> Get-CronJob |Format-Table -AutoSize
Minute Hour DayOfMonth Month DayOfWeek Command
------ ---- ---------- ----- --------- -------
2 * * * * python ./pythonexmaple.py
5 1 * * * rm -rf /tmp/clr*.*
15 * * * * /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
*/15 * * * * /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
0,15,30,45 * * * * /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
*/15 10-12 * * * /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
*/15 10-12 * Jan,Mar sun,tue,fri /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
- 要查看 crontab 配置文件中已安排任务的内容,请使用
Get-CronTab。
PS > Get-CronTab
2 * * * * python ./pythonexmaple.py
5 1 * * * rm -rf /tmp/clr*.*
*/15 * * * * /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
0,15,30,45 * * * * /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
*/15 10-12 * * * /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
*/15 10-12 * Jan, Mar, Jun, Sep, Dec sun, tue, fri /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
它是如何工作的...
PowerShell 实现的 crontab 技术上是一个包装器,它封装了在 Linux 中工作的 crontab 命令。如果底层功能就是 crontab,为什么还要使用 PowerShell 来安排任务呢?原因是使用的一致性、稳定性以及面向对象的方法。如果你会使用 PowerShell,那就是你使用 crontab 的全部需求。这就是目标所在。
New-CronTab cmdlet 用于定义新任务。可用的参数定义了任务执行的频率。Cron 任务以执行 New-CronTab cmdlet 时所用的权限运行。换句话说,如果你以超级用户身份启动 PowerShell 并运行 New-CronTab 来定义调度,那么按指定调度运行的命令将以超级用户权限执行。
在这些步骤中,运行的命令通过 -Command 参数列出。
让我们简要了解一下 New-CronTab 接受哪些输入。有两种方式可以每 15 分钟运行一次命令:第一种是指定每分钟的时间:
0,15,30,45 * * * * /path/command
或者,你可以将其简化为 */15。
*/15 * * * * /path/command
也可以通过指定范围来安排任务。在我们的示例中,任务将在上午 10 点、11 点和中午 12 点运行,使用的是范围选项。
*/15 10-12 * * * /path/command
在最后一个示例中(名称格式),任务计划根据星期几(Sun, Tue, Fri)和月份(Jan, Mar)参数来运行。
*/15 10-12 * Jan,Mar sun,tue,fri /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
使用 Get-CronTab cmdlet 读取 Cron 表配置。每一行表示有关计划任务的元数据记录;它指定了任务的执行频率和应执行的命令/脚本。
目前的一个注意事项是,如果你设置的 Cron 任务不正确,它们可能会悄无声息地失败。Cron 有自己的保留 syslog 功能,因此你应该查看 /etc/syslog.conf 文件(或你 Linux 发行版中等效的文件),看看来自 cron 的消息被发送到了哪里。常见的目标包括 /var/log/cron、/var/log/messages 和 /var/log/syslog。
在 PowerShell 中移除任务
在这个教程中,我们将看到从 CronTab 文件中移除条目的步骤。
如何操作...
既然我们已经在 cron 配置文件中创建了条目,让我们尝试删除这些条目。
- 使用
Get-CronJobcmdlet 列出任务。这将帮助你获取通过读取CronTab文件创建的任务列表。
PS> Get-CronJob | Format-Table -AutoSize
- 现在,应用条件逻辑来使用
Where-Object子句隔离所需的任务条目。你将在 通过管道传递数据 章节中进一步了解这一点。
PS > Get-CronJob | Where-Object {$_.Month -match 'Jan'} | Format-Table -AutoSize
Minute Hour DayOfMonth Month DayOfWeek Command
------ ---- ---------- ----- --------- -------
*/15 10-12 * Jan,Mar,Jun,Sep,Dec sun,tue,fri /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
*/15 10-12 * Jan,Mar sun,tue,fri /usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"
- 使用
Remove-CronJob删除条目。
PS > Get-CronJob | Where-Object {$_.Month -match 'Jan'} | Remove-CronJob
Confirm
Are you sure you want to perform this action?
Performing the operation "Remove" on target "/usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): Y
Confirm
Are you sure you want to perform this action?
Performing the operation "Remove" on target "/usr/bin/pwsh -c "cd /tmp/; ./DataLoading.PS1;"".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): Y
PS /home/PacktPub>
它是如何工作的...
当我们想要移除 cron 任务时,我们会列出 cron 任务,然后通过管道将任务对象传递给 cmdlet Remove-CronJob,从配置文件中删除这些任务。
第五章:通过管道传递数据
本章内容包括以下主题:
-
从输出中选择列
-
限制 cmdlet 返回的项目数量
-
扩展选择
-
过滤对象
-
对输出进行分组
-
对输出进行排序
-
对返回的对象采取行动
-
理解启用管道的参数
-
将内容导入 PowerShell
介绍
现在是信息大餐的时刻了。在上一章,PowerShell 管理的第一步中,我们探讨了一些简单的概念,例如操作日期和进程。在此过程中,我们也学到了如何使用 PowerShell 的一些技巧,比如衡量输出对象。
在本章中,我们将学习使用 PowerShell 高效且友好的工具之一:管道。
大多数 Linux 管理员在其 shell 命令或 shell 脚本中使用过管道。并且使用任何形式的 shell 脚本的管理员都知道,管道将一个命令的输出作为输入传递给下一个命令。在 Bash(及其衍生版本)的情况中,管道会将文本从前一个命令传递到下一个命令。
大多数 PowerShell cmdlet 输出对象。PowerShell 中的管道将输出的对象传递给下一个命令。当我们在 PowerShell 中运行 PowerShell 命令(而不是 Linux 命令)时,通常会得到类似表格的输出。我们可能认为这就是所有的输出。
当我们要求 PowerShell 获取任何信息时,它会在后台提取整个对象。然后,这个对象(或包含大量对象的包)会使用 PowerShell 内置的格式化规则进行处理,以便在主机上显示。引用微软的话:
屏幕上显示的是信息的摘要,而不是输出对象的完整表示。
因此,通常情况下,您在屏幕上看到的某个命令的输出,只是冰山一角!只是因为潜藏部分根据格式化规则被选择不显示。在本章中,我们将使用管道获取比默认屏幕显示更多的对象内容,并利用管道的功能,从 PowerShell 中挖掘更多的信息。
从输出中选择列
当我哥哥看到我在探索 awk 时,他说:“伙计,我们过度使用这个命令了!”当然,并不是所有在屏幕上显示的内容都是重要的——甚至并非必要。在本节中,我们将学习如何在 PowerShell 中分隔列,而无需使用 Linux 命令 awk。
准备就绪
转到一个包含一些可以操作的文件的目录。如果你没有这样的目录,可以创建一个并在其中创建一些文件。让文件具有不同的扩展名,以便我们在未来的教程中也可以使用它们。
如果你还没有克隆 Git 仓库,github.com/PacktPublishing/PowerShell-6.0-Linux-Administration-Cookbook。在目录chapter-05下有一个快速而简便的脚本,Initialize-PacktPs6CoreLinuxLab.ps1。运行该脚本以获取必要的文件。
如何操作……
awk命令处理文本,并根据输出文本中的分隔符,将输出分成多个列。这个分隔的输出再次以列的形式显示,使用类似 C 语言中的print函数。PowerShell 的工作方式稍有不同。
让我们开始吧。
- 如果你还没有创建文件,请创建。以下是一些你可以用来创建文件的命令。
PS> New-Item ./random/cities -ItemType Directory -Force
PS> Set-Location ./random/
PS> New-Item random-text.txt, himalayas.jpg, crunched-numbers.csv, bangalore.jpg, screenshot-001.png, screenshot-002.png, screenshot-003.png, demo.doc, my-plugin.rb, ./cities/mumbai.html, ./cities/nyc.html, ./cities/cairo.html, ./cities/dubai.html, ./cities/paris.html -ItemType File
-
你也许想下载一些真实的多媒体内容,以便我们获取
length(文件大小)属性,供以后使用。随便下载一些随机的图片或媒体文件。 -
导航到你保存文件的位置。我将它们保存在我的主目录下一个名为
random的目录中。如果你使用了脚本,应该也会这样。
PS> Set-Location ./random/
使用 Tab 补全来完成 cmdlet 和路径。
- 列出当前目录下的内容。
PS> Get-ChildItem -Path .

- 假设你不需要
Mode列。
PS> Get-ChildItem -Path . | Select-Object LastWriteTime, Length, Name

如果你注意到,select似乎没有遵循 PowerShell 使用的命名或大小写约定。是怎么回事呢?运行Get-Command select来找出原因。
- 在当前的上下文中,这个顺序对你来说并没有太大意义。重新排列列的顺序。
PS> Get-ChildItem -Path . | Select-Object Name, Length, LastWriteTime

这看起来好多了。
- 现在,将
LastWriteTime列的名称改为Modified。
PS> Get-ChildItem -Path . | Select-Object Name, Length, @{Name='Modified'; Expression={$_.LastWriteTime}}
注意最后一列的名称,并与之前的输出进行对比。
- 现在,只选择年份,不要整个日期。
PS> Get-ChildItem -Path . | Select-Object Name, Length, @{Name='Modified'; Expression={$_.LastWriteTime.Year}}
- 查看自上次更改以来已过去多少天。
PS> Get-ChildItem -Path . | select Name, Length, @{Name='DaysSinceModification'; Expression={[math]::Round(((Get-Date) - $_.LastWriteTime).TotalDays)}}
它是如何工作的……
如果你运行了Get-Command select,你现在应该已经明白,select实际上是Select-Object的别名。如前所述,PowerShell 输出对象。这些对象随后使用内置的格式化规则进行格式化,并以特定方式显示在屏幕上。
当我们使用Select-Object时,我们通过指定需要显示哪些对象来覆盖格式化规则。虽然Select-Object的主要目的是选择我们需要的列,但该 cmdlet 还允许我们对输出列进行排序。
PowerShell 同样为我们提供了修改返回列名的自由。在这种情况下,我们使用哈希表来指定我们想要的名称,以及在该列下显示的数据。你甚至可以对返回的数据进行计算,并将其设置为计算属性。在最后一步,我们从当前日期中减去最后修改日期,选取自那时起经过的天数,然后,使用Round方法和[math]加速器来获得最后修改以来天数的四舍五入值。
$_ 或 $PSItem 的概念。
还有更多...
尝试将Select-Object与其他 cmdlet(如 Get-Command)一起使用,来仅选择你需要的那些列。
另见
示例:创建并初始化一个简单的哈希表。
限制输出对象的数量
在前面的示例中,从输出中选择列,我们学习了如何使用 Select-Object cmdlet 来仅选择我们需要的列。在本示例中,我们将学习如何将 cmdlet 的输出限制为返回的所有项目的子集。
准备开始
确保你在一个包含一些文件的位置。列出当前目录的内容,并计算返回的项目数量。如果数量少于五个,你可能需要考虑向目录中添加更多项目。
- 输入以下命令以计算当前路径下文件和目录的数量。
PS> (Get-ChildItem -Path .).Count
- 如果你想减少输入的字符数,考虑到我们仅在终端运行命令,你可以使用 cmdlet 的别名和默认设置。
PS> (gci).Count
这些括号是必须的;括号的工作方式与数学中的相似:括号内的指令会先被处理。在这种情况下,我们希望首先执行 gci,然后选择从返回对象中获取的 Count 属性。
- 获取
Select-Object的参数列表。
PS> Get-Help Select-Object
我们看到诸如 First 和 Last 的参数,它们接受整数值。
如何做到...
导航到包含文件的目录。
- 使用
First参数,仅从返回的列表中选择前五个文件和目录。
PS> Get-ChildItem . | Select-Object -First 5
- 假设你想要选取最后五个元素。
PS> Get-ChildItem . | Select-Object -Last 5
- 跳过前三个对象:
PS> Get-ChildItem . | Select-Object -Skip 3
- 如果你想跳过最后两个对象:
PS> Get-ChildItem . | Select-Object -SkipLast 2
- 从输出中选取第四个元素:
PS> Get-ChildItem . | Select-Object -Index 3
- 现在,将其与我们在前一个示例中学到的结合起来。仅选取前四个元素的文件名和最后修改时间。
PS> Get-ChildItem . | select -Property Name, LastWriteTime -First 4
它是如何工作的...
这是一个简单的示例,帮助你处理输出中返回项目的数量。在 PowerShell 中使用 Select-Object,我们不需要通过输出循环并计数,只为了获取我们想要的元素数量。Select-Object cmdlet 已内置该功能。
参数名称不言自明。也许除了 Index 参数,它的工作方式与大多数编程语言中的数组元素编号方式相同:从零开始。因此,数组中的第四个元素的索引是 3,而不是 4。
Property 和 First 这两个参数可以结合使用,因为它们都出现在 Select-Object 的帮助文档中的同一个参数集里。
展开属性中的属性
到目前为止,从我们所看到的两件事已经很明确:
-
对象输出显得更加丰富,使用起来也更方便。
-
一个对象可以包含更多的对象。
我们已经通过使用 Select-Object cmdlet 处理了第一个点,其中我们只选择了需要的对象属性,忽略了其他的。这一部分教程旨在分解第二个点,帮助更好地理解。
让我们带着一个想法进入这个教程:对象可以包含对象,而这些对象又可以包含更多对象。为了演示这一点,我们将使用 Get-Process cmdlet。
如何做到...
让我们从列出所有进程开始;我们将查看 Get-Process cmdlet 提供的所有属性,并寻找复杂的对象。
- 选择
Get-Processcmdlet 输出中的所有属性。只选择第一个对象,以免你的控制台输出过多内容。
PS> Get-Process | Select-Object -Property * -First 1
观察 Threads 属性。
- 选择进程的名称、ID 和线程。
PS> Get-Process | Select-Object -Property Name, Id, Threads
- 列出
pwsh进程的所有线程。
PS> Get-Process pwsh | Select-Object -ExpandProperty Threads
- 输出内容很丰富。假设我们只需要结果输出中的 ID、优先级和启动时间。
PS> Get-Process pwsh | Select-Object -ExpandProperty Threads | Select-Object -Property Id, PriorityLevel, StartTime
这为我们提供了所有在 pwsh 下运行的线程的 ID、优先级和启动时间。
- 如果我们对 ID 使用
ExpandProperty呢?
PS> Get-Process pwsh | Select-Object -ExpandProperty Threads | Select-Object -ExpandProperty Id

它是如何工作的...
正如我们已经看到的,Select-Object 的默认参数是 Property。它接受属性名称并在输出中显示其值。该参数可以用于提取多个属性的值(如 Name、Id、Threads)。
然而,一些属性内部包含更复杂的对象,比如我们在 Get-Process 返回的对象的 Threads 属性中看到的那样。识别复杂对象的简单方法是看看它们是否被大括号包围。
ExpandProperty 参数仅接受单个属性名称作为输入,并展开该属性以显示其内部对象。这个展开后的结果还可以通过管道传递给 Select-Object(或任何其他相关的 cmdlet)进行进一步处理。
在对象不复杂的情况下,ExpandProperty 只会显示输出而没有头部。如果你注意到 ExpandProperty 的输出,它不包含展开的对象名称(在我们案例中是 Threads);它只包含值。这个值又包含了多个属性。
在简单属性的情况下,使用 ExpandProperty 会简单地从输出中去除属性名称。
过滤对象
在食谱《限制 cmdlet 返回的项数》中,我们看到如何根据数量限制输出内容。在本食谱中,我们将学习如何根据某个特定标准而非数量来过滤输出。
准备就绪
如果你还没有阅读,返回阅读食谱《与日期属性一起工作》。本食谱使用日期对象的一个属性来过滤内容。虽然理解对象的过滤并非关键,但它仍然展示了基于对象属性的过滤是多么简单。
如何操作...
导航到你为本书创建文件的位置。
- 列出该目录的内容,查看你有哪些内容。(如果你像我一样,可能甚至忘记了你在哪里创建了这个目录,以及你放了什么内容。)
PS> Set-Location ~/random
PS> Get-ChildItem -Path .
注意你得到的属性名称。
- 现在,只挑选那些大于
0字节的文件。
PS> Get-ChildItem -Path . | Where-Object -Property Length -GT -Value 0
- 从文件中挑选所有 JPG 文件。为此,在现有条件上再加一个条件。尽管这次使用的是
FilterScript参数,而不是Property。
PS> Get-ChildItem -Path . | Where-Object -FilterScript {$_.Length -GT 0 -and $_.Extension -EQ '.jpg'}
- 添加一个条件,获取文件名以“c”开头的文件。
PS> Get-ChildItem -Path . | Where-Object -FilterScript {$_.Length -GT 0 -and $_.Extension -EQ '.jpg' -and $_.Name -CMatch '^c'}
- 现在,选择那些在任何小时的第 30 分钟之前创建的文件。
PS> Get-ChildItem -Path . | Where-Object -FilterScript {$_.Length -GT 0 -and $_.Extension -EQ '.jpg' -and $_.Name -CMatch '^c' -and $_.LastWriteTime.Minute -LT 30}

工作原理...
在 PowerShell 中,输出的过滤非常简单。由于内容输出是一个对象,我们可以直接使用对象内部的属性进行过滤。
本食谱展示了两种过滤模式:
-
使用一个属性,并将其值与输入进行比较,
-
使用一个包含多个值和多个条件的过滤脚本。
Property参数只接受一个属性。为过滤输出,条件操作符和比较值被添加到语句中。
另一方面,FilterScript参数可以处理更复杂的过滤操作,例如,当我们需要输出满足多个条件时。
使用Property和FilterScript的一个显著区别是自动变量$_的使用。该变量包含管道中的当前对象。例如,在本食谱中,我们通过管道将Get-ChildItem返回的对象传递给Where-Object。自动变量$_包含Get-ChildItem返回的对象,Where-Object可以对其进行处理。在本例中,$_.LastWriteTime获取由Get-ChildItem返回的对象的LastWriteTime属性。
此外,LastWriteTime对象属于System.DateTime或DateTime类型。(运行(Get-ChildItem .).LastWriteTime | Get-Member了解更多信息。)因此,它可以进一步拆解为天、小时、分钟等,这也是为什么可以使用$_.LastWriteTime.Minute进行过滤的原因。
还有更多...
尝试在其他输出上使用Where-Object cmdlet,例如Get-Process的输出。
对输出进行分组
有些情况下,我们可以对输出的对象进行分组,以便更好地处理每个组,或者仅仅为了更有组织的输出。在本示例中,我们将通过管道将一个 cmdlet 的输出传递给Group-Object,并根据某个属性对输出进行分组。
如何操作...
要根据属性对对象进行分组,我们使用Get-ChildItem cmdlet 对本书中使用的文件进行操作。
- 转到你创建或下载文件的位置。
PS> Set-Location ~/random
- 仅列出文件(排除目录)。
PS> Get-ChildItem -Path . -File
或者使用简写版本。
PS> gci -File
- 根据扩展名对对象进行分组。
PS> Get-ChildItem . -File | Group-Object Extension
- 这个简写版本是:
PS> gci -File | group Extension

- 使用
Select-Objectcmdlet 只显示扩展名和每个扩展名下的文件数。
PS> Get-ChildItem -Path . -File | Group-Object -Property Extension | Select-Object -Property Name, Count
- 有没有更简单的方法来做到这一点?
PS> Get-ChildItem -Path . -File | Group-Object -Property Extension -NoElement
- 现在我们已经了解了如何处理分组和展开的属性选择,让我们从中挑选出所有 JPG 文件。
PS> Get-ChildItem -Path . -File | Group-Object -Property Extension | Where-Object Name -EQ .jpg | Select-Object -ExpandProperty Group

它是如何工作的...
在这个示例中,我们所做的操作无疑是不直观的,因为我们本可以仅使用Where-Object和Get-ChildItem。然而,本示例的目的是展示如何使用Group-Object。Group-Object cmdlet 会根据我们指定的标准创建组,并根据该标准命名这些组。在我们的例子中,分组的标准是扩展名,因此,组的名称就是扩展名。
每个组包含其元素,这些元素本身也是对象。如果我们只想要组的数量和名称,我们可以使用NoElement参数。如果我们只想要元素,则使用Select-Object cmdlet 的ExpandProperty参数并展开所有元素。被展开的元素是Group-Object前一个 cmdlet 的对象(在我们的例子中是Get-ChildItem)。
如果Where-Object和其他 cmdlet 可以以复杂的方式做Group-Object能做的事,那为什么还要有Group-Object呢?继续阅读对返回对象采取行动,了解更多信息。
还有更多...
如果你已经掌握了 Tab 补全功能,你甚至无需选择那些默认未显示的属性。你只需要在管道后输入Group-Object,加一个空格并按 Tab 键;PowerShell 会显示可用于分组的对象。试试看:
PS> Get-ChildItem . -File | Group-Object<Space><Tab>
另见
示例 05.07:对返回的对象采取行动
排序输出
现在到了另一个简单的 cmdlet,它将为“对返回的对象采取行动”这一示例奠定基础。在本示例中,我们将对输出对象进行排序,以满足我们的要求。
如何操作...
我们将继续使用为本书创建的目录和文件。如果你的所有文件都是零字节,下载几个有内容的文件。文件类型无关紧要。
- 列出目录中的文件。
PS> Get-ChildItem -Path .
- 过滤输出,只有文件。
PS> Get-ChildItem -Path . -File
- 将对象传递给
Sort-Objectcmdlet,以根据文件大小对输出进行排序。
PS> Get-ChildItem -Path . -File | Sort-Object -Property Length
- 该表达式的简写为:
PS> gci . -File | sort Length
- 排序文件,从最大文件到最小文件。
PS> Get-ChildItem -Path . -File | Sort-Object -Property Length -Descending
- 从中选择三个最大的文件。
PS> Get-ChildItem -Path . -File | Sort-Object -Property Length -Descending -Top 3
- 创建两个文件,并使用文本编辑器向它们添加一些内容。(或者使用以下脚本块创建一些随机文本。)
PS> $($i = 0; while ($i -lt 520) {(-join ((65..90) + (97..122) | Get-Random -Count 8 | ForEach-Object {[char]$_})).ToString(); $i++}) -join ' ' | Out-File ./random-text-1.txt; Start-Sleep -Seconds 60; $($i = 0; while ($i -lt 500) {(-join ((65..90) + (97..122) | Get-Random -Count 8 | ForEach-Object {[char]$_})).ToString(); $i++}) -join ' ' | Out-File ./random-text-2.txt
- 现在,按照文件大小排序目录中的文件,然后按文件名排序。
PS> Get-ChildItem -Path . -File | Sort-Object -Property Length, Name
观察你得到的输出。
- 按降序排序列表。
PS> Get-ChildItem -Path . -File | Sort-Object -Property Length, Name -Descending

它是如何工作的...
这个示例是通过管道传递对象的另一个演示。在Sort-Object的帮助文档中,我们看到一个名为InputObject的参数。这个参数是 PowerShell 中的一个通用术语,用来描述通过管道传递的输入参数。
Sort-Object接受来自管道的输入,并根据指定的属性进行排序。如果没有指定属性,则使用前一个命令输出对象的默认属性进行排序。如果输入多个属性,则根据指定属性的顺序进行排序。
排序可以按升序(默认)或降序进行。
对返回的对象执行操作
在本章中,我们一直在使用管道对对象执行各种操作。我们已经将对象从一个 cmdlet 传递到另一个 cmdlet,实际上对返回的对象进行了操作。因此,从技术角度来看,这个示例并不新颖。然而,为了让我们更熟悉管道的使用,并且展示管道不仅仅用于选择、过滤和排序,我们还将使用管道执行一些删除操作。
准备工作
如果你在我们创建的演示目录中没有文件,请继续创建一些文件。确保其中一些文件有内容。
假设你收到一个需求。有一个团队希望删除每种类型中最大的两个文件。如果某种类型的文件只有一个,那就不删除该文件。
如何操作...
以下是你可能需要执行的步骤概述:
-
获取指定路径下的所有文件。
-
按文件类型(扩展名)分组文件。
-
过滤掉包含多个项目的组。
-
展开每个组,按大小(长度)排序文件。
-
从每个组中选择两个最大的文件。
-
删除文件。
虽然我们正在使用一个沙箱目录,并且采取了不删除重要文件的预防措施,但最好仅使用 ShouldPerform(WhatIf 参数)来原型化操作。这样,文件不会被实际删除,而是 PowerShell 只会告诉你如果运行该命令会执行什么操作。
让我们开始吧。
- 列出当前目录的内容,并根据扩展名分组输出。
PS> Get-ChildItem -Path . -File | Group-Object -Property Extension
- 过滤以丢弃每个扩展名的单个文件。
PS> Get-ChildItem -Path . -File | Group-Object -Property Extension | Where-Object Count -GT 1
- 现在让我们来看一个循环结构。我们将在后续章节中详细了解它是如何工作的。目前,只需知道它能正常运行。这里的目标是充分利用管道。
PS> Get-ChildItem -Path . -File | Group-Object -Property Extension | Where-Object Count -GT 1 | ForEach-Object {$_.Group | Sort-Object Length -Bottom 2}

- 使用
Remove-Itemcmdlet 删除这些文件。如果您不想实际删除文件,请使用WhatIf开关。
PS> Get-ChildItem -Path . -File | Group-Object -Property Extension | Where-Object Count -GT 1 | ForEach-Object {$_.Group | Sort-Object Length -Bottom 2} | Remove-Item -WhatIf
它的工作原理...
当任何 cmdlet 通过管道读取对象时,它读取对象的全部内容。而任何设计为通过管道接受输入的 cmdlet,则从对象中选择正确的属性,然后对这些对象执行操作。在这个例子中,选择的是 Path 属性,以便识别要删除的文件。
要知道某个 cmdlet 是否接受来自管道的输入,请使用 Full 参数运行 Get-Help 命令,并查看 Accept pipeline input? 的值是否为 true。Where-Object 的 InputObject 参数或 Move-Item 的 Path 参数就是其中的一些示例。
理解启用管道的参数
如果您计划仅在控制台上运行 PowerShell 命令,则了解管道不是必须的;它被很好地封装,并且 cmdlet 被设计得很好,能够处理在 cmdlet 之间传递对象的情况。然而,如果您计划创建自定义函数和模块,则理解管道的概念是您希望深入了解的内容。
在本例中,我们将研究 cmdlet 接受输入的两种方式。在创建函数的时候,我们将看看如何为参数启用管道输入。
如何做到...
我们将主要使用帮助文档来演示两种不同类型的管道输入。
- 在提示符处,键入以下命令:
PS> Get-Help Get-Item -Parameter Path

它说该参数接受字符串输入,并且也接受通过属性名称和值输入。
- 输入以下内容以查看是否接受有效字符串。
PS> '/home/ram/random' | Get-Item

- 让我们尝试类似的操作,使用
Get-Date。
PS> Get-Help Get-Date -Parameter *
Date 参数通过管道接受值。但类型是 DateTime,而不是字符串。
- 尝试通过管道发送有效字符串,看看它是否被转换为日期。
PS> '21 June 2018' | Get-Date
它确实将字符串转换为日期和时间。

- 现在,让我们回到获取当前目录详细信息的步骤。不过,这次我们只会选择对象的 FullName 属性。
PS> Get-Item . | Select-Object FullName
- 通过管道将此传递给
Get-ChildItemcmdlet。
PS> Get-Item . | Select-Object FullName | Get-ChildItem
出现了一个错误。

- 将属性名称更改为
LiteralPath。
PS> Get-Item . | Select-Object @{Name = 'LiteralPath'; Expression = {$_.FullName}}
- 通过管道将对象传递给
Get-ChildItem。
PS> Get-Item . | Select-Object @{Name = 'LiteralPath'; Expression = {$_.FullName}} | Get-ChildItem
那成功了。
它的工作原理...
通过管道有两种类型的输入:
-
ByPropertyName -
ByValue
ByValue 类型也许是最常见的。如果一个参数通过管道按值接受输入,它会查找与输出中定义的数据类型匹配的数据类型,并将输出作为其输入。在数据类型与定义的类型不相同,但可以转换为所需类型的情况下,参数会将值转换为它所接受的数据类型并进行处理。这在 Get-Date 中发生过,我们将日期作为字符串发送,并通过管道传递给 Get-Date。
在 Get-Item 中,Path 参数接受字符串输入并处理命令。在 Get-Date 中,Date 参数将字符串转换为 DateTime 对象并处理请求。
与 ByValue 相比,ByPropertyName 会查找与参数完全相同名称的属性。在我们的例子中,Get-ChildItem 的 LiteralPath 在我们传递 FullName 属性时抛出了错误,尽管它本质上是对象的字面路径,并且是一个字符串值。错误的原因是属性并没有被命名为 LiteralPath。当我们将属性名称更改为 LiteralPath 时,Get-ChildItem 通过管道接受了输入,并给出了期望的输出。
导入内容到 PowerShell
管理多个计算机的管理员需要将某些输入传递给 cmdlet,以便自动化任务。虽然大多数 Linux 管理员熟悉将输入发送到文件,但在 PowerShell 中,重要的一点是,除了接受基于文件的输入(例如:Get-Content),PowerShell 还能够导入输入。这种导入的输入是一个 PowerShell 对象。
在这个食谱中,我们将研究两种导入 cmdlet,并学习如何使用它们。
如何做到这一点...
在食谱 从文本到对象的解析输入 中,我们使用 Import-Csv 导入了一个以逗号分隔的值文件,将其中的数据转换为 PowerShell 对象。让我们回顾一下我们学到的内容,不过这次,由于我们已经知道如何处理对象,我们将以某种方式使用导入的内容。
在导入内容之前,让我们首先将一些内容导出到 CSV 文件。这样,我们就有了一些相关的内容可以进行操作。
- 导航到你为本章创建文件的位置。列出目录的内容。尽管本书在控制台中使用(并推荐)完整的 cmdlet(当然使用 Tab 补全),但如果你愿意,也可以使用别名。
PS> Get-ChildItem -Path . | Select-Object Name, FullName, CreationTime, LastWriteTime, Extension, Length
- 将内容导出到 CSV 文件。
PS> Get-ChildItem -Path . | Select-Object Name, FullName, CreationTime, LastWriteTime, Extension, Length | Export-Csv ./file-list.csv
- 使用 LibreOffice Calc 或文本编辑器等电子表格处理器打开该文件,以查看其内容。
PS> Get-Content ./file-list.csv
这是 Get-ChildItem 返回的对象的纯文本表示。
- 导入 CSV 文件的内容,将文本转换为对象。
PS> Import-Csv ./file-list.csv
- 查找此命令返回的对象类型。
PS> Import-Csv ./file-list.csv | Get-Member
对象类型是 System.Management.Automation.PSCustomObject。
- 检查这是否与
Get-ChildItem返回的相同。
PS> Get-ChildItem . | Get-Member
Import-Csv返回的对象不同。是否可以像对待其他对象一样对待它?使用成员访问运算符获取CreationTime。
PS> (Import-Csv ./file-list.csv).CreationTime
- 只选择年份。
PS> (Import-Csv ./file-list.csv).CreationTime.Year
- 查找上一个命令返回的对象类型。
PS> (Import-Csv ./file-list.csv).CreationTime | Get-Member
TypeName: System.String
- 尝试将
LastWriteTime转换为DateTime对象。只选择第一个记录。
PS> Get-Date (Import-Csv ./file-list.csv | Select-Object CreationTime -First 1).CreationTime
- 如果我们必须保留
Get-ChildItem返回的对象中的所有对象,包括它们的对象类型,该怎么办?
PS> Get-ChildItem -Path . | Export-Clixml ./file-list.xml
- 现在,将 XML 的内容导入到会话中。
PS> Import-Clixml ./file-list.xml
- 查找导入命令返回的对象类型名称。
PS> Import-Clixml ./file-list.xml | Get-Member
- 选择
CreationTime属性并查找它的类型。
PS> (Import-Clixml ./file-list.xml).CreationTime | Get-Member
TypeName: System.DateTime
- 只选择年份。
PS> (Import-Clixml ./file-list.xml).CreationTime.Year
它是如何工作的……
从 CSV 导入内容是一个直接的过程。CSV 中的列是由逗号分隔的。PowerShell 会根据输入的内容创建一个对象,每一列都会成为 PowerShell 自定义对象的一个属性。我们对对象执行的操作也可以作用于PSCustomObject,然而Import-Csv的唯一限制是属性不能是多值的,也不能包含其他子属性。虽然使用 CSV 有可能实现多值属性,但这需要在对象导入 PowerShell 后进行一些处理。一个方法是通过分隔符分隔属性的值,然后在导入后分割这些分隔值。
另一方面,CLIXML(或称公共语言基础结构 XML)是一个完整的.NET 对象。当 PowerShell 对象导出为 CLIXML 时,该对象会原封不动地保留下来。也就是说,CLIXML 保留了所有属性(理论上可以达到任意深度),以及输出对象的所有方法。换句话说,可以说 CLIXML 导出在对象成员方面几乎是无损的。
另见
方案:使用 ASCII 字符生成随机文本
第六章:使用变量和对象
在本章中,我们将讨论以下主题:
-
使用环境变量
-
存储 .NET Core 对象实例的输出
-
为对象创建自定义属性
-
从返回的对象创建自定义对象
-
理解类型数据的扩展
-
在会话间保留对象的修改
-
删除自定义类型数据
介绍
变量在编程中非常重要,因为它们充当在程序执行过程中存储信息的容器。尽管管道使得 PowerShell 非常灵活,但它们仍然无法替代变量,因为通过管道传递的对象必须立即被消费,而不是所有脚本都以这种方式工作,因为我们的需求各异。
我们都知道各种数据类型:int、double、string、char、array 等。在 PowerShell 中,另外两种重要的变量类型是哈希表和对象。哈希表是由键值对构成的字典表。正如我们所看到的,PowerShell 中的对象可以是简单的,也可以是复杂的,能够存储不同类型的值。
在 PowerShell 中,对象可以存储到变量中。例如,
$Processes = Get-Process
会将所有进程存储到变量 $Processes 中。
需要记住的另一个关于变量的点是作用域。默认情况下,变量具有局部作用域,这意味着它们在指定的函数内有效。全局变量在整个程序中有效。通常,最佳实践是使用局部变量。
全局变量通过 $Global: 前缀声明和使用,例如 $Global:MyVariable。
使用环境变量
在本示例中,你将了解环境变量。当通过 Shell 会话与系统交互时,Shell 需要许多信息来确定程序访问、可用资源、默认配置、系统属性等。这些设置中有一部分被配置为变量,这些设置通常被称为环境变量。
尽管在安装期间硬编码这些信息是一种做法,但它会使整个系统成为一个不可重新配置的整体,除非通过所谓的“重建并重新安装”方法来解决。通过环境变量,引入了另一层灵活性。
如何操作...
让我们通过以下步骤开始使用环境变量:
- 要显示环境变量,请键入
Get-ChildItem env:或Get-Item env:然后按回车键。
PS> Get-ChildItem env:
PS> Get-Item env:
-
Env:也是 PowerShell 提供的一个提供程序,它的工作方式类似于文件系统。因此,你也可以Set-Location到Env:并使用Get-ChildItem .来列出系统中可用的变量。 -
要显示特定环境变量的值,运行以下命令。
PS> Get-ChildItem Env:/PATH | Select-Object value | Format-Table -Wrap
在键入 Get-ChildItem Env:/ 后,你可以使用 Tab 键补全来列出所有环境变量。
-
对
PATH环境变量执行Get-Member来查找其类型。 -
在更改环境变量之前,使用
$env:PATH | Get-Member列出PATH变量。它是一个字符串。请将内容保存在安全的地方。 -
现在,通过将
sqlcmd工具的可执行文件路径添加到$env:PATH变量中,更新PATH环境变量。此操作只是将一个字符串附加到现有值中。
你可以将任何位置添加到 PATH。如果你坚持获取 sqlcmd,请访问 Microsoft 文档,了解如何获取 SQL Server 2017 的指南。
PS> $env:PATH = $env:PATH + ':/opt/mssql-tools/bin'
PS /root> sqlcmd
上面代码块的输出如下图所示:

- 让我们退出当前会话,启动一个新会话,检查
PATH变量,并运行sqlcmd可执行文件。
PS> sqlcmd
sqlcmd : The term 'sqlcmd' is not recognized as the name of a cmdlet, function, script file, or operable program.
它是如何工作的...
如同我们在 列出 PowerShell 中的各种提供者 中看到的那样,Env 是一个包含环境特定配置的提供者。这些配置选项大多也以环境变量的形式暴露给我们。我们可以使用 cmdlet Get-ChildItem 和 Get-Item 列出可用环境变量的值,因为 Env: 是 Env 提供者中的一个驱动器。由于环境变量没有子项,Get-Item 和 Get-ChildItem 返回相同的输出。
由于这些变量决定了你的会话如何与系统交互,因此对这些值所做的更改是暂时性的。换句话说,$env:PATH 的值是从你 Linux 计算机上的配置中获取的。在 PowerShell 会话中手动更改 $env:PATH 的值,只要 PowerShell 会话处于活动状态,该更改就会生效。但对 $env:PATH 的更改并不会改变系统本身的值。
如果你希望某些更改是永久性的,可以编辑 .bashrc 或 ~/.bash_profile 配置文件。如果你希望使用 PowerShell 而不是 Bash,请在 PowerShell 配置文件中进行更改。有关如何操作的说明,请参见章节 使用 PowerShell 为管理做准备 中的配方 启用每次加载时自动执行命令。
还有更多...
你还可以使用 .NET 类型加速器及其方法来访问环境变量。
PS> [environment]::GetEnvironmentVariable("PATH")
另见
-
关于环境变量:
Get-Help about_Environment_Variables -
配方 1.11:列出 PowerShell 中的各种提供者
-
配方 3.4:启用每次加载时自动执行命令
存储 .NET Core 对象实例的输出
.NET 是面向对象的,并且基于类和对象工作。PowerShell 作为该框架的扩展,允许你与 .NET 框架和 COM 接口一起工作,从而执行许多系统管理任务。这样,你不仅限于使用 cmdlet 执行的任务。
在本配方中,我们将在 PowerShell 中定义一个简单的类。
准备工作
我们建议在这个食谱中使用 Visual Studio Code。要了解如何安装和配置它,请访问本章的食谱 安装 Visual Studio Code,该章节在 使用 PowerShell 准备管理工作 中。
如何操作...
启动 Visual Studio Code 并处理它要求的任何即时需求。
-
在 Visual Studio Code 中打开一个新文件,并设置文件类型为 PowerShell。
-
在脚本窗格中输入以下内容。文件不需要保存。
class Person {
[string]$Name
[Int32]$Age
[int32]$Salary
Person () {}
Person ([string]$Name, [int32]$Age) {
$this.Name = $Name
$this.Age = $Age
}
[int32] sal ([int32]$Salary, [int32]$Comm) {
return $Salary * $Comm
}
}
- 要查找类的构造函数,请调用静态方法
New;在提示符下输入以下内容。
PS> [Person]::New
- 这样我们就有了两个同名的函数;这些是重载定义。现在,通过传递参数来创建一个新对象。
PS> [Person]::New('Prashanth',34)

- 现在,让我们以 PowerShell 的方式调用构造函数,这会调用 .NET 的构造函数。这样会创建一个 Person 类的实例(根据定义,它是一个对象)。
PS> $Person = New-Object -TypeName Person -ArgumentList 'Prashanth', 34
- 要列出对象的属性(以及类的属性),可以运行
Get-Membercmdlet。
PS> $Person | Get-Member
PS> $Person | Select-Object Name, Age, Salary | Format-Table -AutoSize
PS> $Person.sal(200,2)

- 接下来,让我们看看其他示例。我们将使用系统定义的类库。
PS> $MailClient = New-Object -TypeName System.Net.Mail.SmtpClient 'packtpub.smtpdomain.com'
PS> $Message = New-Object System.Net.Mail.MailMessage('pjayaram@packtpub.com', 'pjayaram@packtpub.com', 'Subject', 'Welcome to Packt!')
PS> $MailClient.Send($Message)
- 让我们看看使用
System.Management.Automation类对象进行密码加密的另一个示例。
PS> $User = 'Prashanth'
PS> $Password = 'Y94b^E$85CBLU%at'
PS> $SecurePassword = ConvertTo-SecureString $Password -AsPlainText -Force
PS> $Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $SecurePassword
PS> $Credentials | Get-Member

它是如何工作的...
这个食谱的目标是演示 PowerShell 如何与 .NET 紧密配合工作,并且按照 .NET 方式 创建的对象可以轻松在 PowerShell 中调用(并存储)。首先,我们使用 class 关键字声明一个类。接着声明它的参数,然后创建两个构造函数:一个是默认构造函数,一个是带参数的构造函数。
我们还创建了一个名为 sal 的方法。我们定义了它的返回类型为 int32,并定义了两个参数。在其中,我们定义了需要返回的内容。
回到 PowerShell 提示符,我们调用类的静态方法(而非对象的方法)New。语法是先提及类名,然后是两个连续的冒号 (::),在全局命名空间中调用它,接着是方法名。
如果你按照步骤操作,你还会看到使用两个不同系统类库的例子,System.Net.Mail.SmtpClient 和 System.Net.Mail.MailMessage,并通过将 mail-message 的输出与 SMTP-client 对象结合来实例化一个方法。
接下来,我们使用系统类库 System.Management.Automation.PSCredential。在这个示例中,我们将一些文本发送给 ConvertTo-SecureString,它接受明文输入并将其转换为安全字符串。然后,我们将加密后的字符串传递给 PSCrendital 系统类,将用户名和安全字符串存储到 $Credentials 变量中。
另见
- 食谱 3.1:安装 Visual Studio Code
向对象添加自定义属性
PowerShell cmdlet 使管理员能够处理大多数任务。然而,也有一些情况,返回的对象不能满足某个脚本的管理需求。在这种情况下,我们可能需要根据现有的 .NET 类创建自己的自定义对象,或者至少为对象添加自定义属性。
虽然字符串是System.String类型的对象,Get-Process cmdlet 返回的是 System.Diagnostics.Process 类型的对象,Get-ChildItem 返回的是 System.IO.FileInfo 类型的对象,而自定义创建的对象的类型是 PSCustomObject。
如何做到这一点...
现在让我们继续创建一个自定义对象。
- 要创建自定义对象,请使用
New-Objectcmdlet。
PS /home/PacktPub> New-Object -TypeName PSCustomObject
- 这对我们帮助不大。让我们向其中添加几个属性及其值,并将这些值存储在一个变量中。
PS> $MyCustomObject = [pscustomobject]@{
Name = 'Prashanth Jayaram'
Title = 'PowerShell'
Publisher = 'Packt'
}
PS> $MyCustomObject
- 要向对象添加属性,请使用
Add-Membercmdlet。
PS> $MyCustomObject | Add-Member -MemberType NoteProperty -Name 'Location' -Value 'United States'
PS> $MyCustomObject
- 要从对象中移除属性,使用以下命令。
PS> $MyCustomObject.PsObject.Properties.Remove('Location')
PS> # To see if the property is still available, run:
PS> $MyCustomObject.Location
- 要访问属性,可以使用成员访问操作符 (
.)。
PS> $MyCustomObject.Name
Prashanth Jayaram
PS> $MyCustomObject.Title
PowerShell
PS> $MyCustomObject.Publication
Packt
它是如何工作的...
创建对象很简单。你所需要做的就是告诉 PowerShell 你正在创建一个自定义对象,通过调用[psobject]加速器并指定你想要添加到对象的属性。在调用加速器后,我们指定属性的名称,并为它们赋值。记录是逐个添加的,使用哈希文字符号表示法(Name = 'Prashanth')。
在此阶段,由于我们在创建对象时使用了哈希文字符号表示法,容易将哈希表与 PSObject 混淆。请记住,它们彼此之间非常不同。我们将在 数组与哈希表 这一章中深入讨论哈希表是什么。
我们在创建的自定义对象中提到了三个属性:Name、Title 和 Publisher。如果我们以后想添加更多属性,可以简单地使用 Add-Member cmdlet,在其中我们指定对象(通过管道传递对象),指定属性的名称,并为其赋值。
移除属性稍微复杂一些,因为我们没有 Remove-Member cmdlet,也没有我们在步骤中使用的成员容易被发现。例如,[PSCustomObject] | Get-Member 并不会揭示 PsObject 是它的成员。我们在 PSObject 对象的 Properties 对象中调用 Remove 方法,这是我们创建的自定义对象的一部分。(这么多对象!)
访问对象中的属性就像使用成员访问操作符并附上属性名称一样简单。
另见
- 配方:创建并初始化一个简单的哈希表
从返回的对象创建自定义对象
我们现在知道如何从零创建自定义对象。在这个方法中,我们将使用一个返回的对象,并修改它来创建一个自定义对象。当我们学习循环时,我们将扩展这个功能,创建更通用的自定义对象;在生产环境中,这一功能可以用来做更多的事情!
如何实现...
学习某个事物的第一步是将其简化。因此,我们将只选择一个对象实例并与之操作。
- 获取所有进程,只选择名称、ID、工作集和启动时间,选取列表中的第 5 个进程并将其分配给一个变量。
PS> $Process = (Get-Process | Select-Object Name, Id, WS, StartTime)[4]
- 假设我们不喜欢对象中属性的名称。让我们来更改这些名称。
PS> $CustomProcess = New-Object -TypeName PSObject -Property @{
ProcessName = $Process.Name
ProcessId = $Process.Id
WorkingSet = $Process.WS
StartedAt = $Process.StartTime
}
PS> $CustomProcess

- 但这不是我们指定的顺序。让我们按顺序获取它们。
PS> $CustomProcess = [ordered]@{
ProcessName = $Process.Name
ProcessId = $Process.Id
WorkingSet = $Process.WS
StartedAt = $Process.StartTime
}
PS> New-Object -TypeName PSObject -Property $CustomProcess

- 重新创建对象,展示进程运行了多长时间,而不是它何时启动。
PS> $CustomProcess = [ordered]@{
ProcessName = $Process.Name
ProcessId = $Process.Id
WorkingSet = $Process.WS
RunningMins = [math]::Floor(((Get-Date) - $Process.StartTime).TotalMinutes)
}
PS> New-Object -TypeName PsObject -Property $CustomProcess

它是如何工作的...
在这个方法中,我们从一个输出对象($Process)创建了一个自定义对象($CustomProcess)。可以使用成员访问运算符访问$Process中的属性。来自$Process的值作为$CustomObject中的属性值,但$CustomObject中的名称不同。在这个方法中,我们还使用了一个计算属性RunningMins。
这里需要注意的一个重要点是,这个方法的结果也可以通过使用计算属性和Select-Object来实现。然而,在对象的通用性很重要并且对象实例数量显著增多的情况下,使用自定义对象会更简单。我们将在未来的章节中扩展这一功能,在学习如何使用循环结构后。
另一个需要注意的点是使用[ordered]加速器。通常,自定义对象的属性不会按我们提到的顺序出现。在这个方法中,我们将哈希字面量表示法与自定义对象的创建分开。我们首先创建一个有序的哈希表,其中包含属性名称和属性值,然后,指定创建的哈希表作为New-Object cmdlet 的Property参数,来创建具有我们想要顺序的属性的自定义对象。
另见
- 创建 .NET 和 COM 对象(Microsoft 文档)
理解类型数据的扩展
在例子中,选择输出的列,我们使用了一个哈希表来设置自定义列的名称和表达式。后来,我们还在Select-Object语句中使用了一个小的计算来获取计算后的输出。如果你尝试通过新名称选择列,那也会起作用。从技术上讲,你已经扩展了对象。但是,类型数据到底是什么?为什么我们需要一个例子来扩展它,而不只是使用Select-Object呢?
深入探讨类型数据是什么以及如何与.NET 类和对象一起使用可能会让这个概念变得“高级”。历史上,我们大多数学习者一直拖延学习“高级”主题。因此,我们将坚持简单的部分,只与 PowerShell 一起工作。这个例子将作为一个启动平台,帮助你通过简化并让学习变得有趣的方式理解它,避免任何“高级”内容,以利于学习。
有两种方法可以扩展类型数据:
-
使用 PowerShell cmdlets(了解其工作原理)
-
使用 XML 文件(为了可移植性)
准备工作
为了理解我们在说什么,这里你需要先阅读以下例子:
-
从输出中选择列
-
从返回的对象创建一个自定义对象
让我们回到这个问题:为什么要扩展类型数据,而我们可以使用Select-Object呢?
效率。虽然你可以更改名称、添加计算并使用新名称引用计算后的属性,但该更改仅会在当前上下文中存在。如果你将其添加到变量中,就像在上一个例子中创建一个从返回对象生成的自定义对象那样,做了些巧妙的名称更改并创建了一个自定义的NoteProperty,那将是一条很长的路要走。于是,进入:类型数据的扩展。
经验法则是,如果你做某事很频繁,说明存在问题:你没有考虑自动化它。例如,如果你运行:
PS> Get-ChildItem -Path . | select Name, Length, @{Name='Age'; Expression={[math]::Round(((Get-Date) - $_.LastWriteTime).TotalDays)}}
而不是:
PS> Get-ChildItem -Path . | select Name, Length, Age
如果你一天做十五次,你应该考虑扩展对象,以便获得你关心的内容。
如何做…
导航到你为实验使用创建文件的位置。
- 输入以下命令:
PS> $FilesWithAge = Get-ChildItem . | Select-Object Name, Length, LastWriteTime
- 现在,为变量
$FilesWithAge添加一个属性;该属性应该是每个文件的年龄,单位为天。
PS> $FilesWithAge | Add-Member -MemberType ScriptProperty -Name Age -Value { [math]::Round(((Get-Date) - $this.LastWriteTime).TotalDays) }
- 为它添加另一个属性,命名为
ComputerName,即你的本地主机名。
PS> $ComputerName = hostname
PS> $FilesWithAge | Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName
- 添加另一个属性,作为
LastWriteTime的别名,命名为Modified。
PS> $FilesWithAge | Add-Member -MemberType AliasProperty -Name Modified -Value LastWriteTime
- 要将其格式化为一个漂亮的表格,请使用以下命令:
PS> $FilesWithAge | Format-Table -AutoSize

- 现在,删除变量,并查询当前目录中的文件(因为变量实际上保存的就是这些内容)。
PS> Remove-Variable FilesWithAge
PS> Get-ChildItem .
你看不到Age、ComputerName或Modified属性。如果你愿意,可以尝试使用Get-Member。
接下来,我们看看如何扩展类型数据本身,以便每次运行Get-ChildItem时,你也能得到我们添加到变量中的三个属性。
- 获取运行
Get-ChildItem时返回的对象。
PS> Get-ChildItem | Get-Member
- 你会得到
System.IO.DirectoryInfo以及System.IO.FileInfo。我们选择System.IO.FileInfo。运行以下命令。
PS> $ComputerName = hostname
PS> Update-TypeData -TypeName System.IO.FileInfo -MemberType NoteProperty -MemberName ComputerName -Value $ComputerName
PS> Update-TypeData -TypeName System.IO.FileInfo -MemberType AliasProperty -MemberName Modified -Value LastWriteTime
PS> Update-TypeData -TypeName System.IO.FileInfo -MemberType ScriptProperty -MemberName Age -Value { [math]::Round(((Get-Date) - $this.LastWriteTime).TotalDays) }
- 查询当前目录的内容,并可以选择将输出格式化为表格。
PS> Get-ChildItem . | Select-Object Name, Length, ComputerName, Age, Modified | Format-Table -AutoSize

- 对文件系统中的任何目录执行相同的操作,唯一的条件是目录应该包含至少一个文件,而不仅仅是更多的目录。
PS> Get-ChildItem ./cities/ | Select-Object Name, Length, ComputerName, Age, Modified | Format-Table -AutoSize
它是如何工作的……
在第一部分,我们向某个变量中的对象添加了成员。这是对创建自定义对象从返回的对象的命令行提示样式扩展,在这种情况下,效率稍微更高一些。然而,我们对对象所做的更改仅在变量$FilesWithAge有效时有效。通过Get-ChildItem返回的对象没有被修改。
这里有一个重要的要点需要注意:$this。我们在处理通过管道传递的对象时曾遇到过自动变量$_(或者在 PowerShell V3 之后是$PSItem);该变量保存管道中当前对象的实例。然而,当我们需要执行对象扩展时,我们使用自动变量$this。为什么?因为,被引用的属性是在父对象中被引用(即通过Get-ChildItem返回的对象)。外部方法可以使用$_。从某种意义上说,$_在执行成员添加时甚至还不存在。而且,$this实际上指的是对象本身,它是由Get-ChildItem返回的,而不仅仅是它的一个实例。
当我们希望将某个成员作为对象本身的一部分时,无论某个变量或对象实例的有效性如何,我们都会扩展类型数据本身。因此,无论在什么上下文中运行 cmdlet,你都会得到你添加的附加成员。当然,PowerShell 中的格式化规则仍然可能不会默认显示这些成员。你始终可以调用特定的成员,例如使用Select-Object获取属性,或者简单地使用成员访问操作符访问属性:(Get-ChildItem .).Age。
为此,我们使用Update-TypeData cmdlet。Update-TypeData在此上下文中需要TypeName,正如我们已经看到的,可以通过对返回对象使用Get-Member来确定。
我们在Update-TypeData语句中提到四个内容:
-
类型名称
-
成员类型
-
成员名称
-
成员值
成员类型接受多个值,我们使用三种:
-
AliasProperty,它只是指向对象中另一个成员的引用。新属性只是现有属性的另一个名称。因此,Value参数可以直接使用现有成员的名称。 -
NoteProperty,它是一个静态值。在我们的例子中,我们可以使用主机名作为静态值。 -
ScriptProperty,本质上是一个计算。我们计算上次修改日期和当前日期之间的时间跨度。这个计算是该成员的Value,并接受一个脚本块。
对对象的这种修改只要会话有效,就会生效;该更改在会话间不具有持久性。这将引导我们进入下一个食谱,即让对象修改在会话间保持有效。
另见
- 食谱 5.1:从输出中选择列
保持对象修改在会话间有效
在上一个食谱中,我们了解了类型数据扩展的内容,我们使用了 Update-TypeData cmdlet 来添加成员。然而,我们提到过,更新在会话有效期间是有效的。现在,有两种方法可以使类型数据在会话间保持有效:
-
使用 PowerShell 配置文件。
-
使用 XML 文件
PowerShell 配置文件是直接明了的。然而,通常情况下,类型数据扩展和格式化规则是作为 PowerShell 模块的一部分打包的。在这种情况下,将代码添加到配置文件并不特别有用。在这个食谱中,我们将编写一个简单的 XML(.ps1xml)文件,我们将加载它以扩展类型数据。
准备工作
重新启动你的 PowerShell 会话,以便丢弃自定义数据类型扩展。
如何操作…
我们需要一个 XML 文件。你可以使用 New-Item cmdlet 创建一个,或者简单地使用你喜欢的文本编辑器。我们在这个食谱中使用 Visual Studio Code。
-
打开文本编辑器,创建一个新的空文件,并将其保存为
CustomTypes.ps1xml。 -
将以下内容添加到 XML 文件中。确保不要更改大小写。
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>System.IO.FileInfo</Name>
<Members>
<AliasProperty>
<Name>Modified</Name>
<ReferencedMemberName>LastWriteTime</ReferencedMemberName>
</AliasProperty>
<ScriptProperty>
<Name>Age</Name>
<GetScriptBlock>[math]::Round(((Get-Date) - $this.LastWriteTime).TotalDays)</GetScriptBlock>
</ScriptProperty>
<NoteProperty>
<Name>ItemType</Name>
<Value>File</Value>
</NoteProperty>
</Members>
</Type>
</Types>
-
将文件保存在一个方便的位置。
-
回到终端(或 PowerShell 集成控制台),输入以下命令以使用 XML 更新类型数据。注意
PrependPath参数。
PS> Update-TypeData -PrependPath ~/Documents/code/github/powershell/chapter-06/CustomTypes.ps1xml
- 现在,列出你选择的任何目录中的所有文件。
Get-ChildItem . -File | Select-Object Name, Length, Age, Modified
你应该能看到你创建的新属性。

建议不要修改 $PSHome 目录中的 PS1XML 文件。这些文件由 Microsoft 数字签名,并且在升级或打补丁时可能会被替换为新版本。
工作原理…
自定义类型和格式主要在创建自定义模块时使用。管理员很少需要修改标准 PowerShell 模块的类型或格式。当你确实需要修改自定义类型或格式时,应该创建一个新的 PS1XML 文件;不要修改标准文件,因为它们是数字签名的,修改它们会破坏你的设置。
把这个 PS1XML 文件当作一个普通的 XML 文件。这里有一种更简单的方式来展示结构。记住,每个类型以及其中的每个成员,都必须有一个名称。
Types
-- Type
---- Members
------ [The custom properties and methods you define]
本质上,使用 XML 扩展类型与我们在上一个教程中执行类型扩展的方式没有太大区别,只不过这次使用了 XML 文件,使得设置更具可移植性。当我们开始创建自定义模块时,我们将会研究如何将类型与模块一起打包,到那时我们会详细探讨如何处理路径。目前,我们通过手动指定文件的确切路径来加载 XML。如果你希望在每次会话中加载这些自定义类型,你可以轻松地从个人配置文件中调用 PS1XML。这里的可移植性在于,XML 可以轻松共享或部署;加载过程将是手动的,或者通过个人配置文件加载——比将 cmdlet 添加到配置文件中要简单得多。
当使用 cmdlet 更新类型数据时,我们使用了 Name 和 Value 参数,以及 MemberType 参数。PowerShell 会理解上下文并相应地设置类型。然而,对于 XML,必须记住为每个成员类型使用正确的标签。例如,对于 AliasProperty,标签应该是 Name 和 ReferencedMemberName(如下所示);对于 ScriptProperty,标签应该是 Name 和 GetScriptBlock;对于 NoteProperty,标签应该是 Name 和 Value。
<AliasProperty>
<Name>Modified</Name>
<ReferencedMemberName>LastWriteTime</ReferencedMemberName>
</AliasProperty>
另外,记住在将语句放入 GetScriptBlock 标签时,不要 用大括号将整个脚本块括起来:
<GetScriptBlock>
[math]::Round(((Get-Date) - $this.LastWriteTime).TotalDays)
</GetScriptBlock>
和不是:
<GetScriptBlock>
{ [math]::Round(((Get-Date) - $this.LastWriteTime).TotalDays) }
</GetScriptBlock>
在加载 XML 时,我们使用 PrependPath 参数将 XML 加载到内置类型之前。如果希望在内置类型之后加载它们,则无需使用 AppendPath 参数,除非实际情况需要,因为 AppendPath 是默认值。为什么参数很重要?它们决定了类型加载的优先级。
显示的 XML 文件可能看起来像有大量的项目。可以使用 Visual Studio Code 中的缩进指南帮助你阅读 XML。实际上,该文件非常易于阅读。阅读它将帮助你理解属性是如何定义的。
另请参见
-
Update-TypeData cmdlet(Microsoft 文档)
-
Types.ps1xml 文件(Microsoft 文档)
删除自定义类型数据
现在我们已经知道如何创建和更新类型数据,接下来的步骤是学习如何删除类型数据。删除类型数据需要先获取类型数据。在本教程中,我们将学习删除类型数据的过程;无论它是通过 cmdlet 还是 XML 更新的。
如何操作...
第一步是理解一个 cmdlet 可以输出一个或多个类型的对象。例如,在我们的案例中,Get-ChildItem 输出了 System.IO.DirectoryInfo 以及 System.IO.FileInfo。让我们学习如何处理这些对象。
- 获取 cmdlet 返回的对象类型。
PS> Get-ChildItem | Get-Member | Select-Object TypeName -Unique
- 返回了两种类型。我们在第二种类型中创建了自定义成员。将此分配给一个变量。
PS> $TypeData = Get-ChildItem | Get-Member | Select-Object -ExpandProperty TypeName -Unique -Last 1
- 获取类型数据信息。展开其成员,看看是否显示自定义成员。
PS> Get-TypeData -TypeName $TypeData | Select-Object -ExpandProperty Members

- 现在,移除类型数据。
PS> Remove-TypeData -TypeName $TypeData
工作原理如下……
过程很简单。如果查询 Remove-TypeData 的 TypeName 参数的帮助信息,你会注意到它接受类型名称,并通过管道按属性值以及属性名接受输入。回想一下配方,理解启用管道的参数,并查看由 Get-TypeData 返回的对象,以获取更多见解。
Remove-TypeData cmdlet 从当前会话中移除类型数据。此移除与添加类型的效果一样短暂。因此,默认类型和格式并未被该 cmdlet 永久移除;这就是你可以在 PowerShell 中运行它而无需管理员权限的原因。同时,请记住底层 XML 文件 也未被删除:无论是自定义的还是存储的。
另请参阅
- 配方 5.8:理解启用管道的参数
这结束了本章。现在是时候休息一下,这样可以帮助您消化所学的知识。稍后,尝试不同的数据类型,创建变量,看看它们包含什么类型的对象以及每个对象包含哪些成员。在本章中,我们并没有明确讨论变量,除了环境变量之外,但我们几乎在每个配方中都使用了变量。思考(或更好地,实际尝试)以下问题:
-
可以分配什么给变量?
-
变量包含什么内容;整个对象吗?
-
如果变量确实包含整个对象,我是否可以引用该对象的一个单一属性?
-
是否可以将某个成员分配给变量,而不是整个对象?
-
如果成员本身就是一个完整的对象怎么办?(提示:对变量使用
Get-Member查看。) -
当我使用
Select-Object和-ExpandProperty并将值分配给变量时会发生什么?那时变量的类型是什么?
这将帮助您更好地理解变量,因为您已经如此频繁地使用它们。
第七章:使用分支和循环的流程控制
在本章中,我们涵盖以下主题:
-
使用 If–ElseIf–Else 条件来控制脚本流程
-
使用 Switch–Case 条件来控制脚本流程
-
学习使用延迟
-
编写基本的循环结构
-
在预定义数组上编写更复杂的循环
-
使用 For 循环结构
-
使用 While 循环结构
-
使用 Do–While 结构清理空目录
-
使用 Do–Until 结构清理空目录
简介
就像处理字符串一样,这种流程控制对我来说也是 PowerShell 中喜欢的部分。
大多数 Linux 管理员和 Bash 脚本编写者可能已经熟悉循环和分支的概念,涵盖了这些技术。我们唯一会看到的主要区别是我们将处理对象,并且脚本编写的过程更加可读、流畅。
使用 If–ElseIf–Else 条件来控制脚本流程
我们已经看到了操作符如何工作。尝试更多的操作符,直到它几乎成为您的第二天性。大多数脚本中的流程控制和过滤都是使用这些操作符进行的。
在这个技巧中,我们将看看条件脚本流程,也称为分支。基本上,分支根据两个条件(正确:$true 和 $false)工作。通常使用两种结构来实现:
-
If–ElseIf–Else 结构
-
Switch–Case 结构
在这个技巧中,我们将给自己提出一个简单的问题来解决:找出今天是否是周末。
准备工作
这些技术要求您尝试以理解它们的工作原理。这些在集成脚本环境(最好是 Visual Studio Code)中比在提示符中更有效。要在 VS Code 中运行脚本,请使用 F5 键。此外,这些技术在 Windows PowerShell 3.0+ 中也应该完全相同:这些结构的语法在 PowerShell 和 Windows PowerShell 中完全相同。
如何做到...
在 Visual Studio Code 中打开一个新的 PowerShell 文件。
- 在代码窗格中输入以下内容,以简单查找是否为周末。
$Date = Get-Date
if ($Date.DayOfWeek -in 'Saturday', 'Sunday') {
Write-Host 'We party on weekends!' -BackgroundColor Yellow -ForegroundColor Black
}
- 如果您想在工作日也获得输出:
$Date = Get-Date
if ($Date.DayOfWeek -in 'Saturday', 'Sunday') {
Write-Host 'It is a weekend!'
}
else {
Write-Host 'It is a weekday.'
}
- 如果您想稍微添点料,并使用整个
if–elseif–else:
$Date = Get-Date
if ($Date.DayOfWeek -in 'Saturday', 'Sunday') {
Write-Host 'We party on weekends!' -BackgroundColor Yellow -ForegroundColor Black
}
elseif ($Date.DayOfWeek -eq 'Wednesday') {
Write-Host 'Half the week is over, and I want to do so much more!'
}
else {
Write-Host 'Work is worship. Ahem!'
}
一般来说,Write-Output 是一个比 Write-Host 更好的 cmdlet 使用。Write-Host 只将输出写入主机;这个输出不能在不进行操作的情况下发送给其他地方。仅在需要时使用 Write-Host。
它的工作原理...
简单的分支可以仅使用 If 语句。该语句检查真值,并根据条件结果执行代码块。在只有两个条件的情况下,使用 If–Else。当存在超过两种可能结果时,请使用 If–ElseIf–Else。else 块是捕捉所有的部分。
使用 Switch–Case 条件来控制脚本流程
If–ElseIf–Else 适用于当你有按类别划分的结果时(例如工作日是五天,周末是两天)。当结果是具体的和/或数量过多时,使用 If–ElseIf–Else 结构可能会有些繁琐。为了更好地理解这一点,让我们假设一个场景。
你工作场所的着装规范由一个小孩决定,她喜欢每个人穿上彩虹的颜色。由于她不喜欢任何人在周末工作,不太喜欢橙色,而且非常讨厌黄色,她想出了以下方案(她还不知道“星期一忧郁症”):星期一穿红色,星期二穿紫色,星期三穿靛蓝色,星期四穿蓝色,星期五穿绿色,周末穿橙色。
如何实现...
这种情况需要通过一整套 If–ElseIf–Else 语句来编程。处理这种情况的更高效方法是使用 Switch–Case 结构。
- 打开一个新的 PowerShell 文件并添加以下内容:
$Date = Get-Date
switch ($Date.DayOfWeek) {
'Monday' { Write-Output 'Red' }
'Tuesday' { Write-Output 'Violet' }
'Wednesday' { Write-Output 'Indigo' }
'Thursday' { Write-Output 'Blue' }
'Friday' { Write-Output 'Green' }
Default { Write-Output 'Orange' }
}
- 如果你更愿意省略
Default并使用通配符来匹配周末:
$Date = Get-Date
switch -Wildcard ($Date.DayOfWeek) {
'Monday' { Write-Output 'Red' }
'Tuesday' { Write-Output 'Violet' }
'Wednesday' { Write-Output 'Indigo' }
'Thursday' { Write-Output 'Blue' }
'Friday' { Write-Output 'Green' }
'S*' { Write-Output 'Orange' }
}
如何工作...
正如我们所看到的,在这种情况下,编写、执行以及排查 Switch–Case 结构的故障比编写一系列 if–elseif–elseif–elseif–...–else 语句更高效。要在 Switch–Case 中定义一个通用操作,请使用Default。
Switch–Case 结构也支持通配符(-Wildcard)和正则表达式(-Regex)。通常来说,除非所有可能的结果都已涵盖,否则省略Default不是一个好习惯。
Switch–Case 的工作原理很简单:它将条件与定义中的所有值进行比较,每当它找到一个条件为真时,就执行相应的脚本块。因此,如果五个条件中有三个条件满足,则这三个脚本块会被执行。这与 If–ElseIf–Else 稍有不同,后者在遇到第一个$true时就会退出分支结构并执行脚本块。因此,如果你只希望构造出一个结果,请考虑在每个脚本块中添加break关键字,以便在遇到$true时立即退出结构。例如:
...
'Monday' {
Write-Output 'Red'
break
}
...
当然,在这种特定情况下,没有必要这样做,因为结果是独占的。
学会使用延迟
PowerShell 通常会一个接一个地运行语句;当前语句必须返回缓冲区(可以这么说),下一个步骤才能开始执行。然而,在某些情况下,你仍然需要等待;也就是说,缓冲区可能会在期望的结果达成之前就已经返回。
虽然设置这种情况可能需要一些操作,但为了简化起见,我们可以假设我们需要五秒钟来注册今天是星期几(当然,不相信的话可以这么想),然后才会被告知穿什么衣服。
如何实现...
让我们将之前食谱中的两个脚本结合起来,使用条件来控制脚本流程。
- 打开一个新文件并键入以下内容:
$Date = Get-Date
if ($Date.DayOfWeek -in 'Saturday', 'Sunday') {
Write-Host 'We party on weekends!' -BackgroundColor Yellow -ForegroundColor Black
}
elseif ($Date.DayOfWeek -eq 'Wednesday') {
Write-Host 'Half the week is over, and I want to do so much more!'
}
else {
Write-Host 'Work is worship. Ahem!'
}
Start-Sleep -Seconds 5
$Date = Get-Date
switch ($Date.DayOfWeek) {
'Monday' { Write-Output 'Wear red.'; break }
'Tuesday' { Write-Output 'Wear violet.'; break }
'Wednesday' { Write-Output 'Wear indigo.'; break }
'Thursday' { Write-Output 'Wear blue.'; break }
'Friday' { Write-Output 'Wear green.'; break }
Default { Write-Output 'Poor you, working today. Wear orange.'; break }
}

它是如何工作的...
之所以这是一个单独的示例,是因为它属于流控制的一部分,并且在某些特定情况下非常有用。Start-Sleep cmdlet 接受 Seconds 或 Milliseconds 作为输入,并在处理下一个指令之前等待指定的时间。
编写一个基本的循环结构
如果没有循环,自动化可能只有一半完成。毕竟,在大多数情况下,自动化的意义在于让计算机做那些重复的工作。PowerShell 中一共有六种循环结构:
-
使用
Foreach-Object循环 -
Foreach 循环
-
For 循环
-
While 循环
-
Do–While 循环
-
Do-Until 循环
Foreach-Object 循环结构可能是最简单的一种。
假设我们有五位宾客参加某个活动,并且你想分别向他们打招呼。
如何操作
假设宾客分别是 Mr Jain、Mr Jacobs、Ms Sanders、Mr Shah 和 Mr Hugo。
- 在 Visual Studio Code 中打开一个新文件,在脚本窗格中输入以下内容。
$GuestsRaw = Read-Host "Enter the guest names, separated by commas"
$Guests = $GuestsRaw -split ",$([regex]'[\s]*')"
$Guests | ForEach-Object { Write-Output "Welcome, $PSItem!" }
- 运行脚本,在提示符下输入宾客的姓名:
PS> Mr Jain, Mr Jacobs, Ms Sanders, Mr Shah, Mr Hugo
它是如何工作的...
首先通过主机的提示符接收输入。原始的宾客名单被接收。PowerShell 然后根据逗号和正则表达式匹配分割输入字符串(这样可以保证无论输入后是否有空格,它都能工作:并不是所有用户都一样)。我们使用 -split 和类型转换操作符来完成此操作。
循环部分出现在 Foreach-Object。你将数组对象传入管道。Foreach-Object cmdlet 会处理数组,并一次处理一个元素。在循环中引用元素时,你使用 $PSItem(即 $_)自动变量,因为对象已经通过管道传递。
在预定义数组上编写更复杂的循环
在上一个示例中,编写基本的循环结构时,我们通过管道传递内容编写了一个简单的循环。现在,假设我们有这些相同的宾客,但已经为他们分配了特定的座位。我们想要向宾客展示座位。你将得到以下 CSV 格式的表格。
| 姓名 | 座位 |
|---|---|
| Mr Jain | A-12 |
| Mr Jacobs | C-28 |
| Ms Sanders | B-17 |
| Mr Shah | M-22 |
| Mr Hugo | E-08 |
如何操作...
这里的假设是每行有四十个座位,过道通过大厅的中心。
- 如果你还没有克隆这个仓库,你可以自己创建一个 CSV 文件。你可以从下面复制以逗号分隔的内容,或者使用 PowerShell 创建一个 CSV。
PS> @'
Name,Seat
Mr Jain,A-12
Mr Jacobs,C-28
Ms Sanders,B-17
Mr Shah,M-22
Mr Hugo,E-08
'@ | Out-File -Path './chapter-08/05-Write-GuestSeatDetails.csv'
- 在 Visual Studio Code 中打开一个新的 PowerShell 文件,并输入以下脚本:
$Guests = Import-Csv './chapter-08/05-Write-GuestSeatDetails.csv'
foreach ($Guest in $Guests) {
$RowIdentifier = [byte]char[0].ToUpper())
$RowNumber = ($RowIdentifier - 64).ToString()
switch -Regex ($RowNumber) {
'1(1|2|3)$' { $RowNumber += 'th'; break }
'.?1$' { $RowNumber += 'st'; break }
'.?2$' { $RowNumber += 'nd'; break }
'.?3$' { $RowNumber += 'rd'; break }
Default { $RowNumber += 'th'; break }
}
$SeatNumber = ($Guest.Seat -split "-")[1]
if ($SeatNumber -gt 20) {
$Side = 'right'
}
else {
$Side = 'left'
}
Start-Sleep -Seconds 1
Write-Host "Welcome, $($Guest.Name)! " -NoNewline
Start-Sleep -Seconds 1
Write-Host "Your seat is in the $RowNumber row, to the $Side the aisle."
}
- 运行脚本。

它是如何工作的...
这里的主要思想是使用 foreach。在使用 Foreach 循环结构时,你用一个变量引用每个元素;在这种情况下,变量不带有索引。对于我们的每次迭代,$Guest 从 $Guests 中选择一个值,按顺序进行,以便可以使用 $Guest 引用当前元素。
座位号有一个模式:一个字母行标识符,后跟一个连字符,然后是数字座位标识符。
座位号是一个字符串。我们在 - 处拆分它,选取结果数组中的第一个元素,将其转换为大写(如果它不是),将这个字母转换为字符,然后使用 [byte] 强制转换运算符找到其 ASCII 标识符。是的,我们从中减去 64(所以 A 变成 1)。
为了能够在 switch-case 中使用数字而不出错,我们将数字转换回字符串。我们使数字适合在句子中使用。我们取数组的第二部分(数字部分),以查看座位位于哪一侧的过道。我们将所有这些组合起来,得到最终的问候和指导字符串,这些字符串在每秒延迟后显示一次。
使用 For 循环结构
Foreach 循环和 For 循环的不同之处在于后者使用索引执行操作。我们需要一个变量来控制流程,其值形成一个范围。让我们重新实现与在预定义数组上编写更复杂的循环相同的解决方案,但使用 For 循环结构而不是 Foreach 循环。
如何做...
所做的假设与在预定义数组上编写更复杂的循环中的假设相同。
- 打开一个新文件,并将以下内容粘贴到其中。
$Guests = Import-Csv './chapter-08/05-Write-GuestSeatDetails.csv'
for ($CurrentGuest = 0; $CurrentGuest -lt $Guests.Length; $CurrentGuest++) {
$Guest = $Guests[$CurrentGuest]
$RowIdentifier = [byte]char[0].ToUpper())
$RowNumber = ($RowIdentifier - 64).ToString()
switch -Regex ($RowNumber) {
'1(1|2|3)$' { $RowNumber += 'th'; break }
'.?1$' { $RowNumber += 'st'; break }
'.?2$' { $RowNumber += 'nd'; break }
'.?3$' { $RowNumber += 'rd'; break }
Default { $RowNumber += 'th'; break }
}
$SeatNumber = ($Guest.Seat -split "-")[1]
if ($SeatNumber -gt 20) {
$Side = 'right'
}
else {
$Side = 'left'
}
Start-Sleep -Seconds 1
Write-Host "Welcome, $($Guest.Name)! " -NoNewline # Subexpression `$Guest.Name` to be computed first.
Start-Sleep -Seconds 1
Write-Host "Your seat is in the $RowNumber row, to the $Side the aisle."
}
- 运行脚本。
它的工作原理...
你们中的一些人可能会指出,在这种情况下,foreach 结构比 for 结构更简单,你们是对的。在我担任管理员期间,过去三年里我可能少于十次使用了 for 结构,而使用了 foreach 数百次。For 结构有其自己的用途;在预定义数组中使用是效率较低的之一。
不管怎样,for 在对象为数组时都可以工作。$CurrentGuest 是我们使用的索引,其范围从 0 到 $Guests 的总元素数减 1。在这种情况下,循环从索引为 0 开始初始化,并且在脚本块的每次迭代后递增索引($CurrentGuest++)。
for 比 foreach 更有效的一个操作是,如果每个交替访客都必须被问候(粗鲁,我知道)。在这种情况下,for 定义的第三部分将是 $CurrentGuest += 2。
$CurrentGuest + 2 而不是 $CurrentGuest += 2 会使这个循环无限循环,因为 $CurrentGuest 的值根本不会改变。
使用 While 循环结构
在这个教程中,我们将创建两个脚本:第一个用于建立 For 和 While 之间的相似性,另一个用于为理解 Do–While 和 Do–Until 结构打下基础。这样,学习将是渐进的,理解也会更加容易。
对于第一个脚本,场景与上一个教程中描述的使用 For 循环结构相同。对于第二个任务,目标是输入年份并找到该年份的母亲节日期。
如何操作
首先,继续使用你在上一个教程中的假设。
- 打开一个新文件,将以下内容粘贴到其中。
$Guests = Import-Csv './chapter-08/05-Write-GuestSeatDetails.csv'
$CurrentGuest = 0
while ($CurrentGuest -lt $Guests.Length) {
$Guest = $Guests[$CurrentGuest]
$RowIdentifier = [byte]char[0].ToUpper())
$RowNumber = ($RowIdentifier - 64).ToString()
switch -Regex ($RowNumber) {
'1(1|2|3)$' { $RowNumber += 'th'; break }
'.?1$' { $RowNumber += 'st'; break }
'.?2$' { $RowNumber += 'nd'; break }
'.?3$' { $RowNumber += 'rd'; break }
Default { $RowNumber += 'th'; break }
}
$SeatNumber = ($Guest.Seat -split "-")[1]
if ($SeatNumber -gt 20) { $Side = 'right' }
else { $Side = 'left' }
Start-Sleep -Seconds 1
Write-Host "Welcome, $($Guest.Name)! " -NoNewline
Start-Sleep -Seconds 1
Write-Host "Your seat is in the $RowNumber row, to the $Side the aisle."
$CurrentGuest++
}
-
运行脚本;输出结果应该与上一个教程的结果相同。
-
现在,创建一个新的 PowerShell 文件并添加以下代码:
$Year = Read-Host "Enter the year (YYYY) you would like to find Mothers’ Day for"
$CurrentDay = Get-Date "01 May $Year"
while ($CurrentDay.DayOfWeek -ne 'Sunday') {
$CurrentDay = $CurrentDay.AddDays(1)
}
$MothersDay = $CurrentDay.AddDays(7)
Write-Output "Mothers’ Day falls on $($MothersDay.ToLongDateString())."
- 运行脚本。输入任何年份,你应该能得到该年份母亲节的日期。
工作原理...
每个有限循环结构需要三样东西:
-
起始点
-
停止点
-
前进到停止点的方式
对于Foreach结构,整个逻辑是预先编写好的。它获取整个数组,找到起始点和停止点,并一步一步地向停止点移动。For结构则在循环声明语句中声明了这些参数,每个参数用分号分隔。
While循环没有太大不同。它的起始点定义在循环外部,停止点是传递的唯一条件,索引上的操作在循环内部指定。这三个参数在脚本中已经突出了。初始化应该在循环外发生,这样它在循环过程中就不会被修改。对索引的操作应该在每次迭代时发生,这样循环才会朝某个方向移动。因此,这个操作应当在循环内部。
对while循环要稍加小心;你更容易遇到无限循环,因为很容易忘记添加起始点或向停止点移动的操作。
现在,来看看母亲节。我们首先获取该年份 5 月 1 日的日期对象,这样就可以通过它的成员进行操作。这个日期就是起始点。然后,我们指定条件,“如果不是星期天,继续循环。”每次迭代时,我们加一天。每次迭代后,变量会与条件进行比较。最终,当第一个星期天到达时,变量将跳出循环。然后我们再加上一周,找到第二个星期天,并输出$MothersDay的长日期格式。
使用 Do–While 结构清理空目录
在上一个教程中,我们通过输入年份找到了五月的第二个星期天。我们使用了While循环。While循环在开始迭代前会检查条件。如果条件在一开始就返回false,循环就不会开始;例如,如果输入的年份是2016。
Do–While 有些不同;无论条件是否为真,循环都会执行一次。条件检查仅在第一次迭代 之后 进行。
本配方的场景是我们想要删除某个目录下的所有空目录。
准备工作
为了与这个配方一起使用,让我们在 $HOME/random 中创建一些空目录。使用本书的 Git 仓库中的输入文件来创建这些目录。
PS> Get-Content ./chapter-08/08-input-file.txt | ForEach-Object { New-Item $($PSItem -replace '\.', "$HOME/random") -ItemType Directory }
如何操作…
如果你在 random 实验目录中运行以下配方,所有其空的子目录将被删除。
- 打开一个新文件,并将以下内容粘贴到其中。
do {
$AllDirectories = (Get-ChildItem -Path $HOME/random -Recurse -Directory).FullName
$EmptyDirectories = $AllDirectories | Where-Object {(Get-ChildItem $PSItem).Count -eq 0}
$EmptyDirectories | Remove-Item
} while ($EmptyDirectories.Count -gt 0)
- 让我们添加一些日志记录,这样我们就能知道发生了什么。
$Iteration = 0
do {
$AllDirectories = (Get-ChildItem -Path $HOME/random -Recurse -Directory).FullName
$EmptyDirectories = $AllDirectories | Where-Object {(Get-ChildItem $PSItem).Count -eq 0}
$EmptyDirectories | Remove-Item
"Iteration $Iteration. Removed the following $($EmptyDirectories.Count) directories."
$EmptyDirectories
$Iteration++
} while ($EmptyDirectories.Count -gt 0)

它是如何工作的…
执行方式类似于 While 构造,然而,正如 Iteration 0 所示,Do–While 构造会在不检查条件的情况下先执行一次脚本块。在我们的例子中,我们在循环内查询了空目录。如果我们将查询放在循环外面,我们也必须将其放入循环中进行控制,这样的做法效率较低。
PowerShell 在执行脚本块时找到了九个空目录。当条件检查在执行后发生时,9 被评估为大于 0。于是循环再次执行。这一次,$EmptyDirectories.Count 的值被评估为 4。循环再次执行,这一次仍然是 4,大于 0;条件仍然是 $true。在下一次运行时,$EmptyDirectories.Count 被评估为 2,然后是 1,最后在最后一次运行时为 0。此时,条件的结果变为 $false,循环退出。本质上,循环语句是,“当计数大于零时,继续 执行。”
另见
- 配方 5.7:对返回的对象采取行动
使用 Do–Until 构造清理空目录
很容易混淆 Do–While 和 Do–Until,因为它们有很多相似之处。然而,它们之间的区别实际上是相当明确的。Do–While 循环只要条件检查的结果是 $true 就会执行,条件一旦变为 $false 就会退出。而 Do–Until 则正好相反:循环会持续执行,直到条件检查返回 $true,才会停止。
让我们使用相同的清理空文件夹的场景,但这次使用 Do–Until 循环。
如何操作…
你需要重新运行命令来创建那些空目录。
- 打开一个新文件,并将以下内容粘贴到其中。
$Iteration = 0
do {
$AllDirectories = (Get-ChildItem -Path $HOME/random -Recurse -Directory).FullName
$EmptyDirectories = $AllDirectories | Where-Object {(Get-ChildItem $PSItem).Count -eq 0}
$EmptyDirectories | Remove-Item
$Count = $EmptyDirectories.Count
"Iteration $Iteration`nRemoved the following $Count directories. '$Count = 0' is $($Count -eq 0)"
$EmptyDirectories
$Iteration++
} until ($Count -eq 0)
- 注意这次行为的变化,并与 Do–While 的运行进行比较。

它是如何工作的…
这一次,在交互式输出中,它的工作方式变得非常清楚。第一次运行时没有检查条件。在运行结束时,条件被检查。9 大于 0,并且不相等。结果是 $false,因此循环继续。这样持续进行,直到计数降到 0(使条件的结果为 $true),此时循环退出。本质上,循环语句是“继续直到计数变成零”。
这就是我们关于流程控制章节的内容。我希望你在学习这些技巧时度过了一段愉快的时光,并且这一章能帮助你在学会使用 PowerShell 后完成你希望进行的自动化工作。
第八章:字符串操作
敬请期待…
第九章:执行计算
在本章中,我们讨论以下主题:
-
执行算术运算
-
对输出进行计算
-
使用行政常量
-
使用计算属性
-
使用二进制数字
-
执行进制转换
技术要求
以下是本章工作的技术要求。
-
计算机上安装 PowerShell。请参考《安装、参考和帮助》中的步骤。
-
Visual Studio Code(推荐)。请参考章节《使用 PowerShell 准备管理》中的配方《安装 Visual Studio Code》。
-
一些用于配方的临时文件。请从书籍 GitHub 仓库中的
ch05目录运行脚本Initialize-PacktPs6CoreLinuxLab.ps1。
本章中使用的脚本可在以下位置找到:github.com/PacktPublishing/PowerShell-6.0-Linux-Administration-Cookbook/tree/master/ch09。
介绍
执行计算是自动化的一个关键部分。当然,PowerShell 可以实现这一点;它还通过提供管理员所谓的行政常量来提升操作级别,从而简化计算。我们将在接下来的配方中讨论这些概念。我们将首先介绍常见的算术运算,然后使用计算属性输出的概念,正如我们在通过管道传递对象中所看到的那样。
我们还将讨论如何使用二进制数字简化自动化,通过标志识别、进行进制转换,并最终使用一些 .NET 加速器/强制转换运算符来简化脚本编写。
执行算术运算
作为管理员,我们很少使用像正弦、余弦、对数和指数等算术运算。然而,PowerShell 是可以进行这些运算的,因为它能够利用 .NET。通常,管理员可能会使用以下运算:Abs(绝对值)、Ceiling、Floor、Round 和 Truncate。在本配方中,我们将根据实际场景使用这三种方法。其他方法的使用方式非常相似。以下是我们的场景:
你有一个应用程序,它会在一天中生成日志。这些日志占用了大量空间。你希望清理掉 30 至 31 天前的日志文件。你不能使用双重条件进行比较。
创建一个函数,查找 30 天前的文件。你的脚本还应该显示清理了多少日志空间,四舍五入到最接近的兆字节。
准备工作
从书籍 GitHub 仓库的 ch05 目录中运行脚本 Initialize-PacktPs6CoreLinuxLab.ps1。这是为了让我们有一些文件可以操作。接下来,从仓库的 ch09 目录中运行脚本 Set-LastWriteTime.ps1。
如果你想指定一个不同于~/random的路径用于内容,可以在脚本的最后一行添加-Path '//your/custom/path'。请确保在两个脚本中都进行此修改。
如何操作……
导航到包含实验文件的目录;所有的操作都发生在这里。如果你在运行脚本时没有指定自定义的-Path,默认路径应该是~/random。
-
打开终端,使用代码编辑器(如
vi、nano或 VS Code),创建一个名为Clear-LogFiles.ps1的新文件。记住,CamelCase 只是一个约定。 -
输入以下内容。建议自己手动输入,而不是复制粘贴。
$Today = Get-Date
$TotalFileSize = 0
$FilesToDelete = Get-ChildItem . -Recurse -File | Where-Object {[math]::Floor(($Today - $_.LastWriteTime).TotalDays) -eq 30}
Write-Host "The following files will be deleted:"
Write-Host $FilesToDelete.FullName
foreach ($File in $FilesToDelete) {
$TotalFileSize += $File.Length
Remove-Item -Path $File -WhatIf
}
Write-Host "Total space cleared: $([math]::Round($TotalFileSize/[math]::Pow(1024, 2))) MB"
无需实际删除文件。如果你还是想删除文件,请移除第 12 行中的-WhatIf开关。
推荐使用 Visual Studio Code 来编写这个脚本,因为它的 IntelliSense 自动完成功能非常有帮助。你也可以直接在终端编写这个脚本,在这种情况下,必要时使用更简短的参数版本和 Tab 自动补全功能。
它是如何工作的……
[math]::加速器允许我们使用System.Math类的方法。Round()方法接受一个或两个参数;如果只有一个参数,四舍五入会发生到最接近的整数。可选的第二个参数指定小数点后的位数。
Pow()方法几乎不言自明:在我们的案例中是 1024²。Floor()方法将 30.00 的数字降低到小于 31 的数值,最终降至 30。
我们做了什么:
-
列出了要删除的文件:我们将
LastWriteTime与今天的日期进行比较,并对结果进行Floor操作,因为我们不允许使用-and运算符。 -
显示文件。
-
我们对每个文件执行了删除操作,同时将每个文件的大小加到
$TotalFileSize中。 -
最终,我们将
$TotalFileSize除以 1024²,并四舍五入到最接近的 MB。
另见
- System.Math 方法(Microsoft 文档)
对输出进行计算
在上一个例子中,我们对对象本身进行了巧妙的计算,并列出了 30 天前的文件。在这个过程中,我们还输出了要清理的空间总量。在这个例子中,我们将以 PowerShell 对象的形式输出这些信息,并且在输出过程中进行一个小的计算。以下是场景:
修改你在执行算术操作时创建的脚本,使其输出一个结构化的 PSCustomObject。输出应包含目录中的文件总数、30 天前的文件,以及清理的总空间量(无需进行计算)。另外,单独显示清理过程中释放的空间量,以 MB 为单位。
准备好
如果你没有实验文件或在前面的步骤中删除了文件,可以运行脚本Initialize-PacktPs6CoreLinuxLab.ps1,该脚本位于书籍 GitHub 仓库的ch05目录中。接着,运行位于ch09目录中的Set-LastWriteTime.ps1脚本。
如何实现
我们将简单地在前一个步骤的基础上进行改进,以节省时间和精力。前一个脚本中的更改已加粗。
- 打开你最喜欢的代码编辑器,将以下内容输入到文件中,并将文件保存为
ps1文件。
$Today = Get-Date
$TotalFileSize = 0
$AllFiles = Get-ChildItem . -Recurse -File
$FilesToDelete = $AllFiles | Where-Object {[math]::Floor(($Today - $_.LastWriteTime).TotalDays) -eq 30}
foreach ($File in $FilesToDelete) {
$TotalFileSize += $File.Length
Remove-Item -Path $File -WhatIf
}
New-Object -TypeName psobject -Property @{
TotalFiles = $AllFiles.Count
FilesToDelete = $FilesToDelete.Count
SpaceCleared = $TotalFileSize
}
- 调用 PowerShell 脚本。
& $HOME/Documents/code/github/powershell/ch09/02-Clear-LogFiles.ps1
这是输出的一部分。

- 现在,仅提取
SpaceCleared参数。
(& $HOME/Documents/code/github/powershell/ch09/02-Clear-LogFiles.ps1).SpaceCleared
注意参考SpaceCleared属性,并且在提示符之前的输出。

- 将输出除以 1024²,以获得 MB 为单位的值。
(& $HOME/Documents/code/github/powershell/ch09/02-Clear-LogFiles.ps1).SpaceCleared/[math]::Pow(1024, 2)
与上面类似;清理的总空间(单位:MB)。

原理
不仅是 cmdlet 和函数,脚本也会输出对象。在前面的步骤中,输出是一个字符串对象;在这里,它是一个PSCustomObject。这样,尽管从两个步骤中收集到的信息是相同的,我们仍然能够通过动态计算进一步处理输出。
还有更多
-
对
SpaceCleared执行Round()操作,以便将输出以 MB 为单位并保留两位小数。 -
显示被排除在删除之外的文件数量。
将脚本的输出对象赋值给一个变量:$FileCleanupInfo = (& //path/to/02-Clear-LogFiles.ps1),以便更轻松操作。
与管理常量的工作
可能让我们一些人感到困扰的一件事是将Length属性转换为 MB 是多么困难。我们不得不使用[math]::Pow(1024, 2)。简化前面步骤的输出。
同时,假设你在一块 250 GB 的 SSD 上加载了这些文件,并且你想查看清理了多少空间的百分比。
准备工作
如果你没有实验文件或在前面的步骤中删除了文件,可以运行脚本Initialize-PacktPs6CoreLinuxLab.ps1,该脚本位于书籍 GitHub 仓库的ch05目录中。接着,运行位于ch09目录中的Set-LastWriteTime.ps1脚本。
如何实现
我们将使用与前面步骤相同的脚本。
- 将前一步骤返回的对象赋值给一个变量。
$FileCleanupInfo = (& //path/to/02-Clear-LogFiles.ps1)
- 调用
SpaceCleared属性,并将其除以管理常量。
$FileCleanupInfo.SpaceCleared/1KB
$FileCleanupInfo.SpaceCleared/1MB
$FileCleanupInfo.SpaceCleared/1GB
注意结果不仅仅是将数字除以 1000 的幂。

- 显示从 250 GB SSD 清理了多少空间:
($FileCleanupInfo.SpaceCleared)/(250*1e9)*100
在我们的案例中几乎是一个可以忽略的小数字,但它就在这里。

原理
PowerShell 是为管理员设计的。考虑到我们定期依赖大量的文件操作,PowerShell 包含了管理员常量。这些常量代表 1024 的幂(1024¹,1024²,1024³ 等等)。
硬盘和闪存驱动器的制造商通常使用 1000 的幂来表示驱动器大小。在我们的例子中,我们使用科学计数法将驱动器的大小转换为字节。
使用计算属性
如果你已经阅读了 从输出中选择列 和 通过管道传递数据 的内容,可以跳过本例。此例的目的是为了提供上下文,特别是对于那些跳过了前面章节/示例的读者。
计算属性是另一种即时进行计算的方式。这里是一个场景:
你需要一份报告,列出某个目录中所有文件的文件名、最后修改日期、完整路径和以 MB 为单位的大小。
准备工作
如果你没有实验文件,或者删除了上一个例子中的文件,请从书籍 GitHub 仓库的 ch05 目录中运行脚本 Initialize-PacktPs6CoreLinuxLab.ps1。接着,从仓库的 ch09 目录中运行脚本 Set-LastWriteTime.ps1。
如何操作
这将是一个单行命令。
在终端中,输入以下内容并按 Enter 键。
Get-ChildItem -file -rec | Select-Object name, lastwritetime, @{n = 'Size'; e = {[math]::Round($_.Length/1MB, 3)}}
它是如何工作的
计算属性包含两个部分:属性名称和生成期望结果的表达式。从技术角度来看,它是一个哈希表,其中 n(或 Name)和 e(或 Expression)是两个名称-值对。Expression 本身是一个脚本块。当分解为行时,查询如下所示:
Get-ChildItem -File -Recurse | Select-Object Name, LastWriteTime, @{
Name = "Size"
Expression = {
[math]::Round($PSItem.Length/1MB, 3)
}
}
由于我们通过一行将 Name 和 Expression 分开,因此不再需要分号。
计算属性在不想创建新脚本的情况下非常有用,而是希望在终端本身获取一些信息,就像运行查询一样。Name 的值是属性名称,列中的值由表达式决定。
处理二进制数字
为了确保精确,我们将在本例中使用十六进制表示。如果你在电脑上处理过颜色,可能已经知道 RGB。三种原色的级别通常表示为从 0 到 255 的数字(24 位颜色)。你也许遇到过这些颜色的十六进制表示,特别是在处理 HTML/CSS 时。
这里的场景是编写一个简单的脚本,将任何给定的十六进制代码转换为十进制 RGB。
如何操作
我们假设输入可能带或不带 #(即 55bc9a 或 #55bc9a)。
- 打开一个新的 PowerShell 文件,并输入以下内容:
$Rgb = Read-Host "Enter the hexadecimal RGB value"
$TrimmedRgb = $Rgb.Substring($Rgb.Length - 6)
$R = $TrimmedRgb.Substring(0, 2)
$G = $TrimmedRgb.Substring(2, 2)
$B = $TrimmedRgb.Substring(4, 2)
"Here are the R, G and B levels for the supplied hex value:"
$R, $G, $B | ForEach-Object { int }
-
运行脚本。
-
输入任意有效的十六进制 RGB 值,带或不带前缀
#。按 Enter 键。
这里是一个示例。

它是如何工作的
首先,看看脚本是如何分离 R、G 和 B 值的。我们首先修剪十六进制字符串,以去除其中的 #(如果有)。我们需要字符串的最后六个字符。因此,我们让 PowerShell 从第 1 个字符开始截取子字符串(以防有 #),如果字符串有六个字符,则从第 0 个字符开始。接着,我们每次取两个字符,从修剪后的字符串的 0、2、4 开始。
转换发生在最后一行。首先,我们指示 PowerShell 使用类型转换运算符将输出结果设为整数。然后,我们告诉 PowerShell 该字符串是一个十六进制字符串,方法是给它加上 0x 前缀。
这是一种将十六进制转换为整数的简单方法。接下来,我们将查看其他几种转换方法。
执行进制转换
上一个示例是一个简单的转换,使用了类型转换运算符和字符串相加。接下来,我们将看看如何将一个整数转换为多个进制,比如八进制、十六进制和二进制字符串。
如何操作
输入将作为字符串处理。输出也将是字符串,但包含八进制、十六进制和二进制表示。我们将使用 .NET 加速器来实现这一点。
- 打开一个新的 PowerShell 文件,并输入以下内容:
$InputString = Read-Host "Enter an integer"
Write-Host "Octal representation: " -NoNewline
Write-Host "$([Convert]::ToString($InputString, 8))"
Write-Host "Hexadecimal representation: " -NoNewline
Write-Host "$([Convert]::ToString($InputString, 16))"
Write-Host "Binary representation: " -NoNewline
Write-Host "$([Convert]::ToString($InputString, 2))"
- 运行脚本并输入一个整数,即可获得它的八进制、十六进制和二进制表示。
不要忘记尝试负数。

它是如何工作的
这个示例利用了 [System.Convert] .NET 加速器。脚本的输入和输出都是字符串。
ToString() 方法接受的输入形式为 int64。当只传递一个参数时,整数会按原样输出,但对象类型不再是 int,而是 string。传递给方法的可选第二个参数是基数:2 代表十进制,8 代表八进制,16 代表十六进制。
还有更多内容
当我学习 PowerShell 时,我遇到了 Lee Holmes 提供的这篇示例,它展示了 PowerShell 中文件属性标志的工作方式。要查看 PowerShell 中文件和目录的可用属性,以及它们的十进制和二进制表示,可以在 PowerShell 提示符下输入以下内容。
PS> [Enum]::GetValues([System.IO.FileAttributes]) | Select-Object `
@{ n = 'Property'; e = { $_ } },
@{ n = 'Decimal'; e = { [int]$_ } },
@{ n = 'Binary'; e = { [Convert]::ToString([int]$_, 2) } }
这是该命令的输出结果。

本章到此结束。基本的算术计算已被省略,因为它们与任何常见的编程语言并无区别。
第十章:使用数组和哈希表
敬请期待...
第十一章:处理文件和目录
即将到来...
第十二章:构建脚本和函数
敬请期待...
第十三章:函数的高级概念
即将到来…
第十四章:深入探索:调试与错误处理
即将推出...
第十五章:与环境协作
敬请期待…
第十六章:PowerShell 中的安全性
即将推出...
第十七章:使用 PowerShell 进行企业管理
敬请期待...
第十八章:最佳实践汇总
敬请期待...
第十九章:PowerShell 和云操作
即将到来...
第二十章:使用 PowerShell 进行 SQL 数据库管理
即将推出...


浙公网安备 33010602011771号