PowerShell 技能连载 - Office365邮箱恢复删除(第 2 部分)

假设有人离开了公司,您删除了其Office365用户帐户。事实证明,这也会删除附加的邮箱。如果您想将此邮箱附加到其他人身上,即仍然能够访问重要的公司或客户数据,则可以按照以下步骤操作。

首先,请检查邮箱是否已“软删除”并且仍然可以恢复:

1
Get-Mailbox -SoftDeletedMailbox | Select-Object Name,ExchangeGUID

此列表中每个可恢复的邮箱都具有唯一的GUID。要将此邮箱附加到其他人身上,还需要找出要将已删除的邮箱附加到其中的仍处于活动状态的邮箱的GUID:

1
$liveMailbox = Get-Mailbox existingPerson@somecompany.onmicrosoft.com | Select-Object Name,ExchangeGUID

接下来,在获取两个GUID后,发出请求以将旧邮件箱数据连接到新邮件箱,并指定目标根文件夹(例如“旧邮件内容”)。这将是新邮件箱显示在其下面的电子邮件文件夹:

1
New-MailboxRestoreRequest -SourceMailbox [ENTER_GUID_OF_SOFTDELETED_MAILBOX] -TargetMailbox $liveMailbox.ExchangeGUID -AllowLegacyDNMismatch -TargetRootFolder "Old Mailbox Content

PowerShell 技能连载 - Office365邮箱恢复删除(第 1 部分)

如果您已删除 Office365 用户帐户,但后来意识到仍需要其邮箱中的数据,则可能可以恢复该邮箱。

首先,请检查邮箱是否已“软删除”。下一个命令列出了所有可恢复的邮箱:

1
Get-Mailbox -SoftDeletedMailbox | Select UserPrincipalName, WhenSoftDeleted

每个邮箱都与用户主体名称相关联。要取消删除邮箱,您需要临时重新创建此帐户。接下来,您可以撤消邮件箱的删除操作。只需在以下命令中替换主体名称即可:

1
Undo-SoftDeletedMailbox -SoftDeletedObject username@company.onmicrosoft.com

PowerShell 技能连载 - 重命名属性(简单方法)

Select-Object 不仅可以选择属性,还可以重命名。假设您需要获取一个文件夹中的文件列表,并包含文件大小信息,这行代码就足够了:

1
2
PS> Get-ChildItem -Path c:\windows -File | Select-Object -Property Length, FullName
}

现在你只能看到两个选定的属性“Length”和“FullName”。但是如果您希望此信息以不同的名称显示,例如“Size”和“Path”,该怎么办?

只需通过提交每个属性的哈希表来重命名属性。在每个哈希表内,使用“Name”键来重命名属性,“Expression = '[OriginalPropertyName]' }”键保持原始属性内容不变:

1
Get-ChildItem -Path c:\windows -File | Select-Object -Property @{Name='Size';Expression='Length'}, @{Name='Path';Expression='FullName'}

这样,您可以轻松地重命名任何对象中的任何属性。

PowerShell 技能连载 - 常见陷阱和奇怪结果:比较运算符

你能发现下面代码中的错误吗?

1
2
3
4
5
6
7
8
9
10
$result = 'NO'

if ($result = 'YES')
{
'Result: YES'
}
else
{
'Result: NO'
}

无论 $result 包含“NO”还是“YES”,它总是返回“Result: YES”。

从其他脚本或编程语言转换到 PowerShell 的人经常遇到这个错误:在 PowerShell 中,“=”是一个独占赋值运算符,而对于比较,您需要使用“-eq”。因此,正确的代码(和简单的修复)应该是这样的:

1
2
3
4
5
6
7
8
9
10
$result = 'NO'

if ($result -eq 'YES')
{
'Result: YES'
}
else
{
'Result: NO'
}

让我们看一下为什么最初的代码总是返回“Result: YES”。

当您意外使用赋值运算符而不是比较运算符时,您将不会得到任何结果,因此这个 NULL 值应该真正评估为 $false,并且条件应该始终返回“Result: NO”。然而,情况恰恰相反。

这是由于另一个PowerShell鲜为人知的怪异之处:当您分配一个值并将分配放在大括号中时,分配也将被返回。最初错误的代码将使用实际响应于赋值值的条件。

每当您向$result(所有都被解释为FALSE)分配0、’’、$false 或 $null 时,代码都会返回“Result: NO”。任何其他赋值都会返回“Result: YES”。

所有这些令人困惑的行为只是因为用户肌肉记忆使用了操作符“=”而实际上需要“-eq”操作符。

PowerShell 技能连载 - PowerShell 废弃功能(第 2 部分:Exchange Online 中的远程 PowerShell (RPS))

Exchange Online 中的 PowerShell cmdlet 使用“远程 PowerShell”作为远程技术,这是一种在今天世界中存在安全风险的传统技术。这就是为什么 Exchange 团队最初考虑在2023年6月停用 Remote PowerShell。

由于相当多的用户似乎无法切换到新的、更安全的基于 REST 的 v3 PowerShell 模块来管理 Exchange,团队决定添加一种方法让客户重新启用 Remote PowerShell 以延长宽限期(至少适用于 Microsoft 云租户)。

简而言之:如果您仍在使用 Exchange Online 中的 Remote PowerShell,则需要开始计划过渡到基于 REST 的 v3 PowerShell 模块。

PowerShell 技能连载 - PowerShell 废弃功能 (第 1 部分:PowerShell 2.0)

Windows PowerShell 2.0 仍然是任何 Windows PowerShell 的一部分,用于向后兼容。当启用时,它是一个严重的安全风险 - PowerShell 2.0 简单地没有 PowerShell 5 及更高版本中发现的所有先进恶意软件检测工具。

如果您拥有管理员权限,则此行将告诉您系统上是否启用了 PowerShell 2.0:

1
PS> Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2

如果它已启用,请确保将其禁用。

PowerShell 技能连载 - 更丰富的打印机信息

Get-Printer 返回所有本地打印机的基本信息。当您添加开关参数 -Full 时,它会返回更详细的信息,例如打印机权限,但乍一看,无论是否指定了 -FullGet-Printer 似乎输出相同的信息。

要查看增强信息,必须要求 PowerShell 显示所有属性,因为默认情况下所有增强属性都是隐藏的。以下代码揭示了通过指定 -Full 所做出的差异:

1
2
Get-Printer | Select-Object -Property * | Out-GridView -Title 'Without -Full'
Get-Printer -Full | Select-Object -Property * | Out-GridView -Title 'With -Full'

最显著的区别在于 PermissionSDDL 属性。

PowerShell 技能连载 - 使用 PowerShell 解决问题(第 4 部分)

PowerShell 为您提供了丰富的方法来解决任务。它们总是归结为相同的策略。在这个小系列中,我们将逐一说明它们。

以下是要解决的问题:获取计算机的 MAC 地址。

在我们之前的提示中,我们看过命令和运算符。但如果你仍然无法获得所需信息怎么办?

即使如此,PowerShell 也有选项可供选择,因为您可以直接访问 .NET Framework 类型和方法。或者换句话说:如果没有程序员替您完成工作并提供特定命令,则可以自己编写功能。

显然,这可能很快成为最困难的方法,因为现在您需要具备程序员技能,并且需要更多地搜索谷歌。你需要找到一个内置的 .NET 类型来处理你所需的信息。

经过一番研究后,您可能会发现 [System.Net.NetworkInformation.NetworkInterface] .NET类型管理网络适配器,并且在 PowerShell 中,所有 .NET 类型后添加两个冒号,即可访问其方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS C:\> [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()


Id : {DC7C0CE2-B070-4070-B5CB-1C400DA69AF8}
Name : vEthernet (Default Switch)
Description : Hyper-V Virtual Ethernet Adapter
NetworkInterfaceType : Ethernet
OperationalStatus : Up
Speed : 10000000000
IsReceiveOnly : False
SupportsMulticast : True

Id : {44FE83FC-7565-4B18-AAC8-AB3EC075E822}
Name : Ethernet 2
Description : USB Ethernet
NetworkInterfaceType : Ethernet
OperationalStatus : Up
Speed : 1000000000
IsReceiveOnly : False
SupportsMulticast : True

...

由于 .NET 返回结构化数据,因此 PowerShell cmdlet 返回的结果与原始 .NET 方法返回的结果之间没有区别。您可以以相同的方式消耗和处理数据:

1
2
3
4
5
6
7
8
9
10
11
PS C:\> [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() | Select-Object -Property Name, NetworkInterfaceType, Description

Name NetworkInterfaceType Description
---- -------------------- -----------
vEthernet (Default Switch) Ethernet Hyper-V Virtual Ethernet Adapter
Ethernet 2 Ethernet USB Ethernet
Local Area Connection* 1 Wireless80211 Microsoft Wi-Fi Direct Virtual Adapter
Local Area Connection* 2 Wireless80211 Microsoft Wi-Fi Direct Virtual Adapter #2
WLAN Wireless80211 Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)
Bluetooth Network Connection Ethernet Bluetooth Device (Personal Area Network)
Loopback Pseudo-Interface 1 Loopback Software Loopback Interface 1

但是,您找到的.NET方法可能不直接包含所需信息。在我们的示例中,GetAllNetworkInterfaces() 返回所有网络适配器,但要获取它们的 MAC 地址,则需要深入了解 .NET 对象树。这就使得使用这种技术变得更加困难(但也令人兴奋和有益)。

这是获取有关您的网络适配器的所有所需信息的完整 .NET 代码,甚至包括特定适配器当前是否处于活动状态的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
[System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() |
ForEach-Object {
$nic = $_

[PSCustomObject]@{
Name = $_.Name
Status = $_.OperationalStatus
Mac = [System.BitConverter]::ToString($nic.GetPhysicalAddress().GetAddressBytes())
Type = $_.NetworkInterfaceType
SpeedGb = $(if ($_.Speed -ge 0) { $_.Speed/1000000000 } )
Description = $_.Description
}
}

这就是整个工作闭环之处:通过将此原始 .NET 代码封装到 PowerShell 函数中,您可以向 PowerShell 词汇表添加一个新的简单命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Get-MacAddress
{
[System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() |
ForEach-Object {
$nic = $_

[PSCustomObject]@{
Name = $_.Name
Status = $_.OperationalStatus
Mac = [System.BitConverter]::ToString($nic.GetPhysicalAddress().GetAddressBytes())
Type = $_.NetworkInterfaceType
SpeedGb = $(if ($_.Speed -ge 0) { $_.Speed/1000000000 } )
Description = $_.Description
}
}
}

一旦你运行了这个函数定义代码,就可以再次使用一个高度专业且易于使用的 PowerShell 命令,并附加你常用的一组 PowerShell cmdlet(Select-ObjectWhere-Object),将数据处理成所需的形式:

1
2
3
4
5
6
7
8
9
10
PS C:\> Get-MacAddress | Where-Object Mac | Select-Object -Property Name, Status, Mac, SpeedGb

Name Status Mac SpeedGb
---- ------ --- -------
vEthernet (Default Switch) Up 00-15-5D-58-46-A8 10
Ethernet 2 Up 80-3F-5D-05-58-91 1
Local Area Connection* 1 Down 24-EE-9A-54-1B-E6
Local Area Connection* 2 Down 26-EE-9A-54-1B-E5
WLAN Down 24-EE-9A-54-1B-E5 0,78
Bluetooth Network Connection Down 24-EE-9A-54-1B-E9 0,003

PowerShell 博客文章汇总 (2022-04 ~ 2023-03)

2022 年 03 月

2022 年 04 月

2022 年 05 月

2022 年 06 月

2022 年 07 月

2022 年 08 月

2022 年 09 月

2022 年 10 月

2022 年 11 月

2023 年

2023 年 01 月

2023 年 02 月

2023 年 03 月

PowerShell 技能连载 - 翻译数据

哈希表和字典是完美的查找表:每当您的原始数据包含难以理解的数字或命令返回仅为数值返回值时,哈希表可以将这些数字转换为友好的文本。

由于您可以自由地向哈希表添加任何键,因此要翻译的数字也不必是连续的数字范围。

以下是一个示例,从 WMI 查询操作系统信息,然后将您的 Windows SKU 从数字转换为描述性文本:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# get any info, i.e. some WMI information about your OS
$info = Get-CimInstance -ClassName Win32_OperatingSystem
# it may include information that is cryptically encoded as some number:
$rawData = $info.OperatingSystemSKU
# by using a hash table, you can easily translate the numbers to text:
$translation = @{
0 = 'UNDEFINED'
1 = 'ULTIMATE'
2 = 'HOME_BASIC'
3 = 'HOME_PREMIUM'
4 = 'ENTERPRISE'
5 = 'HOME_BASIC_N'
6 = 'BUSINESS'
7 = 'STANDARD_SERVER'
8 = 'DATACENTER_SERVER'
9 = 'SMALLBUSINESS_SERVER'
10 = 'ENTERPRISE_SERVER'
11 = 'STARTER'
12 = 'DATACENTER_SERVER_CORE'
13 = 'STANDARD_SERVER_CORE'
14 = 'ENTERPRISE_SERVER_CORE'
15 = 'ENTERPRISE_SERVER_IA64'
16 = 'BUSINESS_N'
17 = 'WEB_SERVER'
18 = 'CLUSTER_SERVER'
19 = 'HOME_SERVER'
20 = 'STORAGE_EXPRESS_SERVER'
21 = 'STORAGE_STANDARD_SERVER'
22 = 'STORAGE_WORKGROUP_SERVER'
23 = 'STORAGE_ENTERPRISE_SERVER'
24 = 'SERVER_FOR_SMALLBUSINESS'
25 = 'SMALLBUSINESS_SERVER_PREMIUM'
26 = 'HOME_PREMIUM_N'
27 = 'ENTERPRISE_N'
28 = 'ULTIMATE_N'
29 = 'WEB_SERVER_CORE'
30 = 'MEDIUMBUSINESS_SERVER_MANAGEMENT'
31 = 'MEDIUMBUSINESS_SERVER_SECURITY'
32 = 'MEDIUMBUSINESS_SERVER_MESSAGING'
33 = 'SERVER_FOUNDATION'
34 = 'HOME_PREMIUM_SERVER'
35 = 'SERVER_FOR_SMALLBUSINESS_V'
36 = 'STANDARD_SERVER_V'
37 = 'DATACENTER_SERVER_V'
38 = 'ENTERPRISE_SERVER_V'
39 = 'DATACENTER_SERVER_CORE_V'
40 = 'STANDARD_SERVER_CORE_V'
41 = 'ENTERPRISE_SERVER_CORE_V'
42 = 'HYPERV'
43 = 'STORAGE_EXPRESS_SERVER_CORE'
44 = 'STORAGE_STANDARD_SERVER_CORE'
45 = 'STORAGE_WORKGROUP_SERVER_CORE'
46 = 'STORAGE_ENTERPRISE_SERVER_CORE'
47 = 'STARTER_N'
48 = 'PROFESSIONAL'
49 = 'PROFESSIONAL_N'
50 = 'SB_SOLUTION_SERVER'
51 = 'SERVER_FOR_SB_SOLUTIONS'
52 = 'STANDARD_SERVER_SOLUTIONS'
53 = 'STANDARD_SERVER_SOLUTIONS_CORE'
54 = 'SB_SOLUTION_SERVER_EM'
55 = 'SERVER_FOR_SB_SOLUTIONS_EM'
56 = 'SOLUTION_EMBEDDEDSERVER'
57 = 'SOLUTION_EMBEDDEDSERVER_CORE'
58 = 'PROFESSIONAL_EMBEDDED'
59 = 'ESSENTIALBUSINESS_SERVER_MGMT'
60 = 'ESSENTIALBUSINESS_SERVER_ADDL'
61 = 'ESSENTIALBUSINESS_SERVER_MGMTSVC'
62 = 'ESSENTIALBUSINESS_SERVER_ADDLSVC'
63 = 'SMALLBUSINESS_SERVER_PREMIUM_CORE'
64 = 'CLUSTER_SERVER_V'
65 = 'EMBEDDED'
66 = 'STARTER_E'
67 = 'HOME_BASIC_E'
68 = 'HOME_PREMIUM_E'
69 = 'PROFESSIONAL_E'
70 = 'ENTERPRISE_E'
71 = 'ULTIMATE_E'
72 = 'ENTERPRISE_EVALUATION'
76 = 'MULTIPOINT_STANDARD_SERVER'
77 = 'MULTIPOINT_PREMIUM_SERVER'
79 = 'STANDARD_EVALUATION_SERVER'
80 = 'DATACENTER_EVALUATION_SERVER'
84 = 'ENTERPRISE_N_EVALUATION'
85 = 'EMBEDDED_AUTOMOTIVE'
86 = 'EMBEDDED_INDUSTRY_A'
87 = 'THINPC'
88 = 'EMBEDDED_A'
89 = 'EMBEDDED_INDUSTRY'
90 = 'EMBEDDED_E'
91 = 'EMBEDDED_INDUSTRY_E'
92 = 'EMBEDDED_INDUSTRY_A_E'
95 = 'STORAGE_WORKGROUP_EVALUATION_SERVE'
96 = 'STORAGE_STANDARD_EVALUATION_SERVER'
97 = 'CORE_ARM'
98 = 'CORE_N'
99 = 'CORE_COUNTRYSPECIFIC'
100 = 'CORE_SINGLELANGUAGE'
101 = 'CORE'
103 = 'PROFESSIONAL_WMC'
105 = 'EMBEDDED_INDUSTRY_EVAL'
106 = 'EMBEDDED_INDUSTRY_E_EVAL'
107 = 'EMBEDDED_EVAL'
108 = 'EMBEDDED_E_EVAL'
109 = 'NANO_SERVER'
110 = 'CLOUD_STORAGE_SERVER'
111 = 'CORE_CONNECTED'
112 = 'PROFESSIONAL_STUDENT'
113 = 'CORE_CONNECTED_N'
114 = 'PROFESSIONAL_STUDENT_N'
115 = 'CORE_CONNECTED_SINGLELANGUAGE'
116 = 'CORE_CONNECTED_COUNTRYSPECIFIC'
117 = 'CONNECTED_CAR'
118 = 'INDUSTRY_HANDHELD'
119 = 'PPI_PRO'
120 = 'ARM64_SERVER'
121 = 'EDUCATION'
122 = 'EDUCATION_N'
123 = 'IOTUAP'
124 = 'CLOUD_HOST_INFRASTRUCTURE_SERVER'
125 = 'ENTERPRISE_S'
126 = 'ENTERPRISE_S_N'
127 = 'PROFESSIONAL_S'
128 = 'PROFESSIONAL_S_N'
129 = 'ENTERPRISE_S_EVALUATION'
130 = 'ENTERPRISE_S_N_EVALUATION'
135 = 'HOLOGRAPHIC'
138 = 'PRO_SINGLE_LANGUAGE'
139 = 'PRO_CHINA'
140 = 'ENTERPRISE_SUBSCRIPTION'
141 = 'ENTERPRISE_SUBSCRIPTION_N'
143 = 'DATACENTER_NANO_SERVER'
144 = 'STANDARD_NANO_SERVER'
145 = 'DATACENTER_A_SERVER_CORE'
146 = 'STANDARD_A_SERVER_CORE'
147 = 'DATACENTER_WS_SERVER_CORE'
148 = 'STANDARD_WS_SERVER_CORE'
149 = 'UTILITY_VM'
159 = 'DATACENTER_EVALUATION_SERVER_CORE'
160 = 'STANDARD_EVALUATION_SERVER_CORE'
161 = 'PRO_WORKSTATION'
162 = 'PRO_WORKSTATION_N'
164 = 'PRO_FOR_EDUCATION'
165 = 'PRO_FOR_EDUCATION_N'
168 = 'AZURE_SERVER_CORE'
169 = 'AZURE_NANO_SERVER'
171 = 'ENTERPRISEG'
172 = 'ENTERPRISEGN'
175 = 'SERVERRDSH'
178 = 'CLOUD'
179 = 'CLOUDN'
180 = 'HUBOS'
182 = 'ONECOREUPDATEOS'
183 = 'CLOUDE'
184 = 'ANDROMEDA'
185 = 'IOTOS'
186 = 'CLOUDEN'
}
# use the raw data as key to the hash table
# AND MAKE SURE you convert numeric data to [int]!
# (WMI returns unusual data types like [byte] and [UInt16],
# and hash table keys are type-aware)
$translation[$rawData -as [int]]

这个基本的概念适用于所有类型的翻译。以下是一个示例,可以将 ping.exe 提供的返回值进行翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$translation = @{
0 = 'SUCCESS'
1 = 'FAILURE'
2 = 'ERROR'
}

1..255 | ForEach-Object {
# create the IP address to ping
# make sure you adjust this to your segment!
$ip = "192.168.2.$_"
# execute ping.exe and disregard the text output
ping -n 1 -w 500 $ip > $null
# instead return the translated return value found in $LASTEXITCODE
[PSCustomObject]@{
IpAddress = $ip
Status = $translation[$LASTEXITCODE]
}
}

以下是执行结果:

IpAddress   Status
---------   ------
192.168.2.1 SUCCESS
192.168.2.2 FAILURE
192.168.2.3 FAILURE
192.168.2.4 FAILURE
192.168.2.5 FAILURE
192.168.2.6 FAILURE
...