c# – 在Visual Studio中运行PowerShell脚本时无法获取Active Di
我遇到了一个奇怪的问题,也许有人可以帮助我.
我试图在使用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; } } 我得到这个例外:
当我在使用Windows Server 2012作为操作系统的计算机上运行Visual Studio 2015中的上述代码时,它工作正常!我确保我的Windows 10机器也安装了RSAT. 奇怪的是,当我从Windows 10机器上的PowerShell控制台运行脚本时,它可以工作!以下是我的PowerShell脚本的确切代码: $user = [ADSI]"LDAP://192.111.222.33:389/CN=SomePerson,DC=local" $user.psbase.Username = "administrator" $user.psbase.Password = "adminPassword" Write-Output "Terminal Services profile path:" Write-Output $user.psbase.invokeget("TerminalServicesProfilePath") 以下是PowerShell的输出: 我也尝试在Visual Studio的PowerShell交互式窗口中运行该脚本,这也是有效的.这是截图和输出: 我发现了一个与我的Here非常相似的帖子,但提供的答案不起作用. 上述帖子的答案表明属性已将名称更改为: > msTSAllowLogon 但是,当我使用这些属性名称运行脚本时,我没有收到正确的信息.它们似乎不是同一个属性.以下是我的用户在Active Directory用户和计算机中的几个屏幕截图: 您可以在上面看到我尝试检索的属性. 当我在“属性编辑器”选项卡上查看用户的属性时,您可以看到msTSProfilePath,它包含不同的值: 使用msTSProfilePath运行脚本将返回上面在“属性编辑器”窗口(????????)中看到的属性. 附加信息: 我已针对两个单独的Active Directory域对此进行了测试: >一个具有Windows Server 2012的林和域功能级别 我在另一台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-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,$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,$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#实例化,以某种方式通过将整个脚本包装在变量中并传递给它,将版本恢复为“完整”功能版本 invoke-command -computername localhost -scriptblock $yourScriptVar 此逻辑仅在C#解释器下执行invoke-command,然后传递给本地计算机上的新会话,并且具有任何默认值.当Orchestrator强制进入PowerShell 1.0时,我常常这样做.您可以更进一步,如果它在本地不起作用,则通过-computername直接在域控制器上运行该命令. 如果事实证明这不起作用,那么我将非常怀疑您在本地使用的脚本是依赖于本地系统上缓存的内容.这部分纯粹是一种预感. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |