Here is a simple scenario in C#:
var intList = new List<int>();
intList.Add(4);
intList.Add(7);
intList.Add(2);
intList.Add(9);
intList.Add(6);
foreach (var num in intList)
{
if (num == 9)
{
intList.Remove(num);
Console.WriteLine("Removed item: " + num);
}
Console.WriteLine("Number is: " + num);
}
This throws an InvalidOperationException because I am modifying the collection while enumerating it.
Now consider similar PowerShell code:
$intList = 4, 7, 2, 9, 6
foreach ($num in $intList)
{
if ($num -eq 9)
{
$intList = #($intList | Where-Object {$_ -ne $num})
Write-Host "Removed item: " $num
}
Write-Host "Number is: " $num
}
Write-Host $intList
This script actually removes the number 9 from the list! No exceptions thrown.
Now, I know the C# example uses a List object while the PowerShell example uses an array, but how does PowerShell enumerate a collection that will be modified during the loop?
The foreach construct evaluates the list to completion and stores the result in a temporary variable before it starts iterating over it. When you do that actual removal you are updating $intList to reference a new list. In other words in actually doing something like this under the hood:
$intList = 4, 7, 2, 9, 6
$tempList=$intList
foreach ($num in $tempList)
{
if ($num -eq 9)
{
$intList = #($intList | Where-Object {$_ -ne $num})
Write-Host "Removed item: " $num
}
Write-Host "Number is: " $num
}
Write-Host $intList
Your call to:
$intList = #($intList | Where-Object {$_ -ne $num})
Actually creates a completely new list with the value removed.
If you change the removal logic to remove the last item in the list (6) then I think you'll find that it's still printed even though you think it's removed because of the temporary copy.
The answer is already given by #Sean, I am just providing the code which shows that the original collection is not changed during foreach: it enumerates through the original collection and there is no contradiction therefore.
# original array
$intList = 4, 7, 2, 9, 6
# make another reference to be used for watching of $intList replacement
$anotherReferenceToOriginal = $intList
# prove this: it is not a copy, it is a reference to the original:
# change [0] in the original, see the change through its reference
$intList[0] = 5
$anotherReferenceToOriginal[0] # it is 5, not 4
# foreach internally calls GetEnumerator() on $intList once;
# this enumerator is for the array, not the variable $intList
foreach ($num in $intList)
{
[object]::ReferenceEquals($anotherReferenceToOriginal, $intList)
if ($num -eq 9)
{
# this creates another array and $intList after assignment just contains
# a reference to this new array, the original is not changed, see later;
# this does not affect the loop enumerator and its collection
$intList = #($intList | Where-Object {$_ -ne $num})
Write-Host "Removed item: " $num
[object]::ReferenceEquals($anotherReferenceToOriginal, $intList)
}
Write-Host "Number is: " $num
}
# this is a new array, not the original
Write-Host $intList
# this is the original, it is not changed
Write-Host $anotherReferenceToOriginal
Output:
5
True
Number is: 5
True
Number is: 7
True
Number is: 2
True
Removed item: 9
False
Number is: 9
False
Number is: 6
5 7 2 6
5 7 2 9 6
We can see that $intList is changed when we "remove an item". It only means that this variable now contains a reference to a new array, it is the variable changed, not the array. The loop continues enumeration of the original array which is not changed and $anotherReferenceToOriginal still contains a reference to it.
The problem here is that you're not comparing equivalent code samples. In the Powershell sample you are creating a new list vs modifying the list in place as is done in the C# sample. Here is a sample which is closer in functionality to the original C# one
$intList = new-object System.Collections.ArrayList
$intList.Add(4)
$intList.Add(7)
$intList.Add(2)
$intList.Add(9)
$intList.Add(6)
foreach ($num in $intList) {
if ($num -eq 9) {
$intList.Remove($num)
Write-Host "Removed item: " $num
}
Write-Host "Number is: " $num
}
Write-Host $intList
And when run it produces the same error
Number is: 4
Number is: 7
Number is: 2
Removed item: 9
Number is: 9
An error occurred while enumerating through a collection: Collection was modifi
ed; enumeration operation may not execute..
At C:\Users\jaredpar\temp\test.ps1:10 char:8
+ foreach <<<< ($num in $intList)
+ CategoryInfo : InvalidOperation: (System.Collecti...numeratorSi
mple:ArrayListEnumeratorSimple) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration
4 7 2 6
Related
I have run into a strange problem, and maybe someone can help me.
I am attempting to retrieve the Terminal Services attributes from an Active Directory user using C# on a machine using Windows 10. I am doing this by running a PowerShell script inside my application like so:
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;
}
}
I am getting this exception:
System.Management.Automation.MethodInvocationException:
'Exception calling "InvokeGet" with "1" argument(s):
"Unknown name. (Exception
from HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))"'
When I run the above code in Visual Studio 2015 on a machine using Windows Server 2012 as the OS it works just fine! I made sure that my Windows 10 machine has RSAT installed as well.
What is strange is that when I run the script from a PowerShell console on my Windows 10 machine, it works! Here is the exact code of my PowerShell script:
$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")
And here is the output from PowerShell:
I also tried running the script in Visual Studio's PowerShell Interactive Window, and that works as well. Here is a screenshot and the output of that:
(Identifying info censored)
I found a very similar post to mine Here, but the provided answer does not work.
The answer to the above post states that the properties have changed names to:
msTSAllowLogon
msTSHomeDirectory
msTSHomeDrive
msTSProfilePath
But when I run my scripts with those property names, I do not receive the proper information back. They seem to not be the same properties. Here are a couple of screenshots of my user in Active Directory Users and Computers:
You can see the property that I am attempting to retrieve above.
When I look at the attibutes of the user on the Attribute Editor tab, you can see msTSProfilePath, and it holds a different value:
Running the script with msTSProfilePath returns the property seen above in the Attribute Editor window (????????).
Additional Info:
I have tested this against two separate Active Directory Domains:
One with Forest and Domain function levels of Windows Server 2012
with the DC running on a server with Windows Server 2012 Version 6.2 (Build
9200)
The second with Forest and Domain Function levels of Windows Server 2012
R2 and running on Windows Server 2012 R2 Version 6.3 (Build 9600)
I ran this code on another Windows 10 machine and the problem persists.
Thank you!
This problem really bothered the life out of me as since the days of server 2000 I have been trying to find ways to get at this value. I am unsure why the PowerShell script works on Windows 10 (I don't have a win10 box connected to a domain that I can freely query to test) but I did find a resolution to this problem. I know that you are not going to like it at first but bare with it, you don't have to do anything but copy/paste and append the short list of commands I have listed.
I'm sure you have figured out by now that the value is buried in the userParameters AD attribute. This is an encoded value. You can see the specification to unencode this value here (only if you are interested, not needed to get your value) https://msdn.microsoft.com/en-us/library/ff635169.aspx
Someone that understands this mess better then I do wrote I script that does the hard work for us. This script is located in the third post here: 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
Just copy and paste all the code into your PowerShell script. It only builds 1 object called TSuserParameters. You will use a method off that object called .UnBlob on the userParameters data returned from AD. Here I pull this data with Get-ADUser:
$TSuserParameters.UnBlob((get-aduser -Identity someuser -Properties userparameters).userparameters)
The object will parse the data and store it under the TSAttributes property collection. This intern stores the CtxWFProfilePath which has the data you want. There is meta data stored with the path itself (e.g. the length as this value is a variable width. To understand why, read the documentation in the first link, again, not relevant to getting your data). Becuase there is metadata stored with the object you will only want the first object in the property array [0]:
$TSuserParameters.TSAttributes.CtxWFProfilePath[0]
And now you have the data you want. This SHOULD work as far back as server 2000 as this specification of encoding does not appear to have changed since then.
You will also have access to the other TS attributes with this object.
Here is the full script is Stack is willing to let me post it:
$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
}
}
I know deep down your gut is telling you "But PowerShell works!". Even though I cannot test this, I have a possible work around from my experience with MS Orchestrator and PowerShell 1.0. I suspect that IF a locally started PowerShell instance can get this data via the script you posted above and somehow the workflow in C# cannot then you should be able to use Invoke-Command to break out of the restricted (read: instantiated via C# and somehow broken) version back to a "full" feature version by wrapping your whole script in a variable and passing it to
invoke-command -computername localhost -scriptblock $yourScriptVar
This logic only executes the invoke-command under the C# interpreter and then passes off to a fresh session on the local machine with whatever defaults are in place. I used to do this all the time when being forced into PowerShell 1.0 from Orchestrator. You could go a step further and run the command directly on the domain controller via -computername if it doesn't work locally.
If it turns out that this does not work then I would be highly suspect that the script you are using locally is relying on something cached on your local system. This part is purely a hunch.
The script posted by #Ty Savercool above is correct for retrieving and decoding the userParameters blob, but there are a couple of errors in it that don't allow you to change the properties and re-blobb the userParameters and commit them back to Active Directory.
I'm going to post the fixes here for anyone who may come across this in the future and need to use this script.
The first fix is in line 2 of the script where the property types and values are declared. This may be entirely dependent on the AD domain being contacted as to if this property even comes up, but it is missing the property CtxProfilePathW. If your AD domain does indeed have this property in the userParameters blob and it is not declared here, that value will be left blank, and re-blobbing the userParameters will shift all values over one causing the userParameters value to be corrupt. Here is the line fixed:
$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"}
The next issue is in the AddUpdate method of the script, which allows you to change the values of the userParameters values. Halfway through the method, the parameter variable $NewAttValue gets changed to $AttValue. $AttValue is never declared, so it is null and the loop the first loop that tries to access the length of NewAttValue to update the value is called with $AttValue.
In the AddUpdate method as well; in the ASCII logic of the switch statement on line 5 of the method. The loop that loops over the characters of the $NewAttValue continuously overwrites the first character, never advancing to the next characters. This is fixed by adding a temporary variable that takes the character at the correct position of the $NewAttValue, converts it to hex, then appends it to the $HexString variable that is the new value of the attribute.
If a new attribute is added to the userParameters blob, the count of the attributes needs to be incremented in the AddUpdate method. This is done by adding $this.AttributeCount += 1 at the very end of the method after the new attribute is added to the list of attributes.
The last issue is with the AddUpdate method as well. If the value is Int32 and the Hex representation of it is less than 8 bytes, it corrupts the userParameters object. All of these values need to be 8 bytes long, so zeros need to be added to it. This is done with this line of code inserted into the Int32 logic of the swtich statement:
while ($hexstring.length -lt 8){ $hexstring = "0" + $hexstring}
Below is the AddUpdate method with fixes 2 - 5 applied:
$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
}
}
With these fixes applied, you can retrieve an AD user's terminal services properties, decode, update, and re-encode them with no issue!
Here is an example of how to use the script to do that contacting a remote server:
$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
I also found some issues with the Int32 values that are returned from this script. When you print them out directly, they appear to not make any sense;
For instance, the CtxMaxIdleTime, CtxMaxDisconnectionTime, and CtxMaxConnectionTime are supposed to output their values in minutes. When you print them directly from the script, they appear with strange values like so:
Time Output from Script
1 minute: 1625948160
5 minuites: -527236096
10 minutes: -1071183616
15 minutes: -1598354176
30 minutes: 1081547520
1 hour: -2131872256
This is because the values are stored in Network Byte Order (big-endian) and in Windows x86 architecture, they are read in little-endian order.
Here is a link with more information on endianness.
In order to read these values, we need to convert the values from big-endian order to little-endian.
Below is the code to do that with the CtxMaxIdleTime, CtxMaxDisconnectionTime, and CtxMaxConnectionTime values. This outputs the values in minutes:
# 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
To convert values in minutes back to the correct values, here is how to do it:
# 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
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
For a study project, i have to create a tic tac toe game.
I use an array in my game to keep track of the game board state. At start, the array contains numbers 0-8, as strings. The array is printed to the screen, as follows:
static void TableauDuJeu() // Je crée la fonction pr afficher le tableau.
{
// Création du cadre/
{
Console.WriteLine("_________________ ");
Console.WriteLine(" | | | ");
Console.WriteLine(" {0} | {1} | {2} |", TableauMorpion[0], TableauMorpion[1], TableauMorpion[2]);
Console.WriteLine("_____|_____|_____| ");
Console.WriteLine(" | | | ");
Console.WriteLine(" {0} | {1} | {2} |", TableauMorpion[3], TableauMorpion[4], TableauMorpion[5]);
Console.WriteLine("_____|_____|_____| ");
Console.WriteLine(" | | | ");
Console.WriteLine(" {0} | {1} | {2} |", TableauMorpion[6], TableauMorpion[7], TableauMorpion[8]);
Console.WriteLine("_____|_____|_____| ");
}
When the player presses 0, the case {0} will be printed with a "X" or an "O"; this value will be stored in my array at position 0, corresponding to the key the player pressed.
The game finishes when a victory condition is up (so for example,{0} {1} {2} marked with a "X" or "O"; a new game starts; but my array still contains the "X"'s and "O"'s from the last game, which were stored in the array.
How do I clear the values stored in this array, and reset it back to contain numbers 0-8 (represented as strings)?
I tried Array.Clear(TableauMorpion, 0, 9) but my array is empty. Without the strings {0}->{8} inside.
How can i solve this problem ?
P.S : I am a beginner. So this problem might be easy for you !
You need to loop through the array and set the values to original (my understanding is you'd like to display numbers 0-8 as starting values):
for(int i = 0; i < 9; i++)
{
TableauMorpion[i] = i.ToString();
}
This is presuming your array is declared as a string[] (not clear from your posted code)
I want to reset it with {"0", "1","2" -> "8"} my initial values
TableauMorpion = Enumerable.Range(0,9).Select(x => x.ToString()).ToArray();
or just
TableauMorpion = new[] { "0", "1", "2", "3", "4", "5", "6", "7", "8" };
This question already has answers here:
How do you get a variable's name as it was physically typed in its declaration? [duplicate]
(11 answers)
Closed 7 years ago.
I have an array
int[] arr1 = new int[] { 1, 2, 3 };
Console.WriteLine("How to print the text arr1 ?? ");
I need the array name i.e. 'arr1' in console output/Writeline, seems simple but need help.
Edit :
Output should be
arr1
NOT
1 2 3
Good to know, this is achieved in PHP too.
If you declare/initialize variables in a far more complicated way you could...
echo "<pre>";
$var_name="Foo"; // naming the var "Foo"
$$var_name="Bar"; // declaring the var "Foo"
echo "value: ". $Foo . "<br><br>";
foreach(compact($var_name) as $key=>$value){ // refering to var name
echo "name: " . $key . "<br><br>";
}
echo "pair: ";
print_r(compact($var_name));
echo "<pre>" ;
But we'd still need to get it through compact($var_name) instead of compact($Foo)
After analyzing a snippet of code from this link (the C# portion), I tried doing this on my own for some practice.
However, I'm confused about how the portion below translates to an X,Y index in the string list, and why the if() statement has the Y index before the X.
if (Map[playerY][playerX] == ' ')
Here's what the list looks like:
List<string> Map = new List<string>()
{
"##########",
"# #",
"# > #",
"# > #",
"# #",
"##########"
};
Any help would be appreciated, thank you in advance!
The first [ ] picks one string from the array. The second [ ] picks a character from the string.
Because strings are arrays themselves, calling an indexer function such as: string[n] will get the character at position n.
So when you are trying to get the character the player is on, you get the Y coordinate by indexing the array of strings, because the first string in the array is the top row of the map.
Y |
------------------
0 | ##########
1 | # #
2 | # > #
3 | # > #
4 | # #
5 | ##########
We then pick out the X by matching it to the character at the X position in the string:
X | 0123456789
------------------
| ##########
| # #
| # > #
| # > #
| # #
| ##########
So [Y,X] will get the appropriate character.
The Y index selects which string, as you would expect from a List. The X index actually picks a character from that string. This wouldn't work on a List of, say, ints, because this example is actually using the [] operator on the List and then using it again on the String the List returns.
The snippet below detects from a list of files which of them is a Directory on Ftp
as C# it will be like below
var files = new List<string>(){"App_Data", "bin", "Content"};
var line = "drwxr-xr-x 1 ftp ftp 0 Mar 18 22:41 App_Data"
var dir = files.First(x => line.EndsWith(x));
How I can transalte the last line in PowerShell ?
Something like this...
$files = #("App_Data", "bin", "Content")
$line = "drwxr-xr-x 1 ftp ftp 0 Mar 18 22:41 App_Data"
$dir = $files | Where { $line.EndsWith($_) } | Select -First 1
These versions of the last line would all accomplish the same:
$dir = #($files | Where { $line.EndsWith($_) })[0]
$dir = $files | Where { $line.EndsWith($_) } | Select -index 0
$dir = $files | Where { $line.EndsWith($_) } | Select -First 1
It was pointed out that the above is not exactly equivalent in behavior to Linq.First because Linq.First throws exceptions in two cases:
Throws ArgumentNullException when source or predicate is null.
Throws InvalidOperationException when source sequence is empty or no element satisfies the condition in predicate.
If you wanted that behavior exactly, you'd need some extra guard code.
as Robert Groves said, Select-Object -First Occurence do the tricks, you can also use -Last Occurence.
by the way, like any other static .Net method you can use linq in powershell.
[Linq.Enumerable]::First($list)
[Linq.Enumerable]::Distinct($list)
[Linq.Enumerable]::Where($list, [Func[int,bool]]{ param($item) $item -gt 1 })
Doug Finke produced a great video ( only 7 mins ) about converting C# to Powershell
http://dougfinke.com/video/CSharpToPowerShell.html
Roberts example is very good indeed, though comma delimiting will implicitly be treated as an array
the shortest way of doing it would be to put it all into a single pipeline :
$dir = "App_Data", "bin", "Content" | % { if("drwxr-xr-x 1 ftp ftp 0 Mar 18 22:41 App_Data".EndsWith($_)) { $_ } } | select -first 1
There is a native way to do this using the Powershell Array's Where Function by passing in a WhereOperatorSelectionMode like this:
(1..9).Where({ $_ -gt 3}, "First") # 4
You can also use the mode straight from the enum as well:
$mode = [System.Management.Automation.WhereOperatorSelectionMode]::First
(1..9).Where({ $_ -gt 3}, $mode) # 4
Using any of the values from the WhereOperatorSelectionMode enum
Name
Val
Description
Default
0
Return all items
First
1
Return the first item
Last
2
Return the last item
SkipUntil
3
Skip items until condition is true
Until
4
Return all items until condition is true
Split
5
Return an array of two elements
See Also: Checking Out The Where and ForEach Operators in PowerShell V4
This is a really simple implementation for First:
function First($collection)
{
foreach ($item in $collection)
{
return $item
}
return $null
}
Instead of returning $null, you could throw an InvalidOperationException exception.