gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 14:06:49 26 4
gpt4 key购买 nike

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

我正尝试在使用 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 calling "InvokeGet" with "1" argument(s): "Unknown name. (Exception from 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
  • msTS配置文件路径

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

SomePerson Prop

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

enter image description here

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


附加信息:

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

  1. 一个具有 Windows Server 2012 的林和域功能级别DC 在装有 Windows Server 2012 Version 6.2 (Build9200)
  2. 第二个具有 Windows Server 2012 的林和域功能级别R2 并在 Windows Server 2012 R2 版本 6.3(内部版本 9600)
  3. 上运行

我在另一台 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 脚本中。它只构建 1 个名为 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 的经验,我可以解决这个问题。我怀疑 IF 本地启动的 PowerShell 实例可以通过您在上面发布的脚本获取此数据,而 C# 中的工作流无法以某种方式获取此数据,那么您应该能够使用 Invoke-Command 来突破受限的 (读取:通过 C# 实例化并以某种方式损坏)版本通过将整个脚本包装在一个变量中并将其传递给“完整”功能版本

invoke-command -computername localhost -scriptblock $yourScriptVar

此逻辑仅在 C# 解释器下执行 invoke-command,然后传递到本地计算机上的新 session ,无论默认值是否就位。在从 Orchestrator 强制进入 PowerShell 1.0 时,我曾经一直这样做。如果它在本地不起作用,您可以更进一步,通过 -computername 直接在域 Controller 上运行命令。

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

关于c# - 在 Visual Studio 中运行 PowerShell 脚本时无法获取 Active Directory 终端服务属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45106182/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com