Intro

I recently began a new job and it is unique for me. The organization I work for actually helps to generate revenue, and we are a customer of  corporate IT. This is a first for me where I’m not working directly for IT. I had just left a job where corporate viewed IT as too complex, too costly, and not responsive to the business. As a result, 80%-90% of the staff was fired (or re-badged) and all IT service and infrastructure was outsourced.

As a manager, I had to preside over the knowledge transfer of my teams to the outsource company and then let  them go. At the same time, I had to maintain some productivity in a morale sapping environment. Needless it say is was rough, but many of my former team members got great jobs afterward as did I.

This outsourcing process had already occurred with my current employer. So, the business engages IT to implement projects, but in my case, they were not able to provide this particular service. So I find that I’m running an independent cloud implementation of XenDesktop. This includes AD (users, groups, OU’s, GPO’s – no trust with corporate AD), DHCP, DNS, File servers, XenApp, vDisk creation, Remote Access, Microsoft updates, and software testing/installs/upgrades. Basically everything from soup to nuts.

This project needs to grow to include many more users, but there is no automation present. So I’ve begun examining the biggest “pain” points in this environment to scale it out. First, we will look at user creation.

AD DC

It has been years since I’ve been saddled with user account management (except for service accounts and the like). Our cloud users have to remember a separate set of credentials to use our cloud environment; this is already a barrier to adoption. To relieve this, we have made our usernames match the corporate domain usernames, and left it up to the user to make the passwords the same. User account creation begins with the user filling out a SharePoint form. Then we start a manual process to create the user’s account. To relieve this, I exported a request to a CSV file and wrote a PowerShell script that runs in the cloud to create the user, set the password, set the user profile locations, and assign groups. I reviewed several websites for ideas and I noted them in my script’s help area.

Assumptions

For the purposes of this script, the CSV file has the following header fields:

Status Records status of user request in SharePoint site.
Name In the format of Lastname, Firstname
Account Corporate AD username
Password Corporate AD Password
Cloud Version Used to assign groups
Email Address Corporate email
Position/Title Title – used to assign groups
Office Number Work Phone
Mobile Number Mobile Phone
Department Cost Center Added to notes in user account
Manager Added to notes in user account (we do not assume that the manager has a user account in the Cloud AD domain)
Manager Office Number Added to notes in user account
Location or Region Used for contact info
Modality Used for Organization Info
Group Used for Organization Info

It is assumed that you are running this script as a domain admin and the server you are running it from has the Microsoft AD PowerShell cmdlets installed.

If you wish to get feedback from the script while it is running use the -verbose parameter - i.e. >create-clouduser.ps1 -verbose

TO DO’s

I don’t have an SMTP server setup in my cloud environment. When I do, I will add routines to the script to check a “New User” mailbox for new emails and then send confirmation to these users that their account was created. This will allow me to bridge the 2 domains (until corporate IT agrees to set up a domain trust).

The Script

<#
.SYNOPSIS
    Reads CSV file and creates a user in the YOURCOMPANY domain.
.DESCRIPTION
    Reads CSV file and creates a user in the YOURCOMPANY domain.  

    It is recommended that this script be run as an admin. In addition, the Microsoft Active Directory Powershell Cmdlets must be available for user creation.
.PARAMETER UserOU
    Defaults to OU=Users,DC=YOURCOMPANY,DC=local
    OU location where user account will be created. Input must match above format.
.PARAMETER UserFolder
    Defaults to any CSV file in C:\scripts\NewUserRequests Folder
    Enter a path to folder where CSV files exist. CSV files must have the following fields:
    Status
    Name
    Account
    Password
    Cloud Version
    Email Address
    Position/Title
    Office Number
    Mobile Number
    Department Cost Center
    Manager
    Manager Office Number
    Location or Region
    Modality
    Group
.EXAMPLE
    PS C:\PSScript &gt; .\create-cloudixuser.ps1
    Will use all default values.
    User OU=Users,DC=YOURCOMPANY,DC=local
    UserFolder=c:\scripts\NewUserRequests
.EXAMPLE
    PS C:\PSScript &gt; .\create-cloudixuser.ps1 -verbose
    Will use all default values.
    User OU=Users,DC=YOURCOMPANY,DC=local
    UserFolder=c:\scripts\NewUserRequests
    Will write feedback/progress messages.
.INPUTS
    None.  You cannot pipe objects to this script.
.OUTPUTS
    To see feedback messages use the -verbose common parameter. No objects are output from this script.  This script creates a user creation log.
.NOTES
    NAME: create-clouduser.ps1
    VERSION: 1.00
    CHANGE LOG - Version - When - What - Who
                 1.00 - 06/06/2014 - Initial script - Alain Assaf
    AUTHOR: Alain Assaf
    LASTEDIT: June 6, 2014
.LINK
http://www.linkedin.com/in/alainassaf
http://wagthereal.com
http://windowsitpro.com/blog/what-do-not-do-powershell-part-1
http://stackoverflow.com/questions/6828055/powershell-checking-if-ou-exist
http://blogs.metcorpconsulting.com/tech/?p=1723
http://stackoverflow.com/questions/14902501/powershell-script-with-params-and-functions
http://stackoverflow.com/questions/11526285/how-to-count-objects-in-powershell
http://stackoverflow.com/questions/5203730/cut-off-text-in-string-after-before-seperator-in-powershell
http://dxpetti.com/blog/?p=442
http://technet.microsoft.com/en-us/library/dd378958%28v=ws.10%29.aspx
http://powershell.org/wp/forums/topic/set-aduser-append-to-ad-notes-field/
http://www.out-web.net/?p=1233
http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/11/hey-scripting-guy-weekend-scripter-checking-for-module-dependencies-in-windows-powershell.aspx
http://technet.microsoft.com/en-us/library/ff730937.aspx
#>

Param(
    [parameter(Position = 0, Mandatory=$False )]
    [ValidateNotNullOrEmpty()]
    [string]$UserOU="OU=Users,DC=YOURCOMPANY,DC=local",

    [parameter(Position = 1, Mandatory=$False )]
    [ValidateNotNullOrEmpty()]
    [string]$UserFolder="C:\scripts\NewUserRequests"
)

### FUNCTION: get-mymodule #####################################################
Function Get-MyModule {
    Param([string]$name)
    if(-not(Get-Module -name $name)) {
        if(Get-Module -ListAvailable | Where-Object { $_.name -eq $name }) {
            Import-Module -Name $name
            $true
        } #end if module available then import
        else { $false } #module not available
        } # end if not module
    else { $true } #module already loaded
}
### FUNCTION: get-mymodule #####################################################  

### FUNCTION: get-csvdata #####################################################
function get-csvdata($CSVFolder) {
    $DataFiles = (Get-ChildItem $CSVFolder -recurse -force | Where { $_.Name -like "*.csv" } | Foreach-Object -process { $_.FullName })
    $DataFilesCount = ($DataFiles | measure).count
    Write-Verbose "Discovered $DataFilesCount CSV Data files in $CSVFolder"  

    ForEach ($DataFilesItem in $DataFiles) {
        $FileInfo = Get-Item $DataFilesItem
        $LogDate = $FileInfo.LastWriteTime
        Write-Verbose "Reading data from $DataFilesItem ($LogDate ) "
        [array]$CSVData += Import-CSV $DataFilesItem -header status,name,Account,Password,CloudVer,Email,Title,WorkNumber,MobileNumber,CostCenter,Manager,ManagerWorkPhone,Location,Modality,Group
        ## Only use the header if you want to rename the attributes for the imported objects. If the column headers in the CSV are fine, don't use -header
    }
    [int] $CSVDataCount = $CSVData.Count
    Write-Verbose "Imported $CSVDataCount records"
    Return ($CSVData)
}
### FUNCTION: get-csvdata #####################################################  

#Import Module(s)
if (!(get-mymodule activedirectory)) {
    write-verbose "Microsoft Active Directory PowerShell Cmdlet not available."
    write-verbose "Please run this script from a system with the Microsoft Active Directory PowerShell Cmdlets installed."
    exit
}

#Constants
$datetime = get-date -format "MM-dd-yyyy_HH-mm"
$Domain="@YOURCOMPANY.local"
$CloudProfilePath = "\\PROFILESERVER.YOURCOMPANY.local\upm\"
$CloudRDSProfilePath = "\\PROFILESERVER.YOURCOMPANY.local\profiles\"
$ScriptRunner = (get-aduser $env:username | select name).name  

#Confirm OU is valid
If (!([adsi]::Exists("LDAP://$UserOU"))) {
    write-verbose "$UserOU IS NOT VALID"
    write-verbose "Please use the following format: OU=Users,DC=YOURCOMPANY,DC=local"
    Exit
}

#Confirm CSV Folder exists and has CSV files. If True get the files, otherweise exit
if (test-path $UserFolder) {
    if ((Get-ChildItem $UserFolder | where {$_.name -like "*.csv"}) -ne $null) {
        $UserList = get-csvdata($UserFolder)
    } else {
        write-verbose "No CSV files in $UserFolder."
        Exit
    }
} else {
    write-verbose "$UserFolder is not valid"
    Exit
}

#Create Users
foreach ($user in $UserList) {
    if (!([bool]([adsisearcher]"samaccountname=$user.Account").FindOne()) -and ($user.status -ne 'Status')) {
        #Get first and last name
        $pos = ($user.name).IndexOf(",")
        $SurName = ($user.name).Substring(0, $pos)
        $GivenName = ($user.name).Substring($pos+2)
        #Create other account name data
        $DisplayName = $GivenName+ " " + $SurName
        $Initials = ($GivenName).Substring(0,1) + ($SurName).Substring(0,1)
        $Username = $user.Account
        $UPN=$Username+$Domain
        #Get Cloud password to the Philips CODE password
        $Password = $user.Password
        #Create Description
        $Description = $User.Modality + " " + $user.Title
        #Create Contact Info
        $MobilePhone = $User.MobileNumber
        $OfficePhone = $User.WorkNumber
        $Email = $User.Email
        #Create Organization Info
        $Company = "YOUR COMPANY"
        $Department = $User.Modality + " " + $user.Group
        $Divsion = $User.Modality
        $EmployeeNumber = $User.Account
        $Manager = $user.Manager
        $Office = $User.Location
        $Title = $user.Title
        #Create User Profile Location
        $ProfilePath = $CloudProfilePath + $Username
        #Create User in AD
        new-aduser -name $Displayname -DisplayName $DisplayName -GivenName $GivenName -Surname $SurName -SamAccountName $UserName -UserPrincipalName $UPN -Description $Description -MobilePhone $MobilePhone -OfficePhone $OfficePhone -EmailAddress $Email -Company $Company -Department $Department -Division $Division -EmployeeNumber $EmployeeNumber -Office $Office -Title  $Title -ProfilePath $ProfilePath -Path $UserOU
        write-verbose "New AD User Account created for $DisplayName"
        #Set Password
        set-adaccountpassword -Identity $Username -NewPassword (ConvertTo-SecureString -AsPlainText $Password -Force)
        #Set Remote Desktop Services User Profile
        $RDSProfilePath = $CloudRDSProfilePath + $username
        Get-ADUser $username | ForEach-object {
            $ADSI = [ADSI]('LDAP://{0}' -f $_.DistinguishedName)
            try {
                $ADSI.InvokeSet('TerminalServicesProfilePath',$RDSProfilePath)
                $ADSI.SetInfo()
            } catch { Write-Verbose $Error[0] }
        }
        #Add User to groups
        #Based on Cloud Version - modify as needed to match AD groups
        $GrpArray = @()
        switch ($user.cloudver) {
            "Field Service" {}
            "Work from Home" {$GrpArray += "WFH"}
            "Pilot" {}
            "QA" {}
            "Sales" {}
            "Inventory" {}
        }
        #Based on Title - modify as needed to match AD groups
        switch ($user.title) {
            "Admin Assistant" {}
            "Director" {$GrpArray += "Leadership"}
            "FEP" {$GrpArray += ""}
            "Helpdesk" {$GrpArray += "Helpdesk"}
            "Helpdesk LEAD" {$GrpArray += "Domain Admins"; $GrpArray += "Helpdesk"}
            "FSE" {$GrpArray += "FSE"}
            "Pre-Sales" {$GrpArray += "Sales"}
            "RF Engineer" {$GrpArray += "Some-Application-Users" ; $GrpArray += "RF"}
            "RLM" {}
            "Sales" {$GrpArray += "Sales"}
            "SDC" {$GrpArray += "SDC"}
            "TC" {$GrpArray += "TC"}
            "Other" {}
        }
        Add-ADPrincipalGroupMembership $username -MemberOf $GrpArray
        write-verbose "The following groups were added to $DisplayName:"
        write-verbose "$GrpArray"
        #Add Notes to user
        get-aduser $username -properties info | foreach { Set-ADUser -Identity $_.samaccountname -Replace @{info="$($_.info)Manager - $Manager"} }
        get-aduser $username -properties info | foreach { Set-ADUser -Identity $_.samaccountname -Replace @{info="$($_.info)`r`nManager Phone - $($user.ManagerWorkPhone)"} }
        get-aduser $username -properties info | foreach { Set-ADUser -Identity $_.samaccountname -Replace @{info="$($_.info)`r`nEmployee Cost Center - $($user.costcenter)"} }
        get-aduser $username -properties info | foreach { Set-ADUser -Identity $_.samaccountname -Replace @{info="$($_.info)`r`nAccount created on: $datetime by $ScriptRunner"} }
        #Enable Account
        Enable-ADAccount -Identity $Username
    }
}

Value for Value

If you received any value from reading this post, please help by becoming a supporter.

Thanks for reading,
Alain