What’s new
As can be seen from the new menu, there are new functions available:

Let’s dive right in to the new features:
Retention Policies
For retention policies I have a basic loop set up so that you can configure multiple Retention Policies and with another loop apply multiple Retention Policy Tags to each policy. There are additional options that can be configured, but this code section will at least allow for basic retention policies to be added to a lab environment in a more programmatic format.
# Menu code 10 {# Create retention Policies create-retentionpolices } # Create Retention Policies function create-retentionpolices { $polno = read-host "How many Retention Policies do you want to create" $polcounter = 0 while ($polcounter -lt $polno) { $currentpol = $polcounter +1 $polname = read-host "Specify a name for Rentention Policy number $currentpol" $tagno = read-host "How many Retention Tags do you want to create" $counter = 0 $alltags = @() while ($counter -lt $tagno) { $current = $counter + 1 $name = read-host "Specify a name for Retention Tag number $current" $time = read-host "Specify a time frame to apply the tag (i.e. 60, 90, 120)" $RetentionAction = read-host "Specify a retention action (DeleteAndAllowRecovery, PermanentlyDelete, MoveToArchive)" if ($retentionaction -eq "MoveToArchive") { $type = "all" } else { $type = read-host "Specify which folders will be affect (All, Inbox, etc. )" } $comment = read-host "Specify a comment for the Retention Tag (optional)" $alltags += ,($name) if ($retentionaction -eq "permanentlyDelete") { $archive = $false } else { $archive = $true } New-RetentionPolicyTag -Name $name -Type $type -RetentionAction $retentionaction -RetentionEnabled:$archive -AgeLimitForRetention $time -Comment $comment $counter++ } new-retentionpolicy -name $polname -RetentionPolicyTagLinks $alltags $polcounter++ } }
Here is a sample run through this option. Notice I was able to create two entirely different Retention Tags and apply it to one Retention Policy:
At this time the options are limited and will expanded in part four or five of this series.
Mobile Device Policies
Mobile device policies contain a large list of options to be configured. For this particular script I have the password section included as it is the most commonly configured, perhaps the only section. The policy is not applied to any users, but simply creates one to be used for testing.
# Menu Coding 15 { # Mobile Devices create-mobiledevicepolicy } # Create new mobile device policies function create-mobiledevicepolicy { write-host " " write-host "For now, this function will " -nonewline write-host "only create the password policy portion" -nonewline -ForegroundColor red write-host " of the Mobile Device Policy." write-host " " start-sleep 5 write-host "The existing Mobile Device Mailbox Policies are:" Get-MobileDeviceMailboxPolicy |ft id $name = read-host "What is the name of the new Mobile Device Mailbox Policy" $IsDefault = read-host "Will this policy be the default policy [y or n]" if ($isdefault -eq "y") { $isdefault = $true } else { $isdefault = $false } $password = read-host "Do you want to create a device password policy [y or n]" if ($password -eq "y") { $AlphanumericPasswordRequired = read-host "Will alphanumeric passwords be required [y or n]" if ($AlphanumericPasswordRequired -eq "y") { $AlphanumericPasswordRequired = $true }else { $AlphanumericPasswordRequired = $false } $PasswordRecoveryEnabled = read-host "Will password recovery be enabled [y or n]" if ($PasswordRecoveryEnabled -eq "y") { $PasswordRecoveryEnabled = $true }else { $PasswordRecoveryEnabled = $false } $MaxPasswordFailedAttempts = read-host "How many failed password attempts [4 through 16 or the value Unlimited]" $MinPasswordLength = read-host "What is the minimum password length [1 through 16 or the value $null]" $complex = read-host "Do you want a complex password policy [y or n]" $PasswordHistory = read-host "How many passwords for the device pasword history [1, 3, 7?]" if ($complex -eq "y") { $MinPasswordComplexCharacters = read-host "What will the minumim complex password characters be allowed [valid range is from 1 to 4]" New-MobileDeviceMailboxPolicy -name $name -MinPasswordLength $MinPasswordLength -MaxPasswordFailedAttempts $MaxPasswordFailedAttempts -PasswordEnabled $true -AlphanumericPasswordRequired $AlphanumericPasswordRequired -PasswordRecoveryEnabled $PasswordRecoveryEnabled -MinPasswordComplexCharacters $MinPasswordComplexCharacters -IsDefault $isdefault -PasswordHistory $PasswordHistory } else { New-MobileDeviceMailboxPolicy -name $name -MinPasswordLength $MinPasswordLength -MaxPasswordFailedAttempts $MaxPasswordFailedAttempts -PasswordEnabled $true -AlphanumericPasswordRequired $AlphanumericPasswordRequired -PasswordRecoveryEnabled $PasswordRecoveryEnabled -IsDefault $isdefault -PasswordHistory $PasswordHistory } } if ($password -eq "n") { New-MobileDeviceMailboxPolicy -name $name } }
Here is a quick run-through of this option:
Again, notice the limited amount of controls. I am concentrating on the password policy for this initial iteration. In the next article in the series I’ll explore some more advanced options.
OWA Policies
OWA Mailbox Policies are also notoriously complex with dozens of configurable options. I’ve only added seven options for this iteration of the script. When the advanced version of the script comes out there will be an attempt to categorize and organize the options to make the building of the policy less complex or at least doable.
# Menu Code 16 { # OWA Policies create-OWApolicy } # Create an OWA Policy function create-OWApolicy { write-host " " write-host "For now, this function will " -nonewline write-host "only create a basic version" -nonewline -ForegroundColor red write-host " of the OWA Mailbox Policy." write-host " " start-sleep 5 write-host "The existing OWA Mailbox Policies are:" Get-OWAMailboxPolicy |ft id $name = read-host "What is the name of the new OWA Mailbox Policy" $IsDefault = read-host "Will this policy be the default policy [y or n]" if ($isdefault -eq "y") {$isdefault = $true} else {$isdefault = $false} $AllowOfflineOn = read-host "Do you want to enable OWA offline in this policy [PrivateComputersOnly | NoComputers | AllComputers]" $ChangePasswordEnabled = read-host "Would you like their password change opion to appear [y or n]" if ($ChangePasswordEnabled -eq "y") {$ChangePasswordEnabled = $true} else {$ChangePasswordEnabled = $false} $JunkEmailEnabled = read-host "Enable Junk mail in OWA [y or n]" if ($JunkEmailEnabled -eq "y") {$JunkEmailEnabled = $true} else {$JunkEmailEnabled = $false} $LinkedInEnabled = read-host "Would you like to enable LinkedIn for OWA [y or n]" if ($LinkedInEnabled -eq "y") {$LinkedInEnabled = $true} else {$LinkedInEnabled = $false} $PublicFoldersEnable = read-host "Enable Public folders in OWA [y or n]" if ($PublicFoldersEnable -eq "y") {$PublicFoldersEnable = $true} else {$PublicFoldersEnable = $false} $RulesEnabled = read-host "Enable Rules in OWA [y or n]" if ($RulesEnabled -eq "y") {$RulesEnabled = $true} else {$RulesEnabled = $false} write-host " " write-host "Creating your policy now." -ForegroundColor green write-host " " if ($isdefault -eq $true) { new-OwaMailboxPolicy -name $name set-OwaMailboxPolicy -isdefault $isdefault -identity $name -AllowOfflineOn $AllowOfflineOn -ChangePasswordEnabled $ChangePasswordEnabled -JunkEmailEnabled $JunkEmailEnabled -PublicFoldersEnable $PublicFoldersEnable -RulesEnabled $RulesEnabled } else { new-OwaMailboxPolicy -name $name set-OwaMailboxPolicy -identity $name -AllowOfflineOn $AllowOfflineOn -ChangePasswordEnabled $ChangePasswordEnabled -JunkEmailEnabled $JunkEmailEnabled -PublicFoldersEnable $PublicFoldersEnable -RulesEnabled $RulesEnabled } }
Here is a sample run of creating and setting an OWA policy:
As you can see, the script will display previous OWA policies so names are not duplicated by accident. Each question also lays out the acceptable responses as well.
Enabling Archives
Archives are basically just another mailbox connected to a users account. In this code I give an option to create a new database (specifying name, log location and database location) or use an existing database. then you can decide who to apply it to (all or random). More advance options will be available in the next iteration of this code.
# Menu coding 17 { # Enabling Archives enable-archives } # Enable Archives for all or some users function enable-archives { $selection = read-host "Do you want to enable archives for all mailboxes or random mailboxes [a or r]" $location = read-host "Would you like to create a new mailbox database for the archive mailboxes [y or n]" if ($location -eq "n") { $database = read-host "Which database will the archive mailboxes be placed in" } if ($location -eq "y") { $srv = ($server[0]).name write-host "The new archive database will bew placed on $srv." $directory1 = read-host "Please enter a valid directory to place the database files" $directory2 = read-host "Please enter a valid directory to place the log files" $archivename = "ArchiveDatabase" $edb = $directory1+"\"+$archivename+".edb" $log = $directory2+"\"+$archivename $server = get-mailboxserver new-mailboxdatabase -name $archivename -edbfilepath $edb -logfolderpath $log -server $srv -erroraction silentlycontinue } # Enable the mailboxes to have archive mailboxes if ($selection -eq "a") { $mailboxes = (get-mailbox | where {$_.RecipientTypeDetails -ne "DiscoveryMailbox"}).alias foreach ($line in $mailboxes) { enable-mailbox $line -Archive -ArchiveDatabase $database -erroraction silentlycontinue } $name = (get-mailbox $alias).displayname write-host "An archive mailbox for $name has been created." -foregroundcolor green } else { $mbxcount = (get-mailbox | where {$_.RecipientTypeDetails -ne "DiscoveryMailbox"}).count $range = get-random -minimum 0 -maximum $mbxcount $counter = 0 $mailbox = (get-mailbox | where {$_.RecipientTypeDetails -ne "DiscoveryMailbox"}).alias while ($counter -lt $range) { $alias = $mailbox[$counter] enable-mailbox $alias -Archive -ArchiveDatabase $database -erroraction silentlycontinue $counter++ } $name = (get-mailbox $alias).displayname write-host "All archive mailboxes have been created." -foregroundcolor green } } # End function for archive mailboxes
Sample run-through of this option:
In the above example I used an existing Archive Database that I had created for testing.
Configure Apps
Lastly, one of the most underutilized sections of Exchange is the Apps section. This code will only install one app from the web, the Message Header Analyzer. The only caveat is that you need to XML file and the URL for the XML file is here for downloading. Make sure you download this in order to get the app to install. I will add more apps to the list later.
# Menu Coding 18 { # Configure Apps configure-Apps } # Configuring apps forExchange 2013 function configure-Apps { $option = read-host "Do you want to configure Apps (1) or add new Apps (2)" # Prep Random mailboxes (version 1) $mbxcount = (get-mailbox | where {$_.RecipientTypeDetails -ne "DiscoveryMailbox"}).count $range = get-random -minimum 0 -maximum $mbxcount $mailbox = (get-mailbox | where {$_.RecipientTypeDetails -ne "DiscoveryMailbox"}).alias # Create Random User List $counter = 0 $userlist = @() while ($counter -lt $range) { $alias = $mailbox[$counter] $userlist += @($alias) $counter++ } if ($option -eq "1") { write-host "Currently there are these apps installed in your Exchange Server 2013 environment" get-app -OrganizationApp |ft displayname,providedto,userlist -auto $app = (get-app -OrganizationApp).appid $choice = read-host "Do you want to limit the apps to random users or allow them for all users [r or a]" $mailboxes = (get-mailbox | where {$_.RecipientTypeDetails -ne "DiscoveryMailbox"}).alias if ($choice -eq "a") { $name = (get-mailbox $alias).displayname foreach ($line in $mailboxes) { foreach ($line in $app) { set-app $line -OrganizationApp $true -enabled $true } } write-host "Apps have been configured for all users." -foregroundcolor green } else { foreach ($line in $app) { Set-App $line -organizationapp -ProvidedTo SpecificUsers -UserList $userlist } } } else { # Feature to add later will be user inputed apps - only one app for now - Message Header Analyzer # $choice2 = read-host "Install apps from this script (1) or choose your own apps (2)" # write-host " " $choice = read-host "Do you want to limit the apps to random users or allow them for all users [r or a]" $Data = get-content -Encoding byte -Path "c:\scripts\manifest.xml" -ReadCount 0 # foreach ($line in $applist) { if ($choice -eq "a") { new-app -DefaultStateForUser Enabled -filedata $data -OrganizationApp } else { new-app -DefaultStateForUser Enabled -filedata $data -OrganizationApp -ProvidedTo SpecificUsers -UserList $userlist } #} } }
Quick run through of the options that can be configured:
Verifying that it worked:
Further Reading
Thank you for your articles.
I think that good way to improve your script is publish it on GitHub, CodePlex or other public repository.
After that more people can add function and improve script. You as repository master will have control on main branch of code.
Once my script is complete I was planning on putting it up on the TechNet Wiki site. Look for it in the next month or so.
But TechNet Wiki is not a tool to colaborate with code, for this the better will be a version control system like GitHub or CodePlex – please see how many feature can be helpfull to manage code, reviews, bugs et. https://github.com/features . As a git client for Windows I can recomend https://code.google.com/p/tortoisegit/ , yes the site is not very helpfull but the tool is.
Of course colaboration releases can be less “your” but probably will be created faster. Your choice.
Thanks again for your post.
Understood. I’ve heard of both, never used them as I don’t submit too much code for public modification. Might give it a spin for this script. Thanks for the suggestions.
Basic git operations (especially with GUI) are not difficult. Git you can use also without publish code (only on local system) – in a repository directory are created special/technical structures which are used for tracking version, etc. – you decide when you submit your version). I’m not developer also but understanding/using git took me less than a hour. You can find a lot tutorials related to Git on web.