PowerShell 技能连载 - 删除 Microsoft Teams 缓存数据

如果您使用 Microsoft Teams 进行视频会议,则有时可能需要清理缓存文件并删除驻留在许多子文件夹中的残留数据。

您可以调整上一个示例中的代码来进行清理:

1
2
3
4
5
6
7
8
9
# the folder that contains the Microsoft Teams data
$parentFolder = "$env:userprofile\AppData\Roaming\Microsoft\Teams\*"
# list of subfolders that cache data
$list = 'application cache','blob storage','databases','GPUcache','IndexedDB','Local Storage','tmp'

# delete the folders found in the list
Get-ChildItem $parentFolder -Directory |
Where-Object name -in $list |
Remove-Item -Recurse -Verbose

如果您具有管理员权限,并想为所有用户删除缓存的 Microsoft Teams 数据,请按如下所示更改 $parentFolder

1
$parentFolder = "c:\users\*\AppData\Roaming\Microsoft\Teams\*"

PowerShell 技能连载 - 删除多个子文件夹

有时,可能有必要删除给定文件夹中的一组子文件夹。这是一段简单的代码,可从文件夹名称列表中删除文件夹。

警告:此代码将删除 $list 中列出的子文件夹,而无需进一步确认。如果子文件夹没有退出,则什么也不会发生。

1
2
3
4
5
6
7
8
9
10
11
# the folder that contains the subfolders to remove
# (adjust to your needs)
$parentFolder = $env:userprofile

# list of folder names to remove (adjust to your needs)
$list = 'scratch', 'temp', 'cache'

# delete the folders found in the list
Get-ChildItem $parentFolder -Directory |
Where-Object name -in $list |
Remove-Item -Recurse -Force -Verbose

如果要删除父文件夹内任何位置的子文件夹,请通过向 Get-ChildItem 添加 -Recurse 来扩展搜索以通过子文件夹进行递归。

下面的示例在父文件夹的文件夹结构内的任何位置删除 $list 中的子文件夹。

请注意,这样做可能有风险,因为您现在正在查找可能并不是由您所有并控制的文件夹内的子文件夹,这就是为什么我们向 -Remove-Item 添加 -WhatIf 的原因:该代码实际上不会删除子文件夹,而只是报告其内容完成了。如果您知道自己在做什么,请删除该参数:

1
2
3
4
5
6
7
8
9
# the folder that contains the subfolders to remove
$parentFolder = $env:userprofile
# list of folder names to remove
$list = 'scratch', 'temp', 'cache'

# delete the folders found in the list
Get-ChildItem $parentFolder -Directory -Recurse |
Where-Object name -in $list |
Remove-Item -Recurse -Verbose -WhatIf

PowerShell 技能连载 - 管理自启动项

要在 Windows 上管理自动启动程序,不必费心编写大量脚本。 PowerShell 可以直接打开任务管理器中包含的自动启动管理器,您只需执行以下操作即可:

1
PS C:\> Taskmgr /7 /startup

这将打开一个窗口,并列出所有自动启动程序及其对启动时间的影响。要阻止这些程序中的任何一个自动启动,请单击列表中的一个程序,然后单击右下角的“禁用”按钮。

如果您愿意,可以将命令转换为函数,然后将其放入配置文件脚本中,以备不时之需:

1
2
3
4
function Show-Autostart
{
Taskmgr /7 /startup
}

PowerShell 技能连载 - 下载有用的脚本

PowerShell Gallery 不仅提供带有新 PowerShell 命令的公共模块,而且还提供公共脚本。在投入时间之前,您可能希望调查是否有人创建了可以执行所需功能的 PowerShell 代码。

这是一个简单的示例,说明了如何通过 PowerShell Gallery 搜索和下载脚本的工作方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
# create temporary folder
$destination = Join-Path -Path $env:temp -ChildPath Scripts
$exists = Test-Path -Path $destination
if (!$exists) { $null = New-Item -Path $destination -ItemType Directory }

# offer to download scripts
Find-Script -Name Get-* | Select-Object -Property Name, Description |
Out-GridView -Title 'Select script to download' -PassThru |
ForEach-Object {
Save-Script -Path $destination -Name $_.Name -Force
$scriptPath = Join-Path -Path $destination -ChildPath "$($_.Name).ps1"
ise $scriptPath
}

当您运行此代码时,Find-Script 将查找使用 Get 动词的任何脚本。您当然可以更改此设置并搜索您喜欢的任何内容。接下来,所有与您的搜索匹配的脚本都将显示在网格视图窗口中。现在,您可以选择一个或多个听起来很有趣的东西。

然后,PowerShell 将所选脚本下载到一个临时文件夹,然后在 PowerShell ISE 中打开这些脚本。现在,您可以查看源代码并从中选择有用的内容。

注意:由于 Out-GridView 中的 bug,您需要等待所有数据到达后才能选择脚本。如果选择脚本并在 Out-GridView 仍在接收数据的同时单击“确定”,则不会传递任何数据,也不会下载脚本。

要解决此问题,请等待足够长的时间以完全填充网格视图窗口,或者通过首先收集所有数据然后仅在网格视图窗口中显示数据来关闭实时模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# create temporary folder
$destination = Join-Path -Path $env:temp -ChildPath Scripts
$exists = Test-Path -Path $destination
if (!$exists) { $null = New-Item -Path $destination -ItemType Directory }

# offer to download scripts
Write-Warning "Retrieving scripts, hang on..."
# collecting all results in a variable to work around
# Out-GridView bug
$all = Find-Script -Name Get-* | Select-Object -Property Name, Description
$all | Out-GridView -Title 'Select script to download' -PassThru |
ForEach-Object {
Save-Script -Path $destination -Name $_.Name -Force
$scriptPath = Join-Path -Path $destination -ChildPath "$($_.Name).ps1"
ise $scriptPath
}

PowerShell 技能连载 - 加速 PowerShell 远程操作

PowerShell 远程操作功能极其强大:借助 Invoke-Command,您可以将任意 PowerShell 代码发送到一台或多台远程计算机,并在其中并行执行。

在 Windows 服务器上,通常会启用 PowerShell 远程处理,因此您所需要的只是管理员权限。这是一个简单的示例:

1
PS> Invoke-Command -ScriptBlock { "I am running on $env:computername!" } -ComputerName server1 -Credential domain\adminuser

本技巧不是关于设置 PowerShell 远程操作的,因此我们假定上述调用确实对您有效。相反,我们将重点放在 PowerShell 远程操作的最重要瓶颈之一上,以及如何解决它。

这是一个访问远程系统并从 Windows 文件夹中转储 DLL 的代码。使用一个 stopwatch 测量所需时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# change this to the computer you want to access
$computername = 'server123'

# ask for a credential that has admin privileges on the target side
$cred = Get-Credential -Message "Log on as Administrator to $computername!"

# check how long it takes to retrieve information
$stopwatch = [System.Diagnostics.Stopwatch]::new()
$stopwatch.Start()

$result = Invoke-Command -ScriptBlock {
Get-ChildItem -Path c:\windows\system32 -Filter *.dll
} -ComputerName $computername -Credential $cred

$stopwatch.Stop()
$stopwatch.Elapsed

令人惊讶的是,此代码运行了很长时间。当我们在自己的本地计算机上尝试它时,花费了 95 秒。从 Invoke-Command 返回信息的速度可能非常慢,因为对象需要序列化为 XML 以跨越进程边界,并在它们返回调用者时重新反序列化。

为了加快远程处理速度,请记住这一点,并仅返回尽可能少的信息。通常,信息量很容易减少。

例如,如果您确实需要 Windows 文件夹中所有 DLL 文件的列表,则很可能只需要一些属性,例如 path 和 size。通过添加一个 Select-Object 并指定您真正需要的属性,之前花费了 95 秒的相同代码现在可以在不到一秒钟的时间内运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# change this to the computer you want to access
$computername = 'server123'

# ask for a credential that has admin privileges on the target side
$cred = Get-Credential -Message "Log on as Administrator to $computername!"

# check how long it takes to retrieve information
$stopwatch = [System.Diagnostics.Stopwatch]::new()
$stopwatch.Start()

$result = Invoke-Command -ScriptBlock {
Get-ChildItem -Path c:\windows\system32 -Filter *.dll |
# REDUCE DATA BY SPECIFYING THE PROPERTIES YOU REALLY NEED!
Select-Object -Property FullName, LastWriteTime
} -ComputerName $computername -Credential $cred

$stopwatch.Stop()
$stopwatch.Elapsed

PowerShell 技能连载 - 动态创建 PowerShell 函数

New-Item 可以在任何PowerShell驱动器上创建新对象,包括功能:具有所有 PowerShell 功能的驱动器。

如果需要,您可以在代码内部动态定义新功能。这些新功能将仅存在于定义它们的作用域内。要使它们成为脚本全局脚本,请添加脚本:作用域标识符。这是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
function New-DynamicFunction
{
# creates a new function dynamically
$Name = 'Test-NewFunction'
$Code = {
"I am a new function defined dynamically."
Write-Host -ForegroundColor Yellow 'I can do whatever you want!'
Get-Process
}

# create new function in function: drive and set scope to "script:"
$null = New-Item -Path function: -Name "script:$Name" -Value $Code
}

要测试效果,请运行 New-DynamicFunction。完成后,会有一个称为 Test-NewFunction 的新函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# this function does not (yet) exist:
PS> Test-NewFunction
Test-NewFunction : The term 'Test-NewFunction' is not recognized as the name of a cmdlet, function, script
file, or operable program. Check the spelling of the name, or if a path was included, verify that the path
is correct and try again.

PS> New-DynamicFunction

# now the function exists:
PS> Test-NewFunction
I am a new function defined dynamically.
I can do whatever you want!

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    219      18     3384      10276      89,52  13088   1 AppleMobileDeviceProcess
    574      35    29972      84500       3,50   8548   1 ApplicationFrameHost
    147       9     1376       5644              4472   0 armsvc

请注意,我们如何将新功能的代码定义为大括号中的脚本块。这不是必需的。您还可以将其定义为纯文本字符串,这可以为您提供更多灵活性来编写新函数的源代码:

1
2
3
4
5
6
7
8
9
10
11
$a = "not"
$b = "AD"
$c = "EP"

# use -Force to overwrite existing functions
$null = New-Item -Force -Path function: -Name "script:Test-This" -Value @"
'Source code can be a string.'
$a$c$b
"@

Test-This

还要注意,除非指定 -Force,否则 New-Item 不会覆盖现有函数。

PowerShell 技能连载 - 验证用户账户密码(第 3 部分)

在前面的部分中,我们创建了 Test-Password 函数,该函数可以测试本地和远程用户的密码。

在最后一部分,我们将错误处理添加到 Test-Password 函数中,以便当用户输入不存在或不可用的域时它可以正常响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function Test-Password
{
param
(
[Parameter(Mandatory)]
[string]
$Domain,

[Parameter(Mandatory)]
[string]
$Username,

[Parameter(Mandatory)]
[SecureString]
$Password
)

# load assembly for required system commands
Add-Type -AssemblyName System.DirectoryServices.AccountManagement


# is this a local user account?
$local = $Domain -eq $env:COMPUTERNAME

if ($local)
{
$context = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}
else
{
$context = [System.DirectoryServices.AccountManagement.ContextType]::Domain
}

# convert SecureString to a plain text
# (the system method requires clear-text)
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$plain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

# test password

try
{
$PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($context, $Domain)
$PrincipalContext.ValidateCredentials($UserName,$plain)
}
catch [System.DirectoryServices.AccountManagement.PrincipalServerDownException]
{
throw "Test-Password: Domain '$Domain' not found."
}
}

运行此代码后,将有一个新命令 “Test-Password“。运行它时,系统会提示您输入域(或用于测试本地帐户的本地计算机名称),用户名和掩码密码。

下面是在 PowerShell 7 中运行的示例:第一次调用(测试本地帐户)成功,并产生 $true。第二个调用(指定一个不可用的域)失败,并显示一条自定义错误消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS C:\> Test-Password

cmdlet Test-Password at command pipeline position 1
Supply values for the following parameters:
Domain: dell7390
Username: remotinguser2
Password: ***********
True
PS C:\> Test-Password

cmdlet Test-Password at command pipeline position 1
Supply values for the following parameters:
Domain: doesnotexist
Username: testuser
Password: ********
Exception:
Line |
  47 |        throw "Test-Password: Domain '$Domain' not found."
     |        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Test-Password: Domain 'doesnotexist' not found.

PowerShell 技能连载 - 验证用户账户密码(第 2 部分)

在上一个技巧中,我们展示了 PowerShell 如何验证和测试用户帐户密码,但是该密码是用纯文本形式传入的。让我们进行更改,以使 PowerShell 在输入密码时将其屏蔽。

您可以重用以下用于其他任何 PowerShell 功能的策略,以提示用户输入隐藏的输入。

Here is the function Test-Password:
这是 Test-Password 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function Test-Password
{
param
(
[Parameter(Mandatory)]
[string]
$Domain,

[Parameter(Mandatory)]
[string]
$Username,

[Parameter(Mandatory)]
[SecureString]
$Password
)

# load assembly for required system commands
Add-Type -AssemblyName System.DirectoryServices.AccountManagement


# is this a local user account?
$local = $Domain -eq $env:COMPUTERNAME

if ($local)
{
$context = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}
else
{
$context = [System.DirectoryServices.AccountManagement.ContextType]::Domain
}

# convert SecureString to a plain text
# (the system method requires clear-text)
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$plain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

# test password
$PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($context, $Domain)
$PrincipalContext.ValidateCredentials($UserName,$plain)
}

运行它时,系统会提示您输入域(输入计算机名称以测试本地帐户)和用户名。密码以星号方式提示。密码正确时,该函数返回 $true,否则返回 $false

请注意如何将提示的 SecureString 在内部转换为纯文本。每当需要屏蔽的输入框时,都可以使用类型为 SecureString 的必需参数,然后在函数内部将 SecureString 值转换为纯文本。

PowerShell 技能连载 - 验证用户账户密码(第 1 部分)

PowerShell 可以为您测试用户帐户密码。这适用于本地帐户和域帐户。这是一个称为 Test-Password 的示例函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function Test-Password
{
param
(
[Parameter(Mandatory)]
[string]
$Domain,

[Parameter(Mandatory)]
[string]
$Username,

[Parameter(Mandatory)]
[string]
$Password

)

# load assembly for required system commands
Add-Type -AssemblyName System.DirectoryServices.AccountManagement


# is this a local user account?
$local = $Domain -eq $env:COMPUTERNAME

if ($local)
{
$context = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}
else
{
$context = [System.DirectoryServices.AccountManagement.ContextType]::Domain
}
# test password
$PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($context, $Domain)
$PrincipalContext.ValidateCredentials($UserName,$Password)
}

它需要域名(或本地计算机名),用户名和密码。密码正确时,该函数返回 $true

请注意,此处使用的系统方法需要明文密码。输入明文密码并不安全,因此在我们的下一个技巧中,我们将改进以隐藏方式提示密码的功能。

PowerShell 技能连载 - 读取 4K 哈希

Windows 操作系统可以通过所谓的 4K 哈希来唯一标识:这是一个特殊的哈希字符串,大小为 4000 字节。您可以将此哈希与 “Windows Autopilot” 一起使用,以添加物理机和虚拟机。

4K 哈希是注册 Windows 机器所需的一条信息,但是它本身也很有趣,它也可以唯一地标识 Windows 操作系统用于其他目的。下面的代码通过 WMI 读取唯一的 4K 哈希(需要管理员特权):

1
2
3
4
$info = Get-CimInstance -Namespace root/cimv2/mdm/dmmap -Class MDM_DevDetail_Ext01 -Filter "InstanceID='Ext' AND ParentID='./DevDetail'"
$4khh = $info.DeviceHardwareData

$4khh

由于 Get-CimInstance 支持远程处理,因此您也可以从远程系统(即虚拟机)读取此值。