Author Archives: Andreas Lindahl

Automating the creation of Lync Online DNS Records

Recently I implemented Lync Online with a customer that had a lot of custom domain names. Adding the required DNS records manually would literary take hours, but luckily the customer used Windows DNS both internally and externally, so it was easy for me to write a script to add these DNS records.

In my case both DNS servers (internal and external) are running Windows Server 2008 R2. That gives me the option to use dnscmd.exe to add the records. Here is the script I used to add the records. Please note that you may need to modify the script if you are using subdomains.

#Get all domains registered in Office 365
Import-Module MSOnline
Connect-MsolService
$domains = Get-MsolDomain | Where-Object { $_.Name -notlike '*.onmicrosoft.com' }
$dnsserver = 'srv-dns01'

#Loop through all domains and add the required DNS records for all domains
foreach ($domain in $domains) {
    Write-Verbose "Adding records for domain $($domain.Name)"
    &"dnscmd.exe $dnsserver /RecordAdd $($domain.Name) sip CNAME sipdir.online.lync.com"
    &"dnscmd.exe $dnsserver /RecordAdd $($domain.Name) lyncdiscover CNAME webdir.online.lync.com"
    &"dnscmd.exe $dnsserver /RecordAdd $($domain.Name) _sipfederationtls._tcp SRV 100 1 5061 sipfed.online.lync.com."
    &"dnscmd.exe $dnsserver /RecordAdd $($domain.Name) _sip._tls SRV 100 1 443 sipdir.online.lync.com."
} 

Now I reduced this time-consuming (and boring) task to just a few minutes!

/ Andreas

Advertisements

How to handle SMTP Relay after migrating to Exchange Online

When decomissioning your on-premises Exchange server after moving to Office 365 you need a new solution for SMTP relay to use with for example multi-functional printers. In some cases your internet provider can offer this service, but if you want control over your mail flow I recommend using Office 365 also for outgoing e-mail.

Normally you need a licensed user to be able to send e-mails using SMTP with Office 365. Your applications also need support for TLS encryption. If your application doesn’t support TLS, or if you need to send e-mails from another address than the licensed user’s address you need another solution. Luckily Office 365 can help you. The solution is to set up an inbound connector in Exchange Online Protection.

Setting up an Inbound Connector

An Inbound Connector is easily set up with just a few lines of PowerShell code. First we have to connect to Exchange Online.

$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange `
    -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' `
    -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session -CommandName 'New-InboundConnector'

Now we can create the Inbound Connector. All we have to specify is a Name for our connector, SenderIPAddresses which is the IP addresses to allow relaying from (your external IP address), and SenderDomains which is the domains to accept messages from.

You can also get your external IP address with PowerShell, here I will use a free IP detection tool from Dyn.

$ip = (Invoke-WebRequest -Uri http://checkip.dyndns.com).content `
    -replace '[^\d\.]'
New-InboundConnector `
    -Name 'SMTP Relay' `
    -SenderIPAddresses $ip `
    -SenderDomains '365lab.net'

The settings for Mail Flow are also found in Exchange admin center, navigate to mail flow, and then go to the connectors section. Of course you can also use the GUI to create your connector.

smtprelay

When configuring your application to connect to this SMTP server, use your MX server name as SMTP server and connect using port 25. The server name can be found in Office 365 admin center, and looks something like domain-com.mail.protection.outlook.com.

Increasing security

With this method all computers on your network sharing the same external IP address can use your Inbound Connector. To strengthen the security I recommend only opening port 25 in the firewall from your devices that actually sends email. An even better alternative would be to set up an internal SMTP server that only accept connections from approved devices, and then uses your Inbound Connector as smart host. This can be achieved with the SMTP Service provided with Internet Information Services (IIS) in Windows Server.

Configuring IIS SMTP Service

First we have to install the SMTP Service in Windows. This can be done with PowerShell.

Note that the Add-WindowsFeature cmdlet in Windows Server 2012 is called Install-WindowsFeature, but Add-WindowsFeature still exists as alias for backward compatibility.

Import-Module ServerManager
Add-WindowsFeature SMTP-Server

This will also install the required dependencies for example the IIS 6 Management Console. When the installation is done we are ready to start configuring the service. Let’s set the service to start automatically when Windows starts:

Set-Service SMTPSVC -StartupType Automatic

The next step is to set Office 365 as Smart Host. Replace the Smart Host name with your own MX server.

$smtpsvc = [ADSI]'IIS://localhost/smtpsvc/1'
$smtpsvc.SmartHost = '365lab-net.mail.protection.outlook.com'
$smtpsvc.SetInfo()

We also have to set a Fully Qualified Domain Name to identify our SMTP Server.

$hostname = (Resolve-DnsName $ip).NameHost
$smtpsvc.FullyQualifiedDomainName = $hostname
$smtpsvc.SetInfo()

Finally we have to add our internal IP addresses that are allowed to use the SMTP Service. I prefer using the GUI for this. You will find these settings in the Internet Information Services (IIS) 6.0 Manager. Expand the local computer node, and right click on “SMTP Virtual Server #1” and choose Properties. On the Access tab click on Relay restrictions, and then add your local IP addresses that are allowed to use the SMTP Server. While in the GUI settings I also suggest having a look at the message size limits and logging settings.

Now we are all set. Don’t forget to configure your perimeter firewall to block outgoing SMTP traffic from all your computers except the SMTP Server.

/ Andreas

PowerShell GUI application to set thumbnailPhoto

A while ago I started experimenting with writing GUI applications in PowerShell. This script that updates the thumbnailPhoto attribute of a user was my first attempt, and since it works quite well I thought that I might share it with you.

The thumbnailPhoto attribute is used more and more, and it is now supported by Outlook, SharePoint and Lync. It is also supported by Office 365, and when using DirSync the data is synced to Windows Azure Active Directory.

This script uses Windows Presentation Foundation (WPF) which is part of .Net 3.0. The window form is easily built with Microsoft Visual Studio Express 2013 for Windows Desktop and can be saved as a XAML document, which we can import in our PowerShell script.

Data are saved and retrieved from Active Directory using ADSI, which means no additional PowerShell modules must be installed. The only requirements are PowerShell 2.0 or newer with .Net Framework 3.0. If running PowerShell version 2.0 you also need the -STA parameter to powershell.exe.

The application itself is very simple, just type the username in the search box, and browse for a photo. When clicking Apply the image data is saved to Active Directory. By default all users has permission to change their own thumbnailPhoto. To change other users, permissions needs to be delegated.

There is a limit in picture size, maximum size is 100 kB, but since Office 365 cannot handle that large image I have set the limit in the script to 10 kB. Also recommended resolution is 96×96 pixels.

thumbnailphotoguiapp

Here’s the full script, please note that you need a valid path to the .xaml document.

Set-ThumbnailPhotoGUI-mainform.xaml

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Thumbnail photo changer app" Height="350" Width="525" ResizeMode="NoResize">
    <Grid x:Name="list1" Margin="0,0,0,2">
        <Label Content="Username:" HorizontalAlignment="Left" Width="105" Margin="20,23,0,250" />
        <TextBox x:Name="txtSearch" Margin="138,27,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="122"/>
        <Button x:Name="btnSearch" Content="Search" HorizontalAlignment="Left" VerticalAlignment="Top" Width="50" Margin="271,27,0,0"/>
        <Label Content="Surname:" HorizontalAlignment="Left" Height="30" Margin="20,92,0,0" VerticalAlignment="Top" Width="92"/>
        <Label x:Name="lblSn" Content="" Height="30" VerticalAlignment="Top" HorizontalAlignment="Left" Width="315" Margin="134,92,0,0"/>
        <Label Content="UserPrincipalName:" HorizontalAlignment="Left" Height="30" Margin="20,123,0,0" VerticalAlignment="Top" Width="115"/>
        <Label x:Name="lblUPN" Content="" Height="30" VerticalAlignment="Top" HorizontalAlignment="Left" Width="346" Margin="134,123,0,0"/>
        <Label Content="Givenname:" HorizontalAlignment="Left" Height="30" Margin="20,61,0,0" VerticalAlignment="Top" Width="92"/>
        <Label x:Name="lblGivenName" Content="" Height="30" VerticalAlignment="Top" HorizontalAlignment="Left" Width="315" Margin="134,61,0,0"/>
        <Image x:Name="imgThumb" HorizontalAlignment="Left" Height="96" Margin="29,203,0,0" VerticalAlignment="Top" Width="96"/>
        <Button Name="btnImg" Content="Change photo..." HorizontalAlignment="Left" Height="28" Margin="149,267,0,0" VerticalAlignment="Top" Width="111" IsEnabled="False"/>
        <Label Content="DistinguishedName:" HorizontalAlignment="Left" Height="30" Margin="20,156,0,0" VerticalAlignment="Top" Width="126"/>
        <Label x:Name="lblDN" Content="" Height="30" VerticalAlignment="Top" HorizontalAlignment="Left" Width="373" Margin="134,156,0,0"/>
        <Button x:Name="btnApply" Content="Apply" HorizontalAlignment="Left" Height="28" Margin="278,267,0,0" VerticalAlignment="Top" Width="111" IsEnabled="False"/>
    </Grid>
</Window>

Set-ThumbnailPhotoGUI.ps1

if ([threading.thread]::CurrentThread.ApartmentState.ToString() -eq 'MTA') {
    Write-Error "This script requires PowerShell to run in Single-Threaded Apartment. Please run powershell.exe with parameter -STA"
    Exit
}

Add-Type -AssemblyName PresentationFramework 
[xml]$XAML = Get-Content "Set-ThumbnailPhotoGUI-mainform.xaml"
$XAML.Window.RemoveAttribute("x:Class")
 
$global:userinfo = @{
    dn = $null
    photo = [byte[]]$null
}
 
function Invoke-FileBrowser {
    param([string]$Title,[string]$Directory,[string]$Filter="jpeg (*.jpg)|*.jpg")
    [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
    $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog
    $FileBrowser.InitialDirectory = $Directory
    $FileBrowser.Filter = $Filter
    $FileBrowser.Title = $Title
    $Show = $FileBrowser.ShowDialog()
    If ($Show -eq "OK") { Return $FileBrowser.FileName }
}
 
function Get-LDAPUsers($sam) {
    $activeDomain = New-Object DirectoryServices.DirectoryEntry
    $domain = $activeDomain.distinguishedName
    $searcher = [System.DirectoryServices.DirectorySearcher]"[ADSI]LDAP://$domain"
    $searcher.filter = "(&(samaccountname=$sam)(objectClass=user)(objectClass=person))"
    $result = $searcher.findall()
     
    if ($result.count -gt 1) {
        Write-Warning "More than one user found. Please refine your search."
    } elseif ($result.count -eq 1) {
        $result = $searcher.findone().getDirectoryEntry()
        [pscustomobject]@{
            UserPrincipalName = $result.userprincipalname[0]
            DistinguishedName = $result.distinguishedname[0]
            samAccountName = $result.samaccountname[0]
            Givenname = $result.givenname[0]
            Surname = $result.sn[0]
            ThumbnailPhoto = $result.thumbnailphoto
        }
    }
}
 
function Update-WPFForm($sam) {
   $selecteduser = Get-LDAPUsers $sam
   if ($selecteduser) {
        $global:userinfo.dn = $selecteduser.DistinguishedName
        $global:userinfo.photo = [byte[]]$($selecteduser.thumbnailphoto)
 
        $lblGivenName.Content = $selecteduser.GivenName
        $lblSN.Content = $selecteduser.SurName
        $lblUPN.Content = $selecteduser.UserPrincipalName
        $lblDN.Content = $global:userinfo.dn
        $txtSearch.Text = $selecteduser.SamAccountName
        $imgThumb.Source = $global:userinfo.photo
        $btnImg.IsEnabled = $true
    }
}
 
#region GUI
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $window=[Windows.Markup.XamlReader]::Load($reader)
 
    $btnSearch = $window.FindName("btnSearch")
    $btnImg = $window.FindName("btnImg")
    $btnApply = $window.FindName("btnApply")
    $txtSearch = $window.FindName("txtSearch")
    $lblGivenName = $window.FindName("lblGivenName")
    $lblSn = $window.FindName("lblSn")
    $lblUPN = $window.FindName("lblUPN")
    $lblDN = $window.FindName("lblDN")
    $imgThumb = $window.FindName("imgThumb")
#endregion GUI
 
#region Events
 
    #region btnSearchClick
        $btnSearch_click = $btnSearch.add_Click
        $btnSearch_click.Invoke({
            Update-WPFForm($txtSearch.Text)
        })
    #endregion btnSearchClick
 
    #region btnImgClick
        $btnImg_click = $btnImg.add_Click
        $btnImg_click.Invoke({
            $file = Invoke-FileBrowser -Title "Choose a thumbnail photo" -Filter "Jpeg pictures (*.jpg)|*.jpg"
            if ($file) {
                if ((Get-Item $file).length -gt 10kB) {
                    Write-Warning "Picture too large. Maximum size is 10 kB."
                } else {
                    $global:userinfo.photo = [byte[]](Get-Content $file -encoding byte)
                    $imgThumb.Source = $global:userinfo.photo
                    $btnApply.IsEnabled = $true
                }
            }
        })
    #endregion btnImgClick
     
    #region btnApplyClick
        $btnApply_click = $btnApply.add_Click
        $btnApply_click.Invoke({
            $user = New-Object DirectoryServices.DirectoryEntry "LDAP://$($global:userinfo.dn)"
            $user.put("thumbnailPhoto", $global:userinfo.photo)
            $user.setinfo()
            $btnApply.IsEnabled = $false
            Write-Verbose "User $($global:userinfo.dn) updated."
        })
    #endregion btnApplyClick
     
    #region textChange
        $txtSearch_Change = $txtSearch.add_KeyDown
        $txtSearch_Change.Invoke({
            if ($_.Key -ieq 'Return') {
                Update-WPFForm($txtSearch.Text)
            }
        })
    #endregion textChange
 
#endregion Events
 
$window.ShowDialog() | Out-Null

This is just an example how a useful script with GUI can be written with quite simple means. Now let’s get the camera and take some photos!

/ Andreas

Using Exchange Online as mail gateway for decentralized email domains

In some scenarios it is useful to have a central email server that forwards messages to different email servers. You might for example have a company with different business units, each having an email server of their own. In this post I will show you how to get started with the mail flow using one domain, but with several different email servers in Exchange Online.

Consider the following scenario. Several email servers exists within a company:

  • In USA an on-premises Exchange environment is using @us.contoso.com addresses
  • In Spain an external hosting provider is delivering an email service based on pop3 using @contosospain.com addresses
  • In Poland Exchange Online is used, but with @contoso.pl addresses

A new corporate policy states that all users must have an email address of the form givenname.surname@contoso.com. Also, a central antispam system must be used. At this point no consolidation of email servers will be done. Exchange Online Protection licenses has been bought by the company.

To take care of the mail flow in Exchange Online we have to create our Office 365 tenant and add the contoso.com domain to it. We also have to change our DNS and let the MX records for contoso.com point to Exchange Online. This will give us the following mail flow for a user in for example Poland:

Mail flow

I have a csv file with all users in each location:

aliasfile

For each user we will create a mail enabled user that holds the alias and target email addresses. I will use PowerShell to create these users:

#Import all users
$users = Import-Csv users.csv

#Create a password for all users
$Password = ConvertTo-SecureString 'Pa$$w0rd' -asPlainText -Force

#Create mail-enabled user
ForEach ($user in $users) {
	$Name = $user.Name
	$Email = $user.Alias
	$Target = $user.Email
	
	New-MailUser -Name $Name -MicrosoftOnlineServicesID $Email -ExternalEmailAddress $Target -Password $Password
}

Now all emails sent to the @contoso.com addresses will be delivered to the current regional email server. The next step would be to configure each email server to use @contoso.com as from address, and only accept incoming emails from Exchange Online.

/ Andreas

How to manually configure an Outlook profile for Office 365

Manually configuring Outlook for Office 365 can be tricky. That’s what Autodiscover is for. But in some situations you have to be able to do a manual configuration in for example a pilot deployment. Here are the steps required.

The nice thing with this method is that we will be able configure Outlook without changing our Autodiscover DNS records. That means we can do a full Office 365 test deployment without affecting users in an on-premises Exchange environment.

The first step is to establish a remote PowerShell session to Exchange Online. Here we need the ExchangeGuid attribute from our mailbox. Copy this value and save it for later.

exguid

The next step is to launch the New Profile Wizard in Outlook. We will go for the option to do a Manual setup.

outlookprofile1

In the next screen we select that we have a Microsoft Exchange Server.

outlookprofile2

Now it’s time to enter the exchange server name. Here we need the ExchangeGuid from the first step. Write the Guid followed by @ and the email domain. The user name is our Office 365 user name.
Now click More Settings.

outlookprofile3

On the Security tab we select Anonymous Authentication.

outlookprofile4

On the Connection tab check the box to connect using HTTP and click Exchange Proxy Settings

outlookprofile5

Fill in all values as below

outlookprofile6

Click OK to close all windows. You will be asked for credentials, fill in your Office 365 username and password.

outlookprofile7

Now our Outlook Profile is ready to use and Outlook is configured for Exchange Online, without using Autodiscover.

/ Andreas

Exchange Online: Migrating shared mailboxes and resources

In Exchange 2003 Resource Mailboxes and Shared Mailboxes are often set up as a regular user mailbox. These mailboxes are still possible to migrate using Staged Migration or Cutover Migration, by migrating them as a regular user mailbox. The downside is that they will require an Exchange Online license.

If you are migrating from an Exchange version that supports Hybrid Migration you will be able to migrate resources directly without assigning licenses, but there is a workaround for Cutover and Staged migrations as well.

The process of migrating a Shared Mailbox or Resource Mailbox is as follows:

  1. Migrate mailbox as regular user mailbox
  2. Assign license
  3. Convert mailbox to shared/resource
  4. Remove license

To convert a user mailbox to a shared mailbox we just need one line of PowerShell. Except from changing the type, we also have to modify the mailbox quotas.

Set-Mailbox -Identity "shared@365lab.net" `
	-Type Shared `
	-IssueWarningQuota 9.5GB `
	-ProhibitSendQuota 9.75GB `
	-ProhibitSendReceiveQuota 10GB

The same thing applies to Resource Mailboxes (Rooms and Equipment). The difference here is that we also want to configure AutoAccept policies and set default access rights to the calendar:

Set-Mailbox -Identity "resource@365lab.net" `
	-Type Room `
	-IssueWarningQuota 9.5GB `
	-ProhibitSendQuota 9.75GB `
	-ProhibitSendReceiveQuota 10GB

Set-CalendarProcessing -Identity "resource@365lab.net" `
	-AutomateProcessing AutoAccept 

Add-MailboxFolderPermission "resource@365lab.net:\Calendar" `
	-User Default `
	-AccessRights LimitedDetails

After this is done the license can be removed. You will get a warning that your data will be lost, but it is safe to click Yes on this one.

licensewarning

/ Andreas

Quick Tip: Forcing Office 365 Directory Synchronization to run

Disclaimer
This blog post was written for an older version of the Azure AD Connect Synchronization Service. For information regarding the current version please look at the documentation from Microsoft.

Sometimes you need changes in your on-premises Active Directory to sync to Office 365 as soon as possible. You might, for example, have a new employee at your doorstep one morning, and you quickly have to create an account for him. Normally a sync is only done every three hours. With these commands your data is synced immediately.

Depending on your version of DirSync/AADSync the commands are a bit different:

DirSync prior to version 6862.0000

To start a manual sync you launch the DirSyncConfigShell Console File, located at C:\Program Files\Windows Azure Active Directory Sync.

A sync is triggered by the command

Start-OnlineCoexistenceSync

You can also trigger a full sync by using the -FullSync parameter.

If you are using DirSync with Password Sync you can also run a full password sync with the following lines of PowerShell code:

Set-FullPasswordSync
Restart-Service FIMSynchronizationService -Force

The DirSync result can be viewed in the FIM Client, but to see the result of Password Sync you need to look at the Event Viewer in Windows.

DirSync version 6862.0000 and later

Starting with DirSync version 6862.0000 released on June 5 2014 there is no longer a DirSyncConfigShell Console file in the Program Files folder. Instead you just start a normal PowerShell window and run Import-Module DirSync. After that the Start-OnlineCoexistenceSync cmdlet is available.

Forcing a Password Sync uses the same lines of PowerShell code as the previous versions:

Set-FullPasswordSync
Restart-Service FIMSynchronizationService -Force

Azure Active Directory Synchronization Services (AAD Sync)

A Directory Synchronization is triggered by running an exe file:

C:\Program Files\Microsoft Azure AD Sync\Bin\DirectorySyncClientCmd.exe

force_dirsync

DirectorySyncClientCmd.exe accept two parameters, initial and delta. Running DirectorySyncClientCmd.exe initial initiates to a FullSync, and running DirectorySyncClientCmd.exe delta (or without any parameters) initiates an incremental sync.

The delta synchronization is triggered every 3 hours, and it can also be started manually by running the Scheduled Task “Azure AD Sync Scheduler” in the Task Scheduler.

To force a Password Sync the following lines of PowerShell code is needed. Modify the first two lines to match your environment.

$adConnector  = 'ad.contoso.com'
$aadConnector = 'contoso.onmicrosoft.com - AAD'

Import-Module ADSync

$c = Get-ADSyncConnector -Name $adConnector
$p = New-Object Microsoft.IdentityManagement.PowerShell.ObjectModel.ConfigurationParameter 'Microsoft.Synchronize.ForceFullPasswordSync', String, ConnectorGlobal, $null, $null, $null
$p.Value = 1
$c.GlobalParameters.Remove($p.Name)
$c.GlobalParameters.Add($p)
$c = Add-ADSyncConnector -Connector $c
Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector -TargetConnector $aadConnector -Enable $false
Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector -TargetConnector $aadConnector -Enable $true 

/ Andreas

Exchange Online: Pre-populate language and time zone on new mailboxes

When a new Exchange Online mailbox is created the user is prompted to set language and time zone at first logon. This information is possible to pre-populate to make the experience smoother for the end users. This is particularly useful in a migration scenario.

2014-02-08 21-18-09

These settings are set with the cmdlet Set-MailboxRegionalConfiguration.

Example:

Set-MailboxRegionalConfiguration -identity "aaron.beverly@365lab.net" `
    -TimeZone "W. Europe Standard Time" -Language "sv-SE" -confirm:$false

Some useful links:

/ Andreas

Fix broken shortcuts during file server migrations

During a file server migration several problems arise. Most of them are eliminated by using DFS Namespaces. With DFS all paths are kept the same even though the file server itself is changed. Having that in mind, I really hope that everyone is using DFS for file servers.

In scenarios where UNC paths that points directly to the file server are used we have to find a way to minimize problems for the end users when the paths are changed.

It is very common that end users have shortcuts on their desktops that point to folders and documents on the server. I will give you a simple script that will replace the path for you.

This script is written in VBScript. The first two lines defines the old path, and what to replace it with.

'Define paths
strFind = "\\oldserver\share1\"
strReplace = "\\newserver\share2\"

Set objWsh = CreateObject("WScript.Shell")
Set objWshSysEnv = objWsh.Environment("PROCESS")
strProfilePath = objWshSysEnv("USERPROFILE")

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(strProfilePath & "\Desktop")

'Loop through all files on the desktop
For Each file In objFolder.Files
	if right(file.Name,3)="lnk" then
		'If a shortcut is found
		Set objLink = objWsh.CreateShortcut(file.Path)

		objLink.Arguments = Replace(objLink.Arguments,strFind,strReplace,1,-1,1)
		objLink.TargetPath = Replace(objLink.TargetPath,strFind,strReplace,1,-1,1)
		objLink.WorkingDirectory = Replace(objLink.WorkingDirectory,strFind,strReplace,1,-1,1)

		objLink.Save
	end if
Next

Run this script as a login script on your clients and you will have one problem less the day after your file server migration.

/ Andreas

Offline Domain Join and Direct Access – A real life example

A few days ago I got a new computer at work. I took it home to install it, but to finish the installation and log on with my domain account I needed access to the corporate network.

I had to find a solution to finalize the installation without going back to the office. Luckily we have Direct Access configured at my company, and all I need to connect is a domain joined computer. A perfect opportunity to use the Offline Domain Join feature.

For this to work we need some prerequisites in place:

  • A Windows Server 2012 or newer Direct Access server
  • A domain controller with at least Windows Server 2008 R2
  • At least Windows 8 on the client computer

Note: Offline Domain Join works in Windows 7 as well, but these are the minimum requirements to use it in conjunction with Direct Access

This process is performed in two steps: First we have to provision the computer in Active Directory. We create a computer account, and store metadata about the computer in a file. The metadata file is then used on the computer to finish the process.

The information we have to provide during the provisioning is:

  • The name of the domain that we want to join the new computer to
  • The name of the computer
  • Where to save the metadata file
  • The Direct Access Group Policy, to get all settings to connect using Direct Access
  • We also need to issue a computer certificate for Direct Access, so we have to specify what Certificate Template to use.

Let’s use Djoin.exe to provision the new computer. This command must run on a machine that is already a member of the domain, and you need rights to add workstations to the domain:

Djoin.exe /provision /domain corp.tailspintoys.com /machine PC5441 /savefile c:\OfflineDomainJoin.txt /policynames "DirectAccess Client Settings" /policypaths "\\corp.tailspintoys.com\SYSVOL\corp.tailspintoys.com\Policies\{GUID}\Machine\Registry.pol" /certtemplate Machine

Now, I copy OfflineDomainJoin.txt to the new computer and run Djoin.exe again to insert the metadata into the computer. Please be aware that this file contains sensitive information and should not be sent unencrypted.

Djoin.exe /requestODJ /loadfile C:\OfflineDomainJoin.txt /windowspath %systemroot% /localos 

Finally, a restart is required. After the restart I’m connected with Direct Access and it is possible to log on with my domain credentials.

/ Andreas