Oliver's Blog

My germs!

The 2012 Windows PowerShell Scripting Games are just around the corner. I have participated in the last 2 Scripting Games and had a lot of fun.

I have been invited to be a judge for the second time, along with a lot of MVPs and PowerShell veterans. It will be a lot of work (a lot of scripts to grade) but I am sure it will be worth it. :-)

The Scripting Games have two categories:

  • Beginners: great opportunity to get familiar with PowerShell in real-life scenarios and see what it can do
  • Advanced: elaborate real-life scenarios which can get quite tricky

Taking aside the fun, experience,  contact to other great scripters, opportunity to get feedback from MVPs and Microsoft employees, etc…. There are prizes to be won (for the more materialistic). The prizes for this year have yet to be published, but here is the list of last years prizes.

I hope to see you all there ;-)

2012 Scripting GamesGrab this badge here!

Here is a little function i wrote a while back.
I have been using it a lot lately to send myself (and coworkers) status updates and logs. I also use it to let me know when a job (that kind of job that takes forever to complete) is finished.

function Send-Mail
{
    #Requires -Version 2.0
    <#
        .Synopsis
            Send an Email using .NET

        .Description
            Function to send Emails in PowerShell using .NET

        .Notes
            Author    : Oliver Lipkau <oliver@lipkau.net>
    	    Blog      : http://oliver.lipkau.net/blog
            Version   : v1.1
            Date      : 2010/10/13 09:22

        .Inputs
            System.String

        .Parameter Attatchment
            Path to file(s) which should be attached to the email.

        .Parameter Body
            Body of the email.

            Can be plain text, Rich text or HTML.

        .Parameter From
            Email address of the sender.

        .Parameter Password
            Password to the Username for authentication with the server.

        .Parameter Server
            Exchange or SMTP server.

        .Parameter Subject
            Subject of the email.

        .Parameter To
            Email address to which the email will be sent.

            Can be an array.

        .Parameter Username
            Username for authentication with the server.

        .Example
            Send-Mail -to oliver@lipkau.net -from john@smith.com -subject "Daily Report" -body (get-Content c:\report\$(get-Date -Format yyyy.MM.dd).html) -server smtp.smith.com
            -----------
            Description
            Sends Email with content of the "C:\report\yyyy.MM.dd.html"
    #>
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]$Attatchment,

        [Parameter()]
        [string]$Body,

        [Parameter(Mandatory=$true)]
        [string]$From,

        [Parameter()]
        [string]$Password,

        [ValidateNotNullOrEmpty()]
        [Parameter(Mandatory=$true)]
        [string]$Server,

        [Parameter()]
        [string]$Subject,

        [ValidateNotNullOrEmpty()]
        [Parameter(Mandatory=$true)]
        [string[]]$To,

        [Parameter()]
        [string]$Username
    )

    Begin
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"
        $SmtpClient = New-Object System.Net.Mail.SmtpClient
        $MailMessage = New-Object System.Net.Mail.MailMessage
        $Credentials = New-Object System.Net.NetworkCredential
    }

    Process
    {
        $SmtpClient.Host = $Server

        $MailMessage.From = $From
        $MailMessage.Subject = $Subject
        $MailMessage.Body = $Body
        foreach ($i in $to)
        {
            Write-Verbose "$($MyInvocation.MyCommand.Name):: Adding recipient: $i"
            $MailMessage.To.add($i)
        }
        foreach ($i in $Attatchment)
        {
            Write-Verbose "$($MyInvocation.MyCommand.Name):: Adding attachment: $i"
            $MailMessage.Attachments.Add($i)
        }

        if ($Username -and $Password)
        {
            Write-Verbose "$($MyInvocation.MyCommand.Name):: Using server authentication"
            $Credentials.domain = ""
            $Credentials.UserName = $username
            $Credentials.Password = $password

            $SMTPClient.Credentials = $Credentials
        } else {
            Write-Verbose "$($MyInvocation.MyCommand.Name):: No server authentication set"
        }

        Write-Verbose "$($MyInvocation.MyCommand.Name):: Sending email"
        $SMTPClient.Send($MailMessage)
    }

    End
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"
    }
}

Today Ed Wilson (The Scripting Guy) published my secound guest entry on the Hey Scripting Guy blog.
http://blogs.technet.com/b/heyscriptingguy/archive/2011/08/20/use-powershell-to-work-with-any-ini-file.aspx

Hope you enjoy :-)

It has been a while since I last posted here.

I wanted to finish my module project for my next post, but I didn’t have that much time to work on it. This module allows you to manage Active Directories and Domain Controllers.

Microsoft introduced a built-in PowerShell module for managing Active Directory in Windows 2008R2. However, I have two problems with this module:

  1. It is only available on the server and I want to be able to run stuff (most of all Get-* commands) from my local computer
  2. It doesn’t work with Windows 2008 and 2003 ADs and DCs

So I decided to write my own set of functions, which soon turned into a complete module. Since it is supposed to be a tool to work mainly with older servers, it’s time to finally publish it (Although it is not 100% yet).

There might still be bugs in the functions.

I would really appreciate all kind of input to the script. From criticism, to bug reports, to suggests.

It would be really awesome if more experienced PowerShellers helped me finish testing and writing the module.

To Do:

  • Ideas:
    • Cmdlet to resolve dns to ip and ip to dns?
    • Add -passthru to relevant cmdlets
    • Allow impersonation by providing username and pw instead of PSCredential object
  • To test:
    • Check shouldprocess is in right place
    • Test if Credentials are working correctly
    • Test get-domin/forest in non-domain pc
    • Get-EmptyGroup
    • Get-GroupMember
    • Get-GroupMembership
    • Get-IPSite
    • Get-Object (-recurse)
    • Get-RODC
    • New-User : use function set-user?
    • New-User : AccountExpires
    • Remove-GroupMember
    • Set-User : UserCannotChangePassword + more : AccountExpires
    • Test-IsRODC
  • To add:
    • New-Object
    • Set-Group
    • Set-User -> Try/Catch : AccountExpires
    • Test-ADReplication
    • Unlock-Account
AD Management module - v.0.1
Download!
PowerShell (v2) AD Management module by Oliver Lipkau

In case you don’t know how to use a Module, unzip the content of the file to: C:\Users\your user name\Documents\WindowsPowerShell\Modules\ADManagement, open powershell and typ Import-Module ADManagement

Here is a function I wrote to get computer objects from the AD.

The code:

#Requires -Version 2.0
Function Get-Computer
{
    <#
    .Synopsis
        Retrieves all computer objects in a domain or container.

    .Description
        Retrieves all computer objects in a domain or container.

    .Notes
        Author    : Oliver Lipkau <oliver@lipkau.net>
        Blog      : http://oliver.lipkau.net/blog/
        Version   : v1.0
        Date      : 2010/08/25 18:27

    .Inputs
        System.String

    .Parameter Name
        Value to search for in: sAMAccountName, cn, displayName, dNSHostName and name

    .Parameter SearchRoot
        A search base (the distinguished name of the search base object) defines the location in the directory from which the LDAP search begins

    .Parameter SizeLimit
        Maximum of results shown for a query

    .Parameter SearchScope
        A search scope defines how deep to search within the search base.
            Base , or zero level, indicates a search of the base object only.
            One level indicates a search of objects immediately subordinate to the base object, but does not include the base object itself.
            Subtree indicates a search of the base object and the entire subtree of which the base object distinguished name is the topmost object.

    .Outputs
        System.DirectoryServices.DirectoryEntry

    .Example
        Get-Computer -Name WRK* -Enabled
        -----------
        Description
        Gets all domain enabled computers which names start with WRK

    .Example
        Get-Computer -SearchRoot 'CN=Computers,DC=Domain,DC=com' -Disabled
        -----------
        Description
        Gets all disabled computers from the Computers container

    .Example
        Get-Computer -Name "computer1" -SearchRoot "domain.com"
        -----------
        Description
        Gets specific computer in a different domain

    .Example
        "computer1" | Get-Computer -SearchRoot "domain.com"
        -----------
        Description
        Gets computer from Pipe

    .Link

http://oliver.lipkau.net/blog/category/powershell/admanagement/

    #>

    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [Parameter(ValueFromPipeline = $true,Mandatory=$true)]
        [Alias("CN")]
        [string]$Name = "*",

        [string]$SearchRoot,

        [ValidateNotNullOrEmpty()]
        [int]$PageSize = 1000,

        [ValidateNotNullOrEmpty()]
        [int]$SizeLimit = 0,

        [ValidateNotNullOrEmpty()]
        [ValidateSet("Base","OneLevel","Subtree")]
        [string]$SearchScope = "SubTree",

        [switch]$Enabled,

        [switch]$Disabled
    )

    Begin
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"
        $c = 0

        $disableStr = "userAccountControl:1.2.840.113556.1.4.803:=2"
        $Enabledf = "(!$disableStr)"
        $Disabledf = "($disableStr)"

        $root= New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
        $searcher = New-Object System.DirectoryServices.DirectorySearcher
    }

    Process
    {
        $resolve = "(|(sAMAccountName=$Name)(cn=$Name)(displayName=$Name)(dNSHostName=$Name)(name=$Name))"    

        if(($Enabled) -and (!($Disabled)))
            {$EnabledDisabledf = $Enabledf}
        elseif(($Disabled) -and (!($Enabled)))
            {$EnabledDisabledf = $Disabledf}
        else
            {$EnabledDisabledf = ""}

        $filter = "(&(objectCategory=Computer)(objectClass=User)$EnabledDisabledf$resolve)"

        if (!($SearchRoot))
            {$SearchRoot=$root.defaultNamingContext}
        elseif (!($SearchRoot) -or ![ADSI]::Exists("LDAP://$SearchRoot"))
            {Write-Error "$($MyInvocation.MyCommand.Name):: '$SearchRoot' does not exist";return}
        $searcher.SearchRoot = "LDAP://$SearchRoot"
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Searching in: $($searcher.SearchRoot)"

        $searcher.SearchScope = $SearchScope
        $searcher.SizeLimit = $SizeLimit
        $searcher.PageSize = $PageSize
        $searcher.filter = $filter
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Searching for: $($searcher.filter)"
        try
        {
            $searcher.FindAll() | `
            Foreach-Object `
            {
                $c++
                Write-Verbose "$($MyInvocation.MyCommand.Name):: Found: $($_.Properties.cn)"
                $_.GetDirectoryEntry()
            }
        }
        catch
        {
            {return $false}
        }
    }

    End
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Results found: $c"
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"
    }
}

Here is a function I wrote to add users to a group.

The code:

#Requires -Version 2.0
Function Add-GroupMember
{
    <#
    .Synopsis
        Adds one or more objects to a group in Active Directory.

    .Description
        Adds one or more objects to a group in Active Directory.

    .Notes
        Author    : Oliver Lipkau <oliver@lipkau.net>
        Blog      : http://oliver.lipkau.net/blog/
        Version   : v1.1
        Date      : 2010/10/13 09:22

    .Inputs
        System.DirectoryServices.DirectoryEntry

    .Parameter Member
        User to be added

    .Parameter AddBySID
        Will use the SID of the object to add it to the group.
        This will allows you to add users from different (but trusted) domains and forests.        

    .Parameter Group
        Group to which you want to add a new member

    .Example
        (Get-Group QA).distinguishedName | Add-GroupMember -Member 'CN=Administrator,CN=Users,DC=domain,DC=com'
        -------------
        Description
        Adds the domain administrator account to the QA group 

    .Example
        (Get-Group QA).distinguishedName | Add-GroupMember -Member (Get-User -Name QAUser*).distinguishedName
        -------------
        Description
        Adds multiple accounts to the QA group

    .Example
        Add-GroupMember -Member 'CN=Administrator,CN=Users,DC=domain,DC=com' -Group 'CN=Admin,OU=users,DC=domain,DC=com'

    .Link
        Get-Group
        Get-Object
        Get-objectBySID
        Get-User
    .Link

http://oliver.lipkau.net/blog/category/powershell/admanagement/

    #>

    [CmdletBinding(
        SupportsShouldProcess=$true,
        ConfirmImpact="Medium"
    )]
    param(
        [ValidateNotNullOrEmpty()]
        [Parameter(Mandatory=$true)]
        [ADSI[]]$Member,

        [ValidateNotNullOrEmpty()]
        [Parameter(ValueFromPipeline=$true,Mandatory=$true)]
        [ADSI]$Group,

        [switch]$AddBySID,

        [System.Management.Automation.PSCredential]$Credential
    )

    Begin
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"
        if ($AddBySID)
        {
            if (!(Test-Path function:Get-ObjectSID))
               {Throw "$($MyInvocation.MyCommand.Name):: This command requires the function 'Get-ObjectSID'. Please make sure it's loaded."}
        }
        if ($Credential)
        {
            if (!(Test-Path function:Push-ImpersonationContext))
               {Throw "$($MyInvocation.MyCommand.Name):: This command requires the function 'Push-ImpersonationContext'. Please make sure it's loaded."}
            if (!(Test-Path function:Pop-ImpersonationContext))
               {Throw "$($MyInvocation.MyCommand.Name):: This command requires the function 'Pop-ImpersonationContext'. Please make sure it's loaded."}
        }
    }

    Process
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing Group: $($Group.cn)"
        if ($Group.psbase.SchemaClassName -eq 'group' -and [ADSI]::Exists($Group.Path))
        {
            $Member | `
            Where-Object `
                {$_} | `
            ForEach-Object `
            {
                $user = $_
                if ($user.psbase.SchemaClassName -eq 'user' -and [ADSI]::Exists($user.Path))
                {
                    if ($pscmdlet.ShouldProcess($Group.cn))
                    {
                        Write-Verbose "$($MyInvocation.MyCommand.Name):: Adding user: $($user.cn)"
                        #Load different user context if credential parameter is present
                        if ($Credential)
                            {$null = Push-ImpersonationContext $Credential}

                        if ($AddBySID)
                        {
                            Write-Verbose "$($MyInvocation.MyCommand.Name):: Adding using SID"
                            $SID = Get-ObjectSID $user
                            $Group.add("LDAP://<SID=$SID>")
                        } else
                            {$Group.add($user.Path)}
                        #Restore current user context
                        if ($Credential)
                            {$null = Pop-ImpersonationContext}
                    }
                }
                else
                    {Write-Warning "$($MyInvocation.MyCommand.Name):: $user is not a valid object type (only User objects are allowed) or could not be found."}
                Write-Verbose "$($MyInvocation.MyCommand.Name):: Ended processing user: $($user.cn)"
            }
        } else
            {Write-Warning "$($MyInvocation.MyCommand.Name):: $Group is not a valid object type (only Group objects are allowed) or object could not be found."}
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Ended processing Group: $($Group.cn)"
    }

    End
        {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"}
}

Some functions I wrote make changes to AD objects (such as the script I will post tomorrow).

To make my life easier when using them, I added a Credential parameter to there function. With this I can open a password dialog window with Get-Credential and pass them on only to the commands that really need different credentials.

To be able to use this feature, these functions must be loaded:

# Version History
# 1.1 - Added aliases, error handling, help, and cleanup
# Source: http://poshcode.org/1867
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
   while($script:ImpContextStack.Count) { Pop-ImpersonationContext }
}

$script:UserToysClass = Add-Type -Namespace Huddled -Name UserToys -MemberDefinition @"
   // http://msdn.microsoft.com/en-us/library/aa378184.aspx
   [DllImport("advapi32.dll", SetLastError = true)]
   public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

   // http://msdn.microsoft.com/en-us/library/aa379317.aspx
   [DllImport("advapi32.dll", SetLastError=true)]
   public static extern bool RevertToSelf();
"@ -passthru

$script:ImpContextStack = new-object System.Collections.Generic.Stack[System.Security.Principal.WindowsImpersonationContext]
$script:IdStack = new-object System.Collections.Generic.Stack[System.Security.Principal.WindowsIdentity]

function Push-ImpersonationContext {
<#
.SYNOPSIS
   Sets the network credentials for the current thread
.Description
   Stores an identity on the stack and impersonate it for network connections
.Parameter Credential
   Credentials for authenticating as a new identity.
.Parameter Name
   A user name for authenticating as a new identity.
.Parameter Password
   The password (as a String or SecureString) for authenticating as a new identity
.Parameter Domain
   The domain which goes with the user name for authentication. This is optional, as you can specify a domain or computer name as part of the name using domain\user or user@domain syntax.
.Parameter Passthru
   Causes Push-ImpersonationContext to output the WindowsIdentity that it's impersonating (not the impersonation context).
.Example
   Push-ImpersonationContext (Get-Credential)
.Example
   Push-ImpersonationContext username@domain (Read-Host "Password" -AsSecureString)
.Example
   $Domain1 = Get-Credential
   $Id1 = PushIC $Domain1 -Passthru
#>
[CmdletBinding(DefaultParameterSetName="Credential")]
Param(
   [Parameter(Position=0,Mandatory=$true,ParameterSetName="Credential")]
   [System.Management.Automation.PSCredential]$Credential
,
   [Parameter(Position=0,Mandatory=$true,ParameterSetName="Identity")]
   [Security.Principal.WindowsIdentity]$Identity
,
   [Parameter(Position=0,Mandatory=$true,ParameterSetName="Password")]
   [string]$Name
,
   [Parameter(Position=1,Mandatory=$true,ParameterSetName="Password")]
   [Alias("PW")]
   $Password = (Read-Host "Password" -AsSecureString)
,
   [Parameter(Position=2,Mandatory=$false,ParameterSetName="Password")]
   [string]$Domain
,
   [Alias("PT")]
   [switch]$Passthru
)
   if(!$Identity) {
      if(!$Credential) {
         if($password -is [string]) {
            $secure = New-Object System.Security.SecureString
            $password.GetEnumerator() | %{ $secure.AppendChar( $_ ) }
            $password = $secure
         }
         if($domain) {
            $user = "${name}@${domain}"
         }
         $Credential = new-object System.Management.Automation.PSCredential $user, $password
      }

      Write-Verbose ([Security.Principal.WindowsIdentity]::GetCurrent() | Format-Table Name, Token, User, Groups -Auto | Out-String)

      [IntPtr]$userToken = [Security.Principal.WindowsIdentity]::GetCurrent().Token
      if(!$UserToysClass::LogonUser(
            $Credential.GetNetworkCredential().UserName,
            $Credential.GetNetworkCredential().Domain,
            $Credential.GetNetworkCredential().Password, 9, 0, [ref]$userToken)
      ) {
         throw (new-object System.ComponentModel.Win32Exception( [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() ) )
      }

      $Identity = New-Object Security.Principal.WindowsIdentity $userToken
   }
   $script:IdStack.Push( $Identity )

   $context = $Identity.Impersonate()
   $null = $script:ImpContextStack.Push( $context )

   Write-Verbose ([Security.Principal.WindowsIdentity]::GetCurrent() | Format-Table Name, Token, User, Groups -Auto | Out-String)

   if($Passthru) { $script:IdStack.Peek() }
}

function Pop-ImpersonationContext {
<#
.Synopsis
   Remove the current impersonation context from the stack and clean it up
.Description
   Pops the current impersonation context from the stack and undo and dispose it, leaving the former context in place.
.Param Passthru
   Output the old WindowsIdentity before popping it.
#>
   param( [switch]$Passthru )
   trap {
      Write-Error "Impersonation Context Stack is Empty"
      while($script:ImpContextStack.Count -lt $script:IdStack.Count) { $null = $script:IdStack.Pop() }
      return
   }
   if($Passthru) { $script:IdStack.Peek() }
   $context = $script:ImpContextStack.Pop()
   $null = $script:IdStack.Pop()

   $context.Undo();
   $context.Dispose();
}

function Get-ImpersonationContext {
<#
.Synopsis
   Display the currently active WindowsIdentity
#>
   trap {
      Write-Error "Impersonation Context Stack is Empty"
      return
   }
   Write-Host "There are $($script:ImpContextStack.Count) contexts on the stack"
   while($script:ImpContextStack.Count -lt $script:IdStack.Count) { $null = $script:IdStack.Pop() }
   if($script:ImpContextStack.Count -eq $script:IdStack.Count) {
      $script:IdStack.Peek()
   }
}

New-Alias popic Pop-ImpersonationContext
New-Alias pushic Push-ImpersonationContext
New-Alias gic Get-ImpersonationContext
Export-ModuleMember -Function * -Alias *

A few weeks back, I was having trouble with a script I had to write.
The script was supposed to create replicas of our Active Directory security groups in a new forest.

When I was done with the script Ed Wilson, the Scripting Guy, asked me, if I would like to write a guest blog article for the Hey Scripting Guy blog. And so I did.

My article was published today. Here is the link: Create Exact Copies of Active Directory Security Groups in a New Forest

In the article, I present some simplified versions of three of my functions. If you are interested in my functions with more parameters, take a look at: /blog/category/powershell/admanagement

Here is a function I wrote to create new AD groups.
I wrote this functions, since most of my servers still run Windows Server 2003 R2 and do not support Windows 2008 R2′s Active Directory module.

Here is the function:

  • Updated:
    • 2010/12/01 – v1.1
#Requires -Version 2.0
Function New-Group
{
    <#
    .Synopsis
        Creates a new group in Active Directory.

    .Description
        Creates a new group in Active Directory.

    .Notes
        Author    : Oliver Lipkau <oliver@lipkau.net>
        Blog      : http://oliver.lipkau.net/blog/
        Version   : v1.1
        Date      : 2010/08/25 15:23

    .Inputs
        System.String

    .Parameter Name
        Name for the new group

    .Parameter ParentContainer
        Container in which the group should be created in.

    .Parameter GroupScope
        Group scopes can be:
            Universal:    Can include Accounts, Global groups and Universal groups from the forest.
            Global:       Can include Accounts and Global groups from the same domain.
            Domain Local: Can include Accounts, Global groups and Universal groups from any doamins and Domain Local groups from the same domain.
        Read more at: http://technet.microsoft.com/en-us/library/cc755692(WS.10).aspx

    .Parameter GroupType
        Group types can be:
            Distribution: Distribution groups can be used only with e-mail applications (such as Exchange) to send e-mail to collections of users.
            Security:     Security Groups can assign permissions to security groups on resources and user rights to security groups in Active Directory .
        Read more at: http://technet.microsoft.com/en-us/library/cc781446(WS.10).aspx

    .Example
        New-Group -Name  TestGroup -GroupScope universal -GroupType security -ParentContainer  "OU=Test,DC=domain,DC=com"
        -----------
        Description
        Creates universal security group TestGroup in Test OU

    .Link
        Group Scopes:

http://technet.microsoft.com/en-us/library/cc755692(WS.10).aspx

    .Link
        Group Types:

http://technet.microsoft.com/en-us/library/cc781446(WS.10).aspx

    .Link
        Get-Object
        Get-OU
        Get-User
    .Link

http://oliver.lipkau.net/blog/category/powershell/admanagement/

    #>

    [CmdletBinding(
        SupportsShouldProcess=$true,
        ConfirmImpact="Medium"
    )]
    param(
        [ValidateNotNullOrEmpty()]
        [Parameter(mandatory=$true)]
        [string]$Name,

        [ValidateNotNullOrEmpty()]
        [Parameter(ValueFromPipeline = $true,mandatory=$true)]
        [ADSI]$ParentContainer,

        [ValidateNotNullOrEmpty()]
        [ValidateSet("Universal","Global","DomainLocal")]
        [string]$GroupScope = "Global",

        [ValidateNotNullOrEmpty()]
        [ValidateSet("Security","Distribution")]
        [string]$GroupType = "Security",

        [string]$Description,

        [string]$Email,

        [ADSI]$managedby,

        [string]$Notes,

        [System.Management.Automation.PSCredential]$Credential
    ) 

    Begin
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"

        if ($Credential)
        {
            if (!(Test-Path function:Push-ImpersonationContext))
               {Throw "$($MyInvocation.MyCommand.Name):: This command requires the function 'Push-ImpersonationContext'. Please make sure it's loaded."}
            if (!(Test-Path function:Pop-ImpersonationContext))
               {Throw "$($MyInvocation.MyCommand.Name):: This command requires the function 'Pop-ImpersonationContext'. Please make sure it's loaded."}
        }
    }

    Process
    {
        if (!([ADSI]::Exists($ParentContainer.Path)))
            {Write-Error "$($MyInvocation.MyCommand.Name):: ParentContainer '$($ParentContainer.distinguishedName)' doesn't exist";return}

        switch ($GroupScope)
        {
            "Global"
                {$GroupTypeAttr = 2}
            "DomainLocal"
                {$GroupTypeAttr = 4}
            "Universal"
                {$GroupTypeAttr = 8}
        }

        # modify group type attribute if the group is security enabled
        if ($GroupType -eq 'Security')
            {$GroupTypeAttr = $GroupTypeAttr -bor 0x80000000}

        if ([ADSI]::Exists("LDAP://CN=$Name,$($ParentContainer.distinguishedName)"))
            {Write-Warning "$($MyInvocation.MyCommand.Name):: The group $name already exists in $($ParentContainer.distinguishedName)."}
        else
        {

            #Load different user context if credential parameter is present
            if ($Credential)
                {$null = Push-ImpersonationContext $Credential}

            if ($pscmdlet.ShouldProcess($Name))
            {
                Write-Verbose "$($MyInvocation.MyCommand.Name):: Creating Group: $Name"
                $group = $ParentContainer.Create("group","CN=$Name")
                $null = $group.put("sAMAccountname",$Name)
                $null = $group.put("grouptype",$GroupTypeAttr)

                if ($Description)
                    {$null = $group.put("description",$Description)}

                if ($email)
                    {$null = $group.put("mail",$email)}

                if ($notes)
                    {$null = $group.put("info",$Notes)}

                if ($managedby)
                {
                    $managedby.distinguishedName
                    if ($managedby.psbase.SchemaClassName -match 'User|Contact' -and [ADSI]::Exists($managedby.Path))
                        {$null = $group.put("managedBy","$($managedby.distinguishedName)")}
                     else
                        {Write-Warning "$($MyInvocation.MyCommand.Name):: `$Manager is not a valid object type (only 'User' or 'Contact' objects are allowed) or could not be found."}
                }

                Write-Verbose "$($MyInvocation.MyCommand.Name):: Saving Information for: $Name"
                $null = $group.SetInfo()
            }

            #Restore current user context
            if ($Credential)
                {$null = Pop-ImpersonationContext}

            return $Group
        }
    }

    End
        {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"}
}

Here is a function I wrote to get an object’s SID.

The code:

  • Updated:
    • 2010/12/01 – v1.1
#Requires -Version 2.0
Function Get-ObjectSID
{
    <#
    .Synopsis
        Retrieves AD objects SID.

    .Description
        Retrieves AD objects SID.

    .Notes
        Author    : Oliver Lipkau <oliver@lipkau.net>
        Blog      : http://oliver.lipkau.net/blog/
        Version   : v1.1
        Date      : 2010/08/25 17:59

    .Inputs
        System.DirectoryServices.DirectoryEntry

    .Outputs
        System.String

    .Example
        Get-ObjectSID (Get-User johndoe -SearchRoot domain.com)
        -----------
        Description
        Gets the SID of a specific user

    .Example
        Get-ObjectSID -InputObject 'CN=Guest,CN=Users,DC=Domain,DC=com'
        -----------
        Description
        Gets the SID using the object's DN

    .Link
        Get-Object
        Get-ObjectBySID
        Get-User
    .Link

http://oliver.lipkau.net/blog/category/powershell/admanagement/

    #>

    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [Parameter(ValueFromPipeline = $true,mandatory=$true)]
        [ADSI]$InputObject
    )

    Begin
        {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"}

    Process
    {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing Object: $($InputObject.cn)"
        if([ADSI]::Exists($InputObject.Path))
        {
            $objectSid = [byte[]]$InputObject.objectSid.value
            $sid = new-object System.Security.Principal.SecurityIdentifier $objectSid,0
            $sid.value
        } else
            {Write-Warning "$($MyInvocation.MyCommand.Name):: Object could not be found."}
    }

    End
        {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"}
}

Bad Behavior has blocked 29 access attempts in the last 7 days.