NTS.Tools.Application.psm1

function Install-M365Apps {
    # https://github.com/mallockey/Install-Office365Suite/blob/master/Install-Office365Suite.ps1


    [CmdletBinding(DefaultParameterSetName = 'XMLFile')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = "XMLFile")]
        [string]
        $ConfigXMLPath,

        [Parameter(Mandatory = $false)]
        [string]
        $WorkingDir = "$($env:ProgramData)\NTS\Office",

        [Parameter(Mandatory = $false)]
        [switch]
        $CleanUpInstallFiles,

        [Parameter(Mandatory = $false, ParameterSetName = "DefaultXML")]
        [switch]
        $UseDefaultConfigXML,

        [Parameter(Mandatory = $false, ParameterSetName = "SilentDefaultXML")]
        [switch]
        $UseSilentDefaultConfigXML
    )
    
    $ErrorActionPreference = "Stop"

    # check admin access
    $CurrentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    if (!($CurrentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) {
        Write-Warning "$($env:COMPUTERNAME): Script is not running as Administrator"
        Write-Warning "$($env:COMPUTERNAME): Please rerun this script as Administrator"
        exit
    }

    if (!(Test-Path $WorkingDir)) {
        New-Item -Path $WorkingDir -ItemType Directory | Out-Null
    }

    if ($UseDefaultConfigXML) {
        New-M365AppsXML -ConfigXMLPath $WorkingDir -ConfigXMLFileName "config.xml" -IncludeApps "Outlook", "Word", "Excel", "Teams"
        $ConfigXMLPath = "$($WorkingDir)\config.xml"
    }
    elseif ($UseSilentDefaultConfigXML) {
        New-M365AppsXML -ConfigXMLPath $WorkingDir -ConfigXMLFileName "config.xml" -IncludeApps "Outlook", "Word", "Excel", "Teams" -DisplayLevel None
        $ConfigXMLPath = "$($WorkingDir)\config.xml"
    }

    # verify config xml
    Test-OfficeConfiguration -ConfigurationXMLFilePath $ConfigXMLPath

    # Get Setup.exe
    Get-M365AppsSetup -OutPath $WorkingDir -FileName "Setup.exe"

    # Run the O365 install
    Write-Output "$($env:COMPUTERNAME): Start Installation of Microsoft 365 Apps"
    $Process = Start-Process "$($WorkingDir)\Setup.exe" -ArgumentList "/configure `"$ConfigXMLPath`"" -Wait -PassThru -WindowStyle Hidden
    if ($Process.ExitCode -ne 0) {
        throw "there was an error installing office - $($PSItem.Exception.Message)"
    }

    #Check if Office 365 suite was installed correctly.
    $RegLocations = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
        "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
    )

    $OfficeInstalled = $False
    foreach ($Key in (Get-ChildItem $RegLocations) ) {
        if ($Key.GetValue('DisplayName') -like '*Microsoft 365*') {
            $OfficeVersionInstalled = $Key.GetValue('DisplayName')
            $OfficeInstalled = $True
        }
    }

    if ($OfficeInstalled) {
        Write-Output "$($env:COMPUTERNAME): $($OfficeVersionInstalled) installed successfully!"
    }
    else {
        Write-Warning "$($env:COMPUTERNAME): Microsoft 365 was not detected after the install ran"
    }

    if ($CleanUpInstallFiles) {
        Start-FolderCleanUp -FolderToRemove $WorkingDir
    }
}

function New-M365AppsXML {
    # https://github.com/mallockey/Install-Office365Suite/blob/master/InstallOffice.psm1

    [CmdletBinding(DefaultParameterSetName = 'ExcludeApps')]
    param(
        [Parameter(Mandatory = $false)]
        [string]
        $ConfigXMLPath = "$($env:ProgramData)\NTS\Office\",

        [Parameter(Mandatory = $false)]
        [string]
        $ConfigXMLFileName = "config.xml",

        [Parameter(Mandatory = $false)]
        [ValidateSet('O365ProPlusRetail', 'O365BusinessRetail')]
        [string]
        $OfficeEdition = 'O365ProPlusRetail',

        [Parameter(Mandatory = $false, ParameterSetName = "ExcludeApps")]
        [ValidateSet(
            'Groove', 
            'Outlook', 
            'OneNote', 
            'Access', 
            'OneDrive', 
            'Publisher', 
            'Word', 
            'Excel', 
            'PowerPoint', 
            'Teams', 
            'Lync'
        )]
        [Array]
        $ExcludeApps,

        [Parameter(Mandatory = $false, ParameterSetName = "IncludeApps")]
        [ValidateSet(
            'Groove', 
            'Outlook', 
            'OneNote', 
            'Access', 
            'OneDrive', 
            'Publisher', 
            'Word', 
            'Excel', 
            'PowerPoint', 
            'Teams', 
            'Lync'
        )]
        [Array]
        $IncludeApps,

        [Parameter(Mandatory = $false)]
        [Array]
        $LanguageIDs,

        [Parameter(Mandatory = $false)]
        [ValidateSet("None", "Full")]
        [string]
        $DisplayLevel = "Full",

        [Parameter(Mandatory = $false)]
        [ValidateSet('64', '32')]
        [string]
        $OfficeArch = '64',

        [Parameter(Mandatory = $false)]
        [ValidateSet("TRUE", "FALSE")]
        [string]
        $AcceptEULA = "TRUE",

        [Parameter(Mandatory = $false)]
        [ValidateSet("TRUE", "FALSE")]
        [string]
        $EnableUpdates = "TRUE",

        [Parameter(Mandatory = $false)]
        [ValidateSet('SemiAnnualPreview', 'SemiAnnual', 'MonthlyEnterprise', 'CurrentPreview', 'Current')]
        [string]
        $Channel = 'Current',

        [Parameter(Mandatory = $false)]
        [Switch]
        $IncludeProject,

        [Parameter(Mandatory = $false)]
        [Switch]
        $IncludeVisio,

        [Parameter(Mandatory = $false)]
        [ValidateSet(0, 1)]
        [string]
        $SharedComputerLicensing = '0',

        [Parameter(Mandatory = $false)]
        [bool]
        $RemoveMSI = $true,

        [Parameter(Mandatory = $false)]
        [bool]
        $SetFileFormat = $true
    )

    $ErrorActionPreference = 'Stop'
    $ValidApps = (
        'Groove', 
        'Outlook', 
        'OneNote', 
        'Access', 
        'OneDrive', 
        'Publisher', 
        'Word', 
        'Excel', 
        'PowerPoint', 
        'Teams', 
        'Lync'
    )
  
    if ($ExcludeApps -and $IncludeApps) {
        throw "you can use 'ExcludeApps' or 'IncludeApps' not both"
    }
    elseif ($ExcludeApps) {
        $ExcludeApps | ForEach-Object {
            $ExcludeAppsString += "<ExcludeApp ID =`"$_`" />"
        }
    }
    elseif ($IncludeApps) {
        $ValidApps | Where-Object { $PSItem -notin $IncludeApps } | ForEach-Object {
            $ExcludeAppsString += "<ExcludeApp ID =`"$_`" />"
        }
    }
  
    if ($LanguageIDs) {
        $LanguageIDs | ForEach-Object {
            $LanguageString += "<Language ID =`"$_`" />"
        }
    }
    else {
        $LanguageString = "<Language ID=`"MatchOS`" />"
    }
  
    if ($OfficeArch) {
        $OfficeArchString = "`"$OfficeArch`""
    }
  
    if ($RemoveMSI) {
        $RemoveMSIString = '<RemoveMSI />'
    }
    else {
        $RemoveMSIString = $Null
    }
  
    if ($SetFileFormat) {
        $AppSettingsString = '<AppSettings>
        <User Key="software\microsoft\office\16.0\excel\options" Name="defaultformat" Value="51" Type="REG_DWORD" App="excel16" Id="L_SaveExcelfilesas" />
        <User Key="software\microsoft\office\16.0\powerpoint\options" Name="defaultformat" Value="27" Type="REG_DWORD" App="ppt16" Id="L_SavePowerPointfilesas" />
        <User Key="software\microsoft\office\16.0\word\options" Name="defaultformat" Value="" Type="REG_SZ" App="word16" Id="L_SaveWordfilesas" />
      </AppSettings>'

    }
    else {
        $AppSettingsString = $Null
    }
  
    if ($Channel) {
        $ChannelString = "Channel=`"$Channel`""
    }
    else {
        $ChannelString = $Null
    }
  
    if ($IncludeProject) {
        $ProjectString = "<Product ID=`"ProjectProRetail`"`>$ExcludeAppsString $LanguageString</Product>"
    }
    else {
        $ProjectString = $Null
    }
  
    if ($IncludeVisio) {
        $VisioString = "<Product ID=`"VisioProRetail`"`>$ExcludeAppsString $LanguageString</Product>"
    }
    else {
        $VisioString = $Null
    }
  
    $OfficeXML = [XML]@"
<Configuration>
    <Add OfficeClientEdition=$($OfficeArchString) $($ChannelString) $($SourcePathString) >
    <Product ID="$($OfficeEdition)">
        $($LanguageString)
        $($ExcludeAppsString)
    </Product>
    $($ProjectString)
    $($VisioString)
    </Add>
    <Property Name="SharedComputerLicensing" Value="$($SharedComputerlicensing)" />
    <Display Level="$($DisplayLevel)" AcceptEULA="$($AcceptEULA)" />
    <Updates Enabled="$($EnableUpdates)" />
    $($AppSettingsString)
    $($RemoveMSIString)
</Configuration>
"@

  
    try {
        if (!(Test-Path $ConfigXMLPath)) {
            New-Item -Path $ConfigXMLPath -ItemType Directory | Out-Null
        }

        Write-Output "$($env:COMPUTERNAME): XML Config file will be saved to $($ConfigXMLPath)\$($ConfigXMLFileName)"
        $OfficeXML.Save("$($ConfigXMLPath)\$($ConfigXMLFileName)")
    }
    catch {
        throw "could not create config xml at $($ConfigXMLPath) - $($PSItem.Exception.Message)"
    }
}

function Get-M365AppsSetup {
    # https://github.com/mallockey/Install-Office365Suite/blob/master/Install-Office365Suite.ps1
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $OutPath = "$($env:ProgramData)\NTS\Office\Setup",

        [Parameter(Mandatory = $false)]
        [string]
        $FileName = "setup.exe",

        [Parameter(Mandatory = $false)]
        [switch]
        $KeepODTFiles
    )

    $ODTFolder = "$($OutPath)\ODT"
    $ODTSetupFilePath = "$($ODTFolder)\ODTSetup.exe"

    # get odt
    try {
        if (!(Test-Path $OutPath)) {
            New-Item -Path $OutPath -ItemType Directory | Out-Null
        }
        if (!(Test-Path $ODTFolder)) {
            New-Item -Path $ODTFolder -ItemType Directory | Out-Null
        }
        if (Test-Path -Path $ODTSetupFilePath) {
            throw "$($ODTSetupFilePath) already exits"
        }
        else {
            try {                
                [String]$MSWebPage = Invoke-RestMethod "https://www.microsoft.com/en-us/download/confirmation.aspx?id=49117"
                    $ODTDownloadURL = $MSWebPage | ForEach-Object {
                    if ($PSItem -match 'url=(https://.*officedeploymenttool.*\.exe)') {
                        $matches[1]
                    }
                }
            }
            catch {
                throw "could not find odt download url - $($PSItem.Exception.Message)"
            }

            Invoke-WebRequest -Uri $ODTDownloadURL -OutFile $ODTSetupFilePath
        }
    }
    catch {
        throw "could not download ODTSetup - $($PSItem.Exception.Message)"
    }

    # get setup
    try {
        Write-Output "$($env:COMPUTERNAME): Running the Office Deployment Tool..."
        Start-Process -FilePath $ODTSetupFilePath -ArgumentList "/quiet /extract:`"$($ODTFolder)`"" -Wait

        Write-Output "$($env:COMPUTERNAME): Copy Setup.exe to $($OutPath)"
        Copy-Item -Path "$($ODTFolder)\setup.exe" -Destination "$($OutPath)\$($FileName)" -Force

        if (!($KeepODTFiles)) {
            Start-FolderCleanUp -FolderToRemove $ODTFolder
        }
    }
    catch {
        Write-Warning "$($env:COMPUTERNAME): Error running the Office Deployment Tool - $($PSItem.Exception.Message)"
    }
}

function Test-OfficeConfiguration {
    param(
        [Parameter(Mandatory = $true)]
        [String]
        $ConfigurationXMLFilePath
    )

    $ErrorActionPreference = 'Stop'
      
    try {
        $OfficeXML = Get-Content -Path $ConfigurationXMLFilePath
    }
    catch {
        throw "There was an error generating the XML config file"
    }

    try {
        Write-Output "$($env:COMPUTERNAME): Uploading XML config file to clients.config.office.net to verify syntax"
      
        Invoke-RestMethod -Uri 'https://clients.config.office.net/intents/v1.0/DeploymentSettings/ImportConfiguration' `
            -Method Post  `
            -Body $OfficeXML `
            -ContentType 'text/xml' | Out-Null
      
        Write-Output "$($env:COMPUTERNAME): XML config file was successfully verified"
    }
    catch {
        throw "The XML is not formatted correctly"
    }
}