PowerShell 技能连载 - 探索 PowerShell 模块

大多数 PowerShell 的命令都存在于模块中,通过增加新的模块,就可以将新的命令添加到 PowerShell 环境中。要查找一个命令是否位于某个模块中,请使用 Get-Command 命令。下一行代码返回发布 Get-Service 命令的模块:

1
PS C:\> Get-Command -Name Get-Service | Select-Object -ExpandProperty Module

如果 Module 属性是空值,那么改命令不是通过一个模块发布的。对于所有非 PowerShell 命令,例如应用程序,都是这个情况:

1
PS C:\> Get-Command -Name notepad | Select-Object -ExpandProperty Module

下一步,让我们列出您系统中所有可用的模块:

1
PS> Get-Module -ListAvailable | Select-Object -Property Name, Path

如果更深入地查看这些结果,您会注意到 Get-Module 列出多于一个文件夹。PowerShell 指定的缺省模块文件夹是以分号分隔的列表形式存储在 $env:PSModulePath 环境变量中,类似应用程序的 $env:Path

PS> $env:PSModulePath
C:\Users\tobia\OneDrive\Dokumente\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:
\Windows\system32\WindowsPowerShell\v1.0\Modules

PS> $env:PSModulePath -split ';'
C:\Users\tobia\OneDrive\Dokumente\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules

如果您希望探索一个指定模块的内容,请查看 Path 属性:它通常指向一个 .psd1 文件,其中包含模块的元数据。它是指定模块版本和版权信息的地方,并且通常它的 RootModule 入口指定了模块的代码。如果这是一个使用 PowerShell 代码构建的模块,那么它是一个 .psm1 文件,否则是一个二进制的 DLL 文件。

要检查模块内容,请在 Windows 资源管理器中打开它的父文件夹。例如,下面一行代码在 Windows 资源管理器中打开 “PrintManagement” 模块(假设它存在于您的机器中):

1
2
3
4
PS> Get-Module -Name PrintManagement -ListAvailable | Select-Object -ExpandProperty Path | Split-Path
C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PrintManagement

PS> explorer (Get-Module -Name PrintManagement -ListAvailable | Select-Object -ExpandProperty Path | Split-Path)

这个快速的演练解释了为什么 PowerShell 没有固定的命令集,以及为什么给定命令在一个系统上可用并且在另一个系统上不可用。新模块可以由操作系统(例如 Windows 10 附带的模块多于 Windows 7)、已安装的软件(例如 SQLServer)、已激活的角色(例如域控制器)引入,并且模块也可以手动安装。

例如这行代码从公开的 PowerShell Gallery 中安装一个免费的模块,并且添加了用于创建各种二维码的新命令:

1
PS C:\> Install-Module -Name QRCodeGenerator -Scope CurrentUser -Force

一旦模块安装完毕,您会得到类似这样的命令,用来创建 Twitter 用户数据文件的二维码:

1
PS C:\> New-QRCodeTwitter -ProfileName tobiaspsp -Show

要查看某个模块中所有命令,请试试这行代码:

1
2
3
4
5
6
7
8
PS C:\> Get-Command -Module QRCodeGenerator

CommandType Name Version Source
----------- ---- ------- ------
Function New-QRCodeGeolocation 1.2 QRCodeGenerator
Function New-QRCodeTwitter 1.2 QRCodeGenerator
Function New-QRCodeVCard 1.2 QRCodeGenerator
Function New-QRCodeWifiAccess 1.2 QRCodeGenerator

PowerShell 技能连载 - WMI 浏览器

WMI(Windows管理规范)是一个很好的信息来源:几乎可以在其中找到有关计算机的任何信息。困难的部分不是 WMI 查询本身。困难的部分是找出适当的 WMI 类名称和属性:

例如,要获取您 BIOS 的信息,请运行以下代码:

1
2
3
4
5
6
7
8
PS> Get-CimInstance -ClassName Win32_BIOS


SMBIOSBIOSVersion : 1.0.9
Manufacturer : Dell Inc.
Name : 1.0.9
SerialNumber : 4ZKM0Z2
Version : DELL - 20170001

获取其他信息只需要替换您要查询的类名。因此,例如要获取有关您的操作系统的信息,请运行以下命令:

1
2
3
4
5
6
7
8
9
PS> Get-CimInstance -ClassName Win32_OperatingSystem


SystemDirectory : C:\Windows\system32
Organization :
BuildNumber : 18362
RegisteredUser : tobias.weltner@email.de
SerialNumber : 00330-50000-00000-AAOEM
Version : 10.0.18362

为了找到任何信息并找到合适的 WMI 类名称,我们创建了一个智能的 “WMI Explorer“,它实际上只是一个简单的功能。它需要Windows PowerShell (PowerShell 3-5):

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
function Find-WmiClass
{
# show all properties, not just 4
$oldLimit = $FormatEnumerationLimit
$global:FormatEnumerationLimit = -1
# list all WMI classes...
Get-WmiObject -Class * -List |
# ...with at least 4 properties
Where-Object { $_.Properties.Count -gt 4 } |
# let the user select one
Out-GridView -Title 'Select a class that seems interesting' -OutputMode Single |
ForEach-Object {
# query the selected class
$Name = $_.name
$props = Get-WmiObject -Class $Name |
# take the first instance
Select-Object -Property * -First 1 |
ForEach-Object {
# turn the object into a hash table, and exclude empty properties
$_ | & {
$_.PSObject.Properties |
Sort-Object -Property Name |
Where-Object { $_.Value -ne $null } |
ForEach-Object {
$hashtable = [Ordered]@{}} { $hashtable[$_.Name] = $_.Value } { $hashtable }
} |
# show the properties and let the user select
Out-GridView -Title "$name : Select all properties you need (hold CTRL)" -PassThru |
ForEach-Object {
# return the selected property names
$_.Name
}
}
# take all selected properties
$prop = $props -join ', '
# create the command for it:
$a = "Get-CimInstance -Class $Name | Select-Object -Property $prop"
# place it into the clipboard
$a | Set-Clipboard
Write-Warning "Command is also available from the clipboard"
$a
}
# reset format limit
$global:FormatEnumerationLimit = $oldLimit
}

以下是它的工作原理:

  • 运行上面的代码,将添加一个名为 Find-WmiClass 的新命令
  • 运行 Find-WmiClass 以执行该函数
  • 将打开一个网格视图窗口,并显示标准命名空间 root\cimv2 中所有可用的 WMI 类(如果要搜索其他命名空间,请调整代码并将`-Namespace 参数添加到 Get-WmiObject 调用中)
  • 现在,在网格视图窗口的顶部文本字段中输入您要查找的内容,它就像一个过滤器。例如,如果输入 UserName,则网格视图会将列表限制为名称或任何属性名称中任何位置具有 “UserName” 的所有类。过滤后只有几个类可供选择。
  • 选择要调查的类,例如 “Win32_ComputerSystem”,然后单击网格右下角的“确定”按钮。
  • 现在查询选定的类,并且它的第一个实例显示在另一个网格视图中。每个属性都显示在其自己的行上,因此您可以再次过滤显示。按住 CTRL 键选择要保留的所有属性。然后再次单击确定。
  • 将在控制台中显示创建的命令。它还位于剪贴板中。因此要测试命令的话,请按 CTRL + V,然后按 Enter。

感谢 Find-WmiClass 命令,探索 WMI 并找到有用的 WMI 类和属性变得非常容易。

PowerShell 技能连载 - 将对象转换为哈希表

我们经常需要检视一个对象,例如一个进程或是一个 Active Directory 用户。当您在一个网格视图窗口,例如 Out-GridView 中显示一个对象时,整个对象显示为一个长行。

一个好得多的方法是将一个对象转换为哈希表。通过这种方式,每个属性都在独立的一行中显示,这样您就可以用网格视图窗口顶部的文本过滤器搜索每个属性。另外,您可以完全控制转换的过程,这样您可以对对象的属性排序,并且确保它们按字母顺序排序,甚至排除空属性。

以下是一个将对象转换为哈希表并且可以根据需要排除空属性的函数:

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
function Convert-ObjectToHashtable
{
param
(
[Parameter(Mandatory,ValueFromPipeline)]
$object,

[Switch]
$ExcludeEmpty
)

process
{
$object.PSObject.Properties |
# sort property names
Sort-Object -Property Name |
# exclude empty properties if requested
Where-Object { $ExcludeEmpty.IsPresent -eq $false -or $_.Value -ne $null } |
ForEach-Object {
$hashtable = [Ordered]@{}} {
$hashtable[$_.Name] = $_.Value
} {
$hashtable
}
}
}

让我们来看看一个对象,例如当前进程,缺省情况下在 Out-GridView 是如何显示的:

1
$process = Get-Process -Id $pid | Out-GridView

和以下这个作对比:

1
$process = Get-Process -Id $pid | Convert-ObjectToHashtable -ExcludeEmpty | Out-GridView

现在分析起来好多了。

PowerShell 技能连载 - 对象的魔法(第 4 部分)

将一个对象转换为一个哈希表如何?通过这种方式,当将对象输出到一个网格视图窗口时,可以每行显示一个对象属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
# get any object
$object = Get-Process -Id $pid

# try and access the PSObject
$hash = $object.PSObject.Properties.Where{$null -ne $_.Value}.Name |
Sort-Object |
ForEach-Object { $hash = [Ordered]@{} } { $hash[$_] = $object.$_ } { $hash }

# output regularly
$object | Out-GridView -Title Regular

# output as a hash table, only non-empty properties, sorted
$hash | Out-GridView -Title Hash

PowerShell 技能连载 - 对象的魔法(第 3 部分)

假设您希望隐藏对象所有没有值(为空)的属性。以下是一个简单的实现:

1
2
3
4
5
6
# get any object
$object = Get-Process -Id $pid

# try and access the PSObject
$propNames = $object.PSObject.Properties.Where{$null -ne $_.Value}.Name
$object | Select-Object -Property $propNames

这段代码将只输出包含值的属性。您甚至可以对属性排序:

1
2
3
4
5
6
# get any object
$object = Get-Process -Id $pid

# try and access the PSObject
$propNames = $object.PSObject.Properties.Where{$null -ne $_.Value}.Name | Sort-Object
$object | Select-Object -Property $propNames

PowerShell 技能连载 - 对象的魔法(第 2 部分)

通过隐藏的 “PSObject“ 属性,您可以获取对象成员的详细信息。例如,如果您希望知道哪个属性可以被改变,请试试这段代码:

1
2
3
4
5
# get any object
$object = Get-Process -Id $pid

# try and access the PSObject
$object.PSObject.Properties.Where{$_.IsSettable}.Name

结果是进程对象的可以被赋值的属性列表:

MaxWorkingSet
MinWorkingSet
PriorityBoostEnabled
PriorityClass
ProcessorAffinity
StartInfo
SynchronizingObject
EnableRaisingEvents
Site

类似地,您可以轻松地找出所有当前没有值(为空)的属性:

1
2
3
4
5
# get any object
$object = Get-Process -Id $pid

# try and access the PSObject
$object.PSObject.Properties.Where{$null -eq $_.Value}.Name

PowerShell 技能连载 - 对象的魔法(第 1 部分)

在 PowerShell 中,大多数据是以 PSObject 来表示的,它是一个由 PowerShell 添加的特殊的对象“包装器”。要获取这个特殊的包装器,可以通过对象名为 “PSObject“ 的隐藏属性。让我们来看看:

1
2
3
4
5
6
7
8
9
10
11
# get any object
$object = Get-Process -Id $pid

# try and access the PSObject
$object.PSObject

# get another object
$object = "Hello"

# try again
$object.PSObject

如您所见,该 “PSObject“ 基本上是一个对象的描述。并且它包含了许多有用的信息。以下是一部分:

1
2
3
4
5
6
7
8
9
10
# get any object
$object = Get-Process -Id $pid

# try and access the PSObject
$object.PSObject

# find useful information
$object.PSObject.TypeNames | Out-GridView -Title Type
$object.PSObject.Properties | Out-GridView -Title Properties
$object.PSObject.Methods | Out-GridView -Title Methods

PowerShell 技能连载 - 加密文本(第 2 部分)

这是我们的加密解密系列的第二部分。在第一部分中您学到了如何在一台计算机中安全地加密文本。现在我们来关注解密部分。

要正确地解密文本,必须指定加密时使用的相同编码。基于您的加密参数,您必须指定相同的密码。并且基于 -Scope 设置,解密将只能针对您和/或仅在您加密文本的同一机器上工作。

以下是 Unprotect-Text 函数。我们也从上一个技能中复制了 Protect-Text 函数,这样您可以方便地使用这两种功能:

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
function Protect-Text
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[String]
$SecretText,

[string]
$Password='',

[string]
[ValidateSet('CurrentUser','LocalMachine')]
$scope = 'CurrentUser',

[string]
[ValidateSet('UTF7','UTF8','UTF32','Unicode','ASCII','Default')]
$Encoding = 'Default',

[Switch]
$ReturnByteArray

)
begin
{
Add-Type -AssemblyName System.Security
if ([string]::IsNullOrEmpty($Password))
{
$optionalEntropy = $null
}
else
{
$optionalEntropy = [System.Text.Encoding]::$Encoding.GetBytes($Password)
}
}
process
{
try
{
$userData = [System.Text.Encoding]::$Encoding.GetBytes($SecretText)
$bytes = [System.Security.Cryptography.ProtectedData]::Protect($userData, $optionalEntropy, $scope)
if ($ReturnByteArray)
{
$bytes
}
else
{
[Convert]::ToBase64String($bytes)
}
}
catch
{
throw "Protect-Text: Unable to protect text. $_"
}
}
}



function Unprotect-Text
{
[CmdletBinding(DefaultParameterSetName='Byte')]
param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="Text", Position=0)]
[string]
$EncryptedString,

[Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="Byte", Position=0)]
[Byte[]]
$EncryptedBytes,

[string]
$Password='',

[string]
[ValidateSet('CurrentUser','LocalMachine')]
$scope = 'CurrentUser',

[string]
[ValidateSet('UTF7','UTF8','UTF32','Unicode','ASCII','Default')]
$Encoding = 'Default'

)
begin
{
Add-Type -AssemblyName System.Security

if ([string]::IsNullOrEmpty($Password))
{
$optionalEntropy = $null
}
else
{
$optionalEntropy = [System.Text.Encoding]::$Encoding.GetBytes($Password)
}
}
process
{
try
{
if ($PSCmdlet.ParameterSetName -eq 'Text')
{
$inBytes = [Convert]::FromBase64String($EncryptedString)
}
else
{
$inBytes = $EncryptedBytes
}
$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($inBytes, $optionalEntropy, $scope)
[System.Text.Encoding]::$Encoding.GetString($bytes)
}
catch
{
throw "Unprotect-Text: Unable to unprotect your text. Check optional password, and make sure you are using the same encoding that was used during protection."
}
}
}

以下是如何使用它的示例:

1
2
3
4
5
$text = "This is my secret"

$a = Protect-Text -SecretText $text -scope CurrentUser -Password zumsel

Unprotect-Text -EncryptedString $a -scope CurrentUser -Password zumsel

Protect-Text 创建了一个 Base64 编码的字符串,它可以用 Unprotect-Text 函数来解密。如果您不希望额外的密码,那么只能使用基于 -Scope 的缺省的保护。

要节省空间,您可以使用字节数组来代替 Base64 编码的字符串:

1
2
$b = Protect-Text -SecretText $text -scope CurrentUser -ReturnByteArray
Unprotect-Text -EncryptedBytes $b -scope CurrentUser

PowerShell 技能连载 - 加密文本(第 1 部分)

让我们来看看一种在计算机上加密文本的安全方法。以下的 Protect-Text 函数接受任意文本,并自动加密,不需要密码。它不使用密码,而是使用您的用户帐户和机器,或者只使用您的机器作为密钥。

如果使用 -Scope LocalMachine,任何使用该机器的人都可以解密该文本,但如果该文本泄露给其他人,它无法在其它机器上解密。如果您使用 -Scope CurrentUser,只有加密者可以解密,并且只能在加密的机器上解密。这个加密方案很适合保存您的私人密码。

此外,为了提高安全性,可以在顶部添加(可选)密码。当指定了密码时,将应用上述相同的限制,但此外还需要知道密码才能解密文本。

请注意您也可以控制文本解码。请确保加密和解密使用相同的编码。

以下是加密函数:

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
function Protect-Text
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[String]
$SecretText,

[string]
$Password='',

[string]
[ValidateSet('CurrentUser','LocalMachine')]
$scope = 'CurrentUser',

[string]
[ValidateSet('UTF7','UTF8','UTF32','Unicode','ASCII','Default')]
$Encoding = 'Default',

[Switch]
$ReturnByteArray

)
begin
{
Add-Type -AssemblyName System.Security
if ([string]::IsNullOrEmpty($Password))
{
$optionalEntropy = $null
}
else
{
$optionalEntropy = [System.Text.Encoding]::$Encoding.GetBytes($Password)
}
}
process
{
try
{
$userData = [System.Text.Encoding]::$Encoding.GetBytes($SecretText)
$bytes = [System.Security.Cryptography.ProtectedData]::Protect($userData, $optionalEntropy, $scope)
if ($ReturnByteArray)
{
$bytes
}
else
{
[Convert]::ToBase64String($bytes)
}
}
catch
{
throw "Protect-Text: Unable to protect text. $_"
}
}

结果类似这样:

1
2
3
4
PS> Protect-Text -SecretText 'I am encrypted' -scope LocalMachine
AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAF8Hpm9A5A0upZysoxLlvgwQAAAACAAAAAAAQZgAAAAEAACAAAACbOsUoDuZJXNkWIzfAABxktVg+Txn7A8Rz
SCvFP7I9YQAAAAAOgAAAAAIAACAAAABz7G7Tpuoje9meLOuugzx1WSoOUfaBtGPM/XZHytjC8hAAAAApt/TDhJ9EqeWEPLIDkd4bQAAAAAN0Q503Pa7X
MxIMOnaO7qd3LKXJa4qhht+jc+Z0HaaV5/md83ipP1vefYAAUXdj8qv4eREeCBSGMqvKjbaOsOg=

如果您想了解如何将 Base64 编码的文本转换回原始文本,请看我们的下一个技能!

PowerShell 技能连载 - 创建 NT4 密码哈希

在内部,ActiveDirectory 将所有密码存储为所谓的 NTLM 哈希。有许多安全分析工具可以读取和转储这些哈希。

幸运的是,没有可行的方法来解密这些散列并检索原始密码,您可以使用(已知)密码并将其转换为 NTLM 哈希。这是字典攻击的基本过程:他们获取到很长的“已知密码”列表,将它们转换为 NTLM 散列,当它们与实际的 AD 帐户散列匹配时,就算破解出了密码。

这样,您的安全部门就可以将密码黑名单中的不安全密码(如“P@ssw0rd”)转换为NTLM散列,并将它们与Active Directory的密码散列进行比较,以识别需要更改密码的帐户。

这是将纯文本变为 NTLM HASH 的 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
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
function ConvertTo-NTLMPasswordHash
{
#Work based on code found here: https://www.myotherpcisacloud.com/post/getmd4hash
#Original Author: Ryan Ries, 2014
param(
[Parameter(Mandatory=$true)][string]$password
)

Function Get-MD4Hash
{
Param ([Parameter(Mandatory=$True, ValueFromPipeline=$False)]
[Byte[]]$DataToHash)

Set-StrictMode -Version Latest
Add-Type -TypeDefinition @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class BCrypt
{
[DllImport("bcrypt.dll", CharSet = CharSet.Auto)]
public static extern NTStatus BCryptOpenAlgorithmProvider(
[Out] out IntPtr phAlgorithm,
[In] string pszAlgId,
[In, Optional] string pszImplementation,
[In] UInt32 dwFlags);

[DllImport("bcrypt.dll")]
public static extern NTStatus BCryptCloseAlgorithmProvider(
[In, Out] IntPtr hAlgorithm,
[In] UInt32 dwFlags);

[DllImport("bcrypt.dll", CharSet = CharSet.Auto)]
public static extern NTStatus BCryptCreateHash(
[In, Out] IntPtr hAlgorithm,
[Out] out IntPtr phHash,
[Out] IntPtr pbHashObject,
[In, Optional] UInt32 cbHashObject,
[In, Optional] IntPtr pbSecret,
[In] UInt32 cbSecret,
[In] UInt32 dwFlags);

[DllImport("bcrypt.dll")]
public static extern NTStatus BCryptDestroyHash(
[In, Out] IntPtr hHash);

[DllImport("bcrypt.dll")]
public static extern NTStatus BCryptHashData(
[In, Out] IntPtr hHash,
[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,
[In] int cbInput,
[In] UInt32 dwFlags);

[DllImport("bcrypt.dll")]
public static extern NTStatus BCryptFinishHash(
[In, Out] IntPtr hHash,
[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,
[In] int cbInput,
[In] UInt32 dwFlags);

[Flags]
public enum AlgOpsFlags : uint
{
BCRYPT_PROV_DISPATCH = 0x00000001,
BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008,
BCRYPT_HASH_REUSABLE_FLAG = 0x00000020
}

// This is a gigantic enum and I don't want to copy all of it into this Powershell script.
// Basically anything other than zero means something went wrong.
public enum NTStatus : uint
{
STATUS_SUCCESS = 0x00000000
}
}
'@

[Byte[]]$HashBytes = New-Object Byte[] 16
[IntPtr]$PHAlgorithm = [IntPtr]::Zero
[IntPtr]$PHHash = [IntPtr]::Zero
$NTStatus = [BCrypt]::BCryptOpenAlgorithmProvider([Ref] $PHAlgorithm, 'MD4', $Null, 0)
If ($NTStatus -NE 0)
{
Write-Error "BCryptOpenAlgorithmProvider failed with NTSTATUS $NTStatus"
If ($PHAlgorithm -NE [IntPtr]::Zero)
{
$NTStatus = [BCrypt]::BCryptCloseAlgorithmProvider($PHAlgorithm, 0)
}
Return
}
$NTStatus = [BCrypt]::BCryptCreateHash($PHAlgorithm, [Ref] $PHHash, [IntPtr]::Zero, 0, [IntPtr]::Zero, 0, 0)
If ($NTStatus -ne 0)
{
Write-Error "BCryptCreateHash failed with NTSTATUS $NTStatus"
If ($PHHash -ne [IntPtr]::Zero)
{
$NTStatus = [BCrypt]::BCryptDestroyHash($PHHash)
}
If ($PHAlgorithm -ne [IntPtr]::Zero)
{
$NTStatus = [BCrypt]::BCryptCloseAlgorithmProvider($PHAlgorithm, 0)
}
Return
}

$NTStatus = [BCrypt]::BCryptHashData($PHHash, $DataToHash, $DataToHash.Length, 0)
$NTStatus = [BCrypt]::BCryptFinishHash($PHHash, $HashBytes, $HashBytes.Length, 0)

If ($PHHash -NE [IntPtr]::Zero)
{
$NTStatus = [BCrypt]::BCryptDestroyHash($PHHash)
}
If ($PHAlgorithm -NE [IntPtr]::Zero)
{
$NTStatus = [BCrypt]::BCryptCloseAlgorithmProvider($PHAlgorithm, 0)
}

$HashString = New-Object System.Text.StringBuilder
Foreach ($Byte In $HashBytes)
{
$null = $HashString.Append($Byte.ToString("x2"))
}
$HashString.ToString()

}
Get-MD4Hash -DataToHash ([System.Text.Encoding]::Unicode.getBytes($password))
}