Posts RSS Comments RSS 127 Posts and 199 Comments till now

Archive for July, 2008

Using Citrix MFCOM Inpersonation Remotely

I often get questions about scripts running with no errors, but not returning any valid information.

For example

$mfFarm = New-object -com "MetaframeCOM.MetaFrameFarm"
$mfFarm.Initialize(1)
$mfFarm.FarmName # will return the Farm Name, but that is it.
$mfFarm | gm # all the properties are there, but blank
# But none of the properties provide any info
$mfFarm.Admins # returns nothing



I ran into this a while back and meant to blog it but forgot. I was asked this question again today and decided to finally get to blogging it.

Reason:
Generally the cause is that MFCOM is not configure for impersonation. MFCom needs to be able to act as if it is the user using the script. This is called impersonation. To make this work, you must configure DCOM to use impersonation using the article below.

Solution:
See here for more info and how to change it.
Configuring MFCOM Access Security

Using rootDSE mods to transfer role ownership

A while back I had conversation with Richard siddaway about a blog entry he made HERE.

The basic gist is there seems to be a bug in the TransferRoleOwnership method of .NET Class DomainController. There is clearly a problem, but I say “seems” because strictly speaking the DomainController class does not include Windows 2008.

Thus we fall back on our old trusty rootDSE mods and for those of you that have never heard of rootDSE mods (you may have heard them referred to as Operational Attributes) I definately recommend reading up on them HERE.

Effectively what we do is get the rootDSE for the DC we want to transfer the role to and set one of the following mods to 1:

  • becomeInfrastructureMaster
  • becomePDC
  • becomeRidMaster
  • becomeSchemaMaster
  • becomeDomainMaster
  • You, of course, need to have the valid rights to perform the operation.

    Here is the Script I use

    Param($Server = "127.0.0.1",$role)
    if(!$role){return "Please enter a valid Role: IM,PDC,RID,Schema,DM"}
    $rootDSE = [ADSI]"LDAP://$Server/rootDSE"
    Write-Host
    Write-Host " Moving FSMO Role"
    Write-Host " - Using Server: [$Server]"
    Write-Host " - Using Role:   [$Role]"
    switch -exact ($role)
    {
        "IM"        {$myrole = ‘becomeInfrastructureMaster’}
        "PDC"       {$myrole = ‘becomePDC’}
        "RID"       {$myrole = ‘becomeRidMaster’}
        "Schema"    {$myrole = ‘becomeSchemaMaster’}
        "DM"        {$myrole = ‘becomeDomainMaster’}
        Default     {return "Please provide Valid Role: IM,PDC,RID,Schema,DM"}
    }
    Write-Host " - Performing $MyRole on $Server"
    $rootDSE.put($myRole,1)
    $rootDSE.SetInfo()
    Write-Host

    Getting the UpToDateness Vector (UTDV) in Powershell

    A year or so ago I needed to get the UTDV Table (defined in detail HERE by Laura Hunter of ShutUpLaura.) At the time, the only way I knew how to get this information was to get the replUpToDateVector attribute from the NC on the target DC. I could have used Repadmin shown below, but that wouldn’t have been any fun. I did however end up with the massive script at the bottom of this post. It required decoding the UTDV table and resolving the Invocation ID to a name or “DeletedDSA.”

    Fast forward to the present.

    During the course of a long conversation on an ActiveDir thread (“Domain Controller Version”,) joe (aka joeware) and I got into side conversation about getting the UTDV table (ironically due to a statement/question in the thread by the same Laura, mentioned earlier.)

    This led me to a wonderful little .NET method (GetReplicationCursors) on DirectoryServices.ActiveDirectory.DomainController class. This was so much easier I wanted to kick myself for not finding it sooner.

    In any case, it was a great learning experience so I wanted to share it you. Enjoy my pain and victory.

    Using Repadmin

    repadmin /showutdvec [dc]  "[ DN for Naming Context ]"

    The easy way.
    Using System.DirectoryServices.ActiveDirectory.DomainController

    Write-Host
    $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
    $NC = "DC={0}" -f ($domain.Name -replace "\.",",DC=")
    foreach($dc in $domain.DomainControllers)
    {
        Write-Host "$($DC.Name)"
        Write-Host "================"
        $UDTV =  $dc.GetReplicationCursors($NC)
        $GUID = @{n=‘DSA’;e={if($_.SourceServer){$_.SourceServer}else{$_.SourceInvocationId}}}
        $UDTV | Select-Object $GUID,
                              UpToDatenessUsn,
                              LastSuccessfulSyncTime | Sort DSA -desc | Format-Table -auto
        Write-Host
    }

    The Hard way.
    I initially did all the work myself using this function. This gets the Up To Dateness vector and decodes it. It also translates the Invocation ID (if possible.)

    param([string]$server)

    Begin{
        function Get-InvocationIDFromBytes{
            Param([byte[]]$GuidArray)
            $GUIDSection1 = @(1..4)
            $GUIDSection2 = @(1..2)
            $GUIDSection3 = @(1..2)
            $GUIDSection4 = @(1..2)
            $GUIDSection5 = @(1..6)
       
            $start = 0
           
            $InvocationIDString = ""
           
            foreach($byte in $GuidArray)
            {
                $InvocationIDString = $InvocationIDString + "\" + ([string]::Format("{0:X2}",$byte))
            }
           
            [system.Array]::Copy($GuidArray,$start,$GUIDSection1,0,4)
            [system.Array]::Copy($GuidArray,($start+4),$GUIDSection2,0,2)
            [system.Array]::Copy($GuidArray,($start+6),$GUIDSection3,0,2)
            [system.Array]::Copy($GuidArray,($start+8),$GUIDSection4,0,2)
            [system.Array]::Copy($GuidArray,($start+10),$GUIDSection5,0,6)
            [system.Array]::Reverse($GUIDSection1)
            [system.Array]::Reverse($GUIDSection2)
            [system.Array]::Reverse($GUIDSection3)
           
            [string]$GuidString1 = $GUIDSection1 | %{[string]::Format("{0:X2}",$_)}
            [string]$GuidString2 = $GUIDSection2 | %{[string]::Format("{0:X2}",$_)}
            [string]$GuidString3 = $GUIDSection3 | %{[string]::Format("{0:X2}",$_)}
            [string]$GuidString4 = $GUIDSection4 | %{[string]::Format("{0:X2}",$_)}
            [string]$GuidString5 = $GUIDSection5 | %{[string]::Format("{0:X2}",$_)}
           
            $GuidString1 = $GuidString1.replace(" ","")
            $GuidString2 = $GuidString2.replace(" ","")
            $GuidString3 = $GuidString3.replace(" ","")
            $GuidString4 = $GuidString4.replace(" ","")
            $GuidString5 = $GuidString5.replace(" ","")
           
            $InvocationGUID = "{0}-{1}-{2}-{3}-{4}" -f $GuidString1,$GuidString2,$GuidString3,$GuidString4,$GuidString5
           
            $name = Get-NameFromInvocationID $InvocationIDString
            if(!$DCInvocationID.$InvocationGUID)
            {
                if($name)
                {
                    $DCInvocationID.add($InvocationGUID,($InvocationIDString,$name))
                }
                else
                {
                    $DCInvocationID.add($InvocationGUID,$InvocationIDString)
                }
            }
            return $InvocationGUID
        }
        function Get-USNFromBytes{
            Param([byte[]]$USNArray)
            [system.Array]::Reverse($USNArray)
            [string]$USN = $USNArray | %{[string]::Format("{0:X2}",$_)}
            $usn = $usn.replace(" ","")
            $usn = [int]"0x$usn"
            $usn.PadRight(12)
        }
        function Get-DateFromBytes{
            Param([byte[]]$dateArray)
            [system.Array]::Reverse($dateArray)
            $temp = @(1..5)
            [system.Array]::Copy($dateArray,3,$temp,0,5)
            [string]$date = $temp | %{[string]::Format("{0:X2}",$_)}
            $date = $date.replace(" ","")
            $date = [int64]"0x$date"
            $date = $date + "0000000"
            [system.DateTime]::FromFileTime($date)
        }
        function Get-NameFromInvocationID{
            Param($InvocationID)
            [string]$config = ([adsi]"LDAP://RootDSE").ConfigurationNamingContext
            $de = new-Object System.DirectoryServices.DirectoryEntry("LDAP://$Config")
            $filter = "(&(invocationID=$InvocationID)(objectcategory=ntdsdsa))"
            $ds = new-Object System.DirectoryServices.DirectorySearcher($de,$filter)
            $hresult = $ds.findone()
            if($hresult.Path)
            {
                $ResultDE = $hresult.GetDirectoryEntry()
                $name = ($ResultDE.psbase.parent).DNSHostName
            }
            $name
        }
        function Decode-UpToDateVectorTable{
            Param([byte[]]$table)
           
            $guid = @(1..16)
            $USN = @(1..8)
            $Date = @(1..8)
            $UTDTable = @()
            $name = $null
           
            for($i=16;$i -lt $table.count;$i+=32)
            {
                [system.Array]::Copy($table,$i,$Guid,0,16)
                [system.Array]::Copy($table,$i+16,$USN,0,8)
                [system.Array]::Copy($table,$i+16+8,$Date,0,8)
               
                $GUIDString = Get-InvocationIDFromBytes $GUID
                $USNString  = Get-USNFromBytes $usn
                $dateString = Get-DateFromBytes $Date
                $UTDEntry = "" | Select-Object Name,USN,Date
                [system.Array]$dcname = $DCInvocationID.$GUIDString
                if($dcname[1])
                {
                    $UTDEntry.Name = $dcname[1]
                    $UTDEntry.USN =  $USNString
                    $UTDEntry.Date = $DateString
                    $UTDTable += $UTDEntry
                }
                else
                {
                    $UTDEntry.Name = $GUIDString
                    $UTDEntry.USN =  $USNString
                    $UTDEntry.Date = $DateString
                    $UTDTable += $UTDEntry
                }
            }  
            $UTDTable
        }
        $DCInvocationID = @{}
        $utdTables = @()
        $process = @()
    }
    Process{
        if($_)
        {
            $process += $_
        }
    }
    End{
        if($server){$process += $server}
        foreach($srv in $process)
        {
            $table = ([adsi]"LDAP://$srv").replUpToDateVector[0]
            if($process.count -gt 1)
            {
                $utdTableEntry = "" | Select-Object Name,UpToDatenessVector
                $utdTableEntry.Name = $srv
                $utdTableEntry.UpToDatenessVector = Decode-UpToDateVectorTable $table | Sort-Object Name -des
                $utdTables += $utdTableEntry
            }
            else
            {
                Decode-UpToDateVectorTable $table | Sort-Object Name -des
            }
        }
        if($utdTables){return $utdTables}
    }

    userAccountControl and “User cannot change password”

    Someone asked me a question about setting the “User cannot change password” check box in ADUC. They were creating the user account and setting PASSWD_CANT_CHANGE along with other settings (see my post about HERE about userAccountControl and values) and they couldn’t figure out why the check box wasn’t being applied.

    I thought about this, and my first impression was they had the wrong bit value. So, I posted the “correct” one. They came back and said “That didn’t work.”

    Hmmm, that’s curious. I turned to Dean and asked him if I was missing something obvious (I had a nagging feeling I had been here before) and he informed me that it has to be set with an “Extended Right” (control access right) via an ACE.

    DOH! Now the whole scenario I had THAT feeling about came back to me. I recall having this discussion with someone and providing them a Script that would set the ACE. I searched for the script and I couldn’t find it. This happens to me a lot so I decided a while ago… when I run across this again, BLOG IT!

    Technical Info:

    In the past, permissions on the ‘userAccountControl’ attribute could be edited oftentimes making the effective password policy moot, i.e. you could end up with accounts that don’t comply with the domain’s password policy.

    In Windows 2000, you can’t easily prevent this except by using third party front-end/provisioning tools to manage user objects. In Windows 2003 and later, you can use three newly-added extended rights (Control Access Rights) to prevent these bits from being edited even when the caller has permission to do so. The three (new) ‘Extended Rights’ are -

    • Update password not required bit [controls 'password not required' and maps to ACE in footnote below]
    • Enable per user reversible encryption [controls whether password is stored reversibly encrypted or not]
    • Unexpire password [controls 'password never expires']

    Each of these extended rights MUST be configured on the domain head and scoped as “This Object only” with ALLOW or DENY for the security principals you designate. By default, ‘Authenticated Users’ is granted an ALLOW ACE for each of the three extended rights. This doesn’t mean any old authenticated user can alter the password related bits in the ‘userAccountControl’ attribute; they still require the permission to modify ‘userAccountControl’.

    USAGE SCENARIO - create a single group representing ALL three extended rights (or perhaps ONE group for EACH extended right). Then ACL the group(s) accordingly on the domain head with a DENY ACE. Finally, place the account-administrator users and groups that have management permissions to user objects (i.e. they have write permissions to the ‘userAccountControl’ property) in the group(s) you just created thereby preventing those account-administrators from altering the password related bits on the ‘userAccountControl’ attribute resulting in an enforced password policy.

    IMPORTANT NOTE [Observed Behavior] - When viewing or changing a user’s ability to change their own password (User Cannot Change Password) through the GUI, it no longer appears to touch ‘userAccountControl’s bit 0×40 (64) — rather, it simply grant’s the ‘SELF’ security principal ‘ALLOW’ or ‘DENY’ to ‘Change Password’ — this can be easily verified by viewing the DACL.

    Links:
    Modifying User Cannot Change Password (LDAP Provider)

    So… Here it is (Set-UserCannotChangePassword.ps1)
    Parameters
    -User: The sAMAccountName of the User
    -CheckBox: If passed it checks the box
    -Default: Remove Check box.

    Param($User = $(throw ‘$User is Required’,[switch]$CheckBox)
    Write-Host

    $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"","(&(objectcategory=User)(sAMAccountName=$user))")
    $MyUser = $Searcher.FindOne().GetDirectoryEntry()

    if(!$?){" !! Failed to Get User !!";Return}

    if($CheckBox)
    {
        Write-Host " - Checking Box for User [$($MyUser.distinguishedName)]"
        $self = [System.Security.Principal.SecurityIdentifier]‘S-1-5-10′
        $ExtendedRight = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight
        $deny = [System.Security.AccessControl.AccessControlType]::Deny
        $selfDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule($self,$ExtendedRight,$deny,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
        $MyUser.psbase.get_ObjectSecurity().AddAccessRule($selfDeny)
        $MyUser.psbase.CommitChanges()
    }
    else
    {
        Write-Host " - Removing Check Box for User [$($MyUser.distinguishedName)]"
        $ACL = $MyUser.psbase.get_ObjectSecurity().GetAccessRules($true,$false, [System.Security.Principal.NTAccount])
        $ACEs = $ACL | ?{($_.ObjectType -eq ‘ab721a53-1e2f-11d0-9819-00aa0040529b’) -and ($_.AccessControlType -eq ‘Deny’)}
        foreach($ACE in $ACEs){if($ACE){[void]$MyUser.psbase.get_ObjectSecurity().RemoveAccessRule($ACE)}}
        $MyUser.psbase.CommitChanges()
    }

    Write-Host

    Using Get-Credential to Store Passwords “securely” in a file.

    Lee Holmes of PowerShell Cookbook fame has a post here Importing and Exporting Credentials in PowerShell. After fielding a few questions on EE regarding this post.. I wrote these two scripts to make it a little simpler.

    Set-myCredential: This will prompt you for credentials and store them in the file specified.

    #####################
    #Set-myCredential.ps1
    Param($File)
    $Credential = Get-Credential
    $credential.Password | ConvertFrom-SecureString | Set-Content $File
    #####################

    Get-myCredential: This will get you credentials from a file specified. It require you know the user name.

    #####################
    #Get-myCredential.ps1
    Param($User,$File)
    $password = Get-Content $File | ConvertTo-SecureString
    $credential = New-Object System.Management.Automation.PsCredential($user,$password)
    $credential
    #####################

    With these two script you can do something like this (using VMware Toolkit for example.)

    c:\scripts\Set-myCredential.ps1 c:\tools\mp.txt
    $creds = c:\scripts\Get-myCredential.ps1 MyUserName c:\tools\mp.txt
    Get-ViServer MyVirtualCenter -cred $creds

    What Are “Parameterized Properties?”

    I recently had someone ask me a question about property types. They kept referring to Parameterized Properties and while I knew they were confused, I only had vague understanding of this concept and I knew I definitely could not define it.

    I had tried google and all I came up with was this ETS Parameterized Properties. I shot this description over to a friend at MS and he said that was a bad definition and one didn’t really exist. I asked him to define it the best he can and here is the result. I would consider this source pretty authoritative :)

    Note: These are very common with COM Objects

    Parameterized Property

    Properties are members that are accessed like fields but are actually methods. There is a setter method for setting the value of the property and a getter method for getting the current value of the property. Normally, the setter method takes only one parameter (the value to set) and the getter does not take any.

    However, there are situations where additional parameters are needed. Consider the case where you want to use the property to get and set a member of a collection. To do this, the setter method requires 2 parameters—the index (location) in the collection and the value to set the member to; the getter method requires 1 parameter—the index to retrieve the value from. When a property requires additional parameters to operate, it is called a parameterized property or an indexer.