DeployLibNorEvo.psm1

<#
.SYNOPSIS
  This script contains all functions, including deploy session initialization
 
.DESCRIPTION
  Script is "dot-sourced" from installation script in order to load functions.
 
  Start-NEDeploySession - Logging and temporary folder management. Inventory.
  Write-NELog - Writes events to script log
  Set-NELogonMaintenanceMessage - Set interactive logon message text
  Invoke-NEFileTransfer - Manages file transfers
  Invoke-NEWIMoperations - Handles mounting of WIM files to $Global:WorkingFolder\Mount folder
  Add-NEExitCode - Adds exit codes to be summarized to the script's exit code
  Invoke-NEInstallResult - Evaluates exit code and converts it to 0 (succeeded) or 1 (failure)
  Invoke-NEResetDeployEnvironment - Dismounts WIM files, closes processes and removes AppDeploy* variables
  Invoke-NEFinalAction - Called at last line of script to summarize exit codes, dismount wim files and remove temp directory
  Edit-NEIniFileContent - Manipulates content of INI files
 
.NOTES
  Version history
  1.0.0 2023-10-09 - Initial release (MKa)
 
#>


Function Start-NEDeploySession {
  [CmdletBinding()]
  
  $VerbosePreference = "Continue"
  #$VerbosePreference = "SilentlyContinue"

  ### Logging
  # Set logs folder
  $Global:Logpath = "C:\Logs"
  # Create log directory if missing (used for script and application logs)
  if (!( Test-Path $Global:Logpath -PathType Container )) { New-Item -ItemType "directory" -Path $Global:Logpath  | Out-Null }
  # Script log name
  $Script:ScriptLogFile = $Global:Logpath + "\" + "$Global:ScriptName.log"
  # Rotate Script Log file if exist
  If (Test-Path -Path $Script:ScriptLogFile) { Get-ChildItem -Path $Script:ScriptLogFile | Where-Object { $_.BaseName -notmatch '\d{8}_\d{4}$' } | Rename-Item -NewName { "$($_.BaseName)-$($_.LastWriteTime.ToString('yyyyMMdd_HHmmss'))$($_.Extension)" }}
  # Create Script Log File
  if (!(Test-Path $Script:ScriptLogFile)) { New-Item $Script:ScriptLogFile -Type File | Out-Null }
  
  # Create a session unique folder name and create folder
  $Global:WorkingFolder = "C:\Deploy-" + (-join ((48..57) + (97..122) | Get-Random -Count 8 | ForEach-Object {[char]$_}))
  New-Item -ItemType "directory" -Path $Global:WorkingFolder | Out-Null

  # Check if MDT/SCCM session
  try {
    $TSenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
    $Script:MDTIntegration = $true
  }
  catch { $Script:MDTIntegration = $false }
  finally {
    if ($Script:MDTIntegration) { $Script:TSLogpath = $TSenv.Value("LogPath") }
  }

  # Get originating process
  $repeat = $true
  $Process = Get-WmiObject Win32_Process -Filter "ProcessId = $pid"
  $ProcessTree = "$($Process.Name) (ID: $($Process.ProcessId))"
  # Get parent sessions recursively
  while ($repeat -eq $true) {
    $ProcessId = $Process.ParentProcessId
    $Process = Get-WmiObject Win32_Process -Filter "ProcessId = $ProcessId"
    if ($null -eq $Process -or $ProcessTree -like "*RuntimeBroker*") { $repeat = $false }
    else { $ProcessTree += "`n$($Process.Name) (ID: $($Process.ProcessId))" }
  }
  # Translate $Process into known processes
  switch -Wildcard ($ProcessTree) {
    "*KaseyaTaskRunnerx64*" { $OriginatingProcess = "Kaseya" }
    "*upKeeper*" { $OriginatingProcess = "upKeeper" }
    "*Intune*" { $OriginatingProcess = "Intune" }
    Default { $OriginatingProcess = "Unknown" }
  }
    
  # Check pending reboot status
  $PendingReboot = $false
  if ((get-childitem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" | Select-Object Name) -like '*RebootRequired*') { $PendingReboot = "$true" }
  if ((get-childitem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing" | Select-Object Name) -like '*RebootPending*') { $PendingReboot = "$true" }

  # Check if user executing script is elevated ($UserElevated)
  $e = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())
  if ($e.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) { $UserElevated = $true }
  else { $UserElevated = $false }

  # Remove ExitCode variable (if exists)
  if ($script:DeployExitCode) { Remove-Variable DeployExitCode -Scope Script }
  
  # Set initial error code value = 0
  $script:DeployExitCode = 0

  # Perfom inventory
  $InvComputerMake = (Get-WmiObject -Class:Win32_ComputerSystem).Manufacturer
  $InvComputerSerialNumber = (Get-WmiObject win32_bios).SerialNumber
  $InvComputerModel = (Get-WmiObject -Class:Win32_ComputerSystem).Model
  $InvComputerSystemfamily = (Get-WmiObject -Class:Win32_ComputerSystem).SystemFamily
  $InvSysdrvFreeSpace = [math]::Round(((Get-WmiObject -Class Win32_logicaldisk -Filter "DeviceID = 'C:'").Freespace /1GB),0)
  $InvFunctionsLibVer = (Get-Module | Where-Object { $_.Name -eq "DeployLibNorC" }).Version.ToString()
  if (!($User)) { $User = "N/A" }
  
  # Log system and session info
  Write-NELog " "
  Write-NELog "### System"
  Write-NELog "Computer Name: $env:computername"
  Write-NELog "Serial Number: $InvComputerSerialNumber"
  Write-NELog "Make: $InvComputerMake"
  Write-NELog "Model: $InvComputerModel"
  Write-NELog "Model info: $InvComputerSystemfamily"
  Write-NELog " "
  Write-NELog "### Session"
  Write-NELog "Originating Process: $OriginatingProcess"
  Write-NELog "Functions Library Version: $InvFunctionsLibVer"
  Write-NELog "MDT/SCCM Session: $Script:MDTIntegration"
  Write-NELog "ScriptDir: $Global:ScriptDir"
  Write-NELog "ScriptName: $Global:ScriptName"
  Write-NELog "Script Log: $Script:ScriptLogFile"
  Write-NELog "WorkingFolder: $Global:WorkingFolder"
  Write-NELog "Available disk space: $InvSysdrvFreeSpace GB"
  Write-NELog "Pending reboot: $PendingReboot"
  Write-NELog "Credentails supplied for user: $User"
  Write-NELog "Script running elevated: $UserElevated"
  Write-NELog " "
  Write-NELog "### Custom actions"
}


Function Write-NELog {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true)] [string]$Message,         
    [Parameter()] [ValidateSet(1, 2, 3)] [string]$LogLevel = 1,
    [Parameter()] [string]$WriteToScreen = $true
  )
  $TimeGenerated = "$(Get-Date -Format HH:mm:ss).$((Get-Date).Millisecond)+000"
  $TimeGeneratedHMS = "$(Get-Date -Format HH:mm:ss)"
  $Line = '<![LOG[{0}]LOG]!><time="{1}" date="{2}" component="{3}" context="" type="{4}" thread="" file="">'
  $LineFormat = $Message, $TimeGenerated, (Get-Date -Format MM-dd-yyyy), "$($Global:ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)", $LogLevel
  $Line = $Line -f $LineFormat
  Add-Content -Value $Line -Path $Script:ScriptLogFile

  if($WriteToScreen -eq $true) {
    switch ($LogLevel) {
      '1' { Write-Host $TimeGeneratedHMS "-" $Message -ForegroundColor Gray }
      '2' { Write-Host $TimeGeneratedHMS "-" $Message -ForegroundColor Yellow }
      '3' { Write-Host $TimeGeneratedHMS "-" $Message -ForegroundColor Red }
      Default {}
    }           
  }
# if ($writetolistbox -eq $true) { $result1.Items.Add("$Message") }
}


Function Set-NELogonMaintenanceMessage {
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true)] [string]$AppName
  )
  Write-NELog "Setting logon message for application $AppName"
# [void](New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeCaption" -PropertyType String -Value "Systemunderh�ll" -Force)
# [void](New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeText" -PropertyType String -Value "Applikationen $AppName $Action. V�nligen v�nta med att logga in tills datorn har startas om och detta meddelande inte l�gre visas n�r du trycker Ctrl + Alt + Delete f�r att l�sa upp datorn." -Force)
}
  [void](New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeCaption" -PropertyType String -Value "System Maintenence" -Force)
  [void](New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeText" -PropertyType String -Value "Applikationen $AppName $Action. V�nligen v�nta med att logga in tills datorn har startas om och detta meddelande inte l�gre visas n�r du trycker Ctrl + Alt + Delete f�r att l�sa upp datorn." -Force)



Function Invoke-NEFileTransfer {
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true)] [string]$URL,
    [Parameter(Mandatory=$false)] [INT]$DownloadAttempts = 3,
    [Parameter(Mandatory=$false)] [INT]$VerifySeconds = 5
  )
  # Create Secure String for package downloads if -User and -Pass arguments were passed from command line
  if (($User) -and ($Pass)) {
    $SecPass = ConvertTo-SecureString $Pass -asplaintext -force
    $Credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $User,$SecPass
  }
  #Inital values
  $a = 1 
  #$result.InternalErrorCode = "1"
  while ($result.InternalErrorCode -ne 0 -and $a -le $DownloadAttempts) {
    # Get file name
    $filename = $URL.Split('/')[-1]
    # Create random BITS Transfer ID
    $id = (-join ((48..57) + (97..122) | Get-Random -Count 8 | ForEach-Object { [char]$_} ))
    # Initiate transfer - with/without credentials
    if ($Credentials) {
      Write-NELog -Message "Initiating download of file $filename"
      Write-NELog -Message "using supplied credentials - attempt $a of $DownloadAttempts"
      [Void](Start-BitsTransfer -Authentication Basic -Credential $Credentials -Asynchronous -DisplayName $id -Source $URL -Destination $Global:WorkingFolder)
    }
    else {
      Write-NELog -Message "Initiating download of file $filename"
      Write-NELog -Message "No credentials specified - attempt $a of $DownloadAttempts"
      [Void](Start-BitsTransfer -Asynchronous -DisplayName $id -Source $URL -Destination $Global:WorkingFolder)
    }
    # Wait for transfer to complete
    while (((Get-BitsTransfer | Where-Object {($_.DisplayName -eq $id)}).JobState -eq "Connecting") -or ((Get-BitsTransfer | Where-Object {($_.DisplayName -eq $id)}).JobState -eq "Transferring")) { Start-Sleep -Seconds 1 }
    # Get finish time
    $finishtime = Get-Date
    # Get result before completing
    #$result = (Get-BitsTransfer | Where-Object {($_.DisplayName -eq $id)}).InternalErrorCode
    $result = Get-BitsTransfer | Where-Object {$_.DisplayName -eq $id} | Select-Object *
    # Complete transfer
    Get-BitsTransfer | Complete-BitsTransfer
    # Report successfull download and write stats.
    If ($result.InternalErrorCode -eq 0) {
      # Get file size in appropriate unit
      switch ($result.BytesTransferred) {
        {$_ -ge 0 -and $_ -le 1024} { $transferSize = $_ ; $transferUnit = 'B' }
        {$_ -ge 1025 -and $_ -le 1024000} { $transferSize = [math]::Round($_ / 1KB ,2) ; $transferUnit = 'KB' }
        {$_ -ge 1024001 -and $_ -le 1024000000} { $transferSize = [math]::Round($_ / 1MB ,2) ; $transferUnit = 'MB'}
        {$_ -ge 1024000001} { $transferSize = [math]::Round($_ / 1GB ,2) ; $transferUnit = 'GB'}
      }
      # Get transfer time in seconds
      $transfertime = (New-TimeSpan -Start $result.CreationTime -End $finishtime).Seconds
      # Get transfer speed
      $transferspeeed = [math]::Round(($result.BytesTransferred / $transfertime)/ 1MB ,2)
      Write-NELog "Download succeeded on attempt $a"
      Write-NELog "Transfer statistics"
      Write-NELog " File size: $transferSize $transferUnit"
      Write-NELog " Transfer time: $transfertime second(s)"
      Write-NELog " Average speed: $transferspeeed MB/s"
      # Wait $VerifySeconds seconds and check if file still exists
      Start-Sleep -Seconds $VerifySeconds
      If (Test-Path -Path "$Global:WorkingFolder\$filename") { Add-NEExitCode -ExitCode 0 ; Write-NELog -Message "File remains after $VerifySeconds seconds" }
      else { Add-NEExitCode -ExitCode 1 ; Write-NELog -Message "File missing after $VerifySeconds seconds. Check Anti Virus / Anti Malware!" -LogLevel 3}
    }
    # If download failed, report unsuccessful attempt and wait 5-10 seconds, if not last attempt
    else {
      Write-NELog "Download attempt $a failed"
      # If download attempts remains, Increment attempt counter by 1 and wait 5-10 seconds
      If ($a -lt $DownloadAttempts) {
        $a = $a + 1
        Start-sleep -Seconds (5..10 | get-random)
      }
      # If no download attempt remains, Add Exit Code 1, Write red message and Invoke-FinalAction
      else {
        Add-NEExitCode -ExitCode 1 ; Write-NELog -Message "Download FAILED - Exiting script!" -LogLevel 3
        Invoke-FinalAction
      }
    }
  }
}


Function Invoke-NEWIMoperations {
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true)] [string]$WIMfile
  )
  # Create mount directory if missing
  [Void](if (!(Test-Path "$Global:WorkingFolder\mount")) { New-Item -ItemType Directory -Path "$Global:WorkingFolder\mount" })
  # Mount wim file
  Write-NELog -Message "Mount $WIMfile to $Global:WorkingFolder\mount"
  [Void](Mount-WindowsImage -ImagePath "$Global:WorkingFolder\$WIMfile" -Index 1 -Path "$Global:WorkingFolder\Mount")
  # Check result
  $m =  Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\WIMMount\Mounted Images" | Get-ItemProperty | Where-Object "WIM Path" -eq $Global:WorkingFolder\$WIMfile
  if ($m.Status) { 
    Write-NELog -Message "$WIMfile was mounted to $Global:WorkingFolder\mount"
    Add-NEExitCode -ExitCode 0
  }
  else { 
    Write-NELog -Message "Mount FAILED" -LogLevel 3
    Add-NEExitCode -ExitCode 1
  }
}


Function Add-NEExitCode {
  [CmdletBinding()]
  Param (
    [Parameter(Mandatory=$true)] [INT]$ExitCode,
    [Parameter(Mandatory=$false)] [INT]$RebootRequired
  )
  # set $script:DeployExitCode = 1 if not exist prior, else add to existing
  if (!($script:DeployExitCode)) { $script:DeployExitCode = $ExitCode }
  else { $script:DeployExitCode +=$ExitCode }
  # set $script:RebootRequired
  if ($RebootRequired -eq 1) { $script:RebootRequired = 1 }
}


Function  Invoke-NEInstallResult {
  [CmdletBinding()]
  Param (
    [Parameter(Mandatory=$true)] [string]$ExitCode
  )
  # Exit code 0 - The action completed successfully (ERROR_SUCCESS)
  If (($ExitCode -eq 0) -or ($ExitCode -eq 0x80070000 )) {
    Add-NEExitCode -ExitCode 0
    Write-NELog -Message "Operation Succeeded (0)"
  }
  # Exit code 1638 (999) - Another version of this product is already installed (ERROR_PRODUCT_VERSION)
  elseIf (($ExitCode -eq 1638) -or ($ExitCode -eq 666) -or ($ExitCode -eq 0x80070666 )) {
    Add-NEExitCode -ExitCode 0
    Write-NELog -Message "Operation Succeeded ($ExitCode - product already installed)" -LogLevel 2
  }
  # Exit code 1605 (645) - This action is only valid for products that are currently installed. (ERROR_UNKNOWN_PRODUCT)
  elseIf (($ExitCode -eq 1605) -or ($ExitCode -eq 645) -or ($ExitCode -eq 0x80070645 )) {
    Add-ExitCode -ExitCode 0
    Write-Log -Message "Operation Succeeded $ExitCode - product not installed on system" -LogLevel 2
  }
  # Exit code 3010 - restart is required to complete the install (ERROR_SUCCESS_REBOOT_REQUIRED)
  elseIf (($ExitCode -eq 3010) -or ($ExitCode -eq 0x80070bc2 )) {
    Add-NEExitCode -ExitCode 0 -RebootRequired 1
    Write-NELog -Message "Operation Succeeded (3010 - reboot required)" -LogLevel 1
  }
  # Exit code 1707 - Installation operation completed successfully (ERROR_SUCCESS)
  elseIf (($ExitCode -eq 1707) -or ($ExitCode -eq 0x800706ab )) {
    Add-NEExitCode -ExitCode 0
    Write-NELog -Message "Operation Succeeded (1707)"
  }
  # Exit code 1618 - another installation is already in progress (ERROR_INSTALL_ALREADY_RUNNING)
  elseIf (($ExitCode -eq 1618) -or ($ExitCode -eq 0x80070652 )) {
    Write-NELog -Message "Windows installer already running (1618 - another installation is already in progress) - EXITING SCRIPT" -LogLevel 3
    # Breaking error - exit script with error code 1618
    $script:WindowsInstallerBusy = 1
    Invoke-NEFinalAction
  }
  # Exit code anything else
  else { 
    Add-NEExitCode -ExitCode 1
    Write-NELog -Message "Operation FAILED ($ExitCode)" -LogLevel 3
  }
  Start-Sleep -Seconds 2
}


Function Invoke-NEResetDeployEnvironment {
  # Dismount wim image if mounted
  if ((Test-Path -Path "HKLM:\SOFTWARE\Microsoft\WIMMount\Mounted Images") -and (Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\WIMMount\Mounted Images" | Get-ItemProperty | Where-Object "Mount Path" -eq "$Global:WorkingFolder\Mount")) {
    if ((Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\WIMMount\Mounted Images" | Get-ItemProperty | Where-Object "Mount Path" -eq "$Global:WorkingFolder\Mount").Status) {
      Write-NELog -Message "Mounted Windows images detected (WIM file mounted)"
      # Close any open explorer windows
      Write-NELog -Message "Close any Explorer windows ($Global:WorkingFolder\Mount)"
      $shell = New-Object -ComObject Shell.Application
      $window = $shell.Windows() | Where-Object { $_.LocationURL -like "$(([uri]"$Global:WorkingFolder\Mount").AbsoluteUri)*" }
      $window | ForEach-Object { $_.Quit() }
      # Close any running processes
      Write-NELog -Message "Close any runnig processes ($Global:WorkingFolder\Mount)"
      get-process | Where-Object { $_.Path -like "$Global:WorkingFolder\Mount\*" } | ForEach-Object { Stop-Process -Name $_.Name -Force -ErrorAction SilentlyContinue }
      Start-Sleep -Seconds 5
      # Dismount image
      try {
        Write-NELog -Message "Dismount $Global:WorkingFolder\Mount"
        Dismount-WindowsImage -Path "$Global:WorkingFolder\Mount" -Discard
        $Script:WimStatus = $true
        Write-NELog "Windows image successfully dismounted."
      }
      catch {
        # Failed to Dismount normally. Setting up a scheduled task to unmount after next reboot (exit code 3010)
        Write-NELog -Message "Dismount $Global:WorkingFolder\Mount failed. Setting scheduled task to perform dismount after next reboot." -LogLevel 2
        $Script:WimStatus = $false
        Add-NEExitCode -ExitCode 3010
        $STAction = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -command "& {Get-WindowsImage -Mounted | Where-Object {$_.MountStatus -eq ''Invalid''} | ForEach-Object {$_ | Dismount-WindowsImage -Discard -ErrorVariable wimerr; if ([bool]$wimerr) {$errflag = $true}}; If (-not $errflag) {Clear-WindowsCorruptMountPoint; Unregister-ScheduledTask -TaskName ''CleanupWIM'' -Confirm:$false}}"'
        $STTrigger = New-ScheduledTaskTrigger -AtStartup
        Register-ScheduledTask -Action $STAction -Trigger $STTrigger -TaskName "CleanupWIM" -Description "Clean up WIM Mount points that failed to dismount" -User "NT AUTHORITY\SYSTEM" -RunLevel Highest -Force
      }
    }
  }

  # Remove all AppDeploy* variables
  Write-NELog -Message "Remove AppDeploy* variables"
  Get-Variable | Where-Object { $_.Name -Like "*AppDeploy*" } | Remove-Variable -ErrorAction SilentlyContinue
}


Function Invoke-NEFinalAction {
  Write-NELog -Message " "
  Write-NELog -Message "### Final Actions"
  # Reset environment - Dismount any mounted Windows Images (WIM) files
  Invoke-NEResetDeployEnvironment
  # Remove temporary folder if $Script:WimStatus is $true
  if ( $Script:WimStatus = $true ) {
    # Close any open explorer windows
    Write-NELog -Message "Close any Explorer windows ($Global:WorkingFolder)"
    $shell = New-Object -ComObject Shell.Application
    $window = $shell.Windows() | Where-Object { $_.LocationURL -like "$(([uri]"$Global:WorkingFolder").AbsoluteUri)*" }
    $window | ForEach-Object { $_.Quit() }
    # Close any running processes
    Write-NELog -Message "Close any runnig processes ($Global:WorkingFolder)"
    get-process | Where-Object { $_.Path -like "$Global:WorkingFolder\*" } | ForEach-Object { Stop-Process -Name $_.Name -Force -ErrorAction SilentlyContinue }
    Write-NELog -Message "Remove temporary folder $Global:WorkingFolder"
    Remove-Item $Global:WorkingFolder -Recurse -Force
    if (!(Test-Path -Path $Global:WorkingFolder)) { Write-NELog "Temporary folder was successfully removed" }
    else { Write-NELog -Message "FAILURE - Temorary folder could not be removed." -LogLevel 3 }
    }
  else { Write-NELog -Message "FAILURE - Windows Image dismount FAILED. Wim mount, temporary folder and files will remain on computer and must be removed manually." -LogLevel 3 }

  # Remove logon maintenance message
  Write-NELog "Removing logon message"
  [void](New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeCaption" -PropertyType String -Value "" -Force)
  [void](New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeText" -PropertyType String -Value "" -Force)
  # Exit with "1618" if $script:WindowsInstallerBusy is 1
  if ($script:WindowsInstallerBusy -eq 1) {
    Write-NELog -Message " "
    Write-NELog -Message "Final status: FAILURE - Windows installer already running (1618)"
    # Copy script log to TS log path if $Script:MDTIntegration is true
    if ($true -eq $Script:MDTIntegration) { Copy-Item -Path $ScriptLogFile -Destination $Script:TSLogpath }
    Start-Sleep -Seconds 1
    Exit 1618
  }
  elseif ( ($script:DeployExitCode -eq 0) -and ($script:RebootRequired -eq 1) ) {
    Write-NELog -Message " "
    Write-NELog -Message "Final status: Success - reboot required (3010)"
    # Copy script log to TS log path if $Script:MDTIntegration is true
    if ($true -eq $Script:MDTIntegration) { Copy-Item -Path $ScriptLogFile -Destination $Script:TSLogpath }
    Start-Sleep -Seconds 1
    Exit 3010
  }
  # Exit with "0" if exit code is missing or $script:DeployExitCode is 0 and $script:RebootRequired is not 1 (or missing)
  elseif ( (!($script:DeployExitCode)) -or ($script:DeployExitCode -eq 0)) {
    Write-NELog -Message " "
    Write-NELog -Message "Final status: SUCCESS (0)"
    # Copy script log to TS log path if $Script:MDTIntegration is true
    if ($true -eq $Script:MDTIntegration) { Copy-Item -Path $ScriptLogFile -Destination $Script:TSLogpath }
    Start-Sleep -Seconds 1
    Exit 0
  }
  # Exit with "1" if $script:DeployExitCode is > 0
  else {
    Write-NELog -Message " "
    Write-NELog  -Message "Final status: FAILURE (1)" -LogLevel 3
    # Copy script log to TS log path if $Script:MDTIntegration is true
    if ($true -eq $Script:MDTIntegration) { Copy-Item -Path $ScriptLogFile -Destination $Script:TSLogpath }
    Start-Sleep -Seconds 5
    Exit 1
  }
}


Function Edit-NEIniFileContent {
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true)] [string]$IniFile,
    [Parameter(Mandatory=$true)] [String]$Content,
    [Parameter(Mandatory=$true)] [String]$Section,
    [Parameter(Mandatory=$true)] [String]$Value
  )
  
  if (Test-Path -Path $IniFile) {
    # Install NuGet package manager if missing
    try {
      $packages = (Get-PackageProvider).name
      if (!($packages -like ("NuGet"))) { Install-PackageProvider -name NuGet -Force }
      Import-Module PsIni -Force
    }
    catch {
      Install-Module -Scope CurrentUser PsIni -Force
      Import-Module PsIni -Force
    }
    if ( $Content -ne (Get-IniContent $IniFile)[$Section][$Value]) {
      $ini = Get-IniContent $IniFile
      $ini[$Section][$Value] = $Content
      $ini | Out-IniFile -Force -Encoding Unicode -FilePath $IniFile
      Write-NELog -Message "INI file $IniFile content has been added or modified"
      Invoke-NEInstallResult -ExitCode 0
    }
    else {
      Write-NELog -Message "Content of $IniFile is was up to date, no changees were made" -LogLevel 2
      Invoke-NEInstallResult -ExitCode 0
    }
  }
  else {
    Write-NELog -Message "$IniFile was not found" -LogLevel 3
    Invoke-NEInstallResult -ExitCode 1
  }
}