Repository navigation
Configuration
Important
The kit's first-class deliverable is the SharePoint farm configuration in
scripts/sps/. The PDC, PULL and SQL .psd1 files documented here are
reference samples only — in production you should describe those tiers
in your own tooling and only keep Secrets.psd1 + CfgAppSps.psd1 from
this kit.
SPSConfigKit is driven by two PowerShell data files:
-
scripts/Secrets.psd1— centralised credential material. -
scripts/sps/CfgAppSps.psd1— the SharePoint farmConfigurationDataconsumed byCfgAppSps.ps1.
The configuration scripts (CfgAppSps.ps1, plus the example PDC / PULL / SQL
scripts) import both files and compile one MOF per node defined in
AllNodes.
scripts/
├── Secrets.sample.psd1 ← tracked credentials template (placeholders)
├── Secrets.psd1 ← your real credentials (git-ignored; copy of the sample)
├── init/
│ ├── Initialize-DscNode.psd1 ← prerequisite manifest (modules, certs)
│ ├── Initialize-DscNode.ps1
│ ├── Initialize-DscEncryption.ps1
│ ├── Initialize-SoftwarePackages.psd1 ← package manifest (binaries, LPs, CUs)
│ └── Initialize-SoftwarePackages.ps1
├── sps/
│ ├── CfgAppSps.psd1 ← SharePoint farm ConfigurationData
│ └── CfgAppSps.ps1
├── pdc/ ← REFERENCE EXAMPLE — not for production
├── pull/ ← REFERENCE EXAMPLE — not for production
└── sql/ ← REFERENCE EXAMPLE — not for production
A single serviceAccounts array. Each entry has a stable Name (used as
the variable name when the loader materialises a PSCredential) and an
optional IsAdAccount flag:
| Key | Required | Description |
|---|---|---|
Name |
yes | Stable identifier, used as the PSCredential variable name in the configuration scripts. |
DisplayName |
yes | Human-readable label, surfaced in logs. |
Description |
yes | Free-form description. |
Username |
yes |
DOMAIN\sam for AD accounts; any non-empty value for non-AD containers (the loader requires it). |
Password |
yes | Plain-text password. Replace with your own secret-management hook before checking the file in. |
IsAdAccount |
no | Default $True. Set to $False for containers that aren't real AD accounts (PFX passwords, DSRM, passphrase). |
Example:
@{
serviceAccounts = @(
@{ # AD account — IsAdAccount omitted, defaults to $True
Name = 'SETUP'
DisplayName = 'SharePoint SETUP'
Description = 'SharePoint Setup Account'
Username = 'CONTOSO\svcspssetup'
Password = '************'
}
@{ # Non-AD container — used as a PFX password
Name = 'SharePointCert'
DisplayName = 'SharePoint cert PFX'
Description = 'PFX password for the SharePoint web-app certificate'
Username = 'SharePointCert'
IsAdAccount = $False
Password = '************'
}
)
}Caution
Never commit real passwords to source control. Only the tracked
Secrets.sample.psd1 template ships (with '******************'
placeholders); the real Secrets.psd1 you fill in is git-ignored, so your
credentials never reach the repository. Copy the sample to Secrets.psd1 and
replace the placeholders with a wrapper that pulls from your secret store
(Azure Key Vault, CyberArk, etc.) before the file is read in any environment.
Important
The bundled Secrets.psd1 is an example that lists EVERY account for EVERY
brick of the kit — including bricks you do not deploy. Declaring an account
here does not mean it is used: for the SharePoint brick, only the accounts
listed in the SharePoint.ManagedAccounts allowlist are
actually registered on the farm. Accounts that belong to a brick you are not
deploying (for example the PULL server or the lab AD/PDC) can simply be left
untouched or removed.
The table below maps every account in the sample Secrets.psd1 to the brick
that consumes it, so you can tell at a glance whether a given account is
relevant to your deployment.
| Account | Brick (config) | Optional? |
|---|---|---|
ADSETUP, ADSAFEMODE
|
AD / PDC (CfgAppPdc) |
Yes — lab / demo domain only |
SQLSERVER, SQLServerCert
|
SQL (CfgAppSql) |
No (standard deployment) |
PULLSETUP, IISPULLAPP, DscPullCert
|
PULL server (CfgAppPull) |
Yes — only when a node declares IsPullServer
|
SETUP, FARM, IISAPP, SEARCH, CONTENT, SUPERUSER, SUPEREADER, Passphrase, SharePointCert, OfficeOnlineCert
|
SharePoint (CfgAppSps) |
No — core farm accounts |
ADSYNC |
User Profile AD Import (CfgAppSps; lab grant in CfgAppPdc) |
Yes — only when User Profile AD Import is used |
SWM |
SharePoint Workflow Manager (SWM server) | Yes — only when an SWM server is deployed |
- The AD / PDC brick provisions an Active Directory domain controller. It is meant for labs and demos; in production the domain already exists, so these accounts are not used.
- The PULL server brick is only compiled when at least one node is flagged
IsPullServer. If you deploy in push mode,PULLSETUP,IISPULLAPPandDscPullCertare never consumed. - The SharePoint brick is the core of the kit.
SETUPis the SharePoint installation account; the remaining accounts are the farm / service / app-pool identities actually used to build the farm.
Note
Even within the SharePoint brick, the accounts registered as
SPManagedAccount resources are filtered by the
SharePoint.ManagedAccounts allowlist (default
@('FARM', 'IISAPP', 'SEARCH')). Anything outside that allowlist — including
the PULL / SQL / OOS accounts above — is intentionally ignored and never
reaches the SharePoint MOF.
The User Profile Service Application runs in AD Import mode (NoILMUsed = $true).
AD Import reads user objects directly from Active Directory using a dedicated sync
account (ADSYNC, e.g. CONTOSO\svcspsync). That account must hold the Replicate
Directory Changes permission on the domain it synchronises with.
-
Production: the account and its Replicate Directory Changes grant are provided by
your AD team. Add the account to
Secrets.psd1so the farm can use its credential — the kit changes nothing in your directory. -
Lab / demo (
CfgAppPdc): when anADSYNCentry is present,CfgAppPdccreates the account and grants it Replicate Directory Changes (theDS-Replication-Get-Changesextended right) on the domain root via theADObjectPermissionEntryresource. This runs only on the lab domain controller (inside theIsADSServerblock) and is skipped entirely when noADSYNCentry exists.
The import itself is driven by an optional synchronization connection declared under
NonNodeData.SharePoint.Services.UserProfile.SyncConnection in CfgAppSps.psd1:
SyncConnection = @{
Forest = 'contoso.com' # AD forest to import from
SyncAccount = 'ADSYNC' # Secrets account -> import credential
IncludedOUs = @('OU=INT,DC=contoso,DC=com') # OUs holding the users to import
}CfgAppSps turns this into a native SPUserProfileSyncConnection (an Active Directory
Import connection) attached to the User Profile Service. SyncAccount names the Secrets.psd1
account whose credential is used to read the directory (ADSYNC above). Remove the whole
SyncConnection block to skip the connection — nothing is provisioned then. No
SPUserProfileSyncService is used: that belongs to the legacy FIM/MIM synchronization, whereas
this kit runs User Profiles in AD Import mode (NoILMUsed = $true).
Note
The kit targets SharePoint Subscription Edition. On SPSE the SPUserProfileSyncConnection
resource forces the connection name to the forest with dots replaced by dashes (e.g.
contoso-com) and does not reconcile an excluded-OUs list, so the kit does not expose a Name
or ExcludedOUs setting — the connection is named from the forest automatically, and the import
scope is defined by IncludedOUs.
Once users are imported (AD Import above), you can provision the My Site infrastructure from
an optional NonNodeData.SharePoint.Services.MySite block in CfgAppSps.psd1:
MySite = @{
WebAppUrl = 'https://sharepoint.contoso.com' # web app hosting the My Site host
QuotaTemplateName = 'MySite' # quota template to create/apply
DatabaseNamePrefix = 'DSPS_CONTENT_MySite_' # personal-site content DB prefix
UserCount = 500 # expected number of personal sites
QuotaMaxMB = 2048 # per-site storage limit
QuotaWarningMB = 1843 # per-site warning threshold
MaxDBSizeGB = 100 # cap per content database
# Optional overrides (skip the auto-calculation):
# NumberOfDatabases = 10
# MaximumSiteCount = 50
# WarningSiteCount = 45
}CfgAppSps turns this into an SPQuotaTemplate plus a set of dedicated personal-site
SPContentDatabase resources. Their number is sized from the expected user base so no database
exceeds the Microsoft-supported content-database size:
MaxDBSizeMB = MaxDBSizeGB * 1024
MaximumSiteCount = floor(MaxDBSizeMB / QuotaMaxMB) # sites per DB (capped at 10000)
WarningSiteCount = floor(MaximumSiteCount * 0.9)
NumberOfDatabases = max( ceil(UserCount * QuotaMaxMB / MaxDBSizeMB), # storage-based
ceil(UserCount / MaximumSiteCount) ) # site-capacity-based
Example: 500 users × 2048 MB ÷ (100 × 1024) MB = 10 databases, each holding up to 50 personal
sites. The site-capacity term matters once MaximumSiteCount is floored or capped (e.g. very large
quotas, or the 10 000-site cap), so the kit takes the larger of the two counts. SharePoint
load-balances new personal sites across these databases automatically. Set NumberOfDatabases,
MaximumSiteCount or WarningSiteCount explicitly to override the calculation. Remove the whole
MySite block to skip My Site provisioning — nothing is emitted then. These dedicated
personal-site databases (DSPS_CONTENT_MySite_01, _02, …) are separate from and additional to
the My Site host content database.
The companion scripts/sps/Invoke-MySiteProvisioning.ps1 script pre-creates the personal
sites after the service is configured: it enumerates the User Profile Service profiles, skips the
service accounts listed in Secrets.psd1, calls CreatePersonalSite() for each remaining user,
and applies the quota template. Run it from an elevated SharePoint Management Shell (which
exposes the SharePoint cmdlets — on Subscription Edition they are not loadable via Import-Module
or Add-PSSnapin), as the SharePoint farm account (the "System Account"). The farm account is
required because creating personal sites on behalf of other users goes through self-service site
creation, which only the farm account may perform — any other account (even a User Profile Service
Application administrator) is denied in SelfServiceCreateSite. The script verifies this and stops
if run as any other account or from a non-elevated shell. Use -WhatIf first to preview the sites
that would be created.
Tip
In the User Profile Service Application, under Setup My Sites → My Site Cleanup, set a Secondary Owner (e.g. the SharePoint FARM account). When a user's profile is deleted, their My Site is retained for 30 days and access is granted to the manager or, failing that, this secondary owner, so content can be recovered before the site is removed. This is optional and independent of provisioning, but recommended for governance.
Note
Microsoft-supported limits used by the sizing model: 200 GB per content database and up to 10 000
personal sites per database. The default MaxDBSizeGB = 100 stays comfortably under the 200 GB
limit; the kit warns if you set it higher and caps MaximumSiteCount at 10 000.
Note
Personal sites are created under the personal wildcard managed path (e.g.
https://sharepoint.contoso.com/personal/<user>). You do not need to declare this path in the
web application's ManagedPath list: SharePoint provisions it automatically when the User Profile
Service Application is created with a My Site host location (SPUserProfileServiceApp +
MySiteHostLocation, which the kit already configures). You can confirm it exists with
Get-SPManagedPath -WebApplication <url> (look for personal / WildcardInclusion).
-
IsAdAccount -ne $falseis the filter that selects AD accounts. Because$null -ne $falseis$true, omitting the key includes the entry, which is the desired default for AD accounts. - Containers (
ADSETUP,ADSAFEMODE,DscPullCert,SharePointCert,OfficeOnlineCert,SQLServerCert, ...) must setIsAdAccount = $Falseto be excluded from$spManagedAccountsand similar AD-only collections.
Certificate Name values in CfgAppSps.psd1 are intentionally chosen to
match Secrets entries 1:1 (with a Cert suffix to make the intent
obvious). This lets the configuration scripts resolve each cert's PFX
password directly:
CertificatePassword = (Get-Variable -Name $spCertificate.Name -ValueOnly)A missing or misnamed Secrets entry fails fast at compile time.
The file is a single hashtable with two top-level keys: AllNodes and
NonNodeData.
Per-node settings. The first entry must be the wildcard '*' node that
declares the DSC plain-text/domain-user policy:
AllNodes = @(
@{
NodeName = '*'
PSDscAllowPlainTextPassword = $True
PSDscAllowDomainUser = $True
}
@{
NodeName = 'APP1'
IsMaster = $true
IsSPSServer = $True
SPVersion = 'SE'
SPServerRole = 'Application' # ApplicationWithSearch | DistributedCache | Search | …
LocalAdmins = @('CONTOSO\svcspssetup', 'CONTOSO\svcspsfarm')
}
# WFE1, SCH1, OOS1 …
)Node-selector flags:
| Flag | Used by |
|---|---|
IsSPSServer |
All SharePoint Node blocks. |
IsMaster |
Designates the farm-creator (vs. join-farm) node. |
IsOOSServer, IsOOSSingle
|
Office Online Server Node block. |
SPServerRole |
SPFarm.ServerRole (MinRole). |
IsSrcAdmin, IsSrcCrawl, IsCntProc, IsSrcAnalyt, IsSrcQuery, IsIndexPart
|
Search topology component placement. |
LocalAdmins |
Group BUILTIN\Administrators membership. |
Note
The non-master node selector uses -not $_.IsMaster, so you can omit
IsMaster on non-master nodes — missing key and explicit $false
both work.
The Search Service Application topology places its six components (Admin,
Crawler, ContentProcessing, AnalyticsProcessing, QueryProcessing,
IndexPartition) on the nodes whose Is* flag is set and whose
SPServerRole is either Search (a dedicated search node) or
ApplicationWithSearch (a combined Application + Search node). Set the
relevant Is* flags to $true on whichever node hosts each component.
On a small 2-server farm you can drop the dedicated Search node and
host every search component on the Application master by giving it the
ApplicationWithSearch MinRole plus the six flags:
@{
NodeName = 'APP1'
IsMaster = $true
IsSPSServer = $True
SPVersion = 'SE'
SPServerRole = 'ApplicationWithSearch'
IsSrcAdmin = $True
IsSrcCrawl = $True
IsCntProc = $True
IsSrcAnalyt = $True
IsSrcQuery = $True
IsIndexPart = $True
LocalAdmins = @('CONTOSO\svcspssetup', 'CONTOSO\svcspsfarm', 'CONTOSO\svcspsearch')
}
@{
NodeName = 'WFE1'
IsAFCache = $true
IsSPSServer = $True
SPVersion = 'SE'
SPServerRole = 'WebFrontEndWithDistributedCache'
LocalAdmins = @('CONTOSO\svcspssetup', 'CONTOSO\svcspsfarm')
}Note
A commented 2-server example ships alongside the default 3-node search
layout in scripts/sps/CfgAppSps.psd1.
Flat container of farm-wide settings:
NonNodeData = @{
# The share host is defined ONCE here. Host it on a MEMBER server (e.g. the
# pull server), never on a domain controller (a node already holds a machine
# session to the DC, and Windows refuses a second identity to the same server,
# so a share on the DC fails at apply with "Access is denied").
SourcePath = '\\PULL\Softwarepackages' # binaries + .cer/.pfx share
DomainName = 'contoso.com'
# Set to $false when the customer manages storage themselves. Default $true.
ManageDisks = $true
# Data disks, initialised by scripts/init/Initialize-DscDisks.ps1 (keyed by
# disk Number). This is the single source of truth for storage: the Drives.{Data,Logs,Temp}
# hashtable consumed everywhere else is DERIVED from it by Type (see below).
# Disk numbers are environment-specific — a plain VM is usually 0=OS, 1=Data,
# 2=Logs; an Azure VM WITH a temp disk shifts to 0=OS, 1=<temp>, 2=Data,
# 3=Logs. Adjust Id to match 'Get-Disk' on the node.
Disks = @(
@{ Id = '0'; Letter = 'C'; Type = 'OS' ; FSLabel = 'SYSTEM'; AllocationUnitSize = 4KB }
@{ Id = '2'; Letter = 'F'; Type = 'Data'; FSLabel = 'DATA' ; AllocationUnitSize = 4KB }
@{ Id = '3'; Letter = 'G'; Type = 'Logs'; FSLabel = 'LOGS' ; AllocationUnitSize = 4KB }
)
ADC = @{
# Each entry carries only the .cer / .pfx FILE NAME; the full path is
# derived by the Cfg*.ps1 script as SourcePath + CerFileName / PfxFileName.
# (An explicit CertPath / PfxPath is still honoured for backward compat.)
certificates = @(
@{ Name = 'SharePointCert' ; FriendlyName = 'SharePoint' ; CerFileName = 'SharePoint.cer' ; PfxFileName = 'SharePoint.pfx' }
@{ Name = 'OfficeOnlineCert' ; FriendlyName = 'OOSCertSSL' ; CerFileName = 'OfficeOnline.cer'; PfxFileName = 'OfficeOnline.pfx' }
@{ Name = 'SQLServerCert' ; FriendlyName = 'SQLCertSSL' ; CerFileName = 'SQLServer.cer' ; PfxFileName = 'SQLServer.pfx' }
@{ Name = 'DscPullCert' ; FriendlyName = 'DSCPull' ; CerFileName = 'DscPull.cer' ; PfxFileName = 'DscPull.pfx' }
)
}
SQLAlias = @(
@{ Name = 'ADMIN' ; ServerAlias = 'SINGLE-ADM-SPSSQL' ; ServerName = 'SQL1' ; InstanceName = 'MSSQLSERVER' ; Port = 1433 }
@{ Name = 'SEARCH' ; … }
@{ Name = 'SERVICES'; … }
@{ Name = 'CONTENT' ; … }
)
IIS = @{ LogFolder = 'LOGS\IIS' ; httpErrFolder = 'LOGS\HTTPERR' }
OOS = @{ AllServers = @('OOS') ; URL = 'oosweb.contoso.com' ; … }
SharePoint = @{
ProductKey = '…'
FarmConfigDatabaseName = 'DSPS_Admin_Config'
CentralAdministrationPort = '443'
CentralAdministrationUrl = 'https://sharepoint-admin.contoso.com'
ServiceAppPoolName = 'SharePointServiceApplications'
WebApplications = @(
@{
Name = 'SharePoint'
Url = 'https://sharepoint.contoso.com'
HostHeader = 'sharepoint.contoso.com'
Port = 443
CertName = 'SharePointCert' # ← indirection into ADC.certificates / Secrets
ContentDBName = 'DSPS_CONTENT_MySite'
ApplicationPool = 'PAMYSITE'
Path = 'C:\InetPub\…\SharePoint'
ManagedPath = @( @{ Name = 'Search' ; Explicit = $true ; RelativeUrl = 'search' } )
Sites = @(
@{ Name = 'MySiteHost' ; Url = 'https://sharepoint.contoso.com' ; Template = 'SPSMSITEHOST#0' ; Language = 1033 ; ContentDatabase = 'DSPS_CONTENT_MySite' }
@{ Name = 'SearchCenter' ; Url = 'https://sharepoint.contoso.com/search' ; Template = 'SRCHCEN#0' ; Language = 1033 ; ContentDatabase = 'DSPS_CONTENT_SearchCenter' }
@{ Name = 'AppsCatalog' ; Url = 'https://sharepoint.contoso.com/sites/apps' ; Template = 'APPCATALOG#0' ; Language = 1033 ; ContentDatabase = 'DSPS_CONTENT_AppsCatalog' }
)
}
)
Services = @{
SearchService = @{ Name = 'SVCSearchService' ; DatabaseName = 'DSPS_SCH_SearchService' ; SearchCenterUrl = 'https://sharepoint.contoso.com/search' ; ContentSources = @{ … } }
UserProfile = @{ … }
ManagedMetadataService = @{ … }
AppManagementService = @{ … }
SubscriptionSettingsService = @{ … }
StateService = @{ … }
SessionState = @{ … }
UsageService = @{ … }
}
}
}The full reference file lives at
scripts/sps/CfgAppSps.psd1
and is the canonical schema — treat the snippet above as a map.
-
Cert names end in
Certand match Secrets entries 1:1. -
WebApplications carry a
CertNamefield so a friendlyNamecan be used in MOF identifiers while the cert lookup still resolves through Secrets. -
SourcePathis the SMB share that hosts every binary, language pack, CU,.cer, and.pfxreferenced by the kit. Configure share-level read for the setup account on every node. -
Disksis the storage source of truth — an ordered array of physical data disks the node should own. Each entry declares:Key Required Description Idyes The disk Number as reported by Get-Disk. Portable across bare-metal, VMware, Hyper-V and Azure — not an Azure LUN. Numbers are environment-specific: a plain VM is usually0=OS, 1=Data, 2=Logs, but an Azure VM with a temporary disk shifts to0=OS, 1=<temp>, 2=Data, 3=Logs. Adjust to match the target node.Letteryes Drive letter to assign ( C,F,G, …). Also the key used to deriveDrives.Typeyes Semantic role: OS,Data,Logs, orTemp. Drives the derivation and the OS-exclusion filter.FSLabelyes NTFS volume label (UPPERCASE convention: SYSTEM/DATA/LOGS/TEMP).AllocationUnitSizeyes Cluster size (e.g. 4KB; SQL data/log volumes use64KB).Declaring at least three disks (
SYSTEM/DATA/LOGS) is the recommended baseline for every tier. -
Drivesis DERIVED, never hand-written — eachCfg*.ps1buildsNonNodeData.Drives = @{ Data; Logs; Temp }fromDisks(keyed byType) right after loading the psd1, so the ~30 existingDrives.Data/Drives.Logsconsumers keep working unchanged.Tempfalls back to theDataletter when no dedicatedTempdisk is declared. A drive letter is therefore declared once (inDisks) instead of duplicated.Drives.Data/Drives.Logsprefix every per-node path so each farm role can point at different physical drives without editing the script. -
ManageDisks(bool, default$true) gates disk initialisation by thescripts/init/Initialize-DscDisks.ps1bootstrap script. Disk preparation is a one-time node-prep step (like joining the domain or installing the DSC modules), so it runs during bootstrap — not inside the recurring application MOF — and reads this sameDisksblock via-ConfigPath. When$true, the script onlines each raw non-OSdisk, applies a GPT partition, formats NTFS with the requestedFSLabel/AllocationUnitSizeand assigns the drive letter, so a brand-new farm has its volumes before anything writes to them. TheOSdisk is never touched. SetManageDisks = $falsewhen the customer has already initialised and formatted their volumes: the script exits without touching any disk, butDrivesis still derived by the configuration so every path resolves. The script is idempotent and non-destructive — a disk that already carries data is reported and left intact, never reformatted. -
Per-product path overrides (optional) — if your customer's file hierarchy doesn't match the kit's default (
<SourcePath>\SPS→<Drives.Data>\SoftwarePackages\SPS\{BIN,LP,CU}and the same shape for OOS), override any subset of the following keys inNonNodeData.SharePointand / orNonNodeData.OOS— defaults preserve the original layout:Key Purpose Default SourcePathNetwork share holding the product's setup files. <NonNodeData.SourcePath>\<SPS|OOS>DestinationPathLocal folder the setup files are copied to. <Drives.Data>\SoftwarePackages\<SPS|OOS>Subfolders.BinariesSub-folder of DestinationPathholding the main installer (setup.exe/ prereq).BINSubfolders.LanguagePackSub-folder of DestinationPathholding per-locale Language Pack installers.LPSubfolders.CumulativeUpdateSub-folder of DestinationPathholding the CU package(s).CUExisting absolute values for
SharePoint.UberCumulativeUpdateandOOS.CUFileNamekeep working unchanged: any rooted path is used as-is, otherwise the value is resolved under<DestinationPath>\<Subfolders.CumulativeUpdate>. -
SharePoint.LanguagePacks(optional) — array of SharePoint locale codes (e.g.@('fr-fr','es-es')). Each entry must match a sub-folder under<DestinationPath>\<Subfolders.LanguagePack>\that contains the correspondingsetup.exe. Omit or leave as@()to skip Language Pack installation entirely. -
SharePoint.CentralAdministrationUrl(optional) — a vanity URL for Central Administration. When set to an HTTPS URL (e.g.https://sharepoint-admin.contoso.com), Central Admin is served over SSL on that host instead ofhttp://<node>:<port>. This requires: a certificate namedSharePointAdminCertinADC.certificateswhose SAN covers the host (issued byCfgAppPdc), a matchingSecrets.psd1entry (PFX password), and a DNS record for the host pointing at the Central Admin node (published byCfgAppPdc). SetCentralAdministrationPortto match the scheme (443for HTTPS). Leave the URL empty/absent to keep Central Admin on plain HTTP at the port.On SharePoint Subscription Edition the certificate is bound through SharePoint Certificate Management (never a manual IIS binding), in three coordinated steps:
-
SPFarmprovisions the Central Admin HTTPS binding and host header (it does not assign a certificate, so Central Admin is not yet reachable over HTTPS). - The
SPCertificateloop importsSharePointAdminCertinto the SharePoint certificate store (EndEntity) after the farm exists. - A
Scriptresource binds that managed certificate to the Central Admin Default zone (Get-SPCertificate -Store EndEntity→Set-SPWebApplication -Certificate -UseServerNameIndication), which mirrors howSPWebApplicationExtensionbinds certificates on SPSE and is what makes Central Admin serve HTTPS.
[!NOTE] The SharePoint SE certificate binding for Central Administration should be validated on a real farm. Verify with
Get-SPWebApplication -IncludeCentralAdministration | Format-List DisplayName, Urland confirm the IISSharePoint Central Administration v4site has anhttpsbinding on the expected port with theSharePointAdmincertificate and SNI enabled. -
-
SharePoint.ManagedAccounts(optional) — allowlist ofSecrets.psd1account names that SharePoint should register asSPManagedAccountresources on the farm master. Defaults to@('FARM', 'IISAPP', 'SEARCH'). Extend the list when you introduce a new dedicated SharePoint service account; leave it untouched to keep the historical set. Anything not in the allowlist (PULLSETUP,IISPULLAPP, SQL / OOS / monitoring accounts …) is intentionally ignored and stays out of the SPS MOF. -
SharePoint.EdgeAuthAllowlist(optional, off by default) — when present withEnabled = $true, the kit writes the Microsoft Edge Integrated Windows Auth policyHKLM\SOFTWARE\Policies\Microsoft\Edge\AuthServerAllowliston every SharePoint node. This is a browser policy, normally owned centrally by GPO / Intune — omit the block to let your domain policy stay authoritative (the resource is simply not emitted). OptionalHostsis a list of allowlist patterns; when omitted it defaults to*<DomainName>*(every host in the domain). The value is written withForce, so it overwrites an existing (e.g. GPO-set) value rather than failing.EdgeAuthAllowlist = @{ Enabled = $true Hosts = @('*.contoso.com') # optional; defaults to *<DomainName>* }
[!TIP] The recommended path is to let the domain manage this policy centrally.
CfgAppPdcships anAuthServerAllowlistentry inNonNodeData.EdgePoliciesthat theEdge_browserGPO pushes to every domain-joined server (including the SharePoint nodes). When that GPO is in place, leaveSharePoint.EdgeAuthAllowlistdisabled and let the GPO own the policy. -
SQL Server path overrides (optional) —
scripts/sql/CfgAppSql.psd1exposes the sameSourcePath/DestinationPathkeys underNonNodeData.SQL. Defaults are<NonNodeData.SourcePath>\SQLand<Drives.Data>\SoftwarePackages\SQL. SQL doesn't useSubfolders—setup.exeis expected at the root ofDestinationPath.
scripts/pdc/CfgAppPdc.psd1, scripts/pull/CfgAppPull.psd1, and
scripts/sql/CfgAppSql.psd1 follow the same AllNodes + NonNodeData
pattern and are meant to be read alongside their .ps1 counterparts.
Use them to see how the loader, the certificate import pattern, and the manifest-driven approach work end-to-end — then replace them with your organisation's production AD, pull-server, and SQL provisioning.
The manifest read by Initialize-SoftwarePackages.ps1. It describes the
local file-share repository that backs \\PULL\SoftwarePackages (or
whichever SMB share every node downloads binaries from), and the list of
packages the script should fetch into it.
@{
# Root folder of the local SoftwarePackages repository. Every entry
# below uses a Path RELATIVE to this root.
Repository = 'F:\SoftwarePackages'
SoftwarePackages = @(
@{
Name = 'SharePointServerSE'
Description = 'SharePoint Server Subscription Edition'
FileName = 'OfficeServer.iso'
Url = 'https://download.microsoft.com/.../OfficeServer.iso'
Extract = $true # mount and copy ISO contents
Path = 'SPS\BIN' # relative to Repository
}
@{
Name = 'KB5002773'
Description = 'SharePoint Server SE Cumulative Update'
FileName = 'uber-subscription-kb5002773-fullfile-x64-glb.exe'
Url = 'https://download.microsoft.com/.../uber-subscription-kb5002773-fullfile-x64-glb.exe'
Extract = $false # direct download to Path
Path = 'SPS\CU'
}
# …SQL Server 2022 + CU, Language Packs, .NET 4.8, VC++ 2015-2019,
# Office Online Server CU…
)
}| Key | Required | Description |
|---|---|---|
Repository |
yes | Local root folder backing the SMB share. Every package Path is resolved against it. |
Name |
yes | Identifier used in log output. |
Description |
yes | Human-readable label, surfaced in the per-package banner. |
FileName |
yes | Source file name; for Extract = $true packages, this is the temp-file name in %TEMP% before mounting. |
Url |
yes | Download URL (HTTPS). |
Extract |
yes |
$true to mount the ISO with Mount-DiskImage and copy its contents into Path; $false to download the file directly to Path. |
Path |
yes | Folder under Repository where the file or extracted contents land (e.g. SPS\BIN, SQL\CU, SPS\LP\FR-fr). |
Marker |
no | Sentinel file used to short-circuit re-extraction. Defaults to setup.exe, which is correct for the SQL Server, SharePoint, and LP ISOs. |
- For
Extract = $trueentries, the script skips the package when<Path>\<Marker>already exists. - For
Extract = $falseentries, the script skips the package when<Path>\<FileName>already exists. - Downloaded ISOs cached in
%TEMP%are reused when a previous run was interrupted between download and extraction.
ISO expansion uses Windows' built-in Mount-DiskImage /
Copy-Item -Recurse / Dismount-DiskImage pipeline, so the file-share
host has zero external dependencies beyond Windows itself. Required
capability: Windows 8 / Server 2012 or newer (for Mount-DiskImage).
Note
The script also detects outbound internet access once at startup and skips the download phase entirely on offline hosts. Per-package failures are caught and logged so one bad URL does not abort the whole run.
Before compiling any MOF, run the bundled Pester v5 pre-flight suite
against your .psd1 files. It catches the mistakes that would otherwise
surface mid-run on the customer site — placeholder product key,
duplicate NodeName, missing Secrets.psd1 entry for a managed account,
mismatched CertName ↔ ADC.certificates, malformed Language Pack
locale, unreachable \\share\setup.exe, missing .pfx …
# Full validation (workstation must have the install share mounted).
.\scripts\test\Invoke-ConfigDataTest.ps1 -ConfigPath .\scripts\sps\CfgAppSps.psd1
# Structure-only (skip every Test-Path on the install share / certificates).
.\scripts\test\Invoke-ConfigDataTest.ps1 -ConfigPath .\scripts\sql\CfgAppSql.psd1 -SkipFilesystemThe driver exits with code 1 on the first failure, so it can be wired
into CI or used as a hard gate before Start-DscConfiguration:
.\scripts\test\Invoke-ConfigDataTest.ps1 -ConfigPath .\scripts\sps\CfgAppSps.psd1
if ($LASTEXITCODE -ne 0) { throw 'Config invalid — refusing to compile MOFs.' }Every check is read-only. Sections that don't apply to the file under test
are skipped automatically (e.g. SharePoint checks are not run against
CfgAppSql.psd1).
-
File integrity —
Import-PowerShellDataFilesucceeds;AllNodes/NonNodeDatapresent; the wildcard*node declaresPSDscAllowPlainTextPassword/PSDscAllowDomainUser. -
AllNodes —
NodeNamevalues unique; exactly oneIsMasterper role family (SPS, OOS); at least oneIsSPSServer/IsOOSServer/IsSQLServerwhen the matchingNonNodeDatasection is present. -
Disks & Drives —
NonNodeData.Disksis present and each entry hasId/Letter/Type/FSLabel/AllocationUnitSize; diskIds andLetters are unique; exactly oneOSdisk; theSYSTEM/DATA/LOGSroles are declared. TheDrives.Data/Drives.Logsvalues derived fromDisksmatch^[A-Z]:$. -
SourcePath—NonNodeData.SourcePathis a UNC or rooted local path. -
Product key —
SharePoint.ProductKeymatchesXXXXX-XXXXX-XXXXX-XXXXX-XXXXXand is not the placeholderXXXXX-.... -
Ports —
CentralAdministrationPortandSQLAlias[*].Portare valid integers in1..65535. -
Certificates —
ADC.certificates[*].Nameunique; every cert has a matchingSecrets.psd1entry (drives the PFX password);WebApplications[*].CertNameresolves to a known certName;OOS.CertFriendlyNameresolves to a knownFriendlyName. -
Managed accounts —
SharePoint.ManagedAccounts(or the default@('FARM','IISAPP','SEARCH')when omitted) is a subset ofSecrets.psd1. -
Language packs — each
SharePoint.LanguagePacksentry matchesxx-xx. -
Filesystem reachability (skipped by
-SkipFilesystem) —SourcePath,BIN\setup.exe,LP\<locale>\setup.exe, and the CU package for both SPS and OOS; SQLSourcePath\setup.exe; everyADC.certificates[*].CertPath/PfxPath.
Filesystem checks reuse the exact same Resolve-ProductPaths resolution
as CfgAppSps.ps1, so the per-product SourcePath / Subfolders
overrides documented above are validated against the same paths the MOF
will compile with.
Pester v5 prefixes each It line with a status glyph, and the wrapper
adds a one-line summary that also surfaces the number of skipped tests
so a clean run never looks misleadingly green:
| Glyph | Meaning |
|---|---|
[+] |
Passed |
[-] |
Failed — fix before compiling MOFs |
[!] |
Skipped — the section / context didn't apply (e.g. SPS checks against CfgAppSql.psd1, or filesystem checks under -SkipFilesystem). Not a failure.
|
Tests Passed: 59, Failed: 0, Skipped: 3, Inconclusive: 0, NotRun: 0
59 of 62 test(s) passed; 3 skipped (sections not present in this config
or filesystem checks disabled). ConfigurationData looks healthy.
A run with Failed: 0 is healthy regardless of the skipped count;
sections gate themselves off when the relevant NonNodeData sub-tree is
missing. The wrapper still returns exit code 0 so CI / pre-MOF gates
work the same way.
The suite requires Pester 5.0.0 or later on the authoring host (Windows PowerShell 5.1 ships with Pester 3.x by default):
Install-Module Pester -MinimumVersion 5.0.0 -Force -SkipPublisherCheckNo other module is needed; the test files use only built-in cmdlets and Pester v5.
Continue with the Usage page.