SharePointDSC and ReverseDSC Microsoft Virtual Academy Session

In June of 2017, my colleague Brian Farnhill and I recorded a Full Day Microsoft Virtual Academy (MVA) session on Automating SharePoint with PowerShell Desired State Configuration. The recording is now available for free on the MVA site and covers the following topics:

  • Introduction to PowerShell Desired State Configuration
  • Advanced PowerShell DSC on-premises
  • Introduction to SharePointDSC
  • Azure Automation DSC with SharePoint
  • ReverseDSC for SharePoint
  • SharePointDSC Best Practices and Guidance

Click on the following link to watch the Full Session.

How to Get Started with SharePointDSC

The goal of this article is to help people interested in learning how to use PowerShell Desired State Configuration (DSC) to configure their SharePoint environment get started. While it is totally possible for you to configure a SharePoint Farm on an environment that has PowerShell 4.0 installed on, it is our recommendation that you try to use PowerShell 5+ as much as possible, as it offers a lot of improvements on the DSC side. The example covered in this article will be a Single Server SharePoint 2016 farm deployed with SQL Server 2016, on Windows Server 2016. We will be using DSC in push mode, meaning that we will manually execute the Start-DSCConfiguration cmdlet on the environment, and will ensure all dependent DSC Modules are put on the server prior to attempting to configure it.

The end-goal for this article, is to have a brand new Windows Server 2016 virtual machine with nothing on it to begin with, and then let DSC do the following automatically:

  • Install and configure Active Directory Domain Services;
  • Create all required SharePoint accounts;
  • Install SQL Server 2016;
  • Install the SharePoint 2016 Prerequisites;
  • Install SharePoint 2016;
  • Install the SharePoint 2016 French Language Pack;
  • Install the SharePoint 2016 Security Update (KB3115299); This update needs to be extracted in the updates folder of the SharePoint installation (slipstreamed).
  • Configure the SharePoint Farm;

The binaries to install SharePoint and SQL Server will be put on a Shared location, to which our Virtual Machine will have access to. Also, the Security Update (KB3115299) is required for the farm to get properly configured. Without it, you will get the following error thrown when calling the SPFarm DSC Block (at the New-SPConfigurationDatabase step): An error occurred while getting information about the user sp_farm at server contoso.com: The RPC server is unavailable. This installation will be slipstreamed within the SharePoint 2016 installation binaries.

Prerequisites

In this section we will cover the various prerequisites that have to be in place before initiating the DSC configuration.

Shared Location

In the current example, I will create a first Virtual Machine that will act as a file server. The SharePoint 2016 binaries, the SQL Server 2016 binaries, the Windows SXS folder, and the SharePoint 2016 language packs will be put on it. The Virtual Machine will be named DSC-Share, and will expose the following Shared Folders:

  • \\DSC-Share\Media\SP2016Binaries
  • \\DSC-Share\Media\SP2016LanguagePack
  • \\DSC-Share\Media\SQL2016Binaries
  • \\DSC-Share\Media\SXS (Needs to contain the SXS content of the Windows Server 2012 R2 installation Media)
SharePoint 2016 and SQL 2016 Binaries on Shared Drive

SharePoint 2016 and SQL 2016 Binaries on Shared Drive

The SXS folder contains the /Sources/SXS content from the Windows Server 2012 R2 installation media. It contains files that are required by the Prerequisites installer to install the .NET 3.5 components. During the DSC process, the SXS folder will be copied from the Remote Network Share onto the local disk. This is required for the Windows Feature to be properly installed on the local server.

Also, it is important for you to extract the Language pack in the specified folder. By default, when you download a SharePoint language pack, you get a file called serverlanguagepack.exe, however that won’t work with SharePointDSC. In order to properly extract its content you need to run the following command: .\serverlanguagepack.exe /extract:path. This will generate the folder structure expected by SharePointDSC.

SharePoint Virtual Machine

When I said earlier that the goal of this article was to start with a plain vanilla Virtual Machine with nothing installed on it….I lied. Just a tiny bit. Because we will be using DSC in Push mode for our example, the destination server (in my case named DSC-SP) needs to have all the required DSC modules installed on it first. The modules required for our example are the following:

  • xActiveDirectory
  • xNetworking
  • xSQLServer
  • SharePointDSC

The steps to get these properly installed on your server will differ depending on whether or not your Virtual Machine has internet connectivity or not.

Virtual Machine has Internet Connectivity

If your SharePoint server Virtual Machine has internet connectivity, then you are in luck. With the help of the Package Management component of PowerShell 5+, you can simply run the following cmdlets to have PowerShell automatically download the modules from the PowerShell Gallery:

Install-Module xActiveDirectory -Force
Install-Module xNetworking -Force
Install-Module xSQLServer -Force
Install-Module SharePointDSC -Force

Virtual Machine without Internet Connectivity

If your Virtual Machine doesn’t have internet connectivity, then you will have to manually copy the required modules inside the PowerShell modules repository. The easiest way to do that, is to download all the required module from a machine that has internet connectivity (see section just above), and then copy the downloaded modules manually onto the server. When you call the Install-Module cmdlet, PowerShell actually installs the specified module in C:\Program Files\WindowsPowerShell\Modules. Simply make sure you copy all the folders from that machine that has internet connectivity and from where you executed the Install-Module cmdlets, over to your SharePoint Virtual Machine, under the same path.

PowerShell DSC Modules

PowerShell DSC Modules

Set Network Share as being Part of the Intranet Zone

By default, the network share won’t be recognized as a trusted location. If you were to navigate to it from your SharePoint server and try to execute a program from it, you will get prompted with a Security Warning prompt that allows you to trust the file. PowerShell DSC is faced with the same issue in the background and this actually causes the process to hang. In order for PowerShell Desired State Configuration to be able to properly execute remote executable such as the SharePoint Prerequisite Installer, you need to add your network share to the Intranet zone in Internet Explorer. Simply launch the IE options, switch to the security tab, select Intranet, click on Sites and add the path to your network share (in my case file://dsc-share).

The Script

The following DSC script will be used to configure our environment. Its variables will be passed as Configuration data (defined in the next section below). The script itself is very self-explanatory and I will not go over each section in details. Note that for an offline installation, the script expects to see the following files in the prerequisiteinstallerfile folder in the root of your SharePoint 2016 binary installation folder:

  • AppFabric-KB3092423-x64-ENU.exe
  • dotNetFx45_Full_setup.exe
  • MicrosoftIdentityExtensions-64.msi
  • NDP453-KB2969351-x86-x64-AllOS-ENU.exe
  • setup_msipc_x64.exe
  • sqlncli.msi
  • Synchronization.msi
  • vc_redist.x64.exe
  • vcredist_x64.exe
  • WcfDataServices.exe
  • WindowsServerAppFabricSetup_x64.exe
SharePoint 2016 Prerequisites

SharePoint 2016 Prerequisites

You will also notice that at the bottom of the screen there is a section that will automatically compile a Meta-Mof file to configure the Local Configuration Manager (LCM) process on that machine so that it will automatically reboot the server when needed (prerequisites, domain-join, etc.) and automatically pickup the configuration where it left it upon being restarted.

SPStandAlone.ps1

Configuration SPStandAlone
{
    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName xActiveDirectory
    Import-DSCResource -ModuleName xNetworking
    Import-DSCResource -ModuleName xSQLServer
    Import-DscResource -ModuleName SharePointDSC
 
    #region Credentials
    $Script:FarmAdmin = Get-Credential -Username "contoso\sp_farm" -Message "Farm Admin"
    $Script:FarmDomainAdmin = Get-Credential -Username "contoso\administrator" -Message "Domain Administrator"   
    #endregion
 
    node $AllNodes.NodeName
    {
        xFireWall SQLFirewallRule
        {
            Name = "AllowSQLConnection"
            DisplayName = 'Allow SQL Connection'
            Group = 'DSC Configuration Rules'
            Ensure = 'Present'
            Enabled = 'True'
            Profile = ('Domain') 
            Direction = 'InBound'
            LocalPort = ('1433') 
            Protocol = 'TCP'
            Description = 'Firewall Rule to allow SQL communication'
            DependsOn = @("[xADDomain]Domain","[xADUser]FarmAdmin")
        }
 
        WindowsFeature ADDS
        {
            Name = "AD-Domain-Services"
            IncludeAllSubFeature = $true
            Ensure = "Present"
        }
 
        WindowsFeature ADDSTools
        {
            Name = 'RSAT-AD-Tools'
            IncludeAllSubFeature = $true
            Ensure = "Present"
        }
 
        xADDomain Domain
        {
            DomainName = $AllNodes.DomainName
            DomainAdministratorCredential = $Script:FarmDomainAdmin
            SafemodeAdministratorPassword = $Script:FarmDomainAdmin
            DependsOn = "[WindowsFeature]ADDS"
        }
 
        xADUser FarmAdmin
        {
            DomainName = $AllNodes.DomainName
            Username = $Script:FarmAdmin.UserName.Replace(($AllNodes.DomainNetBIOS + "\"),"")
            Password = $Script:FarmAdmin
            PasswordNeverExpires = $true
            DependsOn = "[xADDomain]Domain"
        }
         
        xSQLServerSetup SQLSetup
        {
            InstanceName = "MSSQLServer"
            SourcePath = $AllNodes.SQLBinaryPath
            Features = "SQLENGINE"
            InstallSharedDir = "C:\Program Files\Microsoft SQL Server"
            SQLSysAdminAccounts = $Script:FarmAdmin.UserName
            SQLSvcAccount = $Script:FarmDomainAdmin
            AgtSvcAccount = $Script:FarmDomainAdmin
            PSDscRunAsCredential = $Script:FarmDomainAdmin
            DependsOn = @("[xADDomain]Domain","[xADUser]FarmAdmin","[xFirewall]SQLFirewallRule")
        }

	File SXSFolder
	{
	    SourcePath = $AllNodes.SXSRemotePath
	    Type = "Directory"
	    DestinationPath = $AllNodes.SxsLocalPath
	    Recurse = $true;
	    Credential = $Script:FarmDomainAdmin
	    Force = $true
	    PSDSCRunAsCredential = $Script:FarmDomainAdmin
	}
 
        SPInstallPrereqs SPPrereqs
        {
            InstallerPath = $AllNodes.SharePointBinaryPath + "\prerequisiteinstaller.exe"
            OnlineMode = $false
            SQLNCli = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\sqlncli.msi"
            DOTNETFX = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\dotNetfx45_Full_setup.exe"
            NETFX = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\NDP453-KB2969351-x86-x64-AllOS-ENU.exe"
            Sync = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\Synchronization.msi"
            AppFabric = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\WindowsServerAppFabricSetup_x64.exe"
            IDFX11 = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\MicrosoftIdentityExtensions-64.msi"
            MSIPCClient = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\setup_msipc_x64.exe"
            WCFDataServices56 = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\WcfDataServices.exe"
            KB3092423 = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\AppFabric-KB3092423-x64-ENU.exe"
            MSVCRT11 = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\vcredist_x64.exe"
            MSVCRT14 = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\vc_redist.x64.exe"
            ODBC = $AllNodes.SharePointBinaryPath + "\prerequisiteinstallerfiles\msodbcsql.msi"
            DependsOn = @("[xSQLServerSetup]SQLSetup","[File]SXSFolder")
	    SXSPath = $AllNodes.SXSLocalPath
            PSDSCRunAsCredential = $Script:FarmDomainAdmin
        }
 
        SPInstall InstallSharePoint 
        { 
             Ensure = "Present" 
             BinaryDir = $AllNodes.SharePointBinaryPath 
             ProductKey = $AllNodes.ProductKey
             DependsOn = @("[SPInstallPrereqs]SPPrereqs", "[xFirewall]SQLFirewallRule")
 	     PSDSCRunasCredential = $Script:FarmDomainAdmin
        }
         
        SPInstallLanguagePack InstallLPBinaries
        {
            BinaryDir = $AllNodes.LanguagePackPath
            Ensure = "Present"
            DependsOn = "[SPInstall]InstallSharePoint"
            PsDscRunAsCredential = $Script:FarmDomainAdmin
        }
         
        SPFarm SharePointFarm
        {
            Passphrase = New-Object System.Management.Automation.PSCredential ('Passphrase', (ConvertTo-SecureString $AllNodes.Passphrase -AsPlainText -Force));
            AdminContentDatabaseName = "SP2016_CENTRAL_ADMIN";
            FarmAccount = $Script:FarmAdmin;
            FarmConfigDatabaseName = "SP2016_Config";
            CentralAdministrationPort = 7777;
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            CentralAdministrationAuth = "NTLM";
            RunCentralAdmin = $True;
	    ServerRole = "SingleServerFarm"
            Ensure = "Present";
            DatabaseServer = $AllNodes.NodeName;
            DependsOn = @("[SPInstallLanguagePack]InstallLPBinaries");
        }
        SPManagedAccount b3c4904a-3e85-4ddd-896b-1359901667e7
        {
            Account = $Script:FarmAdmin;
            AccountName = $Script:FarmAdmin.Username;
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            Ensure = "Present";
            EmailNotification = 5;
            PreExpireDays = 2;
        }
	
        SPServiceAppPool SearchServiceAppPool
        {
            Name = "SP2016-Search";
            ServiceAccount = $Script:FarmAdmin.Username;
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            Ensure = "Present";
        }  
        SPWebApplication SP2016
        {
            DatabaseName = "SP2016-Content";
            Url = "http://" + $AllNodes.NodeName + "/";
            ApplicationPool = "SP2016-AppPool";
            Path = "C:\inetpub\wwwroot\wss\VirtualDirectories\80";
            UseSSL = $False;
            AllowAnonymous = $False;
            Name = "SP2016";
            AuthenticationMethod = "NTLM";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            ApplicationPoolAccount = $Script:FarmAdmin.Username;
            Ensure = "Present";
            Port = "80";
            DatabaseServer = $AllNodes.NodeName;
            AuthenticationProvider = "Windows Authentication";
        }
        SPServiceAppPool MMS        
        {
            Name = "MMS";
            ServiceAccount = $Script:FarmAdmin.Username;
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            Ensure = "Present";
        }  
        SPContentDatabase SP2016-Content
        {
            Enabled = $True;
            MaximumSiteCount = 5000;
            Name = "SP2016-Content";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            WarningSiteCount = 2000;
            DatabaseServer = $AllNodes.NodeName;
            WebAppUrl = "http://" + $AllNodes.NodeName;
        }                    
        SPQuotaTemplate 10bac15d-d097-471a-b09c-82a63d1818bb
        {
            Name = "10GB";
            MaximumUsagePointsSolutions = 300;
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            StorageMaxInMB = 10240;
            WarningUsagePointsSolutions = 275;
            StorageWarningInMB = 8192;
        }   
        SPSite 50721b23-6892-4353-a104-814a4295ea42
        {
            OwnerAlias = $Script:FarmAdmin.Username;
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            Url = "http://" + $AllNodes.NodeName
            Language = 1033;
            QuotaTemplate = "10GB";
            CompatibilityLevel = 15;
            Template = "STS#0";
            ContentDatabase = "SP2016-Content";
            DependsOn =  @("[SPWebApplication]SP2016");
        }
        SPSite 50721b23-6892-4353-a104-814a2395ea42
        {
            OwnerAlias = $Script:FarmAdmin.Username;
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
            Url = "http://" + $AllNodes.NodeName + "/sites/searchcenter"
            Language = 1033;
            QuotaTemplate = "10GB";
            CompatibilityLevel = 15;
            Template = "SRCHCEN#0";
            ContentDatabase = "SP2016-Content";
            DependsOn =  @("[SPWebApplication]SP2016");
        }
        SPServiceInstance CentralAdministrationInstance
        {
            Name = "Central Administration";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance ManagedMetadataWebServiceInstance
        {
            Name = "Managed Metadata Web Service";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance MicrosoftSharePointFoundationIncomingE-MailInstance
        {
            Name = "Microsoft SharePoint Foundation Incoming E-Mail";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance MicrosoftSharePointFoundationWebApplicationInstance
        {
            Name = "Microsoft SharePoint Foundation Web Application";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance MicrosoftSharePointFoundationWorkflowTimerServiceInstance
        {
            Name = "Microsoft SharePoint Foundation Workflow Timer Service";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance SearchHostControllerServiceInstance
        {
            Name = "Search Host Controller Service";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance SearchQueryandSiteSettingsServiceInstance
        {
            Name = "Search Query and Site Settings Service";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance SharePointServerSearchInstance
        {
            Name = "SharePoint Server Search";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
        SPServiceInstance UserProfileServiceInstance
        {
            Name = "User Profile Service";
            Ensure = "Present";
            PsDSCRunAsCredential = $Script:FarmDomainAdmin;
        }
    }
}
 
#region LCM Config
[DSCLocalConfigurationManager()]
Configuration LCMConfig
{
    Node $env:ComputerName
    {
        Settings
        {
            ActionAfterReboot = 'ContinueConfiguration';
            RebootNodeIfNeeded = $true;
        }
    }
}
LCMConfig
Set-DscLocalConfigurationManager LCMConfig -Force -Verbose
#endregion
 
SPStandAlone -ConfigurationData .\SPStandAlone-ConfigData.psd1

The Configuration Data

If you pay close attention to the last line of the script above, you’ll notice that upon calling our DSC Configuration’s name (in our case SPStandAlone), that we are passing it a path to a .psd1 file for the -ConfigurationData parameter. This basically tells PowerShell Desired State Configuration that it needs to read the variables contained in that .psd1 file in order to properly compile itself.

That PowerShell Data File (.psd1) is where we will specify all of the variables for our environment. This allows us to keep the .ps1 script generic so that it can be used to generate 100’s of Virtual Machines having the same configuration without having to modify it for each one. We simply need to modify the .psd1 file in order for the changes to be picked up upon the MOF compilation job. In our example, we will be specifying the following content in our .psd1 file. Note how the script above is accessing each of these variables by using $AllNodes.<Variable>. This $AllNodes is a reserved keyword that allows us to access values in the ConfigurationData of any given DSC configuration script. You could also expand the .psd1 to include variables unique for a specific node, if your DSC script node ever defined more than one node (like it should be the case for most SharePoint farms).

SPStandAlone-ConfigData.psd1

@{
    AllNodes = @(    
    @{
        NodeName = $env:COMPUTERNAME;
        PSDscAllowPlainTextPassword = $true;
        PSDscAllowDomainUser = $true;

        #region Parameters
        Passphrase = "pass@word1"
        DomainName = "contoso.com"
        DomainNetBIOS = "contoso"
        ProductKey = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"  
        LanguagePackPath = "\\DSC-Share\Media\SP2016LanguagePack"
        SharePointBinaryPath = "\\DSC-Share\Media\SP2016Binaries"   
        SQLBinaryPath = "\\DSC-Share\Media\SQL2016Binaries"
        SXSLocalPath = "c:\SXS" # The content from the Network Share will be copied locally at that location; 
        SXSRemotePath = "\\DSC-Share\Media\SXS\" 
        #endregion  
    }
)}

Executing the DSC Script

This section describes the steps you need to take in order to initiate the deployment and configuration of your SharePoint 2016 Farm using the PowerShell Desired State Configuration scripts above.

  1. Copy both the SPStandAlone.ps1 and SPStandAlone-ConfigData.psd1 file onto the SharePoint server. In my case, I put hem both under C:\temp\.
    DSC Script and its associated Configuration data

    DSC Script and its associated Configuration data

  2. Open a new PowerShell console as an administrator and browse to the directory where you’ve copied the 2 files.
  3. Execute the SPStandAlone.ps1 script and provide both the SharePoint Farm Admin and Domain Admin credentials when prompted. Even though this user doesn’t yet exist, the credentials you provide here will be used to create the account in Active Directory.
    Compiling your SharePoint DSC MOF file

    Compiling your SharePoint DSC MOF file


    SharePoint DSC MOF File Generated

    SharePoint DSC MOF File Generated

  4. Your .MOF file has now been generated in a new folder named by our Configuration (SPStandAlone).
    SPStandAlone Compiled MOF File

    SPStandAlone Compiled MOF File


    All that is now left to do is to call the following PowerShell cmdlet to initiate the deployment process:

    Start-DSCConfiguration SPStandAlone -Force -Wait -Verbose
    

    The server will automatically reboot several times, and upon rebooting, you will loose the verbose PowerShell console, but don’t worry DSC is still being executed in the background. If you need to check the DSC execution logs, simply open Event Receiver and navigate to Applications and Services Logs > Microsoft > Windows > Desired State Configuration > Operational. After about an hour or so depending on the performance of your environment, you will have a fully working SharePoint 2016 Standalone machine.

How to Write your Own ReverseDSC Orchestrator

ReverseDSC is a module that allows you to extract the PowerShell Desired State Configuration out of an existing environment, in order for you to analyze it, onboard it onto DSC, or replicate it somewhere else. ReverseDSC as it stands is a technology Agnostic PowerShell Module. It only provides methods to allow you to properly convert extracted values into a DSC notation. In order to obtain these values, you need to dynamically call into the Get-TargetResource function of a given DSC Resource.

Every DSC Resource needs to include 3 core functions in order for it to be valid: Get-TargetResource, Set-TargetResource, and Test-TargetResource. For more information on the role of each of these function, you can consult the readme content on the SharePointDSC.Reverse repository. As explained in my How to use the ReverseDSC Core article, in order for you to obtain the values of a Resource instance, you need to call the Get-TargetResource for it, passing in the mandatory parameters that will allow the function to retrieve the instance (e.g. Primary Key of the instance).

An Orchestrator script, is responsible for determining these mandatory parameters and for calling the Get-TargetResource function for each instance, to obtain the complete set of key/value pairs for that instance. It then calls the ReverseDSC Core for each of these key/value pair to obtain the DSC notation, collects them all, and saves them into a resulting .ps1 file. The Orchestrator script is technology Specific, meaning that it requires the person writing the script to be familiar to some level with the technology stack it is for. As an example, when writing the Orchestrator script for SharePoint, when trying to retrieve information about all the Web Applications, you need to be able to know how to call the Get-SPWebApplication cmdlet in order to retrieve the URL (Primary key) of a Web Application instance.

ReverseDSC is all about community effort, and to help contributors get started I published a new Orchestrator Script Template to allow people to quickly get their script up and running. In the script, you will find several instances of placeholders starting with “[**“. Simply replace these with the values specified to begin with. The next thing for you to do is to start writing the set of Read- (Read-Dash) methods in the Reverse Functions section of the template. For every DSC Resource you wish to reverse, you should define a unique Read-Dash function. The template provides a very generic example on how to write that method, but you may wish to refer to existing Orchestrator scripts for more complex scenarios and see how they are done.

The last thing left for you to do once all your Read-Dash functions have been written, is to make sure that you are actually calling them from within the Orchestrator function. Try to proceed each of these calls with a Verbose output line that will help the users identify where we are at with the script’s execution. Once you script is completed, you should be able to execute it by simply executing the .ps1 file within a PowerShell session. In order to properly test your script, make sure that you don’t get any errors running it, but also try to execute the resulting output .ps1 file, which will attempt to compile the .MOF file, and make sure you don’t get errors at compilation time either.

Should you have any questions or comments regarding the Orchestrator templates or on how to get started, please use the issue section on the GitHub repository for the templates.

Add Site Title in the Search Filters

In this article I will be covering the process of adding Sites’ Title in the Search Filter of a SharePoint 2013/2016 site. A client I am currently working with has a dozen of what they call “legacy” sites. They went ahead and created a dedicated Result Source to allow people in their organization to search only for content stored in these sites. What they want, is for a new “Site Title” section to show up under this ResultSource to allow people to filter their search result based on a specific site.

Scenario

The SharePoint site has a wildcard managed path named “Legacy” and has 3 site collections created:

  • /legacy/HR
  • /legacy/Finances
  • /legacy/Communications

I have a Search Center created at /sites/Search, and a Result Source defined within it that only search content of sites located under the Legacy managed path

Process

Create a New Managed Property

By default, searching for documents inside the Legacy Result Source will only provide me with the Time Range slider and Author list as Filters (see Figure below).

In order for us to add sites’ title as a filter, we first need to ensure it has an associated Search Managed Property. To do so, navigate to your Search Service Application in Central Administration and select Search Sources from the left navigation.

SharePoint Search Schema

SharePoint Search Schema

Once in there, search for a RefinableString property that is not yet mapped to any crawled properties. In my case, I will select RefinableString00.

On the managed property edit page, scroll to the bottom of the page, to the Mappings to crawled properties section. Click on Add a mapping.

On the Crawled property selection, search for and select the ows_SiteName property.

Back on the managed property screen, click OK to save the mapping. go back to the Search Administration page and initiate a Full Crawl of you content.

Configure the Filter Panel

Now that the information about our sites’ titles is available as a managed property within Search, we can go ahead and update the refinement (filter) webpart on our Legacy search page to include it as a refiner. To do so, navigate to your search page and edit the page. Click on the Refinement web part and select Edit Web Part.

In the Refinement properties panel on the right hand side, click on Choose Refiners….

In the Available refiners section, find the Refinablestring00 property we just modified, and click on Add > to add it to the Selected refiners section on the right. Once that is done, Change its Display name to Site Title and click OK.

Click OK in the web part properties panel on the right, and save (or check-in) your page. You should now be able to use your new refiner as shown in the Figure below.

Deploy a SharePoint 2016 Standalone VM in Azure using PowerShell Desired State Configuration (DSC)

In the PowerShell Desired State Configuration (DSC) world, you really have two options when it comes down to configuring a machine. You can either use Push mode to manually “push” a DSC script into a machine’s Local Configuration Manager (LCM) memory, or use a Pull Server and connect your machine to it and let them obtain their DSC script themselves. In the Azure world, we have something called “Azure Automation DSC” which is effectively a PowerShell Desired State Configuration Pull Server in the cloud, managed as “Software-as-a-Service” (SaaS). With Azure Automation DSC, you can manage both on-premises and Azure VMs by having them connect back to your Azure Automation Account as if it was just a regular DSC Pull Server.

In this article, we will go through the process of setting up a SharePoint 2016 Standalone Virtual Machine using nothing but Azure Automation DSC. The idea is for people to easily create SharePoint Development machines in Azure Infrastructure-as-a-Service (IaaS).

The Scenario

In Azure IaaS I already have setup two VMs:

  • SPTechCon-DC is a Windows Server 2016 VM acting as a Domain Controller for the contoso.com domain.
  • SPTechCon-Share is a Windows Server 2016 VM that acts as a File Share, where I have the installation media for both SQL Server 2016 Enterprise and SharePoint Server 2016. These two shares are exposed at:
    • \\SPTechCon-Share\Share\SQL2016Media\
    • \\SPTechCon-Share\Share\SP2016Media

The Process

By following the following steps in order you will be able to deploy a new Windows Server 2016 VM in Azure IaaS, have it automatically join the contoso.com domain, and install both SQL Server 2016 and SharePoint 2016 on it. By the end of this process, you will have a fully functioning SharePoint 2016 development VM that you can simply go and install Visual Studio 2017 on to use as you main development environment for developers within your enterprise. This process is completely reusable, and can help your enterprise ensure your development team all have VMs with a configuration that matches your production environment.

1 – Create a new VM

In this article, this is the only manual process. Off course this could be automated, by for this example here, I will leave it up to you to decide how you wish to create your VM. In my case, I will be creating my VM with the following specs:

  • Name: SPTechCon-Share
  • OS Version: Windows Server 2016 Datacenter
  • Memory: 7Gb of RAM
  • CPU: 2 cores

To create the VM, start by selecting Windows Server as you Template category.

Creating Azure Windows VM

Creating Azure Windows VM

From the following screen, I select Windows Server 2016 Data Center.

Make sure you select Resource Manager as your deployment model and click Create

Azure Resource Manager

Azure Resource Manager

Fill in all the mandatory information, give your machine a meaningful name and make sure you create it as part of the same resource group where your Domain Controller and File Share servers are. Click OK.

Create an Azure Virtual Machine

Create an Azure Virtual Machine

Choose an appropriate VM size for your environment. In my case, I use a DS11_V2 Standard size. Click Select.

DS11_V2 Azure Machine

DS11_V2 Azure Machine

Leave out the default values on the Settings screen. Click OK.

Review the details for your machine. Click OK.

Summary of Azure VM

Summary of Azure VM

Wait a few minutes until you receive notification that the VM was successfully provisioned.

Azure VM being provisioned

Azure VM being provisioned


2 – Create a New Azure Automation Account

Remember we mentioned that Azure Automation is somewhat a DSC Pull Server in the Cloud. What we need to do next is create an instance of an Azure Automation Account to manage our DSC configurations. Azure Automation Accounts are available in the marketplace. Simply do a search for Azure Automation to find and select it.

Create a new Azure Automation Account

Create a new Azure Automation Account

Click on it to select it from the marketplace and then click Create.

Azure Automation Account

Azure Automation Account

Give your Azure Automation Account a name, make sure you select the same Resource Group as all the VMs we have created so far in this demo. Click Create.

Setting up Azure Automation

Setting up Azure Automation

The Azure Automation Account creation process is almost instantaneous, it should only take a few seconds to get created.

Review the Desired State Configuration Script

To configure our Standalone SharePoint box, we will be using the following PowerShell Desired State Configuration (DSC) script. I strongly encourage you quickly read through it an try to understand what is really happening under the covers. This below, is the complete script.

Configuration SharePoint2016StandAlone
{
    param(
        [String]$ParamDomain,
        [String]$ParamInternalDomainControllerIP,
        [String]$ParamMachineName,
        [String]$ParamProductKey,
        [String]$ParamUsername,
        [String]$ParamPassword,
        [String]$ParamShareName
	)

    Import-DSCResource -ModuleName xDSCDomainJoin
    Import-DSCResource -ModuleName xNetworking    
    Import-DSCResource -ModuleName SharePointDSC
    Import-DSCResource -ModuleName xSQLServer    

    $secdomainpasswd = ConvertTo-SecureString $ParamPassword -AsPlainText -Force
    $ParamCredsJoindomain = New-Object System.Management.Automation.PSCredential($ParamUsername, $secdomainpasswd)

    Node $ParamMachineName
    {
        xFireWall SQLFirewallRule
        {
            Name = "AllowSQLConnection"
            DisplayName = 'Allow SQL Connection' 
            Group = 'DSC Configuration Rules' 
            Ensure = 'Present' 
            Enabled = 'True' 
            Profile = ('Domain') 
            Direction = 'InBound' 
            LocalPort = ('1433') 
            Protocol = 'TCP' 
            Description = 'Firewall Rule to allow SQL communication' 
        }

        xDNSServerAddress DNS
	{
	    Address = $ParamInternalDomainControllerIP
	    AddressFamily = "IPv4"
	    InterfaceAlias = "Ethernet 2"
	}

        xDSCDomainJoin Join
	{
	    Domain = $ParamDomain
	    Credential = $ParamCredsJoindomain
	    DependsOn = "[xDNSServerAddress]DNS"
	}		

        xSQLServerSetup SQLSetup
        {
            SetupCredential = $ParamCredsJoindomain
            InstanceName = "MSSQLServer"
            SourcePath = "\\$ParamShareName\Share\SQL2016Media\"
            Features = "SQLENGINE,FULLTEXT,RS,AS,IS"
            InstallSharedDir = "C:\Program Files\Microsoft SQL Server"
            SQLSysAdminAccounts = $ParamCredsJoindomain.UserName
            DependsOn = "[xDSCDomainJoin]Join"
        }

        SPInstallPrereqs SP2016Prereqs
        {
            InstallerPath = "\\$ParamShareName\Share\SP2016Media\prerequisiteinstaller.exe"
            OnlineMode = $true
            DependsOn = "[xSQLServerSetup]SQLSetup"
        }

        SPInstall InstallSharePoint 
        { 
             Ensure = "Present" 
             BinaryDir = "\\$ParamShareName\Share\SP2016Media\" 
             ProductKey = $ParamProductKey
             DependsOn = @("[SPInstallPrereqs]SP2016Prereqs", "[xFirewall]SQLFirewallRule")
        } 

        SPCreateFarm CreateSPFarm 
        { 
            DatabaseServer           = $ParamMachineName
            FarmConfigDatabaseName   = "SP_Config" 
            Passphrase               = $ParamCredsJoindomain 
            FarmAccount              = $ParamCredsJoindomain 
            AdminContentDatabaseName = "SP_AdminContent" 
            PsDSCRunAsCredential     = $ParamCredsJoindomain
            ServerRole               = "SingleServerFarm"
            CentralAdministrationPort = 7777
            DependsOn                = "[SPInstall]InstallSharePoint" 
        }

        SPManagedAccount FarmAccount
        {
            AccountName = $ParamCredsJoindomain.UserName
            Account = $ParamCredsJoindomain
            PsDSCRunAsCredential     = $ParamCredsJoindomain
            DependsOn = "[SPCreateFarm]CreateSPFarm"
        }

        SPServiceAppPool SharePoint80
        {
            Name = "SharePoint - 80"
            ServiceAccount = $ParamCredsJoinDomain.UserName
            PsDSCRunAsCredential     = $ParamCredsJoindomain
            DependsOn = "[SPManagedAccount]FarmAccount"
        }

        SPWebApplication RootWebApp
        {
            Name = "RootWebApp"
            ApplicationPool = "SharePoint - 80"
            ApplicationPoolAccount = $ParamCredsJoinDomain.UserName
            Url = "http://$ParamMachineName"
            DatabaseServer = $ParamMachineName
            DatabaseName = "WebApp-SharePoint-80"
            Port = 80
            PsDSCRunAsCredential = $ParamCredsJoinDomain
            DependsOn = "[SPServiceAppPool]SharePoint80"
        }

        SPSite RootSite
        {
            Url = "http://$ParamMachineName"
            OwnerAlias = $ParamCredsJoinDomain.UserName
            Template = "STS#0"         
            PsDSCRunAsCredential = $ParamCredsJoinDomain
            DependsOn = "[SPWebApplication]RootWebApp"
        }
	}
}

Let’s take a closer look at what the script actually defines. Note that the Configuration script actually expects 7 parameters to be passed at compilation time. These parameters are:

Parameter Name Value Description
ParamDomain contoso.com Specifies the domain name that our machine will be joining.
ParamInternalDomainControllerIP 10.0.10.5 Internal IP address of our Domain Controller VM. (Note that this will likely differ for you).
ParamMachineName SPTechCon-SA Name of the Azure VM we created at Step 1 above.
ParamProductKey XXXXX-XXXXX-XXXXX-XXXXX-XXXXX Your own SharePoint 2016 (Standard or Enterprise) Product Key.
ParamUsername contoso\sp_farm Username for your SharePoint Farm Account.
ParamPassword pass@word1 Password for the SharePoint Farm Account used.
ParamShareName SPTechCon-Share Name of the File Share VM.

We will now break down each resource block an give you a quick overview of what it actually does.

The following creates a Domain Firewall rule on port 1433, to allow connections to our SQL Server (in our case hosted on the local machine) in case we wished to add more servers to our farm.

xFireWall SQLFirewallRule
{
    Name = "AllowSQLConnection"
    DisplayName = 'Allow SQL Connection' 
    Group = 'DSC Configuration Rules' 
    Ensure = 'Present' 
    Enabled = 'True' 
    Profile = ('Domain') 
    Direction = 'InBound' 
    LocalPort = ('1433') 
    Protocol = 'TCP' 
    Description = 'Firewall Rule to allow SQL communication' 
}

This block changes the DNS Server IP address to point to our domain controller.

xDNSServerAddress DNS
{
    Address = $ParamInternalDomainControllerIP
    AddressFamily = "IPv4"
    InterfaceAlias = "Ethernet 2"
}

The following joins the machine to the contoso.com domain.

xDSCDomainJoin Join
{
    Domain = $ParamDomain
    Credential = $ParamCredsJoindomain
    DependsOn = "[xDNSServerAddress]DNS"
}

This block installs SQL Server 2016 from our Shared Media Installation location.

xSQLServerSetup SQLSetup
{
    SetupCredential = $ParamCredsJoindomain
    InstanceName = "MSSQLServer"
    SourcePath = "\\$ParamShareName\Share\SQL2016Media\"
    Features = "SQLENGINE,FULLTEXT,RS,AS,IS"
    InstallSharedDir = "C:\Program Files\Microsoft SQL Server"
    SQLSysAdminAccounts = $ParamCredsJoindomain.UserName
    DependsOn = "[xDSCDomainJoin]Join"
}

This block installs the SharePoint 2016 pre-requisites. The server will automatically reboot itself once it reaches that step and will automatically resume the DSC configuration process.

SPInstallPrereqs SP2016Prereqs
{
    InstallerPath = "\\$ParamShareName\Share\SP2016Media\prerequisiteinstaller.exe"
    OnlineMode = $true
    DependsOn = "[xSQLServerSetup]SQLSetup"
}

This block installs the actual SharePoint 2016 bits on the machine.

SPInstall InstallSharePoint 
{ 
    Ensure = "Present" 
    BinaryDir = "\\$ParamShareName\Share\SP2016Media\" 
    ProductKey = $ParamProductKey
    DependsOn = @("[SPInstallPrereqs]SP2016Prereqs", "[xFirewall]SQLFirewallRule")
} 

This block creates the SharePoint Farm. Think of it as being the equivalent of running PSConfig.

SPCreateFarm CreateSPFarm 
{ 
    DatabaseServer           = $ParamMachineName
    FarmConfigDatabaseName   = "SP_Config" 
    Passphrase               = $ParamCredsJoindomain 
    FarmAccount              = $ParamCredsJoindomain 
    AdminContentDatabaseName = "SP_AdminContent" 
    PsDSCRunAsCredential     = $ParamCredsJoindomain
    ServerRole               = "SingleServerFarm"
    CentralAdministrationPort = 7777
    DependsOn = "[SPInstall]InstallSharePoint" 
}

This block creates a SharePoint Managed Account for our farm admin.

SPManagedAccount FarmAccount
{
    AccountName = $ParamCredsJoindomain.UserName
    Account = $ParamCredsJoindomain
    PsDSCRunAsCredential     = $ParamCredsJoindomain
    DependsOn = "[SPCreateFarm]CreateSPFarm"
}

This block creates a SharePoint Application Pool for our Web Application to be.

SPServiceAppPool SharePoint80
{
    Name = "SharePoint - 80"
    ServiceAccount = $ParamCredsJoinDomain.UserName
    PsDSCRunAsCredential     = $ParamCredsJoindomain
    DependsOn = "[SPManagedAccount]FarmAccount"
}

this block should be self explanatory. It creates a SharePoint Web Application on port 80.

SPWebApplication RootWebApp
{
    Name = "RootWebApp"
    ApplicationPool = "SharePoint - 80"
    ApplicationPoolAccount = $ParamCredsJoinDomain.UserName
    Url = "http://$ParamMachineName"
    DatabaseServer = $ParamMachineName
    DatabaseName = "WebApp-SharePoint-80"
    Port = 80
    PsDSCRunAsCredential = $ParamCredsJoinDomain
    DependsOn = "[SPServiceAppPool]SharePoint80"
}

This last block simply creates a Site Collection at the root of our Web Application.

SPSite RootSite
{
    Url = "http://$ParamMachineName"
    OwnerAlias = $ParamCredsJoinDomain.UserName
    Template = "STS#0"         
    PsDSCRunAsCredential = $ParamCredsJoinDomain
    DependsOn = "[SPWebApplication]RootWebApp"
}

What we need to do now, is upload this DSC configuration into our Azure Automation Account. To do this, start by navigating to your Automation Account and click on DSC Configurations.

DSC Configuration Node

DSC Configuration Node

Click on Add a configuration.

Upload DSC Configuration

Upload DSC Configuration

Click on the folder icon and browse to the .ps1 script above. Note that you will need to copy the complete script and save it locally first. Click on OK.

Our DSC Configuration Script is now in the Cloud, contained within our Azure Automation Account.

4 – Import the Required DSC Module

If you paid close attention to our full script above, you’ve realized that it needs to import 4 different DSC modules:

  • xDSCDomainJoin
  • xNetworking
  • SharePointDSC
  • xSQLServer

However, by default Azure Automation knows nothing about these modules. We need to import them first for Azure Automation to be able to properly configure our servers. Think of this as being the equivalent of putting the required modules and resources on a Pull Server for your registered nodes to consume (in an on-premises type of scenario). To import a resource, you need to go back to the main Azure Automation screen and click on Assets.

On the next screen, select Modules.

Adding Azure Automation Module

From here we have two choices: upload the required resources as individual .zip files from our local machine, or Import them from the PowerShellGallery.com repository. In my case, I choose to import them from the gallery, therefore I need to click on Browse gallery.

PowerShell Gallery Import

In the search box, type in the name of the first module we are trying to import: xDSCDomainJoin. Select the proper module from the search results by clicking on it.

Import DSC Domain Join

To finalize the import process, click on the Import icon.

Module imported

On the next screen, simply click OK.

Imported Module DSC

Repeat the same import process with the remaining missing modules: xNetworking, SharePointDSC, and xSQLServer.

5 – Initiate Compilation Job

In an on-premises scenario, you need to call the Configuration keyword of your DSC script in order for it to get “compiled” as a .MOF file. In Azure Automation, this is normally done by clicking on your DSC Configuration (uploaded at Step 3 above), and by clicking on Compile. However, in our case, we have credentials that need to be passed to our configuration. Therefore, instead of manually initiating the compilation job from the Azure Portal, we will use a local PowerShell script to remotely initiate the compilation job, allowing us to pass in parameters.

We will be using the following PowerShell script to remotely initiate that compilation job. Note that these are all the parameters we mentioned previously that are simply passed up to my Azure Automation Account.

$ProductKey = Read-Host "Please enter your SharePoint 2016 Product Key"
$MachineName = "SPTechCon-SA"
$ConfigData = @{
    AllNodes = @(
        @{
            NodeName = $MachineName
            PSDscAllowPlainTextPassword = $True
        }
    )
}

$Parameters = @{
    ParamDomain = "contoso.com"
    ParamInternalDomainControllerIP = "10.0.10.5"; 
    ParamMachineName= $MachineName
    ParamProductKey = $ProductKey 
    ParamUsername = "contoso\sp_farm"
    ParamPassword = "pass@word1"
    ParamShareName = "SPTechCon-Share"
}

Login-AzureRMAccount
Start-AzureRmAutomationDscCompilationJob -ResourceGroupName "SPTechCon" -AutomationAccountName "SPTechCon-Automation" -ConfigurationName "SharePoint2016StandAlone" -ConfigurationData $ConfigData -Parameters $Parameters

Upon executing this script, you will get prompted to enter your Azure Credentials, which you’ll need to do in order for the compilation jo to get queued up.

Provide Azure credentials

The script should only take a second or two to execute and will automatically initiate a compilation job in Azure.

Azure Automation DSC Compilation

Give Azure about5 minutes to initiate and finalize the compilation. Once the job has completed, the compilation status will get updated to Completed.

Completed Compilation of Azure DSC

6 – Register a DSC Node

If we recap what we have done so far, we started off by creating a new Azure IaaS VM that we wish to configure as a SharePoint 2016 Standalone development machine. We have then wrote the Desired State Configuration script for it, and have uploaded and compiled it into Azure. Now what we need to do is actually associate the VM we created with the DSC script we’ve uploaded. To do this, you need to go back to your Azure Automation’s account main page, and this time click on DSC Nodes.

Register an Azure DSC Node

Azure Automation gives you the option of managing both Azure and on-premises Virtual Machines. On-premises Virtual Machines will be covered in another article, when time permits. In our case we want to register an existing Azure VM. Click on Add Azure VM.

Register Azure VM with DSC

Azure Automation will then ask you for two things: The VM you wish to register, and the DSC Configuration to associate with it. Start off by clicking on Virtual Machines, and select the Virtual Machine we created from the list (in my case SPTechCon-SA). Click OK. One thing that is interesting to note here, is that because we have generalized our DSC script (meaning not valus are hardcoded in it), we can easily select multiple VMs in this step and they will each get assigned the exact same configuration.

Associate Azure VM with Automation DSC in Azure

Now that you have selected your VM, it’s time to pick the DSC Configuration we wish to deploy onto it. Click on Registration. From the Node Configuration Name, pick the Node Configuration we compiled previously. The rest of the properties listed on the page should look familiar to you. They represent the LCM settings that can normally be set via PowerShell in on-premises scenarios. Leave everything as default, with the exception of the Reboot Node if Needed checkbox that absolutely need to be checked for the installation to complete properly. Click OK.

Associate Configuration

The last thing left for us to do now is initiate the registration process by clicking on Create.

Initiate DSC Registration

Now sit back and relax, your machine is going to go and configure itself. Depending on several factors (machine size, region, etc.) the process may take up to 45 minutes to complete

How does it Work?

If you were to connect to your VM before registering it to the Azure Automation Account and run the Get-DSCLocalConfigurationManager cmdlet on it, you would see that by default the machine’s LCM is set to PUSH mode.

Get-DSCLocalConfigurationManager

Upon registering your machine against the Azure Automation Account, a DSC Extension is assigned to your VM. That extension will automatically change the configuration of the machine’s LCM to set it in PULL mode and register it against your Azure Automation’s Pull Server endpoint.

DSC Azure Pull mode