Sunday, March 11, 2012

PowerShell: AD Create OU and Copy Group Policies

A few weeks ago, a need to came up for a script that would create a new dept OU, and standard sub OUs inside it, and then assign the same 60+ group policies from an another dept OU (including the standard sub OUs). Below the script that took care of this request.

########################################################
# Script Name: AD_Create_OU_and_Link_GPOs.ps1
# Author: Dean Bunn
# Description: Creates Dept OU and Assigns GPOs
########################################################

#Load Group Policy Module
Import-Module grouppolicy

#Var for New Department Name
$deptName = "DeptA"

#Var for Distinguished Path of Parent OU
$parentD = "OU=DEPARTMENTS,DC=ChildDomain,DC=MyCollege,DC=EDU"

#Var for GPO Source OU
$gpoSrcOU = "OU=DeptB,OU=DEPARTMENTS,DC=ChildDomain,DC=MyCollege,DC=EDU"

#LDAP String for Parent Path
$ldapParent = "LDAP://" + $parentD

#Var for OU Class
$class = "organizationalUnit"

#Array for Storing OU Info
$arrOUs = @()

#Array of OU Path with ? for Dept Name Place Holder
$deptOUs = @("OU=?",
"OU=?-OU-Computers,OU=?",
"OU=?-OU-Groups,OU=?",
"OU=?-OU-LocalUsers,OU=?",
"OU=?-OU-Servers,OU=?",
"OU=?-OU-Test Servers,OU=?",
"OU=Faculty-Staff-Grad,OU=?-OU-Computers,OU=?",
"OU=IT,OU=?-OU-Computers,OU=?",
"OU=LAB,OU=?-OU-Computers,OU=?",
"OU=STAFF,OU=?-OU-Computers,OU=?"
)

#Retrieve Parent OU using ADSI
$parentOU = [ADSI]$ldapParent

#Split GPO Source OU String to Get OU First Name
$gOFN = ($gpoSrcOU.ToString().Split(","))[0].ToString().ToLower().Replace("ou=","")

#Loop Through OU Paths
foreach($dOU in $deptOUs)
{
#Replace ? Character with Name of Dept
$ouDN = $dOU.ToString().Replace("?",$deptName)
#Create OU Object and Save It
$oOU = $parentOU.create($class,$ouDN)
$oOU.setInfo()
#Replace ? Character with Name of GPO Source OU and Extra Comma (Will Be Used Later to Compare)
$gpoOUStart = $dOU.ToString().Replace("?",$gOFN) + ","
#Var for the Full Path of the New OU (Used Later When Assigning Linked GPOs)
$newOUFullPath = $ouDN + "," + $parentD
#Create PS Object and Assign OU Data
$uEntry = new-Object PSObject
$uEntry | add-Member -memberType noteProperty -name "gpoOUStart" -Value $gpoOUStart.ToString().ToLower()
$uEntry | add-Member -memberType noteProperty -name "newOUFullPath" -Value $newOUFullPath.ToString()
#Add PS Object to OU Array
$arrOUs += $uEntry
}

#Pause the Script for One Minute to Give AD Time to Acknowledge OU Creation
Start-Sleep -Seconds 60

#Var for LDAP String Path
$gSOLP = "LDAP://" + $gpoSrcOU

#Query AD for All OUs in the GPO Source OU Path
$ADsPath = [ADSI]$gSOLP
$Search = New-Object DirectoryServices.DirectorySearcher($ADsPath)
$Search.filter = "(objectClass=organizationalunit)"
$Search.PageSize = 900
$Search.SearchScope = "SubTree"
$Results = $Search.FindAll()

#Loop Through Each Result
foreach($result in $Results)
{
#Retrieve OU Directory Entry
$objOU = $result.GetDirectoryEntry()
#Loop Throuh Each Individual OU PS Object in the OU Array
foreach($iOU in $arrOUs)
{
#See If Source GPO OU Path Starts with the Same Structure as One We Created Earlier
if($objOU.DistinguishedName.ToString().ToLower().StartsWith($iOU.gpoOUStart))
{
#Retrieve the GPOs Linked on the GPO Source OU
$srcGPOS = Get-GPInheritance -target $objOU.DistinguishedName.ToString()
#Loop Through All Linked GPOs
foreach($gp in $srcGPOS.GpoLinks)
{
#Convert the GpoId to a GUID
$guidGPO = [Guid]$gp.GpoId
#Link the GPO to the New OU
New-GPLink -guid $guidGPO -target $iOU.newOUFullPath -LinkEnabled Yes -domain "childdomain.mycollege.edu"
}#End Foreach GpoLinks
}#End OU DN StartsWith Check
}#End Foreach on PS Object Array
}#End Foreach OU in GPO Source OU

PowerShell: Profile Settings

Last week, I found a cool link (listed in references) that showed me how to configure a PowerShell profile. Basically, it’s a PowerShell script that runs every time you start an instance of PowerShell. I like the idea of not having to type in the same forest command each time we started up the EMS (Exchange Management Shell); however, when I tried configure the forest command in profile script and clicked the EMS link it would error out since it ran the profile script first then the RemoteExchange.ps1 script used in the EMS shortcut. I got around this by configuring my PS profile to check for the existence of Exchange script and then running it like the EMS shortcut would. Then I added the forest command and whammo I have a PS console ready to go to work.

Here is the code from my Microsoft.PowerShell_profile.ps1 file.

#Dean's PowerShell Console Settings
#Place Code in C:\Users\UserID\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
#Create Required File Using: New-item -path $profile -type file -force

#Change Console Colors to Black and Green
$host.UI.RawUI.BackgroundColor = "Black";
$host.UI.RawUI.ForegroundColor = "Green";

#Other Available Color Choices
#Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White

#Change Error Text Color to White (If You Were Using Red for Normal Text)
((Get-Host).PrivateData).ErrorForegroundColor = "White";
#Change Warning and Verbose Text Color to Magenta (If You Were Using Yellow for Normal Text)
#((Get-Host).PrivateData).WarningForegroundColor = "Magenta";
#((Get-Host).PrivateData).VerboseForegroundColor = "Magenta";
#Change Error and Warning Background Color to Gray (If You Were Using Black for Normal Text)
#((Get-Host).PrivateData).ErrorBackgroundColor = "Gray";
#((Get-Host).PrivateData).WarningBackgroundColor = "Gray";

#Clear the Console to Load New Color Settings
Clear-Host;

#Load Exchange If On System
if(Test-Path $env:ExchangeInstallPath\bin\RemoteExchange.ps1)
{
.$env:ExchangeInstallPath\bin\RemoteExchange.ps1;
Connect-ExchangeServer -auto;
Set-ADServerSettings -ViewEntireForest $true;
}

#Change the Prompt Configuration
function prompt
{
#Get the Current Directory
$path = Get-Location;
#Set Prompt for the Computer Name then Directory Path
"PS [$env:computername] $path>";
}

#Change the Window Title
$host.UI.RawUI.WindowTitle = "Dean's PS Goodness";

#Change Location to the Desktop
$dsktop = [Environment]::GetFolderPath("Desktop").ToString();
cd $dsktop;


References:

How to use a PowerShell Profile to simplify tasks
http://www.techrepublic.com/blog/networking/how-to-use-a-powershell-profile-to-simplify-tasks/5393
Managing Exchange 2010 with Remote PowerShell
http://www.mikepfeiffer.net/2010/02/managing-exchange-2010-with-remote-powershell/

PowerShell: Map Network Drives

Wanted to explore mapping network drives with PowerShell. Quick search online shows that you will still need to use Wscript.network or the old net use command. Since I came into the game with VBScript I went that path. I configured a scheduled task that runs when my domain account logs onto the system. Below are the parameters of the Action tab of the scheduled task.

Action: Start a program

Settings -

Program/Script: C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe

Add arguments: -noexit -command "C:\Users\userID\Documents\Scripts\Dean_Drive_Mappings.ps1"

----Dean_Drive_Mappings.ps1-----------

#Create Object Instance of Wscript.Network
$net = new-object -ComObject WScript.Network

#Gather Collection of All Network Drive Mappings
$mappedDrives = $net.EnumNetworkDrives()

#Remove All Established Network Drive Mappings
for ($d = 0; $d -lt $mappedDrives.Count(); $d = $d + 2)
{
#Remove Network Drive
$net.RemoveNetworkDrive($mappedDrives.item($d).ToString(),$true,$true)
}

#Pause the Script to Prevent Networking Confusion
Start-Sleep -Milliseconds 800

#Map Required Drives
$net.MapNetworkDrive("i:", "\\server1.mycollege.edu\share1", $true)
$net.MapNetworkDrive("j:", "\\server2.mycollege.edu\share2", $true)
$net.MapNetworkDrive("k:", "\\server3.mycollege.edu\share3", $true)
$net.MapNetworkDrive("l:", "\\server4.mycollege.edu\share4", $true)
$net.MapNetworkDrive("s:", "\\server5.mycollege.edu\share5", $true)