Powershell进阶语(三)中哪些高级技巧和命令用法最实用?

摘要:目录PowerShell 管道管道输出控制管道输出的格式设置Format-ListFormat-TableFormat-Wide管道选择、排序和度量对象排序和分组Sort-ObjectFormat 的 GroupBy分组度量管道中的对象Se
目录PowerShell 管道管道输出控制管道输出的格式设置Format-ListFormat-TableFormat-Wide管道选择、排序和度量对象排序和分组Sort-ObjectFormat 的 GroupBy分组度量管道中的对象Select-ObjectUnique去重Property自定义属性并设置表达式与格式计算从管道中筛选对象比较运算符基本筛选器语法高级筛选器语法组合多个条件True 或 False 的属性筛选使用技巧高级筛选不受简单筛选限制优化筛选器性能枚举枚举管道对象语法写入到文件转换为其他形式的数据表示形式CsvXMLJsonHTML其他输出选项传递管道对象ByValue 传递数据ByPropertyName 传递数据展开属性值深入了解Powershell脚本开发生命周期与安全强化PowerShellGet 模块执行策略以数字方式对脚本进行签名进阶语法ForEach 循环If 构造Switch 构造For 构造其他循环构造Do..WhileDo..UntilWhileBreak 和 Continue导入数据Get-ContentImport-CsvImport-ClixmlConvertFrom-JsonInvoke-RestMethod接受用户输入Read-HostCredential凭证使用Get-CredentialExport-Clixml脚本故障排除与错误处理输出命令的层次与用途脚本中使用断点根据行进行断点根据命令进行断点根据特定变量进行断点错误操作$ErrorActionPreference-ErrorAction函数与模块变量范围Set-Variable指定作用域类似指针修改return创建模块 PowerShell 管道 管道输出 PowerShell 命令不会生成文本作为输出,而是会生成对象,对象是描述内存中数据结构的通用词。 运行 Get-Service 命令时,它会返回服务对象的集合,每个对象都包含 Name、DisplayName 和 Status 等名称的属性。 控制管道输出的格式设置 格式设置 cmdlet 为: Format-List Format-Table Format-Wide Format-Custom Format-Custom cmdlet 需要创建定义格式的自定义 XML 配置文件。 该 cmdlet 不经常使用。 Format-List Format-List cmdlet 将命令的输出格式化为一个简单的属性列表,其中每个属性显示在一个新行上。 如果将输出传递给 Format-List 的命令返回多个对象,则会为每个对象显示单独属性列表。 当命令返回大量很难以表格格式查看的属性时,列表格式尤其有用。 Format-List cmdlet 的别名是 fl。 Format-Table Format-Table cmdlet 将输出格式化为表格,其中每一行表示一个对象,每一列表示一个属性。 这个输出的效果和默认的没啥效果,多了一个就是你可以选择参数来进行定义你输出的效果,下面介绍参数↓↓↓ 可使用多种参数修改此格式,例如: -AutoSize。 此参数可根据数据的宽度来调整列的大小和数量。 在 Windows PowerShell 5.0 及更高版本中,-AutoSize 默认设置为 true。 在更低版本的 Windows PowerShell 中,默认值可能会截断表中的数据。 -HideTableHeaders。 此参数会从输出中移除表标头。 -Wrap。 此参数会使超出列宽的文本换行到下一行。 Format-Table cmdlet 的别名是 ft。 Format-Wide Format-Wide cmdlet 的输出是单个列表中分多列显示的单个属性。 这个其实只需要知道:使用 -Property 参数,指定一个属性去进行多个列展示,不用单个列展示这么难看 下面展示的就是服务的所有名字,但是我们是用3列进行展示 Get-Service | fw -Property Name -Column 3 管道选择、排序和度量对象 排序和分组 Sort-Object Sort-Object 命令接受一个或多个属性名作为排序依据,默认情况下,命令按升序排序 Get-Service | Sort-Object –Property Name –Descending Get-Service | Sort Name –Desc Get-Service | Sort Status,Name 默认情况下,字符串属性的排序不考虑大小写。 (但是Sort-Object 也有参数去支持指定区分大小写的排序、特定区域性的排序规则和其他选项) Format 的 GroupBy分组 Format-List、Format-Table 和 Format-Wide 格式设置 cmdlet 具有接受属性名的 -GroupBy 参数。 通过使用 -GroupBy 参数,可以按指定属性对输出进行分组。 注意:这里说的是Format的几个格式设置的分组,所以要用格式cmdlet然后再使用这个GroupBy参数 Get-Service | Sort-Object Status,Name | fw -GroupBy Status -GroupBy 参数的运行方式与 Group-Object 命令类似。 Group-Object 命令接受管道输入,让你可以更好地控制对象分组。 Group-Object 具有别名 group。 度量管道中的对象 Measure-Object 默认情况下,该命令会对集合中的对象数进行计数,并生成包含计数的测量对象。 使用 Measure-Object 的 -Property 参数可指定单个属性,该属性必须包含数值。 随后,可以添加 -Sum、-Average、-Minimum 和 -Maximum 参数,以计算指定属性的这些聚合值。 (通常可注意到 -Sum、-Average、-Minimum 和 -Maximum 参数被截断为 -Sum、-Ave、-Min 和 -Max) 以下命令计算文件夹中的文件数,并显示文件大小的最小、最大和平均值: (Recurse递归读取) Get-ChildItem -File -Recurse | Measure -Property Length -Sum -Average -Minimum -Max 效果如下图: Select-Object Select-Object 命令具有别名 Select。 这个命令比较简单,用法如下: 用Property指定要选择的对象即可,多个就用逗号隔开 Get-Process | Select-Object -Property 列名1,列名2 选择最少虚拟内存使用量排名前 10 的进程 Get-Process | Sort-Object –Property VM | Select-Object –First 10 选择最后 10 个正在运行的服务并按名称排序 Get-Service | Sort-Object –Property Name | Select-Object –Last 10 选择 CPU 用量最少的五个进程,并跳过使用最少 CPU 的那一个进程 Get-Process | Sort-Object –Property CPU –Descending | Select-Object –First 5 –Skip 1 Unique去重 只需要在指定对象后添加多一个-Unique参数即可 显示某个用户在每个部门中的用户信息 Get-ADUser -Filter * -Property Department | Sort-Object -Property Department | Select-Object Department -Unique Property 没啥好说的,就是select-object 的 参数Property 指定显示的进程属性即可 显示一个表,其中包含本地计算机上运行的所有进程的名称、进程 ID、虚拟内存大小、分页内存大小和 CPU 使用率: Get-Process | Select-Object –Property Name,ID,VM,PM,CPU | Format-Table -Property 参数适用于 -First 或 -Last 参数。 以下命令返回具有最大 CPU 使用率的 10 个进程的名称和 CPU 使用率: Get-Process | Sort-Object –Property CPU –Descending | Select-Object –Property Name,CPU –First 10 自定义属性并设置表达式与格式 定义属性的名字: label、l、name 或 n:这指定计算属性的标签或名称。 由于小写字母 l 在某些字体中类似于数字 1,因此请尝试使用 name、n 或 label。 设置属性计算的式子: expression 或 e:这将指定设置计算属性值的表达式。 Get-Process | Select-Object Name,ID,@{n='VirtualMemory';e={$PSItem.VM}},@{n='PagedMemory';e={$PSItem.PM}} $PSItem是由 Windows PowerShell 创建的特殊变量。 它表示通过管道传输到 Select-Object 命令中的任何对象。 在上一个示例中,这是一个 Process 对象。$PSItem之后的句点允许访问对象的单个成员。 在此示例中,一个计算属性使用 VM 属性,另一个使用 PM 属性。 当我们输入正常的Get-Process 并获取他的所有属性的时候你会看到VM这个属性,所以我们$PSItem.VM是能够获取到的 检查一下是否有VM Get-Process | Get-Member | Select-Object -Property Name | Format-Wide -Column 5 下图可以看到确实有VM属性 为什么用@{} 解释:还记不记得之前创建空数组是用@(),这里是创建hash表,所以用@{},n是属性名,对应键,e是计算式子相当于值,所以说我们还能知道在自定义的时候不能定义同一个名字的属性 计算 你可能想要修改前面的命令以显示内存值(以兆字节 (MB) 为单位)。 PowerShell 将缩写 KB、MB、GB、TB 和 PB 分别表示千字节、兆字节、千兆字节、兆兆字节和拍字节。 因此,可以按如下所示修改命令: ($PSItem是接收传过来的对象,$_也是一样的, 用的比较多的是$_,所以说我们可以在Get-Process出来的对象中一个个用$PSItem去取你想要的属性或者激活方法) Get-Process | Select-Object Name, ID, @{n='VirtualMemory(MB)';e={$PSItem.VM / 1MB}}, @{n='PagedMemory(MB)';e={$PSItem.PM / 1MB}} 生成的值有几个小数位,这在视觉上是不理想的。 若要改进输出,请进行以下更改: Get-Process | Select-Object Name, ID, @{n='VirtualMemory(MB)';e={'{0:N2}' –f ($PSItem.VM / 1MB) -as [Double] }}, @{n='PagedMemory(MB)';e={'{0:N2}' –f ($PSItem.PM / 1MB) -as [Double] }} 上一个示例中的语法可能看起来令人困惑,因为它包含许多标点符号。 从基本表达式开始: '{0:N2}' –f ($PSItem.VM / 1MB) 上一个表达式将 VM 属性除以 1 MB,然后将结果格式化为最多两个小数位的数字。 然后将该表达式放入哈希表中: 该哈希表创建名为 VirtualMemory(MB) 的自定义属性。 @{n='VirtualMemory(MB)';e={'{0:N2}' –f ($PSItem.VM / 1MB) }} 从管道中筛选对象 比较运算符 操作 DESCRIPTION -eq 等于 -ne 不等于 -gt 大于 -lt 小于 -le 小于或等于 -ge 大于或等于 在powershell中默认是不区分大小写,但是在比较的时候难免希望对大小写的区分 所以我们可以在操作中添加一个前缀c作为比较区分大小写的标志 -ceq -cne 不仅仅上述的运算符,其他也都可以加c 当然还有like也可以用来区分大小写去匹配,因为-like运算符类似于-eq -clike 其他更高级的运算符存包括: -in和-contains运算符,用于测试集合中是否存在对象。 -as运算符,用于测试对象是否为指定类型。 将字符串与正则表达式进行比较的-match和-cmatch运算符。 还包含许多运算符来反转比较的逻辑,例如-notlike和-notin。 简单案例: PS C:\> 100 -gt 10 True PS C:\> 'hello' -eq 'HELLO' True PS C:\> 'hello' -ceq 'HELLO' False 基本筛选器语法 Where-Object 命令及其别名 Where 仅显示正在运行的服务的列表 Get-Service | Where Status –eq Running 基本语法的限制 只能对单个比较使用基本语法。 例如,无法显示已停止且具有“自动”启动模式的服务列表,因为需要两个比较。 不能将基本语法用于复杂表达式。 例如,服务对象的 Name 属性由一串字符组成。 PowerShell 使用 System.String 对象包含该串字符,而 System.String 对象具有 Length 属性。 以下命令不适用于基本筛选语法: Get-Service | Where Name.Length –gt 5 目的是显示名称超过五个字符的所有服务。 但是,此命令永远不会生成输出。 一旦超过基本语法的能力,必须改用高级筛选语法。 高级筛选器语法 高级语法使用筛选器脚本 使用 -FilterScript 参数传递该脚本块 对于通过管道传递到命令的每个对象,筛选器脚本都会运行一次。 当筛选器脚本返回 True 时,该对象将作为输出传递到管道中,当筛选器脚本返回 False 时,将从管道中删除该对象。 以下两个命令具有相同的功能。 第一个命令使用基本语法,第二个命令使用高级语法执行相同的操作: Get-Service | Where Status –eq Running Get-Service | Where-Object –FilterScript { $PSItem.Status –eq 'Running' } -FilterScript 参数是位置参数,大多数用户会省略它。 大多数用户还会使用 Where 别名或 ? 别名,此名称长度更短。 推荐使用$_变量而不是$PSItem,因为在 Windows PowerShell 1.0 和 Windows PowerShell 2.0 中只允许使用$_。 以下命令执行与前两个命令相同的任务: Get-Service | Where {$PSItem.Status –eq 'Running'} Get-Service | ? {$_.Status –eq 'Running'} 组合多个条件 高级语法允许通过使用 -and 和 -or 布尔值或逻辑运算符来组合多个条件。 下面是一个示例: Get-EventLog –LogName Security –Newest 100 | Where { $PSItem.EventID –eq 4672 –and $PSItem.EntryType –eq 'SuccessAudit' } 逻辑运算符的任意一侧都必须是一个完整的比较式子,下面给几个错误示范,请不要犯错: Get-Process | Where { $PSItem.CPU –gt 30 –and VM –lt 10000 } Get-Service | Where { $PSItem.Status –eq 'Running' –or 'Starting' } True 或 False 的属性筛选使用技巧 Get-Process 生成的对象具有名为“Responding”的属性,此属性包含 True 或 False。 获取正在响应的进程的列表,可以使用以下命令之一: Get-Process | Where { $PSItem.Responding –eq $True } Get-Process | Where { $PSItem.Responding } 在第一个命令中,特殊 shell 变量$True用于表示布尔值 True。 第二个命令未包含任何比较,但它是有效的,因为 Responding 属性已包含 True 或 False 这类似于反向逻辑,仅列出未响应的进程: Get-Process | Where { -not $PSItem.Responding } 在前面的示例中,-not 逻辑运算符将 True 更改为 False,并将 False 更改为 True。 因此,如果进程未响应,则其 Responding 属性为 False。 -not 运算符将结果更改为 True,这会使进程被传递到管道中,并包含在命令的最终输出中。 高级筛选不受简单筛选限制 现在再去根据字符长度来筛选就没问题了 Get-Service | Where {$PSItem.Name.Length –gt 8} 优化筛选器性能 说是优化,但其实都是靠编写脚本自己的功底 Get-随便 对于下面两个示例,你认为哪一个速度更快? Get-随便 | Sort-Object –Property Letter | Where-Object –FilterScript { $PSItem.Color –eq 'Red' } Get-随便 | Where-Object –FilterScript { $PSItem.Color –eq 'Red' } | Sort-Object –Property Letter 第二个命令速度更快,因为他先是移除了不要的再进行排序,这加速了排序,但是如果你排序了再移除就表示你排序排了没用的东西,然后还要移除,那就慢了。 再比如下面这个查找文件,一看就知道是第二个块了,内置的直接使用更快 Get-ChildItem | Where { -not $PSItem.PSIsContainer } Get-ChildItem -File 枚举 这里的枚举是说powershell取出来的对象在管道中每一个都传递给下一个cmdlet去操作。 比如:停止计算机上每个正在运行的记事本进程,则可以运行以下两个命令之一 Get-Process –Name Notepad | Stop-Process Stop-Process –Name Notepad 比如Get-Process 筛选了名字为Notepad的,那就可能会出现很多个进程,那给到管道后面的Stop-Process来说,他就是在枚举每一个Notepad进程然后执行停止进程操作 枚举管道对象语法 这里官网分了基本语法和高级语法,但其实枚举管道都支持 基本和高级的区分就是:基本的不用脚本,高级的用脚本块 查看下ForEach-Object的帮助文档: 可以看到有两个参数,一个是接脚本块的,一看就是高级用法 基本用法应该就是下面的-MemberName指定属性或方法名的了 基本语法: 两个常见别名:ForEach和%。 与Where-Object一样,ForEach-Object具有基本语法和高级语法。 下面三个都一样,原理都是将Get-ChildItem取出来的对象([System.IO.FileInfo])进行For枚举出来然后参数-MemberName就是取属性或者方法,那我们给的Encrypt就是一个方法(加密),所以我们就相当于给当前遍历的对象执行了该方法,以此类推每一个对象都会执行一次。 Get-ChildItem –Path C:\Encrypted\ -File | ForEach-Object -MemberName Encrypt # 使用了别名和忽略了参数 Get-ChildItem –Path C:\Encrypted\ -File | ForEach Encrypt # 使用了别名和忽略了参数 Get-ChildItem –Path C:\Encrypted\ -File | % Encrypt 你可以特地去查看下是否有该方法: Get-ChildItem | Get-Member -Name Encry* 高级语法:就是使用脚本块 使用高级语法加密一组文件 Get-ChildItem –Path C:\ToEncrypt\ -File | ForEach-Object –Process { $PSItem.Encrypt() } 范围运算符是两个句点 (..),中间没有空格,range 运算符生成从 1 到 3 的整数对象,这 3 个对象通过管道传递给ForEach-Object,迫使脚本块运行 3 次 1..3 | ForEach-Object { Get-Random } 写入到文件 Out-File就相当于cmd里面的文本重定向运算符 > 和 >>,这些运算符可作为 Out-File 的别名,管道末尾的大于号 (>) 将输出定向到文件,从而覆盖内容,两个连续的大于号 (>>) 将输出定向到文件,从而将输出附加到文件中已有的任何文本。 例如: 下面这个虽然说输出到csv文件,但其实就是文本格式输入进去,没有csv格式 Get-Service | Sort-Object –Property Status, Name | Select-Object –Property DisplayName,Status | Out-File –FilePath ServiceList.csv 转换为其他形式的数据表示形式 PowerShell 使用两个不同的谓词进行转换:ConvertTo和Export Csv 使用ConvertTo的命令(如ConvertTo-Csv)接受来自管道的对象作为输入,并将转换后的数据作为输出生成到管道 切记:这个是将对象转换成了csv格式,但是你直接输入进文件可能会存在各种问题,比如ConvertTo-Csv生成的 CSV 包含类型信息行(如#TYPE System.ServiceProcess.ServiceController),这不是标准 CSV 的一部分,可能会干扰某些应用程序的解析,这就需要Export-Csv解决 Get-Service | ConvertTo-Csv | Out-File Services.csv 使用 Export(如Export-Csv)的命令执行两项作:它会转换数据,然后将数据写入外部存储,例如磁盘上的文件 Get-Service | Export-Csv Services.csv XML ConvertTo-Clixml和Export-Clixml Json ConvertTo-Json命令创建 JSON 格式的数据,必须使用Out-File或文本重定向运算符之一将 JSON 数据发送到文件 HTML ConvertTo-Html命令支持此功能,必须使用Out-File或其别名之一来定向输出。 ConvertTo-Html创建编码为 HTML 的简单列表或表,您可以通过各种参数以有限的方式控制HTML格式,例如: ‑Head。 指定 HTML头节的内容。 —标题。 设置 HTML标题标记的值。 -PreContent。 定义应在表或列表输出之前显示的任何内容。 -PostContent。 定义应在表或列表输出之后显示的任何内容。 其他输出选项 Out-*命令的核心功能、常见参数和典型使用场景: Cmdlet 核心功能 常用参数 典型应用场景 Out-Host 将输出发送到主机(控制台)进行显示 -Paging(强制分页显示) 逐页查看长输出,避免滚动过快错过信息 Out-Printer 将输出发送到打印机进行打印 -Name(指定打印机名称) 打印命令结果、报告或配置清单 Out-GridView 在交互式表格窗口中显示输出,支持排序、筛选和复制 -Title(设置窗口标题) 可视化数据分析、快速筛选和分享信息,但无法直接保存 Out-Host:控制台输出管理 Out-Host是 PowerShell 默认的输出方式,即直接将结果呈现在控制台。它的特殊之处在于你可以通过-Paging参数手动控制输出分页 -Paging参数:强制对输出进行分页,显示一页后暂停,按空格键查看下一页,按Q 键退出 与more命令的关系:在 PowerShell 中,more是一个内置函数,它本质上是Out-Host -Paging的别名,两者功能相同 逐页查看系统进程列表 Get-Process | Out-Host -Paging # 也可以使用更简洁的more Get-Process | more Out-Printer:打印输出 Out-Printer允许你将命令的输出直接发送到打印机。 默认行为:不使用参数时,输出会发送到默认打印机 -Name参数:指定目标打印机的名称,打印机名称需与系统中安装的打印机名称匹配(可通过Get-Printercmdlet 查看所有可用打印机) 打印当前运行的进程列表到默认打印机 Get-Process | Out-Printer 将系统服务状态发送到特定打印机(假设打印机名为 "HP-LaserJet"): Get-Service | Out-Printer -Name "HP-LaserJet" 打印到虚拟打印机(如生成PDF):如果你安装了 Microsoft Print to PDF 这类虚拟打印机,也可以使用-Name参数指定它来生成PDF文件。 Get-Service | Where-Object {$_.Status -eq 'Running'} | Out-Printer -Name "Microsoft Print to PDF" Out-GridView:交互式表格输出 Out-GridView(通常简称OGV)是一个非常强大的工具,它会在一个新窗口中以交互式表格的形式显示输出。你可以: 点击列名进行排序(升序/降序)。 使用顶部的筛选框对任何列进行筛选(支持包含、不包含等条件)。 选中行并复制(Ctrl+C),然后粘贴到 Excel 或其他应用程序中。 多选(Ctrl+点击 或 Shift+点击)。 重要限制:正如你所读到的,无法直接从 GridView 窗口保存数据。你需要先通过复制粘贴,或在命令行中就使用Export-Csv等命令保存数据。 可视化并筛选系统服务: # 查看所有服务 Get-Service | Out-GridView # 仅查看正在运行的服务,并自定义窗口标题 Get-Service | Where-Object Status -eq 'Running' | Out-GridView -Title "当前运行的服务" # 结合筛选器:找出所有正在运行且名称中包含 "windows" 的服务 Get-Service | Where-Object { $_.Status -eq 'Running' -and $_.Name -like '*windows*' } | Out-GridView 在弹出的窗口中,你可以进一步点击“状态”列排序,或在“名称”列筛选器输入更多关键字。 分析进程资源占用: Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 | Out-GridView -Title "CPU占用最高的10个进程" 自定义列顺序:如果你使用Select-Object选择并排序了属性,Out-GridView会遵循这个顺序显示列 Get-Service | Select-Object Name, Status, DisplayName, StartType | Out-GridView 传递管道对象 管道传递数据可以有两种方式:ByValue 和 ByPropertyName 它们最根本的区别在于匹配的依据: ByValue:依据管道对象的整体类型进行匹配。 ByPropertyName:依据管道对象的属性名称进行匹配。 例如运行:Get-Help Stop-Process -Full 必须看输入支持哪种类型:那我们只能输入下面的这几种了 那既然我们要传数据给Stop-Process的话那我们就需要知道左边的那个输出符不符合这里面的输入,下面详细讲一下这个! ByValue 传递数据 直接将String类型传给Get-Service 'BITS','WinRM' | Get-Service 是否成功前可以看下Get-Service接受哪些类型的管道输入 Get-Help Get-Service -Full 包含string那就可以传入了 但是这种就不行: Get-LocalUser | Stop-Process 首先我们看下Stop-Process的接受类型 Get-Help Stop-Process -Full 接着再看Get-LocalUser的输出是否符合Stop-Process的输入类型 Get-Help Get-LocalUser -Full 下图可以看到明显不符合,输出的是Object,传到Stop-Process的时候就肯定报错了 如何解决?那就交给ByPropertyName,指定输出一个属性内容即可 ByPropertyName 传递数据 这里感觉没啥好说的,就是在ByValue不成立的时候,我们就需要特别指定某个属性给到下一个要执行的命令,但是前提是左边和右边的属性名字要相同,否则在匹配的时候就不知道该属性给哪个。 (有一个特殊情况就是希望将左边的A属性值传递给右边的B属性,两个名字不一样的时候就需要重命名) 重命名方式,这种就比较特殊 Get-Process有一个参数-ComputerName,它支持通过 ByPropertyName 接收输入。 但Get-ADComputer返回的计算机对象有一个叫Name的属性,没有叫ComputerName的属性。 因此,Name属性无法自动传递给-ComputerName参数 这样就需要重命名了: Get-ADComputer -Filter * | Select-Object @{Name='ComputerName'; Expression={$_.Name}} | Get-Process 若是说在 ByPropertyName 的时候,两边的属性名相同那就会自动匹配去执行操作了(这种就不详细说了) 展开属性值 先看一个动作: Get-Process –ComputerName (Get-LocalUser –Filter *) 这个是想要将Get-LocalUser给到Get-Process的ComputerName参数 先看Get-Process的ComputerName接受什么输入 Get-Help Get-Process -Full 可以看到只接受string类型,那我们直接将整个Get-LocalUser东西穿进去肯定不行 再看看这样行不行 回答:看似可以,其实也不行 Get-Process –ComputerName (Get-LocalUser –Filter * | Select-Object –Property Name) 我们要看这个–Property Name输出的到底是不是string $(Get-LocalUser | Select-Object –Property Name -First 1).GetType() 查看后发现还是不是string,那这时候就需要用到展开属性的操作了 其实就是改了个参数名: 将Property改为ExpandProperty,下面这样就可以了 $(Get-LocalUser | Select-Object –ExpandProperty Name -First 1).GetType() 为什么可以,我们直接看下取出来的Name是不是string即可 复习提问: 在命令行接口将对象从一个命令传递到管道中的另一个命令时,Windows PowerShell 总是优先尝试使用哪种技术? ByValue 深入了解Powershell脚本 开发生命周期与安全强化 PowerShellGet 模块 Cmdlet 说明 Find-Module 使用此 cmdlet 在 PowerShell 库中搜索 Windows PowerShell 模块。 最简单的用法是根据模块名进行搜索,但也可以根据命令名、版本、DscResource 和 RoleCapability 进行搜索。 Find-Script 使用此 cmdlet 在 PowerShell 库中搜索 Windows PowerShell 脚本。 最简单的用法是根据脚本名进行搜索,但也可以根据版本进行搜索。 PowerShell 库需要使用传输层安全性 (TLS) 1.2 来帮助保护通信。 默认情况下,Windows 10 和 Windows Server 2016 不支持在 Windows PowerShell 中使用 TLS 1.2。 因此,需要启用 TLS 1.2 才能下载 PowerShell 库内容。 若要为当前 PowerShell 提示启用 TLS 1.2,请运行以下命令: [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 若要在计算机上永久解决此问题,需要创建注册表项。 可以运行以下两个命令来创建必要的密钥: Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319'-Name 'SchUseStrongCrypto' -Value '1' -Type DWord Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord 执行策略 确保当前配置,可以使用:Get-ExecutionPolicy 执行策略的选项包括: Restricted:不允许运行任何脚本。 AllSigned:仅当脚本经过数字签名后才能运行。 RemoteSigned:下载的脚本只有在经过数字签名后才能运行。 Unrestricted:可以运行所有脚本,但在运行下载但未签名的脚本时会显示确认提示。 Bypass:运行所有脚本且不显示提示。 以数字方式对脚本进行签名 使用现有的代码签名证书(如果你有) 如果你已经从公共证书颁发机构(CA)或企业的内部CA获取并安装了代码签名证书,可以用以下命令查找: # 在当前用户的证书存储中查找所有可用于代码签名的证书 $certs = Get-ChildItem -Path "Cert:\CurrentUser\My" -CodeSigningCert # 如果你确定只有一个,可以直接赋值 $cert = Get-ChildItem -Path "Cert:\CurrentUser\My" -CodeSigningCert Cert:\CurrentUser\My是 PowerShell 证书驱动器(PSDrive)中的一个路径,指向当前用户的“个人”证书存储区域。 -CodeSigningCert参数是Get-ChildItem在证书驱动器中专用的,用于筛选出具有“代码签名”用途的证书。 创建自签名证书(用于测试和学习) 在生产环境中,你需要一个受信任的CA颁发的证书。但在测试和学习时,可以快速创建一个自签名证书 # 以管理员身份运行 PowerShell 执行以下命令 $certParams = @{ Type = 'CodeSigningCert' Subject = 'CN=PowerShell Scripting Test' # 证书主题,CN是通用名 KeyUsage = 'DigitalSignature' # 密钥用法:数字签名 KeyExportPolicy = 'Exportable' # 密钥可导出,方便备份和转移 CertStoreLocation = 'Cert:\CurrentUser\My' # 证书存储位置 HashAlgorithm = 'sha256' # 哈希算法 # FriendlyName 是可选的,便于在证书管理中识别 FriendlyName = 'My PowerShell Test Signing Certificate' } $cert = New-SelfSignedCertificate @certParams 重要提示:自签名证书仅用于测试。因为它不是由受信任的根证书颁发机构颁发的,所以其他计算机默认不会信任它。 (你自己创建的,你自己使用的时候就导入即可,不用设置密码啥的,因为是你自己创建你自己用,除非你导出的时候就要设置密码,因为你导出肯定是要给其他计算机使用) 从证书文件导入 如果你有.pfx或.p12格式的证书文件(通常包含私钥),可以使用Get-PfxCertificatecmdlet 来加载它: # 会弹窗提示输入密码 $cert = Get-PfxCertificate -FilePath "C:\Path\To\Your\CodeSigningCert.pfx" # 或者使用SecureString自动输入密码(注意密码安全) $securePassword = ConvertTo-SecureString -String "YourCertificatePassword" -Force -AsPlainText $cert = Get-PfxCertificate -FilePath "C:\Path\To\Your\CodeSigningCert.pfx" -Password $securePassword 加载的时候需要密码,这需要在证书持有者导出证书的时候设置的那个密码,然而这个pdx或者p12文件是用来签名的,不是用来验证的,用来验证的那个是cert,这也是为啥要输入密码的原因了,这个证书是拿来公章签名的。 拿到证书对象($cert)后,就可以用它来签名脚本了。官网的例子是基础,但强烈建议添加时间戳服务器参数。 # 基础签名(官网示例) Set-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1" -Certificate $cert # 推荐的签名方式(添加时间戳) $signParams = @{ FilePath = "C:\Scripts\MyScript.ps1" # 要签名的脚本路径 Certificate = $cert # 之前获取的证书对象 HashAlgorithm = 'Sha256' # 哈希算法,建议使用Sha256 # 添加时间戳至关重要!即使证书过期,时间戳也能证明签名时证书是有效的。 TimestampServer = 'http://timestamp.digicert.com' # -IncludeChain 参数可选,默认是 'NotRoot'(包含除根CA以外的所有证书) # -Force 参数可选,如果脚本已有签名,强制替换 } Set-AuthenticodeSignature @signParams 签名完成后,务必检查一下: Get-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1" 查看输出中的Status属性: Valid:签名有效且受信任。 UnknownError:签名无效或证书不受信任(常见于自签名证书)。 NotSigned:脚本未签名。 让系统信任你的签名 !!!!!记住这里是验证,不是用来签名,前面说的都是pxf和p12证书,这里讲的是cer文件!!!!!!!! 对于自签名证书,由于它不是公共CA颁发的,你需要将你的自签名证书**导入到“受信任的根证书颁发机构”或“受信任的发布者”存储区。否则,在其他计算机上运行时会显示UnknownError。 将你的证书导出为.cer文件(只包含公钥)。 在需要运行此脚本的计算机上,将这个.cer文件导入到“受信任的根证书颁发机构”或“受信任的发布者”(对于代码签名证书,通常是“受信任的发布者”)。 你可以使用 PowerShell 自动化导入信任证书的过程 $certPath = "C:\Path\To\Exported\Certificate.cer" $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("TrustedPublisher", "LocalMachine") $store.Open("ReadWrite") $store.Add((New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath))) $store.Close() 进阶语法 ForEach 循环 在某些情况下,可能需要使用 ForEach-Object cmdlet 来处理管道中的数据。 将数据存储在数组中时,ForEach 构造支持处理数组中的每个项。 powershell ForEach ($user in $users) { Set-ADUser $user -Department "Marketing" } 在 PowerShell 7 中,已将 -Parallel 参数添加到 ForEach-Object cmdlet。 这样,管道就可以同时处理多个对象。 相较于标准 ForEach 循环,同时处理多个对象所提供的性能更佳。 如果使用的是 PowerShell 7,应考虑这一点。 以下示例说明了如何将 ForEach-Object 与 -Parallel 参数配合使用。 $users | ForEach-Object -Parallel { Set-ADUser $user -Department "Marketing" } 默认情况下,-Parallel 参数支持一次处理五个项,可以使用 -ThrottleLimit 参数将其修改为更大或更小的值。 If 构造 举例子:如果可用磁盘空间不足,则可以使用 If 语句显示警告 If ($freeSpace -le 5GB) { Write-Host "Free disk space is less than 5 GB" } ElseIf ($freeSpace -le 10GB) { Write-Host "Free disk space is less than 10 GB" } Else { Write-Host "Free disk space is more than 10 GB" } 同时也学到大小是可以直接使用MB、GB等单位直接比较 Switch 构造 Switch ($choice) { 1 { Write-Host "You selected menu item 1" } 2 { Write-Host "You selected menu item 2" } 3 { Write-Host "You selected menu item 3" } Default { Write-Host "You did not select a valid option" } } 可以使用 -wildcard 参数,以与 -like 运算符相同的语法来执行模式匹配。 或者,可以使用 -regex 参数通过正则表达式执行匹配。 Switch -WildCard ($ip) { "10.*" { Write-Host "This computer is on the internal network" } "10.1.*" { Write-Host "This computer is in London" } "10.2.*" { Write-Host "This computer is in Vancouver" } Default { Write-Host "This computer is not on the internal network" } } For 构造 For($i=1; $i -le 10; $i++) { Write-Host "Creating User $i" } 处理对象数组时,最好使用 ForEach 构造,因为在处理之前不需要计算数组中的项数。 其他循环构造 Do..While Do..While 构造运行脚本块,直到指定条件为false 此构造保证脚本块至少运行一次 Do { Write-Host "Script block to process" } While ($answer -eq "go") Do..Until Do..Until 构造运行脚本块,直到指定条件为 true 此构造保证脚本块至少运行一次 Do { Write-Host "Script block to process" } Until ($answer -eq "stop") While While 构造运行脚本块,直到指定条件为 false 虽然它类似于 Do..While 构造,但它不能保证脚本块的运行 While ($answer -eq "go") { Write-Host "Script block to process" } Break 和 Continue 使用 Continue 可阻止修改要修改的用户列表中的管理员用户帐户: ForEach ($user in $users) { If ($user.Name -eq "Administrator") {Continue} Write-Host "Modify user object" } Break 用于在最大帐户数已修改时结束循环: ForEach ($user in $users) { $number++ Write-Host "Modify User object $number" If ($number -ge $max) {Break} } 导入数据 Get-Content 直接读取文件内容进来 $computers = Get-Content C:\Scripts\computers.txt 可在 Get-Content 的路径中使用通配符,以便一次获得多个文件的数据 可使用 -Include 和 -Exclude 参数修改所选文件 Get-Content -Path "C:\Scripts\*" -Include "*.txt","*.log" 可以使用 -TotalCount 和 -Tail 参数限制使用 Get-Content 检索的数据量 -TotalCount 参数指定应从文件开头检索多少行 -Tail 参数指定从文件末尾检索多少行 例如: Get-Content C:\Scripts\computers.txt -TotalCount 10 Import-Csv $users = Import-Csv C:\Scripts\Users.csv 输出示例: First,Last,UserID,Department Amelie,Garner,AGarner,Sales Evan,Norman,ENorman,Sales Siu,Robben,SRobben,Sales 当我们存进一个变量后,也可以通过变量访问某个数据 $users[2].UserID Import-Csv 默认分隔符是逗号,有的文件不是以逗号进行分割的话你也可以使用Import-Csv,只要格式相同分隔符不同也可以用这个,前提是你要自己加参数去修改分隔符: 比如说分隔符是分号 Import-Csv -Path .\1.csv -Header h1,h2,h3 -Delimiter ';' Import-Clixml $users = Import-Clixml C:\Scripts\Users.xml 使用 -First 和 -Skip 参数来限制 Import-Clixml 检索的数据 -First 参数指定仅从 XML 文件的开头检索指定数量的对象 -Skip 参数指定从 XML 文件开头忽略指定数量的对象,并检索所有剩余的对象。 ConvertFrom-Json $users = Get-Content C:\Scripts\Users.json | ConvertFrom-Json Invoke-RestMethod Invoke-RestMethod 能够处理JSON、 XML、RSS 源和 ATOM 源。 $users = Invoke-RestMethod "https://hr.adatum.com/api/staff" 接受用户输入 Read-Host 这种会在How many das后加上冒号然后提示用户输入: $answer = Read-Host "How many days" 下面这种会先打印How many days? ,-NoNewline就是不换行,然后也是等待用户输入 这种就没有冒号 Write-Host "How many days? " -NoNewline $answer = Read-Host -MaskInput 或 -AsSecureString 参数在提示符处屏蔽输入用户,这种偏向于输入密码的时候不会直接显示在终端上 $answer = Read-Host "How many days" -AsSecureString 具体使用哪个看情况了,我的电脑使用AsSecureString才行 Credential凭证使用 Get-Credential 它的核心作用就是安全地弹窗收集用户凭据(用户名和密码),并将其封装在一个PSCredential对象中,供其他需要凭据的 cmdlet 使用。 基础用法: 这会弹出一个标准对话框,让你输入用户名和密码。 $cred = Get-Credential 高级用法(自定义提示和用户名): -Message:让提示更清晰,指导用户输入什么凭据。 -UserName:预填用户名字段,用户只需要输入密码即可。$env:COMPUTERNAME是环境变量,代表本机计算机名,这在工作组环境下至关重要。 # 自定义提示信息并预填用户名 $cred = Get-Credential -Message "请输入本地管理员权限凭据" -UserName "$env:COMPUTERNAME\Administrator" 远程管理另一台工作组计算机 假设你想从计算机CLIENT-A远程管理计算机CLIENT-B # 在 CLIENT-A 上运行 # 1. 获取 CLIENT-B 的本地管理员凭据 $cred = Get-Credential -Message "请输入CLIENT-B的本地管理员凭据" -UserName "CLIENT-B\Administrator" # 2. 建立远程会话 (PSRemoting) $session = New-PSSession -ComputerName "CLIENT-B" -Credential $cred # 3. 在远程会话中执行命令(例如:检查磁盘空间) Invoke-Command -Session $session -ScriptBlock { Get-Volume } # 4. 关闭会话 Remove-PSSession $session 本地脚本临时提权 你用自己的标准用户账户登录,但脚本中的某些操作(如修改系统设置)需要管理员权限。 # 检查当前用户权限,如果不是管理员则请求凭据 if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Warning "此操作需要管理员权限。" $adminCred = Get-Credential -Message "请提供本地管理员密码以继续" -UserName "$env:COMPUTERNAME\Administrator" # 使用 Start-Process 以管理员身份启动一个新进程来运行命令 $scriptBlock = { # 这里放需要提权的命令,例如安装Windows功能 Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V" -All } Start-Process "pwsh" -ArgumentList "-Command", $scriptBlock -Credential $adminCred -Wait -NoNewWindow } else { # 如果已经是管理员,直接执行命令 Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V" -All } 访问受保护的网络共享 挂载一个需要特定用户名和密码才能访问的局域网共享文件夹。 $netCred = Get-Credential -Message "请输入访问共享\\FileServer\Data$的凭据" -UserName "FileServer\SomeUser" # 将凭据映射到驱动器号 New-PSDrive -Name "Z" -PSProvider "FileSystem" -Root "\\FileServer\Data$" -Credential $netCred -Persist # 现在可以像访问本地磁盘一样访问 Z:\ Get-ChildItem Z:\ # 使用完毕后断开 Remove-PSDrive -Name "Z" Export-Clixml 首次保存凭据(在一台电脑上): # 弹窗输入一次凭据 $cred = Get-Credential -Message "输入要保存的凭据" -UserName "MyPC\Admin" # 将加密后的凭据保存到文件(只能由你在本机解密) $cred | Export-Clixml -Path "C:\Users\$env:USERNAME\Documents\secureCred.xml" 后续脚本中自动使用保存的凭据: # 无需弹窗,直接读取加密文件获取凭据对象 $savedCred = Import-Clixml -Path "C:\Users\$env:USERNAME\Documents\secureCred.xml" # 使用凭据执行需要权限的操作,例如重启远程计算机 Restart-Computer -ComputerName "192.168.1.100" -Credential $savedCred -Force 脚本故障排除与错误处理 错误发生时,它们将存储在$Error数组中。 最近的错误始终在索引零处。 新错误生成时,会插入到$Error[0]处,其他错误的索引将增加一。 每当需要查看以前的错误消息时,查看$Error中的错误会很有帮助。 例如,如果清除屏幕,则可以通过$Error查看最近的错误消息。 输出命令的层次与用途 PowerShell 的输出命令不是一个简单的“打印”功能,而是一个完整的信息流系统。理解不同命令的定位是关键。 命令 用途 输出位置 是否受*Preference变量影响 适用场景 Write-Host 直接与用户交互 控制台 (主机) 否 显示进度、美观的标题、即时提示。谨慎使用。 Write-Output 将对象放入输出管道 管道 / 控制台 否 脚本的主要输出结果。通常隐式使用(如"Hello")。 Write-Verbose 输出详细信息 控制台 ( verbose流) 是 调试、记录脚本执行的详细步骤。 Write-Debug 输出调试信息 控制台 (debug流) 是 更深入的调试,可在运行时暂停脚本。 Write-Warning 输出警告信息 控制台 (warning流) 是 提示用户潜在的问题,但脚本会继续执行。 Write-Error 输出错误信息 控制台 (error流) 是 报告错误,但不终止脚本执行。 Throw 抛出终止错误 控制台 (error流) - 报告严重错误,并立即终止当前函数/脚本。 Write-Verbose和Write-Debug:真正的调试利器 这两个命令的强大之处在于它们的可控性。默认情况下它们是静默的,只在需要时通过参数开启。 示例脚本 (Test-Service.ps1): [CmdletBinding()] # 启用高级功能,支持 -Verbose 和 -Debug 参数 param ( [Parameter(Mandatory=$true)] [string]$ServiceName ) Write-Verbose "脚本开始执行,传入的服务名参数为: $ServiceName" # 检查服务是否存在 $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue Write-Debug "Get-Service 查询结果: $($service | Out-String)" if (-not $service) { Write-Error "错误: 找不到名为 '$ServiceName' 的服务。" exit 1 } Write-Verbose "服务状态: $($service.Status)" if ($service.Status -ne 'Running') { Write-Warning "服务 '$ServiceName' 当前未运行。" # 尝试启动服务 try { Start-Service -Name $ServiceName Write-Host "服务已成功启动。" -ForegroundColor Green } catch { Throw "启动服务失败: $($_.Exception.Message)" } } else { Write-Output "服务 '$ServiceName' 正在运行。" } Write-Verbose "脚本执行完毕。" 如何使用这个脚本: 默认运行(只看到基本输出): .\Test-Service -ServiceName "WinRM" # 输出: 服务 'WinRM' 正在运行。 查看详细信息(使用-Verbose): .\Test-Service -ServiceName "WinRM" -Verbose # 输出: # 详细: 脚本开始执行,传入的服务名参数为: WinRM # 详细: 服务状态: Running # 服务 'WinRM' 正在运行。 # 详细: 脚本执行完毕。 进行深度调试(使用-Debug): .\Test-Service -ServiceName "SomeService" -Debug 运行后会首先显示Write-Debug的信息,并暂停,提示你: 调试: Get-Service 查询结果: (这里会显示Get-Service返回的详细对象信息) 继续执行? [Y] 是(Y) [A] 全是(A) [N] 否(N) [L] 全否(L) [S] 暂停(S) [?] 帮助 (默认值为“Y”): 按`Y`继续执行每一步,按`A`让它自动执行完所有调试步骤。这让你可以一步步观察脚本的执行流程。 使用$VerbosePreference和$DebugPreference进行全局控制 在当前会话中开启所有Verbose输出: $VerbosePreference = "Continue" # 默认是 "SilentlyContinue" .\Test-Service -ServiceName "WinRM" # 现在即使不加 -Verbose,也会输出Verbose信息 在脚本开头强制开启调试(常用于日志记录): [CmdletBinding()] param() # 在脚本内部设置,强制记录详细信息到日志文件 $VerbosePreference = "Continue" Write-Verbose "$(Get-Date): 脚本启动..." # ... 脚本逻辑 ... 脚本中使用断点 根据行进行断点 可使用 Set-PSBreakPoint cmdlet 设置断点 可以基于脚本行、正在使用的特定命令或正在使用的特定变量来设置断点 以下示例描述如何在脚本的特定行处设置断点: Set-PSBreakPoint -Script "MyScript.ps1" -Line 23 根据命令进行断点 Set-PSBreakPoint -Command "Set-ADUser" -Script "MyScript.ps1" 基于命令设置断点时,可以包含通配符。 例如,可以使用值 *-ADUser 为 Get-ADUser、Set-ADUser、New-ADUser 和 Remove-ADUser 触发断点。 根据特定变量进行断点 Set-PSBreakPoint -Variable "computer" -Script "MyScript.ps1" -Mode ReadWrite 可以使用变量的 -Mode 参数来确定是否要在读取和/或写入变量值时中断。 有效值为 Read、Write 和 ReadWrite。 以上是终端中运行脚本的时候设置的断点,其实我们可以用: Powershell ISE 图形化工具进行根据行去断点 VScode 中也能够进行更多高级的断点方式 错误操作 $ErrorActionPreference 内置全局变量。 当命令生成非终止错误时,命令会检查此变量来决定该执行的操作 变量可具有下面 4 个可能值之一: Continue 是默认值,它告知命令显示错误消息并继续运行。 SilentlyContinue 告知命令不显示错误消息,但要继续运行。 Inquire 告知命令显示提示,询问用户要做什么。 Stop 告知命令将错误视为终止错误并停止运行。 若要设置$ErrorActionPreference变量,请使用以下语法: $ErrorActionPreference = 'Inquire' -ErrorAction 所有 Windows PowerShell 命令都有 –ErrorAction 参数。 此参数具有别名 –EA 当你使用他的时候,会覆盖$ErrorActionPreference但可以设置一样的类型,该参数仅针对当前使用的cmdlet 函数与模块 有参函数: Function Get-SecurityEvent { Param ( [string]$ComputerName ) #end Param Get-EventLog -LogName Security -ComputerName $ComputerName -Newest 10 } 无参函数就很简单了,不写接受参数的变量即可 函数调用如下: Get-SecurityEvent -ComputerName LON-DC1 变量范围 使用范围修饰符 (Scope Modifiers) 指定目标作用域 这是修改其他作用域中变量的关键方法。通过在变量名前加修饰符来指定目标作用域 修饰符 作用 语法示例 说明 $global: 修改全局作用域中的变量。 $global:MyVariable = "新值" 在任何地方(函数、脚本)都能访问和修改这个全局变量。慎用,容易造成污染。 $script: 修改脚本作用域中的变量。 $script:MyVariable = "新值" 在当前脚本文件的任何函数内部修改在脚本顶层定义的变量。这是最常用、最安全的方式。 $using: 在远程命令或脚本块中引用当前局部作用域的变量。 Invoke-Command { ... -Name $using:LocalVar } 用于将本地变量的值传递到远程会话或新脚本块中,而不是在远程会话中修改它。 $private: 在当前作用域创建变量,且该变量不会传递到更高级的作用域。 $private:TempVar = "值" 用于限制变量范围,确保其不会影响父作用域。较少用。 示例: 假设您在脚本顶层定义了$configPath,现在需要在一个函数里修改它: Set-Variable指定作用域 Set-Variable -Name "MyVariable" -Value "新的值" -Scope <ScopeName> -Scope参数值: 'Global': 修改全局作用域。 'Script': 修改脚本作用域。 'Local': 修改当前局部作用域(默认值)。 'Private': 修改为私有作用域。 绝大多数情况下,您应该使用$script:修饰符 这是在函数内修改脚本级变量的最标准、最可读且副作用最小的方式。 类似指针修改 使用[ref]去拿到真实的那个变量在函数里面去修改,而不是说每次修改的都是函数内部接受到的值,他会直接影响到函数外部那个传进来的变量的值 function Modify-ByReference { param ( [ref]$RefValue ) $RefValue.Value = "在函数内部被修改了" # 注意:是修改 .Value 属性 } $OriginalVariable = "原始值" Modify-ByReference -RefValue ([ref]$OriginalVariable) Write-Host $OriginalVariable # 输出:在函数内部被修改了 但是我们是避免这样去修改变量的,要改的话最好是通过return返回值,然后在函数外部去改变某个变量 return 好像没啥好说的,就是return值 function xxx{ xxx return($var) # return "xxx" return $var } $ch = xxx() 创建模块 你必须创建与该文件同名的子文件夹,并将文件放在该子文件夹中 例如,如果你有一个名为 AdatumFunctions.psm1 的模块, 则将其放置在 C:\Program Files\WindowsPowerShell\Modules\AdatumFunctions 中。