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

时间:2017-07-14 15:03:54

标签: c# 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:     '带有“1”参数的异常调用“InvokeGet”:     “名字不明。(例外     来自HRESULT: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 output

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

enter image description here

<小时/> 我找到了一个非常类似的帖子Here,但提供的答案不起作用。
上述帖子的答案表明属性已将名称更改为:

  • msTSAllowLogon
  • msTSHomeDirectory
  • msTSHomeDrive
  • msTSProfilePath

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

SomePerson Prop

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

enter image description here

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

<小时/>

其他信息:

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

  1. 具有 Windows Server 2012 的林和域功能级别的一个 使用 Windows Server 2012版本6.2(Build。)在服务器上运行DC 9200)
  2. 第二个具有 Windows Server 2012的林和域功能级别 R2 并在 Windows Server 2012 R2版本6.3(Build 9600)上运行
  3. 我在另一台Windows 10计算机上运行此代码,问题仍然存在。

    谢谢!

2 个答案:

答案 0 :(得分:6)

这个问题真的困扰了我的生活,因为自服务器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-attribute-to-set-terminal-services?forum=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
    }
}

我深知你的直觉告诉你&#34;但是PowerShell有效!&#34;。虽然我无法测试这个,但我可以通过MS Orchestrator和PowerShell 1.0的经验来解决这个问题。我怀疑 IF 本地启动的PowerShell实例可以通过您在上面发布的脚本获取此数据,并且C#中的工作流无论如何都不能使用Invoke-Command来突破限制(读取:通过C#实例化并以某种方式破坏)版本回到&#34;完整&#34;功能版本通过将整个脚本包装在变量中并将其传递给

invoke-command -computername localhost -scriptblock $yourScriptVar

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

如果事实证明这不起作用,那么我将非常怀疑您在本地使用的脚本是依赖于本地系统上缓存的内容。这部分纯粹是一种预感。

答案 1 :(得分:1)

@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,并且使用NewAttValue调用第一个尝试访问$AttValue长度以更新值的循环。

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

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

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

  6. 以下是应用了修复程序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}
    

    <小时/> EDIT

    我还发现了从此脚本返回的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顺序读取。

    Here is a link提供有关字节序的更多信息。

    为了读取这些值,我们需要将值从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