在Visual Studio中运行PowerShell脚本时无法获取Active Directory终端服务属性

我遇到了一个奇怪的问题,也许有人可以帮助我。

我试图在使用Windows 10的计算机上使用C#从Active Directory用户检索终端服务属性。我这样做是通过在我的应用程序中运行PowerShell脚本,如下所示:

var script = $@"Import-module ActiveDirectory $user=[ADSI]""LDAP://192.111.222.33:389/CN=SomePerson,DC=Domain,DC=local"" $user.psbase.Username = ""administrator"" $user.psbase.Password = ""adminPassword"" $user.psbase.invokeget(""TerminalServicesProfilePath"")"; using (var runspace = RunspaceFactory.CreateRunspace()) { runspace.Open(); using (var pipeline = runspace.CreatePipeline()) { pipeline.Commands.AddScript(script); var test = pipeline.Invoke(); Console.WriteLine("Success: "); return true; } } 

我得到这个例外:

System.Management.Automation.MethodInvocationException:’exception调用“InvokeGet”带有“1”参数:“未知名称。(HRESULTexception:0x80020006(DISP_E_UNKNOWNNAME))”’

当我在使用Windows Server 2012作为操作系统的计算机上运行Visual Studio 2015中的上述代码时,它工作正常! 我确保我的Windows 10机器也安装了RSAT。


奇怪的是,当我从Windows 10机器上的PowerShell控制台运行脚本时,它可以工作! 以下是我的PowerShell脚本的确切代码:

 $user = [ADSI]"LDAP://192.111.222.33:389/CN=SomePerson,DC=Domain,DC=local" $user.psbase.Username = "administrator" $user.psbase.Password = "adminPassword" Write-Output "Terminal Services profile path:" Write-Output $user.psbase.invokeget("TerminalServicesProfilePath") 

以下是PowerShell的输出:

PowerShell输出

我也尝试在Visual Studio的PowerShell交互式窗口中运行该脚本,这也是有效的。 这是截图和输出:
(识别审查的信息)

在此处输入图像描述


我在这里发现了一个非常相似的post,但提供的答案不起作用。
上述post的答案表明属性已将名称更改为:

  • msTSAllowLogon
  • msTSHomeDirectory
  • msTSHomeDrive
  • msTSProfilePath

但是,当我使用这些属性名称运行脚本时,我没有收到正确的信息。 它们似乎不是同一个属性。 以下是我的用户在Active Directory用户和计算机中的几个屏幕截图:

SomePerson Prop

您可以在上面看到我尝试检索的属性。
当我在“属性编辑器”选项卡上查看用户的属性时,您可以看到msTSProfilePath,它包含不同的值:

在此处输入图像描述

使用msTSProfilePath运行脚本将返回上面在“属性编辑器”窗口( ???????? )中看到的属性。


附加信息:

我已针对两个单独的Active Directory域对此进行了测试:

  1. 一个具有Windows Server 2012的林和域function级别,其中DC在运行Windows Server 2012版本6.2的服务器上运行(Build 9200)
  2. 第二个具有Windows Server 2012 R2的林和域function级别并在Windows Server 2012 R2版本6.3(Build 9600)上运行

我在另一台Windows 10计算机上运行此代码,问题仍然存在。

谢谢!

这个问题真的困扰了我的生活,因为自服务器2000以来我一直试图找到获得这个价值的方法。 我不确定为什么PowerShell脚本适用于Windows 10(我没有连接到我可以自由查询以测试的域的win10盒)但我确实找到了解决这个问题的方法。 我知道你一开始不会喜欢它,但是没有它,你不需要做任何事情,只需复制/粘贴并附加我列出的命令的简短列表。

我相信你现在已经知道这个值隐藏在userParameters AD属性中。 这是一个编码值。 您可以在此处查看规范以取消此值(仅在您感兴趣时,不需要获取您的价值) https://msdn.microsoft.com/en-us/library/ff635169.aspx

有人能更好地理解这个混乱,然后我写了一个脚本,为我们做了艰苦的工作。 这个脚本位于第三篇文章: https : //social.technet.microsoft.com/Forums/scriptcenter/en-US/953cd9b5-8d6f-4823-be6b-ebc009cc1ed9/powershell-script-to-modify-the- ActiveDirectory中-userparameters属性到组终端服务?论坛= ITCG

只需将所有代码复制并粘贴到PowerShell脚本中即可。 它只构建一个名为TSuserParameters的对象。 您将在AD返回的userParameters数据上使用名为.UnBlob的对象的方法。 在这里,我使用Get-ADUser提取此数据:

 $TSuserParameters.UnBlob((get-aduser -Identity someuser -Properties userparameters).userparameters) 

该对象将解析数据并将其存储在TSAttributes属性集合下。 此实习生存储具有所需数据的CtxWFProfilePath。 存在与路径本身一起存储的元数据(例如,长度,因为该值是可变宽度。要理解原因,请再次阅读第一个链接中的文档,与获取数据无关)。 因为存在与对象一起存储的元数据,您只需要属性数组[0]中的第一个对象:

 $TSuserParameters.TSAttributes.CtxWFProfilePath[0] 

现在您拥有了所需的数据。 这应该像服务器2000一样工作,因为这个编码规范从那时起似乎没有改变。

您还可以使用此对象访问其他TS属性。


这是完整的脚本是Stack愿意让我发布它:

 $TSuserParameters = New-Object System.Object $TSuserParameters |Add-Member -membertype NoteProperty -name Types -value @{"CtxCfgPresent" = "Int32"; "CtxCfgFlags1" = "Int32"; "CtxCallBack" = "Int32"; "CtxKeyboardLayout" = "Int32"; "CtxMinEncryptionLevel" = "Int32"; "CtxNWLogonServer" = "Int32"; "CtxWFHomeDirDrive" = "ASCII"; "CtxWFHomeDir" = "ASCII"; "CtxWFHomeDrive" = "ASCII"; "CtxInitialProgram" = "ASCII"; "CtxMaxConnectionTime" = "Int32"; "CtxMaxDisconnectionTime" = "Int32"; "CtxMaxIdleTime" = "Int32"; "CtxWFProfilePath" = "ASCII"; "CtxShadow" = "Int32"; "CtxWorkDirectory" = "ASCII"; "CtxCallbackNumber" = "ASCII"} $TSuserParameters |Add-Member -membertype NoteProperty -name TSAttributes -value @{} $TSuserParameters |Add-Member -membertype NoteProperty -name SpecificationURL -value"http://msdn.microsoft.com/en-us/library/cc248570(v=prot.10).aspx" $TSuserParameters |Add-Member -membertype NoteProperty -name Reserved -value [byte[]] $TSuserParameters |Add-Member -membertype NoteProperty -name AttributeCount -value [uint16]0 $TSuserParameters |Add-Member -membertype ScriptMethod -name init -value { $this.TSAttributes = @{} [byte[]]$this.Reserved = [byte[]]$null } $TSuserParameters |Add-Member -membertype ScriptMethod -name UnBlob -value { Param ($Input) $this.init() $ArrayStep = 1 #Add-Type -AssemblyName mscorlib #A new array for writing things back [Byte[]] $resultarray = $NULL #$userInfo.userParameters $char = [char]1 #The value is a binary blob so we will get a binary representation of it #$input.length $userparms = [System.Text.Encoding]::unicode.GetBytes($Input) #$userparms.count #$userInfo.userParameters If ($userparms) #if we have any data then we need to process it { #Write-Host "Processing $userparms" $Valueenum = $userparms.GetEnumerator() $Valueenum.reset() $result = $Valueenum.MoveNext() #Now lets get past the initial reserved 96 bytes as we do not care about this. Write-Host "skipping reserved bytes" for ($ArrayStep = 1; $ArrayStep -le 96; $ArrayStep ++) { [byte[]]$this.reserved += $Valueenum.current #Store the reserved section so we can add it back for storing #Write-Host "Step $ArrayStep value $value" $result = $Valueenum.MoveNext() } #Next 2 bytes are the signature nee to turn this into a unicode char and if it is a P there is valid tem services data otherwise give up #So to combine two bites into a unicode char we do: Write-Host "Loading signature" [Byte[]]$unicodearray = $NULL for ($ArrayStep = 1; $Arraystep -le 2; $ArrayStep ++) { $value = $Valueenum.current #Write-Host "Step $ArrayStep value $value" [Byte[]]$unicodearray += $Value $result = $Valueenum.MoveNext() } $TSSignature = [System.Text.Encoding]::unicode.GetString($unicodearray) Write-Host "Signatire is $TSSignature based on $unicodearray" [uint32] $Value = $NULL If ($TSSignature -eq "P") # We have valid TS data { Write-Host "We have valid TS data so process it" #So now we need to grab the next two bytes which make up a 32 bit unsigned int so we know how many attributes are in this thing #We have no such data type so lets improvise by adding the value of the bytes together after multiplying the higer order byte by 256 $Value = [uint16]$Valueenum.current $result = $Valueenum.MoveNext() $Value += [uint16]$Valueenum.current * 256 $result = $Valueenum.MoveNext() write-Host "Found $value TS Attributes in the blob" $this.AttributeCount = [uint16]$value For ($AttribNo = 1; $AttribNo -le $value; $AttribNo ++)#For each attribute lets get going { #Get the first attribute, 2 bytes for name length, 2 bytes for value length, and 2 bytes for type, followed by the data. #Grab name length $NameLength = [uint16]$Valueenum.current $result = $Valueenum.MoveNext() $NameLength += [uint16]$Valueenum.current * 256 $result = $Valueenum.MoveNext() #Grab Value length $ValueLength = [uint16]$Valueenum.current $result = $Valueenum.MoveNext() $ValueLength += [uint16]$Valueenum.current * 256 $result = $Valueenum.MoveNext() #Grab Type $TypeValue = [uint16]$Valueenum.current $result = $Valueenum.MoveNext() $TypeValue += [uint16]$Valueenum.current * 256 $result = $Valueenum.MoveNext() #Write-Host "NameLength is $NameLength, ValueLength is $ValueLength, Type is $TypeValue" #Now we know how many bytes bellong to the following fields: #Get the name bytes into an array $NameUnicodeArray = $NULL for ($ArrayStep = 1; $Arraystep -le $NameLength; $ArrayStep ++) { [Byte[]]$NameUnicodeArray += $Valueenum.current $result = $Valueenum.MoveNext() } #Get the attribute value bytes into an array $ATTValueASCIICodes = "" for ($ArrayStep = 1; $Arraystep -le $ValueLength; $ArrayStep ++) { $ATTValueASCIICodes += [char][byte]$Valueenum.current $result = $Valueenum.MoveNext() } #Grab the name $AttributeName = [System.Text.Encoding]::unicode.GetString($NameUnicodeArray) Write-Host "UnBlobing: $AttributeName" #manipulate the value array as required #it is sets of two ASCII chars representing the numeric value of actual ASCII chars $AttributeValue = $NULL #$TempStr = "" #tem string for the Hex values #$ValueByteArray | foreach { $TempStr += [char][byte]$_ } #get the bytes into a string as the ASCII chars #write-host "Temp String = $ATTValueASCIICodes it is $($ATTValueASCIICodes.length)" switch ($this.Types.$AttributeName) { "Int32" { $AttributeValue = [convert]::toint32($ATTValueASCIICodes,16) } "ASCII" { $AttributeValue = "" #$ASCIIString = [System.Text.Encoding]::ASCII.GetString($TempStr)# make them into an ascii string for ($ArrayStep = 0; $Arraystep -lt $ATTValueASCIICodes.length; $ArrayStep += 2) { $FinalChar = [char][byte]([convert]::toint16( $ATTValueASCIICodes[($ArrayStep) ] + $ATTValueASCIICodes[$ArrayStep + 1],16)) #Grab the char created by this conversion $AttributeValue += $FinalChar #add them to the array. } } Default { $AttributeValue = "Attribute type Not defined" } } If ($this.TSAttributes.containsKey($AttributeName)) { $this.TSAttributes.$AttributeName = @($AttributeValue,$ATTValueASCIICodes,$NameLength,$ValueLength,$TypeValue) } else { $this.TSAttributes.add($AttributeName,@($AttributeValue,$ATTValueASCIICodes,$NameLength,$ValueLength,$TypeValue)) } } Write-Host "================================" } else { write-host "Signature is not valid, no TS Data" } } } $TSuserParameters |Add-Member -membertype ScriptMethod -name Blobify -value { #Lets build this thing #Start with the reserved bytes [byte[]]$result = $this.Reserved #now add the Signature "P" as we are writing valid data [byte[]]$result += [System.Text.Encoding]::unicode.GetBytes("P") #Now for the number of attributes being stored, we need to reverse the bytes in this 16 bit unsigned int $byte1 = [byte](($this.AttributeCount -band 65280) % 256) $byte2 = [byte]($this.AttributeCount -band 255) [byte[]]$result += $byte2 [byte[]]$result += $byte1 #Now for the attributes: $this.TSAttributes.getenumerator() | foreach { $Valuearray = $_.value $attname = $_.key #Get the reversed bytes for the NameLength field $byte1 = [byte](($Valuearray[2] -band 65280) % 256) $byte2 = [byte]($Valuearray[2] -band 255) [byte[]]$result += $byte2 [byte[]]$result += $byte1 #And again for the ValueLength $byte1 = [byte](($Valuearray[3] -band 65280) % 256) $byte2 = [byte]($Valuearray[3] -band 255) [byte[]]$result += $byte2 [byte[]]$result += $byte1 #And again for the typevalue $byte1 = [byte](($Valuearray[4] -band 65280) % 256) $byte2 = [byte]($Valuearray[4] -band 255) [byte[]]$result += $byte2 [byte[]]$result += $byte1 #Now add the propertyname in plain ASCII text Write-Host "Blobifying `"$attname`"" #$attnamearray = [System.Text.Encoding]::unicode.GetBytes("$attname") #Write-Host "Attname array = $($attnamearray.count), valuelength = $($Valuearray[2])" [byte[]]$result += [System.Text.Encoding]::unicode.GetBytes("$attname") #write-Host "$($result.count)" #for ($loopcount = 1; $loopcount -le $attname.length; $loopcount ++) #{ # [byte[]]$result += [BYTE][CHAR]$attname[$loopcount - 1] #} #And finaly add the value to the result using the ASCII conversion #New array of bytes to add the att value to so we can see how big it is $HexString = $Valuearray[1] [byte[]]$attvalbytes = $null switch ($this.Types.$attname) { "ASCII" { #now for each part of the hex string lets get the value for that ascii char $HexString.ToCharArray() | foreach { [byte[]]$attvalbytes += [BYTE][CHAR]($_) } } "Int32" { #For each char we need to store the byte value $HexString.ToCharArray() | foreach { [byte[]]$attvalbytes += [BYTE][CHAR]($_ ) } } } $result += $attvalbytes write-Host "att value is $($attvalbytes.count) and was $($Valuearray[3])" Write-Host "NewASCII = $([System.Text.Encoding]::ASCII.GetString($attvalbytes))" Write-Host "OldASCII = $($Valuearray[1])" Write-Host "================================" #[System.Text.Encoding]::unicode.GetString($result) } return [System.Text.Encoding]::unicode.GetString($result) } $TSuserParameters |Add-Member -membertype ScriptMethod -name AddUpdate -value { Param ($Attname,$NewAttValue,$TypeValue) $HexString = "" switch ($this.Types.$Attname) { "ASCII" { Write-host "ascii" for ($loopcount = 0; $loopcount -lt $AttValue.length; $loopcount ++) { #Lets get the Hex value for this char as a string $HexString = [convert]::tostring([BYTE][CHAR]($AttValue[$loopcount]),16) #As the hex conversion drops the leading zero on the first char if we have a less than 10 value add 0 toi the front if we do not have an even number of chars If (($hexstring.length % 2) -eq 1){ $hexstring = "0" + $hexstring} } } "Int32" { #convert the int32 to hex $HexString = [convert]::tostring($AttValue,16) #As the hex conversion drops the leading zero on the first char if we have a less than 10 value add 0 toi the front if we do not have an even number of chars If (($hexstring.length % 2) -eq 1){ $hexstring = "0" + $hexstring} #There is also the special case of the ctX flags value which is always stored as the full 32bits even when there ere empty bits: if (($attname -eq "CtxCfgFlags1") -and ($hexstring.length -lt 8)) { $Loopmax = $hexstring.length for ($loopcount = 1; $loopcount -le (8 - $Loopmax); $loopcount ++) {$HexString = "0" + $HexString ; Write-host "Done"} } } } $namelenght = ([System.Text.Encoding]::unicode.GetBytes($Attname)).count #Now change the values in the table: If ($this.TSAttributes.containsKey($Attname)) { #If we do not have an type value we can look in the table and get it from there as it is unlikely this attribute will change types If (-not $TypeValue)#If we are not offered a type value by the user we will assum it is the standard of 1 { $TypeValue = $this.TSAttributes.$Attname[4] } $this.TSAttributes.$Attname = @($NewAttValue,$HexString,$namelenght,$HexString.length,$TypeValue) } else { If (-not $TypeValue)#If we are not offered a type value by the user we will assum it is the standard of 1 { $TypeValue = 1 } $this.TSAttributes.add($Attname,@($NewAttValue,$HexString,$namelenght,$HexString.length,$TypeValue)) } } $TSuserParameters |Add-Member -membertype ScriptMethod -name Remove -value { Param ($Attname) If ($this.TSAttributes.containsKey($Attname)) { $test.remove("12") return $true } else { return $false } } 

我深知你的直觉告诉你“但PowerShell工作!”。 即使我无法对此进行测试,但我可以根据自己对MS Orchestrator和PowerShell 1.0的体验进行操作。 我怀疑如果本地启动的PowerShell实例可以通过您在上面发布的脚本获取此数据,并且C#中的工作流无论如何都不能使用Invoke-Command来突破限制(读取:通过C#实例化,以某种方式通过将整个脚本包装在变量中并传递给它,将版本恢复为“完整”function版本

invoke-command -computername localhost -scriptblock $yourScriptVar

此逻辑仅在C#解释器下执行invoke-command,然后传递给本地计算机上的新会话,并且具有任何默认值。 当我从Orchestrator强制进入PowerShell 1.0时,我常常这样做。 您可以更进一步,如果它在本地不起作用,则通过-computername直接在域控制器上运行该命令。

如果事实certificate这不起作用那么我会高度怀疑你在本地使用的脚本依赖于你本地系统上缓存的东西。 这部分纯粹是一种预感。

上面的@Ty Savercool发布的脚本对于检索和解码userParameters blob是正确的,但是它中有一些错误,它们不允许您更改属性并重新blobb userParameters并将它们提交回Active Directory 。

我将在这里发布修复程序,以便将来可能遇到此问题且需要使用此脚本的任何人。


  1. 第一个修复位于脚本的第2行,其中声明了属性类型和值。 这可能完全取决于被联系的AD域是否出现此属性,但它缺少属性CtxProfilePathW 。 如果您的AD域确实在userParameters blob中具有此属性并且未在此处声明,则该值将保留为空,并且重新填充userParameters将使所有值移位一,从而导致userParameters值损坏。 这是固定的行:

     $TSuserParameters |Add-Member -membertype NoteProperty -name Types -value @{"CtxCfgPresent" = "Int32"; "CtxCfgFlags1" = "Int32"; "CtxCallBack" = "Int32"; "CtxKeyboardLayout" = "Int32"; "CtxMinEncryptionLevel" = "Int32"; "CtxNWLogonServer" = "Int32"; "CtxWFHomeDirDrive" = "ASCII"; "CtxWFHomeDir" = "ASCII"; "CtxWFHomeDrive" = "ASCII"; "CtxInitialProgram" = "ASCII"; "CtxMaxConnectionTime" = "Int32"; "CtxMaxDisconnectionTime" = "Int32"; "CtxMaxIdleTime" = "Int32"; "CtxWFProfilePath" = "ASCII"; "CtxWFProfilePathW" = "ASCII";"CtxShadow" = "Int32"; "CtxWorkDirectory" = "ASCII"; "CtxCallbackNumber" = "ASCII"} 
  2. 下一个问题是脚本的AddUpdate方法,它允许您更改userParameters值的值。 在该方法的中途,参数变量$NewAttValue变为$AttValue 。 永远不会声明$AttValue ,因此它是null,并且使用$AttValue调用第一个尝试访问NewAttValue长度以更新值的$AttValue

  3. AddUpdate方法中也是如此; 在方法第5行的switch语句的ASCII逻辑中。 循环遍历$NewAttValue字符的循环不断覆盖第一个字符,从不前进到下一个字符。 这是通过添加一个临时变量来修复的,该变量将字符放在$NewAttValue的正确位置,将其转​​换为hex,然后将其附加到$HexString变量,该变量是属性的新值。

  4. 如果将新属性添加到userParameters blob,则需要在AddUpdate方法中增加属性的计数。 这是通过在将新属性添加到属性列表后在方法的最后添加$this.AttributeCount += 1来完成的。

  5. 最后一个问题也是使用AddUpdate方法。 如果值为Int32且其hex表示小于8个字节,则会破坏userParameters对象。 所有这些值都需要8个字节长,因此需要添加零。 这是通过插入swtich语句的Int32逻辑中的这行代码完成的:
    while ($hexstring.length -lt 8){ $hexstring = "0" + $hexstring}

下面是应用了修复程序2 – 5的AddUpdate方法:

 $TSuserParameters |Add-Member -membertype ScriptMethod -name AddUpdate -value { Param ($Attname,$NewAttValue,$TypeValue) $HexString = "" switch ($this.Types.$Attname) { "ASCII" { Write-host "ascii" Write-Output $NewAttValue.length for ($loopcount = 0; $loopcount -lt $NewAttValue.length; $loopcount ++) { #Lets get the Hex value for this char as a string $TempHexString = [convert]::tostring([BYTE][CHAR]($NewAttValue[$loopcount]),16) #As the hex conversion drops the leading zero on the first char if we have a less than 10 value add 0 toi the front if we do not have an even number of chars If (($TempHexString.length % 2) -eq 1){ $TempHexString = "0" + $TempHexString} $HexString += $TempHexString } } "Int32" { #convert the int32 to hex $HexString = [convert]::tostring($NewAttValue,16) #As the hex conversion drops the leading zero on the first char if we have a less than 10 value add 0 toi the front if we do not have an even number of chars If (($hexstring.length % 2) -eq 1){ $hexstring = "0" + $hexstring} while ($hexstring.length -lt 8){ $hexstring = "0" + $hexstring} #There is also the special case of the ctX flags value which is always stored as the full 32bits even when there ere empty bits: if (($attname -eq "CtxCfgFlags1") -and ($hexstring.length -lt 8)) { $Loopmax = $hexstring.length for ($loopcount = 1; $loopcount -le (8 - $Loopmax); $loopcount ++) {$HexString = "0" + $HexString ; Write-host "Done"} } } } $namelenght = ([System.Text.Encoding]::unicode.GetBytes($Attname)).count #Now change the values in the table: If ($this.TSAttributes.containsKey($Attname)) { #If we do not have an type value we can look in the table and get it from there as it is unlikely this attribute will change types If (-not $TypeValue)#If we are not offered a type value by the user we will assum it is the standard of 1 { $TypeValue = $this.TSAttributes.$Attname[4] Write-Output $TypeValue } $this.TSAttributes.$Attname = @($NewAttValue,$HexString,$namelenght,$HexString.length,$TypeValue) Write-Output $this.TSAttributes.$Attname } else { If (-not $TypeValue)#If we are not offered a type value by the user we will assum it is the standard of 1 { $TypeValue = 1 } $this.TSAttributes.add($Attname,@($NewAttValue,$HexString,$namelenght,$HexString.length,$TypeValue)) $this.AttributeCount += 1 } } 

通过应用这些修补程序,您可以检索AD用户的终端服务属性,对其进行解码,更新和重新编码,而不会出现任何问题!

以下是如何使用脚本来联系远程服务器的示例:

 $password = ConvertTo-SecureString "adminPassword" -AsPlainText -Force $cred= New-Object System.Management.Automation.PSCredential ("Domain\administrator", $password) $TSuserParameters.UnBlob((get-aduser -Server "192.111.222.33:389" -credential $cred -Identity SomePerson -Properties userparameters).userparameters) $TSuserParameters.AddUpdate("CtxWFProfilePath","New Profile Path!") Parameters = $TSuserParameters.Blobify($TSuserParameters.TSAttributes) Write-Output $Parameters Set-ADUser -Server "192.111.222.33:389" -credential $cred -Identity SomePerson -Replace @{userParameters=$Parameters} 

编辑

我还发现了从此脚本返回的Int32值的一些问题。 当你直接打印它们时,它们似乎毫无意义; 例如, CtxMaxIdleTimeCtxMaxDisconnectionTimeCtxMaxConnectionTime应该以分钟为单位输出它们的值。 当您直接从脚本打印它们时,它们会显示奇怪的值,如下所示:

 Time Output from Script 1 minute: 1625948160 5 minuites: -527236096 10 minutes: -1071183616 15 minutes: -1598354176 30 minutes: 1081547520 1 hour: -2131872256 

这是因为值存储在网络字节顺序(big-endian)和Windows x86体系结构中,它们以little-endian顺序读取。

这是一个链接 ,提供有关字节序的更多信息。

为了读取这些值,我们需要将值从big-endian转换为little-endian。

下面是使用CtxMaxIdleTimeCtxMaxDisconnectionTimeCtxMaxConnectionTime值执行此操作的代码。 这以分钟为单位输出值:

 # Get the Hex value of the number and split it into groups of two $realvalue = ($TSuserParameters.TSAttributes.CtxMaxConnectionTime[1] -split '(..)' | ? { $_ }) # Reverse the order of the Hex values into little-endian $new = $realvalue[3] += $realvalue[2] += $realvalue[1] += $realvalue[0] # Convert the new hex string to decimal, and divide by nanoseconds to get minutes $new1 = ([convert]::toint32($new,16)/60000) Write-Output $new1 

要将值在几分钟内转换回正确的值,请按以下步骤操作:

 # Convert the minutes into nanoseconds and convert to hex $HexString = [convert]::tostring(($new1*60000),16) # Make sure the new hex string is 8 bytes long while($HexString.length -lt 8){ $hexstring = "0" + $hexstring} # Split the hex string into groups of two characters $realvalue = ($HexString -split '(..)' | ? { $_ }) # Reverse the order of the hex characters back to big-endian $new = $realvalue[3] += $realvalue[2] += $realvalue[1] += $realvalue[0] # Convert to decimal $actual = ([convert]::toint32($new,16)) Write-Output $actual