PowerShell 技能连载 - 优先使用 WLAN 连接

当您同时连接到 LAN 和 WLAN,并且希望指定一个优先的连接,那么您可以调整网络跃点。网络跃点值越小,网卡的优先级越高。

要列出当前首选项,请使用 Get-NetIPInterface。例如要将所有 WLAN 网卡的跃点值设成 10,请使用这行代码:

1
2
Get-NetIPInterface -InterfaceAlias WLAN |
Set-NetIPInterface -InterfaceMetric 10

改变网络跃点值需要管理员权限。相关 cmdlet 在 Windows 10 和 Windows Server 2016/2019 中可用。

PowerShell 技能连载 - 向上下文菜单添加个人 PowerShell 命令

您可以针对文件类型,例如 PowerShell 文件,添加个人的上下文菜单。当您右键单击一个 .ps1 文件时,将显示这些上下文菜单命令。它们关联到个人账户,并且不需要管理员权限就可以设置。

以下是一个实现的脚本。只需要调整头两个变量:指定上下文菜单中需要出现的命令,以及需要执行的命令行。在这个命令中,使用 “%1“ 作为右键单击时 PowerShell 脚本路径的占位符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# specify your command name
$ContextCommand = "Open Script with Notepad"
# specify the command to execute. "%1" represents the file path to your
# PowerShell script
$command = 'notepad "%1"'


$baseKey = 'Registry::HKEY_CLASSES_ROOT\.ps1'
$id = (Get-ItemProperty $baseKey).'(Default)'
$ownId = $ContextCommand.Replace(' ','')
$contextKey = "HKCU:\Software\Classes\$id\Shell\$ownId"
$commandKey = "$ContextKey\Command"

New-Item -Path $commandKey -Value $command -Force
Set-Item -Path $contextKey -Value $ContextCommand

当您运行这段脚本时,将生成一个名为 “Open Script with Notepad” 的新的上下文菜单命令。您可以利用这个钩子并且设计任何命令,包括 GitHub 或备份脚本。

请注意:当您对 OpenWith 打开方式选择了一个非缺省的命令,那么自定义命令将不会在上下文菜单中显示。这个命令仅当记事本为缺省的 OpenWith 打开方式应用时才出现。

要移除所有上下文菜单扩展,请运行以下代码:

1
2
3
4
$baseKey = 'Registry::HKEY_CLASSES_ROOT\.ps1'
$id = (Get-ItemProperty $baseKey).'(Default)'
$contextKey = "HKCU:\Software\Classes\$id"
Remove-Item -Path $contextKey -Recurse -Force

PowerShell 技能连载 - 用 PowerShell 锁定屏幕

以下是一个名为 Lock-Screen 的 PowerShell 函数,它可以锁定屏幕,禁止用户操作。可以指定一个自定义消息,并且可以在锁定时将屏幕调暗。

以下是一个调用示例:

1
PS> Lock-Screen -LockSeconds 4 -DimScreen -Title 'Go away and come back in {0} seconds.'

以下是 Lock-Screen 的源码:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
Function Lock-Screen
{
[CmdletBinding()]
param
(
# number of seconds to lock
[int]
$LockSeconds = 10,

# message shown. Use {0} to insert remaining seconds
# do not use {0} for a static message
[string]
$Title = 'wait for {0} more seconds...',

# dim screen
[Switch]
$DimScreen
)

# when run without administrator privileges, the keyboard will not be blocked!

# get access to API functions that block user input
# blocking of keyboard input requires admin privileges
$code = @'
[DllImport("user32.dll")]
public static extern int ShowCursor(bool bShow);

[DllImport("user32.dll")]
public static extern bool BlockInput(bool fBlockIt);
'@

$userInput = Add-Type -MemberDefinition $code -Name Blocker -Namespace UserInput -PassThru

# get access to UI functionality
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore

# set window opacity
$opacity = 1
if ($DimScreen) { $opacity = 200 }

# create a message label
$label = New-Object -TypeName Windows.Controls.Label
$label.FontSize = 60
$label.FontFamily = 'Consolas'
$label.FontWeight = 'Bold'
$label.Background = 'Transparent'
$label.Foreground = 'Blue'
$label.VerticalAlignment = 'Center'
$label.HorizontalAlignment = 'Center'


# create a window
$window = New-Object -TypeName Windows.Window
$window.WindowStyle = 'None'
$window.AllowsTransparency = $true
$color = [Windows.Media.Color]::FromArgb($opacity, 0,0,0)
$window.Background = [Windows.Media.SolidColorBrush]::new($color)
$window.Opacity = 0.8
$window.Left = $window.Top = 0
$window.WindowState = 'Maximized'
$window.Topmost = $true
$window.Content = $label

# block user input
$null = $userInput::BlockInput($true)
$null = $userInput::ShowCursor($false)

# show window and display message
$null = $window.Dispatcher.Invoke{
$window.Show()
$LockSeconds..1 | ForEach-Object {
$label.Content = ($title -f $_)
$label.Dispatcher.Invoke([Action]{}, 'Background')
Start-Sleep -Seconds 1
}
$window.Close()
}

# unblock user input
$null = $userInput::ShowCursor($true)
$null = $userInput::BlockInput($false)
}

请注意 Lock-Screen 需要管理员权限才能完全禁止用户输入。

PowerShell 技能连载 - 禁止用户输入

如果一个 PowerShell 脚本需要进行危险的操作,而且用户操作必须被禁止,那么您可以使用 API 来临时禁止所有键盘输入。锁定键盘输入不需要管理员权限。

以下是一个演示如何阻止所有键盘输入 4 秒钟的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#requires -RunAsAdministrator
# when run without administrator privileges, the keyboard will not be blocked!

# get access to API functions that block user input
# blocking of keyboard input requires administrator privileges
$code = @'
[DllImport("user32.dll")]
public static extern bool BlockInput(bool fBlockIt);
'@

$userInput = Add-Type -MemberDefinition $code -Name Blocker -Namespace UserInput -PassThru

# block user input
$null = $userInput::BlockInput($true)

Write-Warning "Your input has been disabled for 4 seconds..."
Start-Sleep -Seconds 4

# unblock user input
$null = $userInput::BlockInput($false)

PowerShell 技能连载 - 向编码的命令传递参数

对 PowerShell 代码编码是一种在 PowerShell 环境之外运行 PowerShell 代码的方法,例如在批处理文件中。以下是一些读取 PowerShell 代码,对它编码,并且通过命令行执行它的示例代码:

1
2
3
4
5
6
7
8
9
10
$command = {
Get-Service |
Where-Object Status -eq Running |
Out-GridView -Title 'Pick a service that you want to stop' -PassThru |
Stop-Service
}

$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
"powershell.exe -noprofile -encodedcommand $encodedCommand" | clip

当执行这段代码之后,您会发现剪贴板里有 PowerShell 命令。代码类似这样:

1
powershell.exe -noprofile -encodedcommand DQAKAEcAZQB0AC0AUwBlAHIAdgBpAGMAZQAgAHwAIAANAAoAIAAgACAAIABXAGgAZQByAGUALQBPAGIAagBlAGMAdAAgAFMAdABhAHQAdQBzACAALQBlAHEAIABSAHUAbgBuAGkAbgBnACAAfAAgAA0ACgAgACAAIAAgAE8AdQB0AC0ARwByAGkAZABWAGkAZQB3ACAALQBUAGkAdABsAGUAIAAnAFAAaQBjAGsAIABhACAAcwBlAHIAdgBpAGMAZQAgAHQAaABhAHQAIAB5AG8AdQAgAHcAYQBuAHQAIAB0AG8AIABzAHQAbwBwACcAIAAtAFAAYQBzAHMAVABoAHIAdQAgAHwAIAANAAoAIAAgACAAIABTAHQAbwBwAC0AUwBlAHIAdgBpAGMAZQANAAoA

当您打开一个新的 cmd.exe 窗口,您可以将这段代码粘贴到控制台并且执行纯的 PowerShell 代码。您可以在任何有足够空间容纳整行代码的地方执行编码过的命令。因为长度限制,编码过的命令在快捷方式文件(.lnk 文件)以及开始菜单中的运行对话框中工作不正常。

还有一个额外的限制:无法传递参数到编码过的命令。除非使用一个很酷的技能。首先,在代码中加入一个 param() 块,然后使该参数成为必选。然后,从一个外部的 PowerShell 通过管道将参数传递进去。

以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$command = {
param
(
[Parameter(Mandatory)]
[string]
$FirstName,

[Parameter(Mandatory)]
[string]
$LastName
)
"Hello, your first name is $FirstName and your last name is $lastname!"
}

$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
"powershell.exe -noprofile -command 'Tobias', 'Weltner' | powershell -noprofile -encodedcommand $encodedCommand" | clip

命令看起来类似这样:

1
powershell.exe -noprofile -command 'Tom', 'Tester' | powershell -noprofile -encodedcommand DQAKAHAAYQByAGEAbQANAAoAKAANAAoAIAAgACAAIABbAFAAYQByAGEAbQBlAHQAZQByACgATQBhAG4AZABhAHQAbwByAHkAKQBdAA0ACgAgACAAIAAgAFsAcwB0AHIAaQBuAGcAXQANAAoAIAAgACAAIAAkAEYAaQByAHMAdABOAGEAbQBlACwADQAKAA0ACgAgACAAIAAgAFsAUABhAHIAYQBtAGUAdABlAHIAKABNAGEAbgBkAGEAdABvAHIAeQApAF0ADQAKACAAIAAgACAAWwBzAHQAcgBpAG4AZwBdAA0ACgAgACAAIAAgACQATABhAHMAdABOAGEAbQBlAA0ACgApAA0ACgAiAEgAZQBsAGwAbwAsACAAeQBvAHUAcgAgAGYAaQByAHMAdAAgAG4AYQBtAGUAIABpAHMAIAAkAEYAaQByAHMAdABOAGEAbQBlACAAYQBuAGQAIAB5AG8AdQByACAAbABhAHMAdAAgAG4AYQBtAGUAIABpAHMAIAAkAGwAYQBzAHQAbgBhAG0AZQAhACIADQAKAA==

当您运行这段代码,参数 “Tom” 和 “Tester” 将通过管道传递给执行编码过命令的 PowerShell。由于参数是必选的,所以管道的元素会传递给提示符,并且被编码的命令处理。

PowerShell 技能连载 - 删除无法删除的注册表键

删除注册表键通常很简单,用 Remove-Item 就可以了。然而,有时你会遇到一些无法删除的注册表键。在这个技能中我们将演示一个例子,并且提供一个解决方案。

在前一个技能中我们解释了当定义了一个非缺省的打开方式之后,PowerShell 文件的“使用 PowerShell 运行”上下文命令可能会丢失,而且出现了这个注册表键:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice

当您在文件管理器中右键点击一个 PowerShell 脚本,然后通过“选择其他应用”,选择一个 ISE 或 VSCode 之外的非缺省应用,并选中“始终使用此应用打开 .ps1 文件”复选框,来使用其他应用来打开 PowerShell 文件,那么注册表中就会创建上述注册表键。

当上述注册表键存在时,上下文菜单中缺省的 PowerShell 命令,例如“使用 PowerShell 运行”将不可见。用注册表删除这个键修复它很容易,但出于某种未知原因用 PowerShell 命令来删除会失败。所有 .NET 的方法也会失败。

以下命令会执行失败:

1
Remove-Item -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice

以下命令也会执行失败(这是 .NET 的等价代码):

1
2
3
$parent = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1', $true)
$parent.DeleteSubKeyTree('UserChoice',$true)
$parent.Close()

要删除这个键,您需要显示地调用 DeleteSubKey() 方法来代替 DeleteSubKeyTree()。明显地,在那个键中有一些不可见的异常子键导致该键无法删除。

当您只是删除该键(不包含它的子键,虽然子键并不存在),该键可以正常删除,并且 PowerShell 的“使用 PowerShell 运行“命令就恢复了:

1
2
3
$parent = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1', $true)
$parent.DeleteSubKey('UserChoice', $true)
$parent.Close()

另一方面:由于这段代码只操作了用户配置单元,所以不需要任何特权,然而在 regedit.exe 中修复则需要管理员特权。

The Largest PowerShell Community in China has 1716 Members Now!

The largest PowerShell community “PowerShell Tech Interact” in China has 1716 members up to March 29, 2019!

The goal of this community is to:

  1. Help new PowerShellers to get up to PowerShell language
  2. Help every PowerSheller overcome any PowerShell technical difficulties
  3. Share code and information to accelerate the learning process

To contact with the community manager, please visit this MVP link.

Join Us Now! (you may need to install QQ client first)

PowerShell 技能连载 - 修复 PowerShell 上下文菜单

当您在文件管理器中右键点击一个 PowerShell 脚本文件时,通常会见到一个名为“使用 PowerShell 运行”的上下文菜单项,可以通过它快速地执行 PowerShell 脚本。

然而,在某些系统中,“使用 PowerShell 运行“命令缺失了。原因是当您定义了一个非缺省的“打开方式”命令,那么该命令就会隐藏。要修复它,您只需要删除这个注册表键:

1
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice

在 regedit.exe 中删除这个键很简单,而且当这个键移除之后,“使用 PowerShell 运行”上下文菜单就再次可见了。

不过实际中在 PowerShell 删除这个注册表键却不太容易。以下这些命令执行都会失败,报告某些子项无法删除:

1
2
3
4
5
6
7
8
9
PS C:\> Remove-Item HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice

PS C:\> Remove-Item Registry::HKEY:CURRENT_USER:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice

PS C:\> Remove-Item 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice'

PS C:\> Remove-Item HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\'.ps1'\UserChoice

PS C:\> Remove-Item HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice -Recurse -Force

我们明天会提供一个解决方案来应对这种情况。

PowerShell 技能连载 - 让新手运行 PowerShell 脚本

假设您要把一段 PowerShell 脚本传给一个没有经验的用户。如何确保对方正确地运行了您的脚本呢?由于操作系统和组策略的限制,有可能并没有一个上下文菜单命令来运行 PowerShell 脚本。这个用户也有可能改变了执行策略设置。

不过有一个非常简单的解决方案:将您的脚本和一个快捷方式一起分发。这个快捷方式包含所有必须的命令行开关,可以通过双击快速执行。您甚至可以为快捷方式分配一个漂亮的图标。然而,您需要一些小技巧来实现。典型的快捷方式使用绝对路径,所以当您将文件发送给客户时这个快捷方式可能无法执行,因为您无法知道客户将把这个文件保存到哪里。诀窍是在快捷方式中使用相对路径。

只需要确保调整了第一行指向希望执行的脚本,然后运行这段代码,就可以了。

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
# specify the path to your PowerShell script
$ScriptPath = "C:\test\test.ps1"

# create a lnk file
$shortcutPath = [System.IO.Path]::ChangeExtension($ScriptPath, "lnk")
$filename = [System.IO.Path]::GetFileName($ScriptPath)

# create a new shortcut
$shell = New-Object -ComObject WScript.Shell
$scut = $shell.CreateShortcut($shortcutPath)
# launch the script with powershell.exe:
$scut.TargetPath = "powershell.exe"
# skip profile scripts and enable execution policy for this one call
# IMPORTANT: specify only the script file name, not the complete path
$scut.Arguments = "-noprofile -executionpolicy bypass -file ""$filename"""
# IMPORTANT: leave the working directory empty. This way, the
# shortcut uses relative paths
$scut.WorkingDirectory = ""
# optinally specify a nice icon
$scut.IconLocation = "$env:windir\system32\shell32.dll,162"
# save shortcut file
$scut.Save()

# open shortcut file in File Explorer
explorer.exe "/select,$shortcutPath"

这个快捷方式就放在您的 PowerShell 脚本相邻的位置。它使用相对路径,由于我们保证快捷方式和 PowerShell 脚本放在相同的路径,所以它能够完美地工作——这样您可以将两个文件打包,然后将它们发送给客户。当他解压了文件,快捷方式仍然可以工作。您甚至可以将快捷方式改为想要的名字,例如“双击我执行”。

重要:快捷方式使用相对路径来确保这个解决方案便携化。如果您将快捷方式移动到脚本之外的文件夹,那么该快捷方式显然不能工作。。

PowerShell 技能连载 - 修复 PowerShellGet 发布

如果您在使用 Publish-Module 来将您的模块发布到 PowerShell 仓库,并且您一直收到不支持的命令的错误信息,那么可能需要重新安装管理模块上传的可执行程序。当这些可执行程序太旧时,它们可能不能再能和最新的 PowerShellGet 模块同步。

运行这段代码,以管理员权限(对所有用户有效)下载并更新 nuget.exe:

1
2
3
4
5
6
7
$Path = "$env:ProgramData\Microsoft\Windows\PowerShell\PowerShellGet"
$exists = Test-Path -Path $Path
if (!$exists)
{
$null = New-Item -Path $Path -ItemType Directory
}
Invoke-WebRequest -Uri https://aka.ms/psget-nugetexe -OutFile "$Path\NuGet.exe"

运行这段代码只针对当前用户下载并安装 nuget.exe:

1
2
3
4
5
6
7
$Path = "$env:LOCALAPPDATA\Microsoft\Windows\PowerShell\PowerShellGet"
$exists = Test-Path -Path $Path
if (!$exists)
{
$null = New-Item -Path $Path -ItemType Directory
}
Invoke-WebRequest -Uri https://aka.ms/psget-nugetexe -OutFile "$Path\NuGet.exe"