「写了个方便的脚本,就放到共享文件夹里了」── 从那一刻起,维护负债就开始悄悄累积。有人复制后在本地进行了改造,原始文件即使修好了也不会同步到那份改造版本,谁也说不清哪个版本正在哪里运行。几年之后,共享文件夹里就会并排出现 汇总.ps1、汇总_v2.ps1、汇总_修正版_最新.ps1。
在 PowerShell 的世界里,这个问题的答案很明确。做成模块、加上版本号、从仓库分发。仅凭这一点,就能回答「装的是哪个版本」「更新之后是否会送达给所有人」这两个问题。而且从 PowerShell 7.4 开始,实现这一点所需的机制(PSResourceGet)从一开始就已经内置在其中。
本文将以不搭建专用服务器的现实构成为前提,讲解如何把公司内部共享的脚本模块化、搭建内部仓库并进行分发与更新。关于函数模块化本身,先阅读《PowerShell 脚本的参数设计与模块化》会更容易理解。
目标读者・前提环境
| 项目 | 内容 |
|---|---|
| 目标读者 | 正在通过共享文件夹分发 .ps1 的信息系统・运维负责人 |
| 目标版本 | Windows PowerShell 5.1 与 PowerShell 7.x 两者。不过 PSResourceGet 只在 PowerShell 7.4 及以后才随附,在 5.1 中,分发方和使用方都需要提前安装(第 5 章)1 |
| 与现有环境并存 | PSResourceGet 可以与传统的 PowerShellGet 2.2.5 并存。可以在不改写现有脚本的情况下引入1 |
| 示例的验证环境 | 本文末尾提供下载的示例代码是在 PowerShell 7.6 中运行验证的 |
| 所需权限 | 创建并设置仓库用共享文件夹、以及以 -Scope AllUsers 进行安装,都需要管理员权限 |
1. 先说结论
- PSResourceGet(
Microsoft.PowerShell.PSResourceGet)已随附在 PowerShell 7.4 中。由于它可以与传统的 PowerShellGet 2.2.5 并存,因此能在不破坏现有脚本的情况下使用。Windows PowerShell 5.1 中没有随附,因此使用方和分发方都需要提前引入(下一章)。1 - 内部仓库可以从文件共享开始搭建。只需在
Register-PSResourceRepository中指定 UNC 路径即可。不需要专用服务器。2 - 请把清单(
.psd1)当作必需品。没有版本号,就既无法更新,也无法排查故障。3 FunctionsToExport不要使用通配符,而要用数组明确列出。这样命令探索会更快,也能防止内部函数被意外公开。3- 版本号请采用语义化版本管理(Semantic Versioning)。如果无法用主版本号来体现破坏性变更,使用方就无法放心地进行更新。4
- 发布用
Publish-PSResource,获取用Install-PSResource,更新用Update-PSResource。56 - 模块的探索路径在 5.1 和 7 中是不同的。如果不理解
$env:PSModulePath的差异,就会出现「明明装了却找不到」的情况。7 - 如果执行策略是
AllSigned,就必须对每个脚本文件进行 Authenticode 签名。目录签名(catalog signing)用于验证软件包的完整性,并不满足执行策略的要求。8 - 对于无人值守执行所依赖的模块,自动更新要谨慎对待。先验证后再有计划地升级,这样的运维方式更安全。
本文所构建的机制,整体上会呈现出下面这样的形态。
flowchart LR
DEV["开发侧<br/>信息系统人员编写模块"]
TEST["测试与静态分析<br/>Pester / PSScriptAnalyzer"]
REPO["内部仓库<br/>以 UNC 路径指定的文件共享,或<br/>NuGet 兼容源"]
USER["使用侧电脑<br/>Find / Install / Update-PSResource"]
SRV["使用侧服务器<br/>夜间批处理等无人值守执行"]
DEV --> TEST
TEST -->|"Publish-PSResource"| REPO
REPO -->|"Install-PSResource"| USER
REPO -->|"Install-PSResource -Scope AllUsers"| SRV
图中箭头上标注的命令,就是本文所要处理的核心内容。仓库需要由分发方和使用方双方各自在最初只用 Register-PSResourceRepository 注册一次(第 5 章),之后就只靠发布、获取、更新这几条命令来运转(第 6 章)。只有分发负责人拥有写入权限,这是本图中唯一的安全边界。
2. “共享文件夹里的 ps1” 存在的问题
首先,明确一下本文要解决的是什么问题。
| 症状 | 根本原因 |
|---|---|
| 不知道正在运行的是哪个版本 | 不存在版本号这个概念 |
| 即使修复了也不会同步给所有人 | 每个人都各自持有一份副本 |
| 不知道是谁在使用 | 没有获取记录留存(※如后文所述,唯独这一点需要根据分发方式来选择) |
| 只有部分环境会出问题 | 依赖关系(所需模块・PS 版本)未被声明 |
| 想修复却看不清影响范围 | 公开的函数和内部函数没有区分 |
模块化与仓库分发,对上面这四项都能直接起效。但唯独「谁在使用」这一点,取决于所采用的分发机制。下文将要介绍的文件共享仓库,虽然搭建轻松,但不会留下谁在何时获取过的记录(Get-InstalledPSResource 能了解到的,只是执行该命令的那台机器当下的状态)。如果想掌握使用情况,请并用以下方法之一。
- 启用共享文件夹的读取审计(文件访问审计。参见《用 Get-WinEvent 高效排查事件日志》)
- 使用能够获取下载统计信息的 NuGet 兼容源(如 Azure Artifacts 等)
- 在各终端执行
Get-InstalledPSResource并汇总结果(参见《PowerShell Remoting(WinRM)入门》)
3. 模块的最小构成
可分发模块的最小形态,由文件夹、.psm1、.psd1 这三部分组成。
KsOps\
KsOps.psd1 ← 清单(版本・公开函数・依赖关系)
KsOps.psm1 ← 实现(或从 Public/Private 文件夹进行点源加载)
Public\
Get-KsShareUsage.ps1
Invoke-KsArchive.ps1
Private\
ConvertTo-KsSize.ps1
清单可以用 New-ModuleManifest 生成模板,再填写所需的项目。3
$manifest = @{
Path = '.\KsOps\KsOps.psd1'
RootModule = 'KsOps.psm1'
ModuleVersion = '1.0.0'
GUID = [guid]::NewGuid().Guid
Author = '信息系统部'
CompanyName = '示例股份有限公司'
Description = '公司内部运维脚本通用模块(文件服务器盘点・归档)'
PowerShellVersion = '5.1'
CompatiblePSEditions = @('Desktop', 'Core') # 同时用于 5.1 和 7 时
# 不要使用通配符。只明确列出要公开的内容
FunctionsToExport = @('Get-KsShareUsage', 'Invoke-KsArchive')
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
RequiredModules = @() # 如有依赖,请在此声明
Tags = @('internal', 'operations')
ProjectUri = 'https://git.example.co.jp/it/ksops'
}
New-ModuleManifest @manifest
.psm1 可以按照固定套路来编写:加载 Public/Private 中的脚本,只导出公开的函数。
# KsOps.psm1
$public = @(Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1" -ErrorAction SilentlyContinue)
$private = @(Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1" -ErrorAction SilentlyContinue)
foreach ($file in @($public + $private)) {
try { . $file.FullName }
catch { throw "模块加载失败: $($file.FullName) ── $_" }
}
# 公开的只有 Public 目录下的函数(要与清单中的声明保持一致)
Export-ModuleMember -Function $public.BaseName
不把 FunctionsToExport 写成通配符,理由有两个。一个是命令探索的性能:明确列出后,无需解析模块本体,就能判断出「哪个命令在哪里」。另一个是设计上的理由:如果内部辅助函数能从外部调用,它就会变成事实上的公开 API,之后就无法再更改了。3
上面的例子中,CompatiblePSEditions 同时声明了 Desktop(Windows PowerShell 5.1)和 Core(PowerShell 7),但仅凭声明并不会让它在两边都能运行。在 5.1 中使用 PSResourceGet 本身所需的准备工作整理在第 5 章,因 5.1 与 7 的模块探索路径不同而导致「明明装了却找不到」的问题则整理在第 7 章。如果打算同时兼容两个版本进行分发,请先阅读这两章。
4. 版本管理的确定方法
使用方能否放心地进行更新,取决于版本号的编排方式。请采用语义化版本管理(主版本号.次版本号.修订号),并务必用主版本号来体现破坏性变更。4
| 变更内容 | 应提升的部分 |
|---|---|
| 参数名变更、删除函数、返回值形式变更 | 主版本号(1.2.3 → 2.0.0) |
| 添加函数或参数(现有功能照常运行) | 次版本号(1.2.3 → 1.3.0) |
| 仅修复缺陷 | 修订号(1.2.3 → 1.2.4) |
如果想分发验证用的版本,可以使用预发行版(prerelease)。在清单的 PrivateData.PSData.Prerelease 中设置类似 beta1 这样的字符串(与版本号之间的连字符会自动添加,变成 1.3.0-beta1),这样在常规获取中就不会被下载到,只有明确指定 -Prerelease 时才会被安装。字符串中只能使用 ASCII 字母数字和连字符,不能使用句点或 +。4
关于破坏性变更的处理方式,接口设计的思路可以作为参考(参见《DLL・COM 接口的向后兼容性》)。
5. 搭建内部仓库 ── 文件共享就已足够
PSResourceGet 可以把文件共享上的文件夹当作仓库来使用。2 这是引入成本最低的构成方式。
首先确认一下前提条件。PowerShell 7.4 及以后版本已经随附,但 Windows PowerShell 5.1 中没有。要在 5.1 中使用接下来出现的命令(Register-PSResourceRepository 等),请事先引入该模块。1
# 【仅限 Windows PowerShell 5.1】引入 PSResourceGet。
# 5.1 和 7 中加载的模块路径不同,请在实际使用的版本(Edition)中执行
if (-not (Get-Module -ListAvailable -Name Microsoft.PowerShell.PSResourceGet)) {
Install-Module -Name Microsoft.PowerShell.PSResourceGet -Scope AllUsers -Force
}
# 【分发方・使用方都只需执行一次】注册内部仓库
# Trusted: 因为是公司内部分发物,所以当作已信任处理 / Priority: 优先于 PSGallery 进行搜索
$repo = @{
Name = 'KsInternal'
Uri = '\\fileserver\PSRepository'
Trusted = $true
Priority = 10
}
Register-PSResourceRepository @repo
Get-PSResourceRepository | Format-Table Name, Uri, Trusted, Priority
共享文件夹的访问权限,请设置为「只有分发负责人可以写入,使用者只能读取」。如果这里设置得过于宽松,就会变成任何人都能把任意代码分发到全公司的通路。共享访问权限和 NTFS 访问权限两者都要设置(实际生效的权限以较严格的一方为准)。9
# 在文件服务器一侧执行(需要管理员权限)
$path = 'D:\PSRepository'
$null = New-Item -Path $path -ItemType Directory -Force
# 共享: 使用者只能读取,只有分发负责人能够写入
New-SmbShare -Name 'PSRepository' -Path $path `
-ReadAccess 'EXAMPLE\Domain Users' -ChangeAccess 'EXAMPLE\模块分发负责人'
# NTFS: 不是"叠加",而是"用许可列表整体替换"。
# 由于 New-Item -Force 即使对既有文件夹也会执行成功,如果这个文件夹以前曾经
# 通过另一个共享公开过・或曾临时给某人赋予过更改权限,那么仅靠追加
# icacls /grant 是无法清除这条写入通路的。由于这是一个所有终端都会注册为
# Trusted 的仓库,残留下来的这一个人就能够向全公司分发代码
$acl = Get-Acl -Path $path
$acl.SetAccessRuleProtection($true, $false) # 切断继承,且不继承已有的继承 ACE
foreach ($ace in @($acl.Access)) { # 直接授予的 ACE 也一并清除
[void]$acl.RemoveAccessRuleSpecific($ace)
}
# 只保留这里列出的主体
$allow = @(
@{ Id = 'EXAMPLE\模块分发负责人'; Rights = 'Modify' } # 可以发布
@{ Id = 'EXAMPLE\Domain Users'; Rights = 'ReadAndExecute' } # 只能获取
@{ Id = 'BUILTIN\Administrators'; Rights = 'FullControl' }
@{ Id = 'NT AUTHORITY\SYSTEM'; Rights = 'FullControl' }
)
foreach ($a in $allow) {
$acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new(
$a.Id, $a.Rights, 'ContainerInherit, ObjectInherit', 'None', 'Allow'))
}
# 如果把"清空"和"重新写入"分成两次 Set-Acl,就会出现谁都无法访问的瞬间。
# 要一次性完成应用
Set-Acl -Path $path -AclObject $acl
icacls $path # 确认设置结果。用肉眼检查是否列出了意料之外的主体
请不要仅靠追加 icacls /grant 就了事。/grant 不会删除已有的 ACE。即使还有其他人拥有能写入这个文件夹的通路,它也会原样保留下来。9 而且,这个仓库的前提是所有终端都以 Trusted = $true 进行注册。放置在已信任仓库中的模块,无需确认就会被安装。这意味着残留下来的那一个人,就能向全公司的 PowerShell 执行环境分发任意代码,「只有分发负责人才能发布」这个前提到那一刻就崩塌了。
仅靠切断继承(SetAccessRuleProtection($true, $false))也是不够的。因为这个方法能处理的只是继承 ACE,直接授予该文件夹的 ACE 不在其处理范围之内。9 因此要像上面那样,先把直接授予的部分也一并清除,再重新写入许可列表中的主体。如果是新建的文件夹,结果是一样的,但这属于只有在沿用既有文件夹时才会出问题的一类隐患,所以把它写进操作步骤里会更可靠。
「分发负责人」请设置为组,而不是个人账户。因负责人调动而导致模块无法更新,这种停摆情况确实会发生。如果使用需要身份验证的 NuGet 兼容源,请把发给使用方的凭据设置为只读权限,并与用于发布的凭据(API 密钥)分开。关于共享文件夹的权限设计本身,也可以参考《用 PowerShell 盘点文件服务器》。
如果想要更正式地运维,可以注册 Azure Artifacts、GitHub Packages 这类 NuGet 兼容源。对于需要身份验证的仓库,可以构建成从 SecretManagement 的保管库中引用凭据的形式(参见《PowerShell 凭据的安全处理》)。2
6. 发布・获取・更新
# 【分发方】发布模块
Publish-PSResource -Path .\KsOps -Repository 'KsInternal'
# 【使用方】搜索并安装
Find-PSResource -Name 'KsOps' -Repository 'KsInternal'
Install-PSResource -Name 'KsOps' -Repository 'KsInternal' -Scope CurrentUser
# 固定版本安装(生产服务器推荐这种方式)
Install-PSResource -Name 'KsOps' -Version '1.2.3' -Repository 'KsInternal' -Scope AllUsers
# 更新
Update-PSResource -Name 'KsOps' -Repository 'KsInternal'
# 确认已安装的内容(终究只是"这台终端"的状态)
Get-InstalledPSResource -Name 'KsOps' | Format-Table Name, Version, Repository, InstalledDate
# 如果想了解全公司的安装情况,可以在各终端执行并汇总
Invoke-Command -ComputerName $servers -ScriptBlock {
Get-InstalledPSResource -Name 'KsOps' -ErrorAction SilentlyContinue |
Select-Object Name, Version
} | Sort-Object PSComputerName
-Scope 的区分使用很重要。任务计划程序无人值守执行时所用的模块,需要安装到 AllUsers(或服务账户自身的环境)中。「明明在自己的环境中能运行,唯独夜间批处理却报 无法识别 cmdlet 而失败」,其典型原因就是安装到了 CurrentUser 作用域。67
另外,由于 -Scope AllUsers 会写入所有用户共用的位置(Program Files 下),如果不是以管理员身份启动的 PowerShell 就会失败。7 如果在首次引入时出现「访问被拒绝」,请先确认是否已经提升了权限。
7. 模块的探索路径与 5.1/7 的差异
PowerShell 会从 $env:PSModulePath 中列出的文件夹中查找模块。Windows PowerShell 5.1 与 PowerShell 7 的默认路径是不同的。7
| 版本(Edition) | 用户作用域的默认路径 |
|---|---|
| Windows PowerShell 5.1 | %USERPROFILE%\Documents\WindowsPowerShell\Modules |
| PowerShell 7 | %USERPROFILE%\Documents\PowerShell\Modules |
对于两边都要用的模块,请在 CompatiblePSEditions 中同时声明 Desktop 和 Core,并在两种环境中都测试过之后再分发。兼容性验证方面,PSScriptAnalyzer 的 PSUseCompatibleSyntax 很有帮助(参见《用 PSScriptAnalyzer 守护 PowerShell 脚本质量》)。
出现问题时,需要确认以下这三点。
$env:PSModulePath -split ';' # 探索路径
Get-Module -Name KsOps -ListAvailable # 是否能找到・是哪个版本
(Get-Module KsOps -ListAvailable).ModuleBase # 实际读取的位置
8. 签名与执行策略
签名有目的不同的两种机制,如果混淆使用,就会出现「明明签了名却无法执行」的情况。8
| 机制 | 保证的是什么 | 是否满足执行策略 AllSigned |
|---|---|---|
Authenticode 签名(Set-AuthenticodeSignature) |
各个脚本文件的发布者与完整性 | 满足(每个文件都需要签名) |
目录签名(New-FileCatalog + 签名) |
模块整体(软件包)的完整性 | 不满足 |
执行策略验证的,是即将加载的 .ps1 / .psm1 本身的 Authenticode 签名。即使对目录文件(.cat)进行了签名,其中的脚本文件仍然处于未签名状态,因此在 AllSigned 环境下,执行会被阻止。因此,如果正在使用 AllSigned,就必须对每个被执行的文件进行签名。
# (1) AllSigned 环境下必须执行: 对每个被执行的文件进行 Authenticode 签名
Get-ChildItem .\KsOps -Recurse -Include *.ps1, *.psm1, *.psd1 | ForEach-Object {
Set-AuthenticodeSignature -FilePath $_.FullName -Certificate $cert `
-TimestampServer 'http://timestamp.digicert.com'
}
# (2) 此外,并用目录来检测整个软件包是否被篡改
New-FileCatalog -Path .\KsOps -CatalogFilePath .\KsOps\KsOps.cat -CatalogVersion 2
Set-AuthenticodeSignature -FilePath .\KsOps\KsOps.cat -Certificate $cert `
-TimestampServer 'http://timestamp.digicert.com'
# 使用方进行的验证(与执行策略无关,用于确认分发物是否损坏)
Test-FileCatalog -Path .\KsOps -CatalogFilePath .\KsOps\KsOps.cat -Detailed
目录的价值在于能够确认「获取到的整套模块,是否与分发时完全一致」,PSResourceGet 一侧也提供了用于验证签名・目录的 -AuthenticodeCheck。6 请把两者的职责区分开来理解:能否执行由 Authenticode 签名决定,分发物的完整性由目录负责。
添加时间戳后,即使签名证书的有效期过期,签名依然保持有效。执行策略与签名运维的全貌,整理在《PowerShell 的执行策略与脚本签名》中。
9. 应作为运维规则事先确定的事项
比起技术,运维更重要。请至少确定以下事项。
- 谁能够发布。拥有共享文件夹写入权限的人 = 能向全公司分发代码的人
- 变更历史写在哪里。在
ReleaseNotes(清单中的PrivateData.PSData)或 CHANGELOG 中,明确写出破坏性变更 - 生产服务器是否固定版本。无人值守执行所依赖的模块,固定版本后再有计划地更新,这样更安全
- 废弃的步骤。删除函数时,要提升主版本号,并提前预告弃用
- 通过测试和 lint 之后再发布。理想情况下,发布应该从 CI 中进行(参见《用 Pester 完善 PowerShell 测试》)
10. 实务定式(判断表)
| 论点 | 选项 | 判断标准 |
|---|---|---|
| 分发形式 | 共享文件夹的 .ps1 / 模块 + 仓库 |
能否管理版本和更新,是分界点 |
| 模块管理 | PowerShellGet 2.x / PSResourceGet | 7.4 及以后已随附。新写内容用 PSResourceGet1 |
| 仓库 | 文件共享 / NuGet 兼容源 | 先从文件共享开始。需要身份验证・审计时改用兼容源2 |
| 清单 | 省略 / 必需 | 没有版本号,运维就无法成立3 |
| 公开函数 | '*' / 用数组明确列出 |
出于探索性能和内部函数非公开的考虑3 |
| 安装位置 | CurrentUser / 无人值守执行用 AllUsers | 判断基准是服务账户能否看到7 |
| 生产环境的更新 | 自动更新 / 固定版本 + 有计划地更新 | 防止夜间批处理擅自用新版本运行 |
| 签名 | 无 / 各文件的 Authenticode 签名(+目录) | AllSigned 环境下必须按文件签名。目录用于完整性验证8 |
11. 总结
- 通过共享文件夹分发
.ps1,会导致版本・更新・依赖关系・公开范围全部变得无法管理。模块化与仓库分发可以解决这个问题。不过唯独「谁在使用」是个例外。文件共享仓库不会留下获取记录,因此请并用共享文件夹的读取审计、能获取下载统计信息的兼容源、或在各终端汇总Get-InstalledPSResource结果这三种方式之一。 - 清单是必需的。
FunctionsToExport请用数组明确列出,不要公开内部函数。 - 内部仓库只需把文件共享以 UNC 路径注册即可开始使用。写入权限的管理是实质上的安全边界。
- 发布用
Publish-PSResource,获取用Install-PSResource,更新用Update-PSResource。无人值守执行所用的模块,要安装到AllUsers作用域中。 - 5.1 和 7 的探索路径不同。如果要同时兼容两者,请声明
CompatiblePSEditions,并在两种环境中都进行测试。 AllSigned环境下,每个脚本文件都需要 Authenticode 签名。目录签名用于验证分发物的完整性,与能否执行是两回事。请务必添加时间戳。
示例代码下载
本文中出现的代码,已经整理成可以直接运行的形式提供下载。其中包含区分了公开/非公开的整套模块,以及分发到内部仓库的操作步骤。
本文的示例代码,已经在 PowerShell 7.6 中实际运行验证过(Pester 16 项)。执行 zip 中包含的 Invoke-SampleTests.ps1,您也可以在自己的环境中重现同样的验证。
# 语法解析 + 静态分析 + Pester 测试
./Invoke-SampleTests.ps1
配置值(路径、服务器名、租户 ID 等)均为示例。请勿直接在生产环境中运行,请根据贵公司的环境进行相应替换。
相关文章
- PowerShell 脚本的参数设计与模块化 ── 从「能运行的脚本」到「可以交给别人的脚本」
- PowerShell 的执行策略与脚本签名 ── 从「用 Bypass 蒙混过关」的运维方式毕业的实务指南
- 用 PSScriptAnalyzer 守护 PowerShell 脚本质量 ── 规则选型与 CI 导入
- 用 Pester 完善 PowerShell 测试 ── 让运维脚本不易损坏的实务方法
- PowerShell 中凭据的安全处理方式 ── 把明文密码逐出脚本
- Power Automate 的人员依赖对策 ── 让创建者离职后流程也不会停止
相关咨询领域
合同会社小村软件负责为企业内部脚本资产提供模块化改造、分发基础设施建设、人员依赖型运维的标准化,以及既有脚本可维护性改善等服务。
参考链接
-
Microsoft Learn,Package management for PowerShell。关于 Microsoft.PowerShell.PSResourceGet 是替代 PowerShellGet 与 PackageManagement 的模块、随附于 PowerShell 7.4 并可与传统的 PowerShellGet 2.2.5 并存、Windows PowerShell 5.1 也可以从 PowerShell Gallery 引入等内容。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn,Register-PSResourceRepository。关于可以在 -Uri 中指定本地文件夹、文件共享(UNC 路径)或 NuGet 兼容源的 URL 来注册仓库、通过 -Trusted 进行信任设置、通过 -Priority 设定搜索顺序,以及在需要身份验证的仓库中指定凭据等内容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,How to write a PowerShell module manifest。关于用 New-ModuleManifest 创建清单、RootModule・ModuleVersion・GUID・PowerShellVersion・CompatiblePSEditions・RequiredModules 等各个键、FunctionsToExport 等导出指定不应使用通配符而应明确列出的原因(命令探索的性能)等内容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn,Prerelease module versions。关于基于语义化版本管理的版本编号、通过 PrivateData.PSData.Prerelease 指定预发行版、预发行版不属于默认获取对象等内容。 ↩ ↩2 ↩3
-
Microsoft Learn,Publish-PSResource。关于把 -Path 指定的模块文件夹发布到仓库、通过 -Repository 指定发布目标、通过 -ApiKey 进行身份验证等内容。 ↩
-
Microsoft Learn,Install-PSResource。关于通过 -Name / -Version / -Repository 进行安装、通过 -Scope(CurrentUser / AllUsers)指定放置位置、通过 -TrustRepository 省略确认等内容。另请参见通过 Update-PSResource 进行更新的相关内容。 ↩ ↩2 ↩3
-
Microsoft Learn,about_PSModulePath。关于 PowerShell 会从 $env:PSModulePath 中列举的文件夹中搜索模块、Windows PowerShell 与 PowerShell 7 在用户作用域・全用户作用域的默认路径上有所不同等内容。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn,New-FileCatalog。关于可以生成包含文件夹下各文件哈希值的目录文件(.cat)、可以用 Set-AuthenticodeSignature 对目录进行签名、可以用 Test-FileCatalog 将目录与文件群进行比对以检测篡改等内容。执行策略验证的是执行对象脚本文件本身的 Authenticode 签名这一点,请参见 about_Execution_Policies(AllSigned 下只能执行由受信任发布者签名的脚本)以及 Set-AuthenticodeSignature(为文件添加 Authenticode 签名、通过 -TimestampServer 添加时间戳)。 ↩ ↩2 ↩3
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
用 winget + PowerShell 自动化 PC 装机 ── 让操作手册可执行
本文整理了让新员工电脑的初始设置具备可复现性的方法,涵盖通过 winget 进行应用安装与 export/import、WinGet Configuration 的声明式配置、用 PowerShell 补充的设置,直至无人值守执行时的注意事项。
不再使用 Write-Host ── PowerShell 的输出流与日志设计
本文整理 PowerShell 六个输出流的用法区分、Write-Host 存在的问题与正确的使用场景、函数返回值被污染的原因、通过 -Verbose 与 -InformationVariable 实现调用方控制,以及结构化日志的留存方法。
Microsoft Graph PowerShell 入门 ── AzureAD・MSOnline 废弃后的 Microsoft 365 运维
本文是将 AzureAD・MSOnline 模块废弃后的 Microsoft 365 运维迁移到 Microsoft Graph PowerShell 的实务指南,讲解连接与作用域设计、基于证书的无人值守执行,以及用户盘点、许可证汇总等常用脚本。
PowerShell 的安全加固 ── 日志・AMSI・语言模式・JEA
本文整理了在不禁止 PowerShell 的前提下安全使用它的实务要点,讲解启用脚本块日志与转录、禁用 AMSI 与旧版本引擎的绕过路径、通过语言模式加以限制,直至用 JEA 进行权限委任。
用 Get-WinEvent 实务排查事件日志 ── 筛选速度决定调查时间
本文整理用 PowerShell 提升 Windows 事件日志调查效率的方法,涵盖用 Where-Object 筛选为何缓慢、FilterHashtable 与 XPath 的区分使用、重启・登录・应用异常终止的调查方法,直至多台计算机的日志收集。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- PowerShellGet 和 PSResourceGet 有什么区别?应该使用哪一个?
- PSResourceGet(Microsoft.PowerShell.PSResourceGet)是替代传统 PowerShellGet 与 PackageManagement 的新一代模块管理机制,PowerShell 7.4 从一开始就内置了它。由于它可以与传统的 PowerShellGet 2.2.5 并存,因此可以在不破坏现有脚本的情况下完成迁移。如果是新写的内容,推荐使用命令名为 -PSResource 系列(Install-PSResource、Publish-PSResource 等)的 PSResourceGet。Windows PowerShell 5.1 也可以通过从 PowerShell Gallery 导入来使用它。
- 搭建内部仓库需要专用服务器吗?
- 不需要。最简单的方法是把文件共享上的文件夹注册为仓库,只需在 Register-PSResourceRepository 中指定 UNC 路径即可运作。既不需要专用服务器,也不需要数据库。如果作为组织需要发挥访问控制或审计的作用,或者需要从公司外部获取,则可以使用 Azure Artifacts、GitHub Packages 这类 NuGet 兼容源。现实的做法是先从文件共享开始,等出现需要时再迁移。
- 模块清单(.psd1)是必须的吗?
- 在实务中请将其视为必需项。虽然仅凭 .psm1 也能作为模块加载,但如果没有清单,就无法拥有版本号,也就无法知道「装的是哪个版本」。没有版本号,既无法管理更新,出现故障时也无法进行范围划分。此外,通过清单还可以明确要导出的函数、声明依赖模块、指定所支持的 PowerShell 版本和版本(Edition)。可以用 New-ModuleManifest 生成模板,创建成本也很低。
- 在 FunctionsToExport 中写 '*' 会有什么问题?
- 命令的自动探索会变慢,并且意料之外的内部函数也会被公开出去。PowerShell 在加载模块之前,需要知道哪个命令位于哪个模块中,如果使用通配符,就必须解析模块本体才能知道。用数组明确写出要公开的函数,就不需要这个解析过程了。另外,如果内部使用的辅助函数能够从外部调用,它实际上就变成了事实上的公开 API,之后就无法再更改了。
- 可以让使用方自动更新已分发的模块吗?
- 虽然可以用 Update-PSResource 进行更新,但对于业务脚本所依赖的模块,请谨慎设计其自动更新机制。因为这会导致无人值守执行的夜间批处理,在不知不觉中就用新版本运行了起来。在实务中,先在验证环境中确认新版本,再有计划地对生产环境进行更新,这样的运维方式更安全。如果无论如何都要自动化,请设置诸如固定主版本号进行更新(像 -Version '1.*' 这样指定范围)之类的限制措施。