Not every developer in your team has to be Sitecore Publishing Service (SPS) expert. This is why you need simple solution to install SPS on any machine with one click of a button.

Prerequisites

  1. Windows Server Hosting (.NET Core) https://www.microsoft.com/net/download/dotnet-core/runtime-2.0.0

First of all extract Sitecore Publishing Service 2.2.0 rev. 171220.zip into convenient location. I extracted mine into my project which I keep in git so everyone who needs to install locally our custom SPS can quickly clone the repo and run the script.

Lets extract it to a folder C:\SPSProject\PublishingService and create a powershell script in C:\SPSProject\InstallSPS.ps1

If you are using Application Insights you can add your custom files like Serilog Sink for Application Insights. Serilog will give you way more control over logs. I recommend it because the solution for Application Insights provided by Sitecore is very poor.

Import-Module WebAdministration
$force = 0;
$InstKey = "";
$InstanceName = "PublishingService"
foreach ($arg in $args)
{
    if($arg -eq "--force"){
        Write-Host "Working in force mode. All published SPS files will be overwritten." -foregroundcolor "Cyan"
        $force = 1;        
    }else{
        if($arg -match '^[{(]?[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$'){
            $InstKey = $arg;
        }else{    
            if($arg -match '^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};:"\\|,.<>\/?]*$'){
                $InstanceName = $arg;
            }
        }
    }
}

First I check for optional arguments in the command line. I allow to run it with --Force (to overwrite existing files in the folder with previously installed SPS). I use Serilog Sink and allow to install SPS with Instrumentation Key, if not provided AppInsights logs will be disabled.

Last parameter is the instance name, it may come in handy if you want to install multiple SPS on your machine.

function CopySPS
{
    param([string]$from, [string]$outpath)
    Write-Host 'Copying SPS files '$from' to '$outpath -foregroundcolor "gray"
    if($force -eq 0){
        Copy-Item $from -Destination $outpath -Recurse
    }else{
        Remove-Item -path $outpath -recurse 
        Copy-Item $from -Destination $outpath -Recurse
    }    
}  
//Destination path, Change it to your heart's content 
$path = "C:\inetpub\wwwroot\$InstanceName"

$location = Get-Location 
If(!(test-path "$path\Sitecore.Framework.Publishing.Host.exe"))
{
    $publishingServicePath="$location\PublishingService"
    CopySPS $publishingServicePath $path
}
else{
    if($force -eq 0){
        Write-Host 'SPS files has already been copied. If you want to overwrite it run this script with attribute --force' -foregroundcolor "gray"
    }else{
        $publishingServicePath="$location\PublishingService"
        CopySPS $publishingServicePath $path
    }
}

This is where we copy your files to SPS folder. We check if SPS executable already exists and if exists we check if --force attribute has been provided to overwrite existing files.

function ChangeAppInsigtsConfig
{
    Write-Host "Changing application insights config file. InstrumentationKey = "$InstKey" , InstanceName = "$InstanceName". These values are default if not provided as parameters." -foregroundcolor "gray"
    
    if($InstKey -eq ""){
        Write-Host "Instrumentation Key is empty. In order to enable telemetry update sc.applicationInsights.json file manually or run script with guid in parameters as in example below." -foregroundcolor "gray"      
        Write-Host ".\InstallSPS.ps1 InstrumentationKeyGuid YourUniquePublishngServiceInstanceName" -foregroundcolor "Cyan"
    }    
    
    $appInsightsConfig = Get-Content "$path\config\global\sc.applicationInsights.json"
    $appInsightsConfig = $appInsightsConfig | ForEach-Object { $_ -replace '\${InstrumentationKey}', $InstKey }
    Set-Content -Path "$path\config\global\sc.applicationInsights.json" -Value $appInsightsConfig
}
ChangeAppInsigtsConfig

If you don’t use Serilog you can remove that part. It checks for sc.applicationInsights.json and replaces values in ${InstanceName} and ${InstrumentationKey}.

cd $path
Write-Host 'Start updating connection strings' -foregroundcolor "gray"

./Sitecore.Framework.Publishing.Host configuration setconnectionstring core "Server=ServerName;Database=Db_Name_Sitecore_Core;User Id=sitecoreLogin;Password=sitecorePassword;MultipleActiveResultSets=True;"

./Sitecore.Framework.Publishing.Host configuration setconnectionstring master "Server=ServerName;Database=Db_Name_Sitecore_Master;User Id=sitecoreLogin;Password=sitecorePassword;MultipleActiveResultSets=True;"

./Sitecore.Framework.Publishing.Host configuration setconnectionstring web "Server=ServerName;Database=Db_Name_Sitecore_Web;User Id=sitecoreLogin;Password=sitecorePassword;MultipleActiveResultSets=True;"

Write-Host 'Connection strings has been updated' -foregroundcolor "gray"
Write-Host 'Rename default service name to $InstanceName' -foregroundcolor "gray"
./Sitecore.Framework.Publishing.Host configuration set sitecore:publishing:instancename -v $InstanceName

These steps will create \config\global\sc.connectionstrings.json and \config\global\sc.custom.json with info required to connect to database and update its schema

Write-Host 'Update database schema' -foregroundcolor "gray"
./Sitecore.Framework.Publishing.Host schema upgrade --force

This is the big moment. From now your database is SPS ready.

And the last step is to create a website and run status check.

Write-Host 'Create PublishingService IIS website' -foregroundcolor "gray"
./Sitecore.Framework.Publishing.Host iis install --hosts --force --sitename $InstanceName
$request = "http://$InstanceName/api/publishing/operations/status/"
Invoke-WebRequest -UseBasicParsing $request | ConvertFrom-Json | Select Status | foreach-object{ if($_.Status -eq 0){Write-Host "Installation successful. http://$InstanceName/api/publishing/operations/status/ Returned 0" -foregroundcolor "green"}else{Write-Host "Installation error" -foregroundcolor "red"}} 

cd $location

On this point SPS is working, but we wanted to serve SPS over https so that last step will create https binding. You can skip it if you don’t need it.

Write-Host 'Adding https binding' -foregroundcolor "gray"
New-WebBinding -Name $InstanceName -IPAddress "127.0.0.1" -Port 443 -Protocol https -HostHeader $InstanceName
if(!(Test-Path -path IIS:\SslBindings\127.0.0.1!443)){
    Write-Host 'Binding SSL to 127.0.0.1:443' -foregroundcolor "gray"
    $cert = Get-ChildItem -Path Cert:\LocalMachine\My | where-Object {$_.subject -like "*.local*"} | Select-Object -ExpandProperty Thumbprint
    get-item -Path "cert:\localmachine\my\$cert" | new-item -path IIS:\SslBindings\127.0.0.1!443 
}

Run the script as admin.

If service doesn't start that could be caused by web.config which has some unnecessary garbage in it, clean it like shown below:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule"/> 
    </modules>
    <handlers>
      <remove name="WebDAV" />
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath=".\Sitecore.Framework.Publishing.Host.exe" arguments="--environment development" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
  </system.webServer>
</configuration>