Archive/DeployLibNorEvo-1.1.7.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 Install-NEWingetApplication - Install Winget application by ID Uninstall-NEWingetApplication - Uninstall Winget application by ID Invoke-NEWingetInstallOrUpdate - Install or update Winget to min version. Called by functions Install-NEWingetApplication and Uninstall-NEWingetApplication Invoke-NERemovePublicDesktopShortcuts - Removes shortcuts on public desktop added during install session Edit-NEIniFileContent - Manipulate content of INI files 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 .NOTES Version history 1.1.7 2024-01-31 - Everything works excempt install by Winget 1.1.3 2024-01-31 - Merges from template 1.0 and fixes 1.1.2 2024-01-24 - Updated and new functions merged from template 1.0 1.1.0 2023-10-09 - Battling the Publish-Module function 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" } # Inventory public desktop shortcuts pre installation $script:ShortcutsPre = (Get-ChildItem -Path "C:\Users\Public\Desktop" -Filter "*.lnk").Fullname # 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 # Perform 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 "DeployLibNorEvo" }).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)] [string]$DestinationFileName, [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) { # Set $filename If ($DestinationFileName) { $filename = $DestinationFileName } else { $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 credentails from Nordlo dist server if ($Credentials -and ($URL -like "*dist.*.nordlo.cloud*")) { Write-NELog -Message "Initiating download of file $filename (using credentials)" Write-NELog -Message "using supplied credentials - attempt $a of $DownloadAttempts" # With / without DestinationFileName specified if ($DestinationFileName) { $null = Start-BitsTransfer -Authentication Basic -Credential $Credentials -Asynchronous -DisplayName $id -Source $URL -Destination "$global:WorkingFolder\$DestinationFileName" } else { $null = Start-BitsTransfer -Authentication Basic -Credential $Credentials -Asynchronous -DisplayName $id -Source $URL -Destination $global:WorkingFolder } } # Without credentials else { Write-NELog -Message "Initiating download of file $filename (not using credentials)" Write-NELog -Message "No credentials specified - attempt $a of $DownloadAttempts" # With / without DestinationFileName specified if ($DestinationFileName) { $null = Start-BitsTransfer -Asynchronous -DisplayName $id -Source $URL -Destination "$global:WorkingFolder\$DestinationFileName" } else { $null = 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 $m = 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" $m = 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 Install-WingetApplication - Install Winget application by ID function Install-NEWingetApplication { param ( [Parameter(Mandatory=$true)] [string]$Id ) # Make sure Winget is installed and correct min version Invoke-WingetInstallOrUpdate # Resolve winget.exe path $WingetBin = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe\winget.exe" # Check if application matching id is installed $WingetLookup = (& $WingetBin list --id $Id --accept-source-agreements) | Select-Object -Last 1 # Start installation if application is not found on system if (!($WingetLookup.Contains($Id))) { $WingetLogFile = "C:\Logs\" +$ID + "-install-" + (Get-Date -Format "yyyyMMdd_HHmmss") + ".log" Write-NELog -Message "Installing Winget package $Id" $i = Start-Process -FilePath $WingetBin -ArgumentList "install --exact --id $Id --Log $WingetLogFile --accept-source-agreements --accept-package-agreements --silent" -Wait -PassThru Invoke-InstallResult -ExitCode $i.Exitcode if (Test-Path -Path $WingetLogFile) { Write-NELog -Message "See $WingetLogFile for details" } } else { Write-NELog -Message "$Id already installed on system" -LogLevel 2 } } # Function Uninstall-WingetApplication - uninstall Winget application by ID function Uninstall-NEWingetApplication { param ( [Parameter(Mandatory=$true)] [string]$Id ) # Make sure Winget is installed and correct min version Invoke-WingetInstallOrUpdate # Resolve winget.exe path $WingetBin = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe\winget.exe" # Check if application matching id is installed $WingetLookup = (& $WingetBin list --id $Id --accept-source-agreements) | Select-Object -Last 1 # Start Uninstallation if application is found on system if ($WingetLookup.Contains($Id)) { $WingetLogFile = "C:\Logs\" +$ID + "-uninstall-" + (Get-Date -Format "yyyyMMdd_HHmmss") + ".log" Write-NELog -Message "Uninstalling Winget package $Id" $u = Start-Process -FilePath $WingetBin -ArgumentList "uninstall --exact --id $Id --Log $WingetLogFile --accept-source-agreements --silent" -Wait -PassThru Invoke-InstallResult -ExitCode $u.Exitcode if (Test-Path -Path $WingetLogFile) { Write-NELog -Message "See $WingetLogFile for details" } } else { Write-NELog -Message "$Id not found on system" -LogLevel 2 } } # Function Invoke-WingetInstallOrUpdate - Install or update Winget to min version. Called by functions Install-WingetApplication and Uninstall-WingetApplication function Invoke-NEWingetInstallOrUpdate { param () # Winget URLs and settings $Winget_Microsoft_Desktop_Installer = "https://aka.ms/getwinget" $Winget_Microsoft_VC_Libs_x64 = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx" $Winget_Microsoft_UI_Xaml = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.5/Microsoft.UI.Xaml.2.8.x64.appx" $Winget_MinVer = "1.6" # Resolve winget.exe path and get version if exists $WingetBin = (Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe\winget.exe").Path if ($WingetBin) { $WingetVersion = ((& $WingetBin --version)).substring(1) } # Install Winget if missing or too old if (!($WingetBin) -or $WingetVersion -lt $Winget_MinVer) { Write-NELog -Message "Winget missing or too old - Installing latest version" Invoke-FileTransfer -URL $Winget_Microsoft_VC_Libs_x64 -DestinationFileName "Microsoft.VCLibs.x64.14.00.Desktop.appx" Invoke-FileTransfer -URL $Winget_Microsoft_UI_Xaml -DestinationFileName "Microsoft.UI.Xaml.x64.appx" Invoke-FileTransfer -URL $Winget_Microsoft_Desktop_Installer -DestinationFileName "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" $null = Add-AppxProvisionedPackage -Online -PackagePath "$global:WorkingFolder\Microsoft.VCLibs.x64.14.00.Desktop.appx" -SkipLicense $null = Add-AppxProvisionedPackage -Online -PackagePath "$global:WorkingFolder\Microsoft.UI.Xaml.x64.appx" -SkipLicense $null = Add-AppxProvisionedPackage -Online -PackagePath "$global:WorkingFolder\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" -SkipLicense Start-Sleep -Seconds 10 # Proceed if update was successful, abort if not $WingetBin = (Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe\winget.exe").Path $WingetVersion = ((& $WingetBin --version)).substring(1) if ($WingetVersion -ge $Winget_MinVer ) { Write-NELog -Message "Winget was successfully installed/updated" } else { Invoke-InstallResult -ExitCode 1 Write-NELog -Message "Winget could not be updated to minimal version ($Winget_MinVer) - exiting" -LogLevel 3 Invoke-FinalAction } } else { Write-NELog -Message "A supported version of Winget was found" } } # Function Invoke-NERemovePublicDesktopShortcuts - Removes shortcuts on public desktop added during install session function Invoke-NERemovePublicDesktopShortcuts { param () # Get list of current public desktop shortcuts $ShortcutsPost = (Get-ChildItem -Path "C:\Users\Public\Desktop" -Filter "*.lnk").FullName # If public desktop contained shortcuts pre installation if ($ShortcutsPre) { # Compile list of new shortcuts $ShortcutsNew = Compare-Object -ReferenceObject $script:ShortcutsPre -DifferenceObject $ShortcutsPost | Where-Object { $_.SideIndicator -eq '=>' } | ForEach-Object { $_.InputObject } # Remove new shortcuts if ($ShortcutsNew) { $ShortcutsNew | ForEach-Object { if (Test-Path -Path $_) { Write-NELog -Message "Removing shortcut $($_)" Remove-Item -Path $_ } } } else { Write-NELog -Message "No new shortcuts found on public desktop" } } # If public desktop did not contain shortcuts pre installation else { if ($ShortcutsPost) { $ShortcutsPost | ForEach-Object { if (Test-Path -Path $_) { Write-NELog -Message "Removing shortcut $($_)" Remove-Item -Path $_ } } } else { Write-NELog -Message "No new shortcuts found on public desktop" } } } 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-InstallResult -ExitCode 0 } else { Write-NELog -Message "Content of $IniFile is was up to date, no changees were made" -LogLevel 2 Invoke-InstallResult -ExitCode 0 } } else { Write-NELog -Message "$IniFile was not found" -LogLevel 3 Invoke-InstallResult -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 1707 - Installation operation completed successfully (ERROR_SUCCESS) elseif (($ExitCode -eq 1707) -or ($ExitCode -eq 0x800706ab )) { Add-NCExitCode -ExitCode 0 Write-NCLog -Message "Operation Succeeded (1707)" } # 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 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-NEExitCode -ExitCode 0 Write-NELog -Message "Operation Succeeded $ExitCode - product not installed on system" -LogLevel 2 } # 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 Write-NELog -Message "Dismount $global:WorkingFolder\Mount" $m = Dismount-WindowsImage -Path "$global:WorkingFolder\Mount" -Discard Start-Sleep -Seconds 5 $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 "Windows image successfully dismounted." Remove-Item -Path $global:WorkingFolder\Mount -Force $script:WimStatus = $true } else { Write-NELog -Message "Windows image dismount FAILED." -LogLevel 3 $script:WimStatus = $false } } } else { Write-NELog -Message "No mounted Windows images found (ok)" $script:WimStatus = $true } # 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" # Remove public desktop shortcuts created during session if $Global:PublicDesktopShortcuts=Remove if ($Global:PublicDesktopShortcuts -eq "Remove") { Invoke-NERemovePublicDesktopShortcuts } # 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" $null = New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeCaption" -PropertyType String -Value "" -Force $null = New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "LegalNoticeText" -PropertyType String -Value "" -Force # Remove files used by Invoke-NotificationSchedule if (Test-Path -Path "$AppInstallLogDir\NordloGreen.jpg") { Remove-Item -Path "$AppInstallLogDir\NordloGreen.jpg" -Force } if (Test-Path -Path "$AppInstallLogDir\NotificationForm.ps1") { Remove-Item -Path "$AppInstallLogDir\NotificationForm.ps1" -Force } # Exit with "3010" if $script:DeployExitCode is 0 and $script:RebootRequired is 1 if ( ($script:DeployExitCode -eq 0) -and ($script:RebootRequired -eq 1) ){ Write-NELog -Message " " Write-NELog -Message "Final status: Success - reboot required (3010)" 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)" 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 Start-Sleep -Seconds 6 Exit 1 } } |