DSCResources/MSFT_SPSite/MSFT_SPSite.psm1

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Url,

        [Parameter(Mandatory = $true)]
        [System.String]
        $OwnerAlias,

        [Parameter()]
        [System.UInt32]
        $CompatibilityLevel,

        [Parameter()]
        [System.String]
        $ContentDatabase,

        [Parameter()]
        [System.String]
        $Description,

        [Parameter()]
        [System.String]
        $HostHeaderWebApplication,

        [Parameter()]
        [System.UInt32]
        $Language,

        [Parameter()]
        [System.String]
        $Name,

        [Parameter()]
        [System.String]
        $OwnerEmail,

        [Parameter()]
        [System.String]
        $QuotaTemplate,

        [Parameter()]
        [System.String]
        $SecondaryEmail,

        [Parameter()]
        [System.String]
        $SecondaryOwnerAlias,

        [Parameter()]
        [System.String]
        $Template,

        [Parameter()]
        [System.Boolean]
        $CreateDefaultGroups = $true,

        [Parameter()]
        [ValidateSet("TenantAdministration", "None")]
        [System.String]
        $AdministrationSiteType
    )

    Write-Verbose -Message "Getting site collection $Url"

    $result = Invoke-SPDscCommand -Arguments $PSBoundParameters `
        -ScriptBlock {
        $params = $args[0]
        $site = $null

        try
        {
            $centralAdminWebApp = [Microsoft.SharePoint.Administration.SPAdministrationWebApplication]::Local
            $centralAdminSite = Get-SPSite -Identity $centralAdminWebApp.Url -ErrorAction Stop

            $site = New-Object "Microsoft.SharePoint.SPSite" -ArgumentList @($params.Url, $centralAdminSite.SystemAccount.UserToken)
        }
        catch [System.Exception]
        {
        }

        if ($null -eq $site)
        {
            Write-Verbose "Site Collection not found"

            return @{
                Url                      = $params.Url
                OwnerAlias               = $null
                CompatibilityLevel       = $null
                ContentDatabase          = $null
                Description              = $null
                HostHeaderWebApplication = $null
                Language                 = $null
                Name                     = $null
                OwnerEmail               = $null
                QuotaTemplate            = $null
                SecondaryEmail           = $null
                SecondaryOwnerAlias      = $null
                Template                 = $null
                CreateDefaultGroups      = $null
            }
        }
        else
        {
            if ($site.HostHeaderIsSiteName)
            {
                $HostHeaderWebApplication = $site.WebApplication.Url
            }

            if ($null -eq $site.Owner)
            {
                $owner = $null
            }
            else
            {
                if ($site.WebApplication.UseClaimsAuthentication)
                {
                    $principal = New-SPClaimsPrincipal -Identity $site.Owner.UserLogin `
                        -IdentityType "EncodedClaim" `
                        -ErrorAction SilentlyContinue

                    if ($null -ne $principal)
                    {
                        $owner = $principal.Value
                    }
                    else
                    {
                        $owner = $site.Owner.UserLogin
                    }
                }
                else
                {
                    $owner = $site.Owner.UserLogin
                }
            }

            if ($null -eq $site.SecondaryContact)
            {
                $secondaryOwner = $null
            }
            else
            {
                if ($site.WebApplication.UseClaimsAuthentication)
                {
                    $secondaryOwner = (New-SPClaimsPrincipal -Identity $site.SecondaryContact.UserLogin `
                            -IdentityType "EncodedClaim").Value
                }
                else
                {
                    $secondaryOwner = $site.SecondaryContact.UserLogin
                }
            }

            $admService = Get-SPDscContentService
            $quota = ($admService.QuotaTemplates | `
                        Where-Object -FilterScript {
                        $_.QuotaID -eq $site.Quota.QuotaID
                    }).Name

            $CreateDefaultGroups = $true
            if ($null -eq $site.RootWeb.AssociatedVisitorGroup -and
                $null -eq $site.RootWeb.AssociatedMemberGroup -and
                $null -eq $site.RootWeb.AssociatedOwnerGroup)
            {
                $CreateDefaultGroups = $false
            }

            return @{
                Url                      = $site.Url
                OwnerAlias               = $owner
                CompatibilityLevel       = $site.CompatibilityLevel
                ContentDatabase          = $site.ContentDatabase.Name
                Description              = $site.RootWeb.Description
                HostHeaderWebApplication = $HostHeaderWebApplication
                Language                 = $site.RootWeb.Language
                Name                     = $site.RootWeb.Name
                OwnerEmail               = $site.Owner.Email
                QuotaTemplate            = $quota
                SecondaryEmail           = $site.SecondaryContact.Email
                SecondaryOwnerAlias      = $secondaryOwner
                Template                 = "$($site.RootWeb.WebTemplate)#$($site.RootWeb.Configuration)"
                CreateDefaultGroups      = $CreateDefaultGroups
                AdministrationSiteType   = $site.AdministrationSiteType
            }
        }
    }
    return $result
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Url,

        [Parameter(Mandatory = $true)]
        [System.String]
        $OwnerAlias,

        [Parameter()]
        [System.UInt32]
        $CompatibilityLevel,

        [Parameter()]
        [System.String]
        $ContentDatabase,

        [Parameter()]
        [System.String]
        $Description,

        [Parameter()]
        [System.String]
        $HostHeaderWebApplication,

        [Parameter()]
        [System.UInt32]
        $Language,

        [Parameter()]
        [System.String]
        $Name,

        [Parameter()]
        [System.String]
        $OwnerEmail,

        [Parameter()]
        [System.String]
        $QuotaTemplate,

        [Parameter()]
        [System.String]
        $SecondaryEmail,

        [Parameter()]
        [System.String]
        $SecondaryOwnerAlias,

        [Parameter()]
        [System.String]
        $Template,

        [Parameter()]
        [System.Boolean]
        $CreateDefaultGroups = $true,

        [Parameter()]
        [ValidateSet("TenantAdministration", "None")]
        [System.String]
        $AdministrationSiteType
    )

    Write-Verbose -Message "Setting site collection $Url"

    if ($PSBoundParameters.ContainsKey("CreateDefaultGroups") -eq $false)
    {
        $PSBoundParameters.CreateDefaultGroups = $true
    }

    $CurrentValues = Get-TargetResource @PSBoundParameters

    $null = Invoke-SPDscCommand -Arguments @($PSBoundParameters, $MyInvocation.MyCommand.Source, $CurrentValues) `
        -ScriptBlock {
        $params = $args[0]
        $eventSource = $args[1]
        $CurrentValues = $args[2]
        $doCreateDefaultGroups = $false

        $CreateDefaultGroups = $params.CreateDefaultGroups
        $params.Remove("CreateDefaultGroups") | Out-Null

        $site = Get-SPSite -Identity $params.Url -ErrorAction SilentlyContinue

        if ($null -eq $site)
        {
            Write-Verbose -Message ("Starting New-SPSite with the following parameters: " + `
                    "$(Convert-SPDscHashtableToString $params)")

            # Implemented alternative solution (not using New-SPSite) to mitigate a timing
            # issue which exists shortly after creating a new farm.
            $paramsList = $params.GetEnumerator() | Where-Object {
                $_.Key -ne "Verbose"
            } | ForEach-Object {
                if ($_.Key -is [Switch])
                {
                    "-$($_.Key):$($_.Value)"
                }
                else
                {
                    "-$($_.Key) '$($_.Value)'"
                }
            }
            $paramsStr = $paramsList -join " "
            $installedVersion = Get-SPDscInstalledProductVersion
            if ($installedVersion.ProductMajorPart -eq 15 -or $installedVersion.ProductBuildPart -le 12999)
            {
                $newSPSiteCmd = "Add-PSSnapin Microsoft.SharePoint.PowerShell;"
            }
            else
            {
                $newSPSiteCmd = "Import-Module SharePointServer -Verbose:`$false -WarningAction SilentlyContinue;"
            }

            $newSPSiteCmd += "New-SPSite " + $paramsStr
            Write-Verbose "Running command: $newSPSiteCmd"

            $result = Start-Process -Wait `
                -NoNewWindow `
                -FilePath "powershell.exe" `
                -ArgumentList @("-command", "$newSPSiteCmd") `
                -PassThru
            if ($result.ExitCode -ne 0)
            {
                $message = ("[ERROR] An error during site creation. Exit code '$($result.ExitCode)' " + `
                        "was returned. Please check ULS log for more info")
                Add-SPDscEvent -Message $message `
                    -EntryType 'Error' `
                    -EventID 100 `
                    -Source $eventSource
                throw $message
            }

            $site = Get-SPSite -Identity $params.Url -ErrorAction SilentlyContinue
            if ($null -eq $site)
            {
                $message = "Cannot find created site collection, please check ULS log for any error messages!"
                Add-SPDscEvent -Message $message `
                    -EntryType 'Error' `
                    -EventID 100 `
                    -Source $eventSource
                throw $message
            }

            if ($CreateDefaultGroups -eq $true)
            {
                $doCreateDefaultGroups = $true

            }
            else
            {
                Write-Verbose -Message ("CreateDefaultGroups set to false. The default " + `
                        "SharePoint groups will not be created")
            }
        }
        else
        {
            $newParams = @{
                Identity = $params.Url
            }

            if ($params.ContainsKey("QuotaTemplate") -eq $true)
            {
                if ($params.QuotaTemplate -ne $CurrentValues.QuotaTemplate)
                {
                    $newParams.QuotaTemplate = $params.QuotaTemplate
                }
            }

            $centralAdminWebApp = [Microsoft.SharePoint.Administration.SPAdministrationWebApplication]::Local
            $centralAdminSite = Get-SPSite -Identity $centralAdminWebApp.Url -ErrorAction SilentlyContinue

            if ($null -eq $centralAdminSite)
            {
                $message = "Unable to locate central administration site"
                Add-SPDscEvent -Message $message `
                    -EntryType 'Error' `
                    -EventID 100 `
                    -Source $eventSource
                throw $message
            }

            $systemAccountSite = New-Object "Microsoft.SharePoint.SPSite" -ArgumentList @($site.Id, $centralAdminSite.SystemAccount.UserToken)

            if ($params.OwnerAlias -ne $CurrentValues.OwnerAlias)
            {
                Write-Verbose -Message "Updating owner to $($params.OwnerAlias)"
                try
                {
                    $confirmedUsername = $systemAccountSite.RootWeb.EnsureUser($params.OwnerAlias)
                    $systemAccountSite.Owner = $confirmedUsername
                }
                catch
                {
                    Write-Output "Cannot resolve user $($params.OwnerAlias) as OwnerAlias"
                }
            }

            if ($params.ContainsKey("SecondaryOwnerAlias") -eq $true -and `
                    $params.SecondaryOwnerAlias -ne $CurrentValues.SecondaryOwnerAlias)
            {
                Write-Verbose -Message "Updating secondary owner to $($params.SecondaryOwnerAlias)"
                try
                {
                    $confirmedUsername = $systemAccountSite.RootWeb.EnsureUser($params.SecondaryOwnerAlias)
                    $systemAccountSite.SecondaryContact = $confirmedUsername
                }
                catch
                {
                    Write-Verbose -Message ("Cannot resolve user $($params.SecondaryOwnerAlias) " + `
                            "as SecondaryOwnerAlias")
                }
            }

            if ($params.ContainsKey("AdministrationSiteType") -eq $true)
            {
                if ($params.AdministrationSiteType -ne $CurrentValues.AdministrationSiteType)
                {
                    $newParams.AdministrationSiteType = $params.AdministrationSiteType
                }
            }

            if ($newParams.Count -gt 1)
            {
                Write-Verbose -Message "Updating existing site collection"
                Write-Verbose -Message ("Starting Set-SPSite with the following parameters: " + `
                        "$(Convert-SPDscHashtableToString $newParams)")
                Set-SPSite @newParams
            }

            if ($CurrentValues.CreateDefaultGroups -eq $false)
            {
                if ($CreateDefaultGroups -eq $true)
                {
                    $doCreateDefaultGroups = $true
                }
                else
                {
                    Write-Verbose -Message ("CreateDefaultGroups set to false. The default " + `
                            "SharePoint groups will not be created")
                }
            }
        }

        if ($doCreateDefaultGroups -eq $true)
        {
            Write-Verbose -Message ("Creating default groups")

            $centralAdminWebApp = [Microsoft.SharePoint.Administration.SPAdministrationWebApplication]::Local
            $centralAdminSite = Get-SPSite -Identity $centralAdminWebApp.Url
            $systemAccountSite = New-Object "Microsoft.SharePoint.SPSite" -ArgumentList @($site.Id, $centralAdminSite.SystemAccount.UserToken)

            if ($null -eq $systemAccountSite.SecondaryContact)
            {
                $secondaryOwnerLogin = $null
            }
            else
            {
                $secondaryOwnerLogin = $systemAccountSite.SecondaryContact.UserLogin;
            }

            $systemAccountSite.RootWeb.CreateDefaultAssociatedGroups($systemAccountSite.Owner.UserLogin,
                $secondaryOwnerLogin,
                $null)
        }
    }
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Url,

        [Parameter(Mandatory = $true)]
        [System.String]
        $OwnerAlias,

        [Parameter()]
        [System.UInt32]
        $CompatibilityLevel,

        [Parameter()]
        [System.String]
        $ContentDatabase,

        [Parameter()]
        [System.String]
        $Description,

        [Parameter()]
        [System.String]
        $HostHeaderWebApplication,

        [Parameter()]
        [System.UInt32]
        $Language,

        [Parameter()]
        [System.String]
        $Name,

        [Parameter()]
        [System.String]
        $OwnerEmail,

        [Parameter()]
        [System.String]
        $QuotaTemplate,

        [Parameter()]
        [System.String]
        $SecondaryEmail,

        [Parameter()]
        [System.String]
        $SecondaryOwnerAlias,

        [Parameter()]
        [System.String]
        $Template,

        [Parameter()]
        [System.Boolean]
        $CreateDefaultGroups = $true,

        [Parameter()]
        [ValidateSet("TenantAdministration", "None")]
        [System.String]
        $AdministrationSiteType
    )

    Write-Verbose -Message "Testing site collection $Url"

    $CurrentValues = Get-TargetResource @PSBoundParameters

    Write-Verbose -Message "Current Values: $(Convert-SPDscHashtableToString -Hashtable $CurrentValues)"
    Write-Verbose -Message "Target Values: $(Convert-SPDscHashtableToString -Hashtable $PSBoundParameters)"

    if ($PSBoundParameters.ContainsKey("CreateDefaultGroups") -eq $true -and `
            $CreateDefaultGroups -eq $true)
    {
        if ($CurrentValues.CreateDefaultGroups -ne $true)
        {
            $message = "The default site groups are not configured as desired."
            Write-Verbose -Message $message
            Add-SPDscEvent -Message $message -EntryType 'Error' -EventID 1 -Source $MyInvocation.MyCommand.Source

            Write-Verbose -Message "Test-TargetResource returned false"
            return $false
        }
    }

    $result = Test-SPDscParameterState -CurrentValues $CurrentValues `
        -Source $($MyInvocation.MyCommand.Source) `
        -DesiredValues $PSBoundParameters `
        -ValuesToCheck @("Url",
        "QuotaTemplate",
        "OwnerAlias",
        "SecondaryOwnerAlias",
        "AdministrationSiteType")

    Write-Verbose -Message "Test-TargetResource returned $result"

    return $result
}

function Export-TargetResource
{
    $VerbosePreference = "SilentlyContinue"
    $spSites = Get-SPSite -Limit All
    $siteGuid = $null
    $siteTitle = $null
    $dependsOnItems = @()
    $sc = Get-SPDscContentService
    $Content = ''

    $i = 1
    $total = $spSites.Length

    $ParentModueBase = Get-Module "SharePointDsc" -ListAvailable | Select-Object -ExpandProperty Modulebase
    $module = Join-Path -Path $ParentModueBase -ChildPath "\DSCResources\MSFT_SPSite\MSFT_SPSite.psm1" -Resolve

    foreach ($spSite in $spSites)
    {
        try
        {
            if (!$spSite.IsSiteMaster)
            {
                $siteTitle = $spSite.RootWeb.Title
                $siteUrl = $spSite.Url
                Write-Host "Scanning SPSite [$i/$total] {$siteUrl}"

                $dependsOnItems = @("[SPWebApplication]$($spSite.WebApplication.Name.Replace(' ', ''))")
                $params = Get-DSCFakeParameters -ModulePath $module
                $siteGuid = [System.Guid]::NewGuid().toString()
                $siteTitle = $spSite.RootWeb.Title
                if (!$siteTitle)
                {
                    $siteTitle = "SiteCollection"
                }
                $partialContent = " SPSite " + $siteGuid + "`r`n"
                $partialContent += " {`r`n"
                $params.Url = $spSite.Url
                $results = Get-TargetResource @params

                <# WA - Somehow the WebTemplateID returned for App Catalog is 18, but the template is APPCATALOG#0 #>
                if ($results.Template -eq "APPCATALOG#18")
                {
                    $results.Template = "APPCATALOG#0"
                }
                <# If the current Quota ID is 0, it means no quota templates were used. Remove param in that case. #>
                if ($spSite.Quota.QuotaID -eq 0)
                {
                    $results.Remove("QuotaTemplate")
                }
                else
                {
                    $quotaTemplateName = $sc.QuotaTemplates | Where-Object { $_.QuotaId -eq $spsite.Quota.QuotaID }
                    if ($null -ne $quotaTemplateName -and $null -ne $quotaTemplateName.Name)
                    {
                        if ($Global:DH_SPQUOTATEMPLATE.ContainsKey($quotaTemplateName.Name))
                        {
                            $dependsOnItems += "[SPQuotaTemplate]$($Global:DH_SPQUOTATEMPLATE.Item($quotaTemplateName.Name))"
                        }
                    }
                    else
                    {
                        $results.Remove("QuotaTemplate")
                    }
                }
                if (!$results.Get_Item("SecondaryOwnerAlias"))
                {
                    $results.Remove("SecondaryOwnerAlias")
                }
                if (!$results.Get_Item("SecondaryEmail"))
                {
                    $results.Remove("SecondaryEmail")
                }
                if (!$results.Get_Item("OwnerEmail"))
                {
                    $results.Remove("OwnerEmail")
                }
                if (!$results.Get_Item("HostHeaderWebApplication"))
                {
                    $results.Remove("HostHeaderWebApplication")
                }
                if (!$results.Get_Item("Name"))
                {
                    $results.Remove("Name")
                }
                if (!$results.Get_Item("Description"))
                {
                    $results.Remove("Description")
                }
                else
                {
                    $results.Description = $results.Description.Replace("`"", "'").Replace("`r`n", ' `
                    '
)
                }
                $dependsOnClause = Get-DSCDependsOnBlock($dependsOnItems)
                $results = Repair-Credentials -results $results

                $ownerAlias = Get-Credentials -UserName $results.OwnerAlias
                $plainTextUser = $false;
                if (!$ownerAlias)
                {
                    if (!$Global:AllUsers.Contains($results.OwnerAlias) -and $results.OwnerAlias -ne "")
                    {
                        $Global:AllUsers += $results.OwnerAlias
                    }
                    $plainTextUser = $true
                    $ownerAlias = $results.OwnerAlias
                }
                $currentBlock = ""
                if ($null -ne $ownerAlias -and !$plainTextUser)
                {
                    $results.OwnerAlias = (Resolve-Credentials -UserName $results.OwnerAlias) + ".UserName"
                }

                if ($results.ContainsKey("SecondaryOwnerAlias"))
                {
                    $secondaryOwner = Get-Credentials -UserName $results.SecondaryOwnerAlias
                    if ($null -ne $secondaryOwner)
                    {
                        $results.SecondaryOwnerAlias = (Resolve-Credentials -UserName $results.SecondaryOwnerAlias) + ".UserName"
                    }
                    else
                    {
                        if (!$Global:AllUsers.Contains($results.SecondaryOwnerAlias) -and $results.SecondaryOwnerAlias -ne "")
                        {
                            $Global:AllUsers += $results.SecondaryOwnerAlias
                        }
                        $secondaryOwner = $results.SecondaryOwnerAlias
                    }
                }
                $currentBlock = Get-DSCBlock -Params $results -ModulePath $module
                $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "PsDscRunAsCredential"

                if ($null -ne $results.SecondaryOwnerAlias -and $results.SecondaryOwnerAlias.StartsWith("`$"))
                {
                    $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "SecondaryOwnerAlias"
                }
                if ($null -ne $results.OwnerAlias -and $results.OwnerAlias.StartsWith("`$"))
                {
                    $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "OwnerAlias"
                }
                $partialContent += $currentBlock
                $partialContent += " DependsOn = " + $dependsOnClause + "`r`n"
                $partialContent += " }`r`n"

                $properties = @{
                    URL = $SPSite.URL
                }
                $partialContent += Read-TargetResource -ResourceName 'SPSiteUrl' `
                    -ExportParams $properties

                <# Nik20170112 - There are restrictions preventing this setting from being applied if the PsDscRunAsCredential parameter is not used.
                            Since this is only available in WMF 5, we check to see if the node farm we are extracting the configuration from is
                            running at least PowerShell v5 before reading the Site Collection level SPDesigner settings. #>

                if ($PSVersionTable.PSVersion.Major -ge 5 -and $Global:ExtractionModeValue -ge 2)
                {
                    $properties = @{
                        URL   = $SPSite.URL
                        Scope = "SiteCollection"
                    }
                    $partialContent += Read-TargetResource -Resource 'SPDesignerSettings' `
                        -ExportParams $properties
                }

                <# SPSite Feature Section #>
                if (($Global:ExtractionModeValue -eq 3 -and $Quiet) -or $Global:ComponentsToExtract.Contains("SPFeature"))
                {
                    $properties = @{
                        Scope     = "Site"
                        Url       = $SpSite.Url
                        DependsOn = "[SPSite]$($siteGuid)"
                    }
                    $partialContent += Read-TargetResource -ResourceName 'SPFeature' `
                        -ExportParams $properties
                }

                if (($Global:ExtractionModeValue -eq 3 -and $Quiet) -or $Global:ComponentsToExtract.Contains("SPWeb"))
                {
                    $properties = @{
                        Url       = $spSite.Url
                        DependsOn = "[SPSite]$($siteGuid)"
                    }
                    $partialContent += Read-TargetResource -ResourceName 'SPWeb' `
                        -ExportParams $properties
                }
            }
            $i++
        }
        catch
        {
            $_
            $Global:ErrorLog += "[Site Collection]" + $spSite.Url + "`r`n"
            $Global:ErrorLog += "$_`r`n`r`n"
        }
        $i++
        $Content += $partialContent
    }
    return $Content
}

Export-ModuleMember -Function *-TargetResource