Posts RSS Comments RSS 253 Posts and 411 Comments till now

Some AD Functions for DCs and name conversion.

Below are some functions I have written or used recently that I don’t believe I shared before

Get-DC: Gets a DirectoryServices.ActiveDirectory.DomainController object by Name or Domain. If nothing is passed it gets a DC from the current domain

Get-DCConnectionObject: Gets the connection objects for the given DC. Default is all DCs

ConvertTo-Sid: Converts Name to SID.

ConvertTo-Name: Converts Sid to Name.

################################################################################
function Get-DC
{
    Param($Name,$Domain)
   
    if($Name)
    {
        $Context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer",$Name)
        [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($Context)
    }
    if($Domain)
    {
        $Context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$Domain)
        [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($Context)
    }
    if(!$Name -and !$Domain)
    {
        $DCName = ([adsi]"LDAP://rootDSE").dnsHostname.ToString()
        $Context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer",$DCName)
        [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($Context)
    }
}
################################################################################
function Get-DCConnectionObject
{
    Param($name = ".*")
    $Myforest = [DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
    $MyDCs = $Myforest.Domains | foreach-object{$_.DomainControllers} | ?{$_.name -match $name}
    $MyDCs | %{$_.InboundConnections}
}
################################################################################
function ConvertTo-Sid($UserName,$domain = $env:Computername)
{
   $ID = New-Object System.Security.Principal.NTAccount($domain,$UserName)
   $SID = $ID.Translate([System.Security.Principal.SecurityIdentifier])
   $SID.Value
}
################################################################################
function ConvertTo-Name($sid)
{
   $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
   $User = $ID.Translate( [System.Security.Principal.NTAccount])
   $User.Value
}

Getting users group membership (tokengroups)

Around the same time as I wrote my Linked-Value Attribute script I also came up with this little gem. It also uses a constructed attribute provided with Windows 2003 called “tokengroups.” (Did I mention this includes recursive groups?)

Effectively it gets the attribute which returns an array of SIDs (in byte array form) for each group. I then use a function I posted about eariler called ConvertTo-Name to convert that BYTE array into a friendly group name we humans like.

Parameters:

  • – Account: Can be User samAccountName or DN of the user
  • – Verbose: Enables verbose output

More Info:
You may notice the GetInfoEx call I make on the user object. This is because tokengroups is not an actual attribute and does not get “populated” until you specifically request it. The GetInfoEx does exactly that.

Links:
MSDN: GetInfoEx
MSDN: tokenGroups

Get-TokenGroups.ps1

Param($Account,[switch]$Verbose)

if($verbose){$verbosepreference="Continue"}

Write-Host
Write-Verbose " – Account: $Account"
Write-Verbose " – Verbose: $Verbose"

function ConvertTo-Name($sid,[switch]$FromByte) {
    if($FromByte)
    {
        $ID = New-Object System.Security.Principal.SecurityIdentifier($sid,0)
    }
    else
    {
        $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
    }
    if($ID)
    {
        $User = $ID.Translate([System.Security.Principal.NTAccount])
        $User.Value
    }
}
function GetDNfromName{
    Param($name)
    $root = [ADSI]""
    $filter = "(sAMAccountName=$name)"
    $props = @("distinguishedName")
    $Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
    $Searcher.FindOne().properties.distinguishedname
}

if($Account -notmatch "CN=(.*),((OU|DC)=\w*)*")
{
    Write-Verbose " + Getting User DN for [$Account]"
    $Account = GetDNfromName $Account
    Write-Verbose "   – GetDNfromName returned [$Account]"
}

Write-Verbose " – Getting User Object"
$UserAccount = [ADSI]"LDAP://$Account"

Write-Verbose " – Calling GetInfoEx"
$UserAccount.GetInfoEx(@("tokengroups"),0)

Write-Verbose " – Getting tokengroups"
$groups = $UserAccount.Get("tokengroups")

Write-Verbose " + Processing Groups"
foreach($group in $groups)
{
    $GroupName = ConvertTo-Name $group -FromByte
    Write-Verbose "   – Found group [$GroupName]"
    $GroupName
}

Write-Host

Constructed Properties and LVR (Linked-Value Replication)

There was an interesting question that came up on the news group that discussed getting Active Directory replication metadata and while the question really wasn’t directly a Powershell question I found it intriguing. I knew I had the answer to the question back in my brain, but I couldn’t retrieve it.

Here is the Question:

“I’m working on a script where I need to compare the last time the member
attribute of a distribution group was modified (not the AD group object
itself) with the time stamp on a file (I’m exporting distribution group
memberships to a file, but only ones that have changed). The problem
I’m running into is that some Active Directory distribution groups
aren’t returning the member attribute when I look at the replication
meta data.

Any one have any thoughts on why some distribution groups return the
member attribute when I run GetReplicationMetadata and some don’t? This
returns all kinds of other attributes and their metadata, but naturally
not the one I’m interested in (it does some times, on some groups, but
not all). I could run my script by the actual AD object WhenChanged
attribute, but I’ll be processing a large number of lists and I want it
to run as fast as possible and since other attributes can change on a
group object, I don’t want to have to export a 8,000 member group if the
displayName changes, for example (I’m only interested in the member
attribute).
As always, any insight is appreciated.”

I thought about this for a bit and also consulted some AD friends and we determined the issue was LVR (Link Value Replication.) This was introduced in Windows 2003 (specifically 2003 Native Mode.)

Basically, LVR changed the unit of replication for some attributes to be the actual values. Prior to LVR if you changed group membership the entire attribute member would have to replicate. With LVR, just the link you added for user replicates.

As one can imagine this changed the Metadata and therefore GetReplicationMetadata() didnt get retrieve the data for you. Where does this leave us?

There were also a few constructed attributes that were added with 2003. One of which is called “msds-replvaluemetadata.” This attribute provided the metadata for these links in wonderful XML format. You will find code using this XML below.

Some Useful Links regarding LVR and Constructed Attributes:

Below is the script that resulted from the investigation


 

Get-GroupMemberMetadata.ps1

Param($GroupName)

$GroupMembers = @()

$root = [ADSI]""
$filter = "(&(objectclass=group)(name=$GroupName))"
$props = @("msDS-ReplValueMetaData")
$Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
foreach($Group in $Searcher.findall())
{
    foreach($childMember in $Group.Properties."msds-replvaluemetadata")
    {
        $GroupMembers += [xml+site:msdn.microsoft.com”>XML]$ChildMember
    }
}

foreach($Member in $GroupMembers)
{
    $Member.ChildNodes
}

Setting the WriteProperty on Members (ManagedBy Check box)

This has come up three times in the last week which triggers my auto blog response 🙂

Below is a function called New-ADAce. This function creates an Access Control Entry that can be applied to an AD Object (in this case the member property of an AD object.)

Basically what the code below does is:

  • Gets the ID object of the Manager
  • Creates ACE that gives the Manager Write access to the member property
  • Gets the Object to be managed
  • Gets the existing ACL
  • Addes the ACE to the ACL
  • Sets the ManagedBy Property to the DN of the Manager
  • Commits the changes
Param(
   $myGuid = "bf9679c0-0de6-11d0-a285-00aa003049e2", #GUID for the Members property
   $DN = "cn=jloser,cn=users,dc=corp,dc=lab",
   $domain = $env:UserDomain,
   $manager = "jmanager",
   $MangedByDN = ""cn=jmanager,cn=users,dc=corp,dc=lab""
)

function New-ADACE {
   Param([System.Security.Principal.IdentityReference]$identity,
   [System.DirectoryServices.ActiveDirectoryRights]$adRights,
   [System.Security.AccessControl.AccessControlType]$type,
   $Guid)

   $help = @"
   $identity
      System.Security.Principal.IdentityReference
      http://msdn.microsoft.com/en-us/library/system.security.principal.ntaccount.aspx
   $adRights
      System.DirectoryServices.ActiveDirectoryRights
      http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights.aspx
   $type
      System.Security.AccessControl.AccessControlType
      http://msdn.microsoft.com/en-us/library/w4ds5h86(VS.80).aspx
   $Guid
      Object Type of the property
      The schema GUID of the object to which the access rule applies.
"
@
   $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($identity,$adRights,$type,$guid)
   $ACE
}

# Some example code on how to use the New-ADACE function

# Create ACE to add to object
$ID = New-Object System.Security.Principal.NTAccount($domain,$manager)
$newAce = New-ADACE $ID "WriteProperty" "Allow" $myGuid

# Get Object
$ADObject = [ADSI]"LDAP://$DN"

# Set Access Entry on Object
$ADObject.psbase.ObjectSecurity.SetAccessRule($newAce)

# Set the manageBy property
$ADObject.Put("managedBy",$MangedByDN)

# Commit changes to the backend
$ADObject.psbase.commitchanges()

Functions for Active Directory Permissions

Below are some of the function I have written to work with Access Control List in Active Directory.

Here is the list and what they do
Get-ADACL: Gets the Access Control List of an AD Object
Set-ADACL: Set the Access Control List of an AD Object
New-ADACE: Creates a Access Control Entry to add to an AD Object
ConvertTo-Sid: Converts a UserName to a SID
ConvertTo-Name: Converts a SID to the UserName
Get-Self: Gets current Security Identity


function Get-ADACL {
    Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
    function HelpMe{
        Write-Host
        Write-Host " Get-ADACL.ps1:" -fore Green
        Write-Host "   Gets ACL object or SDDL for AD Object"
        Write-Host
        Write-Host " Parameters:" -fore Green
        Write-Host "   -DNPath                : Parameter: DN of Object"
        Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
        Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
        Write-Host "   -Help                  : [SWITCH]:  Displays This"
        Write-Host
        Write-Host " Examples:" -fore Green
        Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
        Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
        Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
        Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
        Write-Host
    }
   
    if(!$DNPath -or $help){HelpMe;return}
   
    Write-Host
    if($verbose){$verbosepreference="continue"}
   
    Write-Verbose " + Processing Object [$DNPath]"
    $DE = [ADSI]"LDAP://$DNPath"
   
    Write-Verbose "   – Getting ACL"
    $acl = $DE.psbase.ObjectSecurity
    if($SDDL)
    {
        Write-Verbose "   – Returning SDDL"
        $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
    }
    else
    {
        Write-Verbose "   – Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
        $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
    }
}
function Set-ADACL {
    Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
    function HelpMe{
        Write-Host
        Write-Host " Set-ADACL.ps1:" -fore Green
        Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
        Write-Host
        Write-Host " Parameters:" -fore Green
        Write-Host "   -DNPath                : Parameter: DN of Object"
        Write-Host "   -ACL                   : Parameter: ACL Object"
        Write-Host "   -sddl                  : Parameter: SDDL String"
        Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
        Write-Host "   -Help                  : [SWITCH]:  Displays This"
        Write-Host
        Write-Host " Examples:" -fore Green
        Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
        Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
        Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
        Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
        Write-Host
    }
   
    if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}
   
    Write-Host
    if($verbose){$verbosepreference="continue"}
    Write-Verbose " + Processing Object [$DNPath]"
   
    $DE = [ADSI]"LDAP://$DNPath"
    if($sddl)
    {
        Write-Verbose "   – Setting ACL using SDDL [$sddl]"
        $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
    }
    else
    {
        foreach($ace in $acl)
        {
            Write-Verbose "   – Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
            $DE.psbase.ObjectSecurity.SetAccessRule($ace)
        }
    }
    $DE.psbase.commitchanges()
    Write-Host
}
function New-ADACE {
    Param([System.Security.Principal.IdentityReference]$identity,
        [System.DirectoryServices.ActiveDirectoryRights]$adRights,
        [System.Security.AccessControl.AccessControlType]$type,
        $Guid)
   
    $help = @"
    $identity
        System.Security.Principal.IdentityReference
        http://msdn.microsoft.com/en-us/library/system.security.principal.ntaccount.aspx
   
    $adRights
        System.DirectoryServices.ActiveDirectoryRights
        http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights.aspx
   
    $type
        System.Security.AccessControl.AccessControlType
        http://msdn.microsoft.com/en-us/library/w4ds5h86(VS.80).aspx
   
    $Guid
        Object Type of the property
        The schema GUID of the object to which the access rule applies.
"
@
    $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($identity,$adRights,$type,$guid)
    $ACE
}
function ConvertTo-Sid($UserName,$domain = $env:Computername) {
   $ID = New-Object System.Security.Principal.NTAccount($domain,$UserName)
   $SID = $ID.Translate([System.Security.Principal.SecurityIdentifier])
   $SID.Value
}
function ConvertTo-Name($sid) {
   $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
   $User = $ID.Translate( [System.Security.Principal.NTAccount])
   $User.Value
}
function Get-Self{
   ([Security.Principal.WindowsIdentity]::GetCurrent())
}

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 0x40 (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

Get/Set-ADACL (ACL and SDDLs for Active Directory!)

A friend had a need to get/set Active Directory ACLs. So I wrote these.

They will use [System.DirectoryServices.ActiveDirectoryAccessRule] objects or SDDLs strings.

Note: I put the .NET classes and MS Spec for SDDLs at the bottom. Dont miss it!

Get-ADACL.ps1

# Get-ADACL.ps1
Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
function HelpMe{
    Write-Host
    Write-Host " Get-ADACL.ps1:" -fore Green
    Write-Host "   Gets ACL object or SDDL for AD Object"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -DNPath                : Parameter: DN of Object"
    Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
    Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
    Write-Host "   -Help                  : [SWITCH]:  Displays This"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
    Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
    Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
    Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
    Write-Host
}

if(!$DNPath -or $help){HelpMe;return}

Write-Host
if($verbose){$verbosepreference="continue"}

Write-Verbose " + Processing Object [$DNPath]"
$DE = [ADSI]"LDAP://$DNPath"

Write-Verbose "   – Getting ACL"
$acl = $DE.psbase.ObjectSecurity
if($SDDL)
{
    Write-Verbose "   – Returning SDDL"
    $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
}
else
{
    Write-Verbose "   – Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
    $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
}

Set-ADACL.ps1

# Set-ADACL.ps1
Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
function HelpMe{
    Write-Host
    Write-Host " Set-ADACL.ps1:" -fore Green
    Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -DNPath                : Parameter: DN of Object"
    Write-Host "   -ACL                   : Parameter: ACL Object"
    Write-Host "   -sddl                  : Parameter: SDDL String"
    Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
    Write-Host "   -Help                  : [SWITCH]:  Displays This"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
    Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
    Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
    Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
    Write-Host
}

if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}

Write-Host
if($verbose){$verbosepreference="continue"}
Write-Verbose " + Processing Object [$DNPath]"

$DE = [ADSI]"LDAP://$DNPath"
if($sddl)
{
    Write-Verbose "   – Setting ACL using SDDL [$sddl]"
    $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
}
else
{
    foreach($ace in $acl)
    {
        Write-Verbose "   – Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
        $DE.psbase.ObjectSecurity.SetAccessRule($ace)
    }
}
$DE.psbase.commitchanges()
Write-Host

More Info
I used the following .NET Classes
System.DirectoryServices.DirectoryEntry
http://msdn2.microsoft.com/en-us/library/system.directoryservices.directoryentry.aspx
System.DirectoryServices.ActiveDirectoryAccessRule
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectoryaccessrule.aspx
System.DirectoryServices.ActiveDirectorySecurity
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectorysecurity.aspx
System.Security.AccessControl.AccessControlSections
http://msdn2.microsoft.com/en-us/library/system.security.accesscontrol.accesscontrolsections(vs.80).aspx

SDDL Info
MS: http://msdn2.microsoft.com/en-us/library/aa379567.aspx

Converting Secure String

I recently heard a question about ConvertTO-SecureString and ConvertFrom-SecureString.

These CMDLets may be a little confusing so let’s talk a little about them.

Before you read the rest, it is key to understand what a Secure String actually is.

A Secure String is simple text that is encrypted in memory. This lets you store passwords or other secure data in memory without having to concern yourself with someone snooping your session or dumping your memory contents to get your data.

More Info Here
http://msdn2.microsoft.com/en-us/library/system.security.securestring.aspx

ConvertTo-SecureString: http://technet.microsoft.com/en-us/library/bb978707.aspx
I think this where it gets a little confusing for people. ConvertTo is meant to take an encrypted string and store it as a Secure String. Specificially for the output from ConvertFrom-SecureString. It will allow you use -asPlainText w/ -Force if you just want to convert a piece of text to Secure String.

ConvertFrom-SecureString: http://technet.microsoft.com/en-us/library/bb978629.aspx
This is used to convert a Secure String to text. It is import to note…. this is NOT the original text but the string representation of the encryption. This is a great tool for exporting the Secure String to a file in an encrypted form.

One other note before I show the code. By default (unless you provide a key) it uses Windows Data Protection API (DPAPI). This is VERY important. This process it is fairly secure, but can only decrypted by you on that specific machine. On the flip side… using a Key is not near as secure and some would argue security by obscurity.

Now… lets look at what we have… nothing magic here. We have three functions Export-EncryptedText. Import-EncryptedText, and Get-EncryptedText.
Export-EncryptedText: This gets a Secure String and exports to a file
Import-EncryptedText: This imports a Secure String Text from a file and returns a Secure String
Get-EncryptedText: This converts a Secure String into the orginal Text

We use “Read-Host -AsSecureString” to create convert our text to a secure string.

function Export-EncryptedText{
    param($text,$file,$key)
    if($key){ConvertFrom-SecureString -SecureString $text -key $key | out-file $file}
    else{ConvertFrom-SecureString -SecureString $text | out-file $file}
}
function Import-EncryptedText{
    Param($file,$key)
    $textFromFile = Get-Content $file
    if($key){ConvertTO-SecureString $TextFromFile -key $key}
    else{ConvertTO-SecureString $TextFromFile}
}
function Get-EncryptedText($text) {
    $Ptr = [System.Runtime.InteropServices.Marshal ]::SecureStringToCoTaskMemUnicode($text)
    $result = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
    [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
    $result
}

# Example Use
$dataToStore = Read-Host -AsSecureString
"My String To Encrypt Here"

# Key… needs to be a 16, 24, or 32 byte array
$key = (200..231)

# Use this to put the password in a file
Export-EncryptedText -text $dataToStore -file c:\data\testfile.secure -key $key

# To get the password back you do this
$myText = Import-EncryptedText c:\data\testfile.secure -key $key

# To see the text use Get-SecurePass
Get-EncryptedText $myText

Sync Folder Script.

I initially wrote this for a guy on the NG, but I decided to use at work to sync some application folders I used to store tools and scripts and such. Please Try it out and tell me what you think.

Basic Flow
– Checks for Folders that Source has the Destination Does NOT and Creates.
– Checks for Folders that Destination has the Source Does NOT and Creates.
– Checks for files that Source has the Destination Does NOT and Copies.
– If the file is found in both, the MD5 of both files are checked… Last Write Wins
– Checks for Folders that Destination has the Source Does NOT
– It does NOT Delete anything yet… I am considering using a mirror switch

Param($Source,$Destination)
function Get-FileMD5 {
   Param([string]$file)
   $mode = [System.IO.FileMode]("open")
   $access = [System.IO.FileAccess]("Read")
   $md5 = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
   $fs = New-Object System.IO.FileStream($file,$mode,$access)
   $Hash = $md5.ComputeHash($fs)
   $fs.Close()
   [string]$Hash = $Hash
   Return $Hash
}
function Copy-LatestFile{
    Param($File1,$File2,[switch]$whatif)
    $File1Date = get-Item $File1 | foreach-Object{$_.LastWriteTimeUTC}
    $File2Date = get-Item $File2 | foreach-Object{$_.LastWriteTimeUTC}
    if($File1Date -gt $File2Date)
    {
        Write-Host "$File1 is Newer… Copying…"
        if($whatif){Copy-Item -path $File1 -dest $File2 -force -whatif}
        else{Copy-Item -path $File1 -dest $File2 -force}
    }
    else
    {
        Write-Host "$File2 is Newer… Copying…"
        if($whatif){Copy-Item -path $File2 -dest $File1 -force -whatif}
        else{Copy-Item -path $File2 -dest $File1 -force}
    }
    Write-Host
}

if(!(test-Path $Destination))
{
    New-Item $Destination -type Directory -force | out-Null
}

# Getting Files/Folders from Source and Destination
$SrcEntries = Get-ChildItem $Source -Recurse
$DesEntries = Get-ChildItem $Destination -Recurse

# Parsing the folders and Files from Collections
$Srcfolders = $SrcEntries | Where-Object{$_.PSIsContainer}
$SrcFiles = $SrcEntries | Where-Object{!$_.PSIsContainer}
$Desfolders = $DesEntries | Where-Object{$_.PSIsContainer}
$DesFiles = $DesEntries | Where-Object{!$_.PSIsContainer}

# Checking for Folders that are in Source, but not in Destination
foreach($folder in $Srcfolders)
{
    $SrcFolderPath = $source -replace "\\","\\" -replace "\:","\:"
    $DesFolder = $folder.Fullname -replace $SrcFolderPath,$Destination
    if($DesFolder -ne ""){
        if(!(test-path $DesFolder))
        {
            Write-Host "Folder $DesFolder Missing. Creating it!"
            new-Item $DesFolder -type Directory | out-Null
        }
    }
}

# Checking for Folders that are in Destinatino, but not in Source
foreach($folder in $Desfolders)
{
    $DesFilePath = $Destination -replace "\\","\\" -replace "\:","\:"
    $SrcFolder = $folder.Fullname -replace $DesFilePath,$Source
    if($srcFolder -ne "")
    {
        if(!(test-path $SrcFolder))
        {
            Write-Host "Folder $SrcFolder Missing. Creating it!"
            new-Item $SrcFolder -type Directory | out-Null
        }
    }
}

# Checking for Files that are in the Source, but not in Destination
foreach($entry in $SrcFiles)
{
    $SrcFullname = $entry.fullname
    $SrcName = $entry.Name
    $SrcFilePath = $Source -replace "\\","\\" -replace "\:","\:"
    $DesFile = $SrcFullname -replace $SrcFilePath,$Destination
    if(test-Path $Desfile)
    {
        $SrcMD5 = Get-FileMD5 $SrcFullname
        $DesMD5 = Get-FileMD5 $DesFile
        If($srcMD5 -ne $desMD5)
        {
            Write-Host "The Files MD5’s are Different… Checking Write Dates"
            Write-Host $SrcMD5
            Write-Host $DesMD5
            Copy-LatestFile $SrcFullname $DesFile
        }
    }
    else
    {
        Write-Host "$Desfile Missing… Copying from $SrcFullname"
        copy-Item -path $SrcFullName -dest $DesFile -force
    }
}

# Checking for Files that are in the Destinatino, but not in Source
foreach($entry in $DesFiles)
{
    $DesFullname = $entry.fullname
    $DesName = $entry.Name
    $DesFilePath = $Destination -replace "\\","\\" -replace "\:","\:"
    $SrcFile = $DesFullname -replace $DesFilePath,$Source
    if($SrcFile -ne "")
    {
        if(!(test-Path $SrcFile))
        {
            Write-Host "$SrcFile Missing… Copying from $DesFullname"
            copy-Item -path $DesFullname -dest $SrcFile -force
        }
    }
}

Write-Host