AlkanePSF.psm1

Add-Type -AssemblyName System.IO.Compression.FileSystem

$script:ApplicationsModel = @()
$script:ProcessesModel = @()

$fileRedirectionDllName = "FileRedirectionFixup.dll"
$regLegacyDllName = "RegLegacyFixups.dll"
$envVarDllName = "EnvVarFixup.dll"
$dynamicLibraryDllName = "DynamicLibraryFixup.dll"
$mfrDllName = "MFRFixup.dll"
$traceDllName = "TraceFixup.dll"


#*******************************************
#********************************************
#function to remove directories with long paths
#*******************************************
#********************************************

function Remove-LongPathDirectory 
{
    Param(
        [string]$FullPath
    )
    & cmd /c rmdir "$FullPath" /s /q
}

#*******************************************
#********************************************
#function to find an exe in the Windows SDK
#*******************************************
#********************************************

function Get-WindowsSDKExe {    
    param(
        [string]$ExeName
    )

    try {
        $sdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Bin\"
        if (!(test-path $sdkPath)) {
            write-host "**WARNING** Could not find $sdkPath" -ForegroundColor DarkYellow
            return
        }

        if($env:PROCESSOR_ARCHITECTURE -eq "x86") {
            $pathToExe = (Get-ChildItem $sdkPath -recurse -include $ExeName -ErrorAction SilentlyContinue | Where-Object FullName -like "*\x86\*" | Select-Object -First 1 -ExpandProperty FullName)
        } else {
            $pathToExe = (Get-ChildItem $sdkPath -recurse -include $ExeName -ErrorAction SilentlyContinue | Where-Object FullName -like "*\x64\*" | Select-Object -First 1 -ExpandProperty FullName)
        }

        return $pathToExe

    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#********************************************
#********************************************
#function to install PSF prereqs
#********************************************
#********************************************

function Install-AlkanePSFPrerequisite() {
param(
    [bool]$ForceReinstall=$false 
)

    try {
                 
        #installs
        #https://developer.microsoft.com/en-gb/windows/downloads/windows-sdk/
        #with
        #Windows SDK Signing Tools for Desktop Apps
        #Windows SDK for UWP Managed Apps
        #Windows SDK for UWP C++ Apps
        #Windows SDK for UWP Apps Localization
    
        $makeAppxPath = Get-WindowsSDKExe -ExeName "makeappx.exe"
              
        if (($null -eq $makeAppxPath -or $makeAppxPath -eq "") -or $ForceReinstall) {
            write-host "Installing Windows SDK tools for MSIX."
                
            $downloadExePath = "$env:temp\wdksetup.exe"
            $url = "https://go.microsoft.com/fwlink/?linkid=2083338"

            if (test-path $downloadExePath) {
                #remove before downloading
                write-host "Removing $downloadExePath."
                remove-item -Path $downloadExePath -Force -Recurse -ErrorAction SilentlyContinue
            }

            write-host "Downloading Windows SDK from $url."

            (New-Object Net.WebClient).DownloadFile($url, $downloadExePath)

            if (test-path $downloadExePath) {
                write-host "Installing Windows SDK."
    
                $result = (Start-Process -FilePath $downloadExePath -ArgumentList "/features","OptionId.SigningTools","OptionId.UWPManaged","OptionId.UWPCPP","OptionId.UWPLocalized","/quiet","/norestart" -Wait -Passthru).ExitCode
            
                if ($result -eq 0) {
                    write-host "Windows SDK installed with exit code $result."     
                } else {
                    write-host "**WARNING** Windows SDK installed with exit code $result." -ForegroundColor DarkYellow  
                }

                #remove download
                write-host "Removing $downloadExePath."
                Remove-Item $downloadExePath -Force -Recurse -ErrorAction SilentlyContinue
            } else {
                write-host "**WARNING** Could not download Windows SDK from $url" -ForegroundColor DarkYellow
            }
        } else {
            write-host "Windows SDK already installed."
        }

        $nupkg = Get-Package | Where-Object Name -eq "Microsoft.PackageSupportFramework" | Select-Object -ExpandProperty Source
          
        if (($null -eq $nupkg) -or $ForceReinstall) {        
            write-host "Installing Microsoft's Package Support Framework."

            $nuget = get-packagesource | Where-Object ProviderName -eq "Nuget"
            if ($null -eq $nuget) {
                Register-PackageSource -Name nuget.org -Location https://www.nuget.org/api/v2 -ProviderName NuGet
                Install-Package -Name Microsoft.PackageSupportFramework -ProviderName Nuget -Force
                write-host "Microsoft's Package Support Framework installed." 
            } else {
                $package = Get-Package | Where-Object Name -eq "Microsoft.PackageSupportFramework"
                if ($null -eq $package) {
                    Install-Package -Name Microsoft.PackageSupportFramework -ProviderName Nuget -Force
                    write-host "Microsoft's Package Support Framework installed." 
                } else {
                    if ($ForceReinstall) {
                        Install-Package -Name Microsoft.PackageSupportFramework -ProviderName Nuget -Force 
                        write-host "Microsoft's Package Support Framework installed." 
                    }
                }
            }
        } else {
            write-host "Microsoft's Package Support Framework already installed."
        }


        $tmPsfLocation = "$env:temp\TMPSF"

        if ((!(test-path $tmPsfLocation)) -or $ForceReinstall) {
            write-host "Installing Tim Mangan's Package Support Framework."

            $extractFolder = "$env:temp\TMPSF"
            $downloadZipPath = "$env:temp\$alkaneTmZipName"
            $url = "https://github.com/TimMangan/MSIX-PackageSupportFramework/raw/develop/$alkaneTmZipName"      
       
            if (test-path $downloadZipPath) {
                #remove before downloading
                write-host "Removing $downloadZipPath."
                remove-item -Path $downloadZipPath -Force -Recurse -ErrorAction SilentlyContinue
            }

            if (test-path $extractFolder) {
                #remove before extracting
                write-host "Removing $extractFolder."
                Remove-LongPathDirectory $extractFolder
            }

            write-host "Downloading Tim Mangan's PSF from $url."

            #download zip
            (New-Object Net.WebClient).DownloadFile($url, $downloadZipPath)

            #if zip downloaded
            if (test-path $downloadZipPath) {

                #extract it
                [IO.Compression.Zipfile]::ExtractToDirectory($downloadZipPath,$extractFolder);

                #if extracted ok, extract the release folder
                if (test-path "$extractFolder\ReleasePsf.zip") {
                    [IO.Compression.Zipfile]::ExtractToDirectory("$extractFolder\ReleasePsf.zip",$extractFolder);
                    write-host "Tim Mangen's Package Support Framework installed."
                }

                #remove download
                write-host "Removing $downloadZipPath."
                Remove-Item $downloadZipPath -Force -Recurse -ErrorAction SilentlyContinue
            }
        } else {
             write-host "Tim Mangan's Package Support Framework already installed."
        }

        write-host "Finished installing prerequisites."
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#********************************************
#********************************************
#function to extract/stage an MSIX package
#********************************************
#********************************************

function New-AlkanePSFStagedPackage { 
    try {
        if (!(test-path $alkanePathToMSIX)) {
            write-host "**WARNING** Could not find $alkanePathToMSIX" -ForegroundColor DarkYellow
            return
        }

        $packageName = (get-item $alkanePathToMSIX).BaseName
        $workFolder = (get-item $alkanePathToMSIX).Directory.FullName
        $stagingFolder    = "$workFolder\$($packageName)_Staged\"
      
        write-host "Removing $stagingFolder"
        if (Test-Path $stagingFolder) {
            Remove-LongPathDirectory $stagingFolder
        }

        write-host "Finding MakeAppx.exe."
        $makeAppxPath = Get-WindowsSDKExe -ExeName "makeappx.exe"

        if ($makeAppxPath -ne "") {     
            write-host "Extracting (staging) package to $stagingFolder."
            $result = (Start-Process -FilePath $makeAppxPath -ArgumentList "unpack","/p","$alkanePathToMSIX","/d","$StagingFolder" -Wait -Passthru).ExitCode

            if ($result -eq 0) {
                write-host "Package extracted (staged) to $stagingFolder with exit code $result." 
            } else {
                write-host "**WARNING** Package extracted (staged) to $stagingFolder with exit code $result." -ForegroundColor DarkYellow  
            }
               
        } else {
            write-host "Cannot find MakeAppx.exe"
        }
       
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#function to extract AppxManifest.xml from MSIX.
#*******************************************
#********************************************

function Get-AlkaneAppxManifest {    
    param(
        [string]$PathToAppx 
    )

    try {
        $zip = [IO.Compression.ZipFile]::OpenRead($alkanePathToMSIX)
        $zip.Entries | Where-Object {$_.Name -eq 'AppxManifest.xml'} | ForEach-Object {
            [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $PathToAppx, $true)
        }
        $zip.Dispose()
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#********************************************
#********************************************
#function to get the name of a filename without the extension
#********************************************
#********************************************

function Get-ExeNameNoExtension {
     param(        
        [string]$PathToEXE
    )

    try {
        if ($PathToEXE -like "*/*" -or $PathToEXE -like "*\*") {
            $PathToEXE = $PathToEXE.replace('\','/').split('/')[-1]
        }

        if ($PathToEXE -like "*.*") {
            $PathToEXE = $PathToEXE.split('.')[0]
        }

        return $PathToEXE
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#function to get a PSF fixup
#*******************************************
#********************************************

function Get-AlkanePSFFixup {
    
    try {
        $allFixups = @()
    
        foreach ($fixup in $script:ProcessesModel.fixups) {
            $allFixups += $fixup
        } 
 
        return ($allFixups)
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#Function to add a supported Fixup application
#*******************************************
#********************************************

Function Add-AlkanePSFApplication {
    param(               
        [string]$FixupApplicationId,          
        [string]$FixupWorkingDirectory,
        [array]$FixupArguments,
        [bool]$FixupInPackageContext=$false      
    )
     
    try {

        if (!(test-path $alkanePathToMSIX)) {
            write-host "**WARNING** Could not find $alkanePathToMSIX" -ForegroundColor DarkYellow
            return
        }

        #check if we have an 'application' in our dynamic config.json
        $targetExe = ($script:ApplicationsModel | Where-Object id -eq "$FixupApplicationId").executable
        if ($null -ne $targetExe -and $targetExe -ne "") {
            write-host "**WARNING** Application $FixupApplicationId already exists. Will not add again." -ForegroundColor DarkYellow
            return
        }
   
        $workFolder = (get-item $alkanePathToMSIX).Directory.FullName
        $packageName = (get-item $alkanePathToMSIX).Basename

        #Create a subfolder to stage the extraction of the package
        $stagingFolder    = "$workFolder\$($packageName)_Staged\" 

        $appxManifest    = "$($stagingFolder)AppxManifest.xml"

        if (!(test-path $appxManifest)) {
            write-host "**WARNING** Could not find $appxManifest" -ForegroundColor DarkYellow
            return
        }

        [xml]$appinfo = Get-Content -Path $appxManifest
               
        $applications = $appinfo.Package.Applications.Application
        if ($null -ne $applications) {
    
            $exe = $applications | Where-Object id -eq "$FixupApplicationId" | Select-Object -ExpandProperty executable
            $availableApps = ($applications.id -join " ")
            if ($null -eq $exe -or $exe -eq "") {
                write-host "**WARNING** Could not find application ID: $FixupApplicationId in AppxManifest.xml. Available app id's are: $availableApps. Please change the application ID." -ForegroundColor DarkYellow
                return
            } else {
                $script:ApplicationsModel += ,([ordered]@{
                    'id' = $FixupApplicationId
                    'executable' = $exe
                    'workingDirectory' = $FixupWorkingDirectory
                    'inPackageContext' = $FixupInPackageContext
                    'arguments' = (($FixupArguments | ForEach-Object {"`"" + $_ + "`""}) -join " ");
                })
            }
        }

        $exe = Get-ExeNameNoExtension $exe

        $script:ProcessesModel += ,([ordered]@{
            'executable' = $exe
        })
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}
    
   
#*******************************************
#********************************************
#Function to add file redirection fixup
#*******************************************
#********************************************

Function Add-AlkanePSFFileRedirectionFixup {
    param(        
        [string]$FixupApplicationId, 
        [ValidateSet("packageRelative","packageDriveRelative","knownFolders")]  
        [string]$FixupType,  
        [string]$FixupBase,     
        [array]$FixupPatterns,
        [string]$FixupId
    )
        
    write-host "Adding File Redirection Fixup for $FixupApplicationId"

    try {
        #check if we have an 'application' in our dynamic config.json
        $targetExe = ($script:ApplicationsModel | Where-Object id -eq "$FixupApplicationId").executable
        if ($null -eq $targetExe -or $targetExe -eq "") {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
            return
        }
        #extract the process name without the extension
        $targetExe = Get-ExeNameNoExtension -PathToEXE $targetExe

        $FileRedirectionFixupModel = [ordered]@{
            dll=$fileRedirectionDllName;
            config=@{
            }
        }

        #check if fixup has been added previously
        $fixups = ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups | Where-Object dll -eq $fileRedirectionDllName
        if ($null -eq $fixups -or $fixups.Count -eq 0) {      
            ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups += @($FileRedirectionFixupModel)        
        }

        $fixupstype = (($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups | Where-Object dll -eq $fileRedirectionDllName).config.redirectedPaths.$FixupType
        if ($null -eq $fixupstype -or $fixupstype.Count -eq 0) {      
            ($script:ProcessesModel.fixups | Where-Object dll -eq $fileRedirectionDllName).config.redirectedPaths += @{ $FixupType = $null }     
        }        

        switch ($FixupType) {
            "packageRelative" { 
                ($script:ProcessesModel.fixups | Where-Object dll -eq $fileRedirectionDllName).config.redirectedPaths.$FixupType += ,@{ 
                    base=$FixupBase;
                    patterns=$FixupPatterns;  
                }  
            }
            "packageDriveRelative" { 
                ($script:ProcessesModel.fixups | Where-Object dll -eq $fileRedirectionDllName).config.redirectedPaths.$FixupType += ,@{ 
                    base=$FixupBase;
                    patterns=$FixupPatterns;  
                }                
            }
            "knownFolders" {
                ($script:ProcessesModel.fixups | Where-Object dll -eq $fileRedirectionDllName).config.redirectedPaths.$FixupType += ,@{                    
                    id=$FixupId;
                    relativePaths = ,@{                           
                        base=$FixupBase;
                        patterns=$FixupPatterns;  
                    }
                }
            }
        }


    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#Function to add reg legacy fixup
#*******************************************
#********************************************

Function Add-AlkanePSFRegLegacyFixup {
    param(        
        [string]$FixupApplicationId, 
        [ValidateSet("ModifyKeyAccess","FakeDelete")]  
        [string]$FixupType, 
        [ValidateSet("HKCU","HKLM")]
        [string]$FixupHive, 
        [ValidateSet("FULL2RW","FULL2R","Full2MaxAllowed","RW2R","RW2MaxAllowed")]
        [string]$FixupAccess, 
        [array]$FixupPatterns
    )

    write-host "Adding Reg Legacy Fixup for $FixupApplicationId"

    try {
        #check if we have an 'application' in our dynamic config.json
        $targetExe = ($script:ApplicationsModel | Where-Object id -eq "$FixupApplicationId").executable
        if ($null -eq $targetExe -or $targetExe -eq "") {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
            return
        }
        #extract the process name without the extension
        $targetExe = Get-ExeNameNoExtension -PathToEXE $targetExe

        $RegLegacyFixupModel = [ordered]@{
            dll=$regLegacyDllName;
            config=@{      
            }
        }

        #check if fixup has been added previously
        $fixups = ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups | Where-Object dll -eq $regLegacyDllName
        if ($null -eq $fixups -or $fixups.Count -eq 0) {      
            ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups += @($RegLegacyFixupModel)        
        }
           

        switch ($FixupType) {
            "ModifyKeyAccess" { 
                ($script:ProcessesModel.fixups | Where-Object dll -eq $regLegacyDllName).config.remediation += ,@{ 
                    type=$FixupType;
                    hive=$FixupHive;
                    access=$FixupAccess;
                    patterns=$FixupPatterns;  
                }  
            }
           "FakeDelete" { 
                ($script:ProcessesModel.fixups | Where-Object dll -eq $regLegacyDllName).config.remediation += ,@{ 
                    type=$FixupType;
                    hive=$FixupHive;
                    access=$FixupAccess;
                    patterns=$FixupPatterns;  
                }  
            }     
        }
                
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#function to add env var fixup
#*******************************************
#********************************************

Function Add-AlkanePSFEnvVarFixup {
    param(     
        [string]$FixupApplicationId,    
        [string]$FixupVarName, 
        [string]$FixupVarValue, 
        [bool]$FixupVarUseRegistry=$true
    )
      
    write-host "Adding Env Var Fixup for $FixupApplicationId"

    try {
        
        #check if we have an 'application' in our dynamic config.json
        $targetExe = ($script:ApplicationsModel | Where-Object id -eq "$FixupApplicationId").executable
        if ($null -eq $targetExe -or $targetExe -eq "") {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
            return
        }
        #extract the process name without the extension
        $targetExe = Get-ExeNameNoExtension -PathToEXE $targetExe

        $EnvVarFixupModel = [ordered]@{
            dll=$envVarDllName;
            config=@{        
            }
        }
            
        #check if fixup has been added previously
        $fixups = ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups | Where-Object dll -eq $envVarDllName
        if ($null -eq $fixups -or $fixups.Count -eq 0) {      
            ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups += @($EnvVarFixupModel)        
        }

        ($script:ProcessesModel.fixups | Where-Object dll -eq $envVarDllName).config.envVars += ,@{ 
            name=$FixupVarName;
            value=$FixupVarValue;
            useRegistry=$FixupVarUseRegistry;
        }  
     
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#Function to add dynamic library fixup
#*******************************************
#********************************************

Function Add-AlkanePSFDynamicLibraryFixup {
    param(        
        [string]$FixupApplicationId, 
        [string]$FixupDllName, 
        [string]$FixupDllFilepath,
        [bool]$FixupForcePackageDllUse=$true     
    )
        
    write-host "Adding Dynamic Library Fixup for $FixupApplicationId"

    try {
        #check if we have an 'application' in our dynamic config.json
        $targetExe = ($script:ApplicationsModel | Where-Object id -eq "$FixupApplicationId").executable
        if ($null -eq $targetExe -or $targetExe -eq "") {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
            return
        }
        #extract the process name without the extension
        $targetExe = Get-ExeNameNoExtension -PathToEXE $targetExe

        $DynamicLibraryFixupModel = [ordered]@{
            dll=$dynamicLibraryDllName;
            config=@{
                forcePackageDllUse=$FixupForcePackageDllUse;
            }
        }

        #check if fixup has been added previously
        $fixups = ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups | Where-Object dll -eq $dynamicLibraryDllName
        if ($null -eq $fixups -or $fixups.Count -eq 0) {      
            ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups += @($DynamicLibraryFixupModel)        
        }

        ($script:ProcessesModel.fixups | Where-Object dll -eq $dynamicLibraryDllName).config.relativeDllPaths += ,@{ 
            name=$FixupDllName;
            filepath=$FixupDllFilepath;
        }  

    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#Function to add MFR fixup
#*******************************************
#********************************************

Function Add-AlkanePSFMFRFixup {
    param(        
        [string]$FixupApplicationId, 
        [ValidateSet("OverrideLocalRedirections","OverrideTraditionalRedirections")]  
        [string]$FixupType, 
        [string]$FixupName,
        [string]$FixupMode,
        [bool]$FixupIlvAware=$false,
        [ValidateSet("disableAll","enablePe","default")]  
        [string]$FixupOverrideCOW="default")
    
    write-host "Adding MFR Fixup for $FixupApplicationId"

    try {
        #check if we have an 'application' in our dynamic config.json
        $targetExe = ($script:ApplicationsModel | Where-Object id -eq "$FixupApplicationId").executable
        if ($null -eq $targetExe -or $targetExe -eq "") {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
            return
        }
        #extract the process name without the extension
        $targetExe = Get-ExeNameNoExtension -PathToEXE $targetExe

        $MFRFixupModel = [ordered]@{
            dll=$mfrDllName;
            config=@{
                overrideCOW=$FixupOverrideCOW;
                ilvAware=$FixupIlvAware;               
            }
        }
   
        #check if fixup has been added previously
        $fixups = ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups | Where-Object dll -eq $mfrDllName
        if ($null -eq $fixups -or $fixups.Count -eq 0) {      
            ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups += @($MFRFixupModel)        
        }

        switch ($FixupType) {
            "OverrideLocalRedirections" { 
                ($script:ProcessesModel.fixups | Where-Object dll -eq $mfrDllName).config.overrideLocalRedirections += ,[ordered]@{ 
                    name=$FixupName;
                    mode=$FixupMode;  
                }
            }
            "OverrideTraditionalRedirections" { 
                ($script:ProcessesModel.fixups | Where-Object dll -eq $mfrDllName).config.overrideTraditionalRedirections += ,[ordered]@{ 
                    name=$FixupName;
                    mode=$FixupMode;  
                }
            }
        }            
        
                
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}


#*******************************************
#********************************************
#function to add trace fixup
#*******************************************
#********************************************

Function Add-AlkanePSFTraceFixup {
    param(     
        [string]$FixupApplicationId, 
        [ValidateSet("printf","eventlog","outputDebugString")]  
        [string]$FixupTraceMethod,      
        [bool]$FixupWaitForDebugger=$false,
        [bool]$FixupTraceFunctionEntry=$false,
        [bool]$FixupTraceCallingModule=$true,
        [bool]$FixupIgnoreDllLoad=$true,
        [string]$FixupType, 
        [ValidateSet("default","filesystem","registry","processAndThread","dynamicLinkLibrary")]  
        [string]$FixupTraceLevelProperty,
        [ValidateSet("always","ignoreSuccess","allFailures","unexpectedFailures","ignore")]  
        [string]$FixupTraceLevelValue,
        [ValidateSet("default","filesystem","registry","processAndThread","dynamicLinkLibrary")]  
        [string]$FixupBreakOnProperty,
        [ValidateSet("always","ignoreSuccess","allFailures","unexpectedFailures","ignore")]  
        [string]$FixupBreakOnValue
    )
      
    write-host "Adding Trace Fixup for $FixupApplicationId"

    try {
        
        #check if we have an 'application' in our dynamic config.json
        $targetExe = ($script:ApplicationsModel | Where-Object id -eq "$FixupApplicationId").executable
        if ($null -eq $targetExe -or $targetExe -eq "") {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
            return
        }
        #extract the process name without the extension
        $targetExe = Get-ExeNameNoExtension -PathToEXE $targetExe

        $TraceFixupModel = [ordered]@{
            dll=$traceDllName;
            config=@{        
                traceMethod=$FixupTraceMethod
                waitForDebugger=$FixupWaitForDebugger
                traceFunctionEntry=$FixupTraceFunctionEntry
                traceCallingModule=$FixupTraceCallingModule
                ignoreDllLoad=$FixupIgnoreDllLoad
                traceLevels=@{
                    $FixupTraceLevelProperty=$FixupTraceLevelValue                
                }
                breakOn=@{
                    $FixupBreakOnProperty=$FixupBreakOnValue
                }
            }
        }

        #check if fixup has been added previously
        $fixups = ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups | Where-Object dll -eq $traceDllName
        if ($null -eq $fixups -or $fixups.Count -eq 0) {      
            ($script:ProcessesModel | Where-Object executable -eq $targetExe).fixups += @($TraceFixupModel)        
        }
                    
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#function to add start script
#*******************************************
#********************************************

Function Add-AlkanePSFStartScript {
    param(        
        [string]$FixupApplicationId, 
        [string]$FixupScriptPath, 
        [array]$FixupScriptArguments, 
        [bool]$FixupRunInVirtualEnvironment=$true,
        [bool]$FixupShowWindow=$false,
        [bool]$FixupStopOnScriptError=$false,
        [bool]$FixupWaitForScriptToFinish=$true,
        [int]$FixupTimeout
    )

    write-host "Adding Start Script for $FixupApplicationId"

    try {
        $foundApp = $false;
   
        foreach ($app in $script:ApplicationsModel) {
            if ($app.id -eq $FixupApplicationId) {
                $foundApp = $true

                $app.startScript = @{
                    scriptPath=$FixupScriptPath;
                    scriptArguments=(($FixupScriptArguments | ForEach-Object {"`"" + $_ + "`""}) -join " ");
                    runInVirtualEnvironment=$FixupRunInVirtualEnvironment;
                    showWindow=$FixupShowWindow;
                    stopOnScriptError=$FixupStopOnScriptError;
                    waitForScriptToFinish=$FixupWaitForScriptToFinish;
                    timeout=$FixupTimeout;
                }
            }
        }
    
        if (!$foundApp) {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
        }
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }    
}

#*******************************************
#********************************************
#function to add end script
#*******************************************
#********************************************

Function Add-AlkanePSFEndScript {
    param(        
        [string]$FixupApplicationId, 
        [string]$FixupScriptPath, 
        [array]$FixupScriptArguments, 
        [bool]$FixupRunInVirtualEnvironment=$true,
        [bool]$FixupShowWindow=$false,
        [bool]$FixupStopOnScriptError=$false,
        [bool]$FixupWaitForScriptToFinish=$true,
        [int]$FixupTimeout
    )

    write-host "Adding End Script for $FixupApplicationId"

    try {
        $foundApp = $false;

        foreach ($app in $script:ApplicationsModel) {
            if ($app.id -eq $FixupApplicationId) {
                $foundApp = $true

                $app.endScript = @{
                    scriptPath=$FixupScriptPath;
                    scriptArguments=(($FixupScriptArguments | ForEach-Object {"`"" + $_ + "`""}) -join " ");
                    runInVirtualEnvironment=$FixupRunInVirtualEnvironment;
                    showWindow=$FixupShowWindow;
                    stopOnScriptError=$FixupStopOnScriptError;
                    waitForScriptToFinish=$FixupWaitForScriptToFinish;
                    timeout=$FixupTimeout;
                }
            }
        }
    
        if (!$foundApp) {
            write-host "**WARNING** Could not find application ID: $FixupApplicationId. Please change the application ID or run: Add-AlkanePSFApplication -FixupApplicationId `"$FixupApplicationId`"." -ForegroundColor DarkYellow
        }
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#Function to apply fixups by updating AppxManifest.xml and generating config.json
#*******************************************
#********************************************

function Set-AlkanePSF {
    param(        
        [ValidateSet("MS","TM")]  
        [string]$FixupPSFType="MS",
        [bool]$FixupOpenConfigJson=$false
    )

    try {
        if (!(test-path $alkanePathToMSIX)) {
            write-host "**WARNING** Could not find $alkanePathToMSIX" -ForegroundColor DarkYellow
            return
        }

        if (!(test-path $alkanePathToCertificate)) {
            write-host "**WARNING** Could not find $alkanePathToCertificate" -ForegroundColor DarkYellow
            return
        }
   
        #get name of MSIX without extension
        $packageName = (get-item $alkanePathToMSIX).Basename
        $workFolder = (get-item $alkanePathToMSIX).Directory.FullName
   
        #Create a subfolder to stage the extraction of the package
        $stagingFolder    = "$workFolder\$($packageName)_Staged" 

        if (!(test-path $stagingFolder)) {
            write-host "**WARNING** Could not find $stagingFolder" -ForegroundColor DarkYellow
            return
        }

        #set the location of config.json and appxmanifest.xml to the staging folder
        $configJSON = "$stagingFolder\config.json"
        $appxManifest    = "$stagingFolder\AppxManifest.xml"

        #locate the bin folder containing PSF DLLs

        if ($FixupPSFType -eq "MS") {
            write-host "Using Microsoft's PSF."
            $nupkg = Get-Package | Where-Object Name -eq "Microsoft.PackageSupportFramework" | Select-Object -ExpandProperty Source
            $psfLocation = (get-item $nupkg).Directory.FullName + "\bin"
        } else {
            write-host "Using Tim Mangan's PSF."
            $psfLocation = "$env:temp\TMPSF"
        }

        #store all fixups
        $allfixups = Get-AlkanePSFFixup
      
        #psfLauncher files
        $psfLauncherExe = "PsfLauncher$alkaneAppArchitecture.exe"
        $psfLauncherDll = "PsfRuntime$alkaneAppArchitecture.dll"
        $psfRunExe = "PsfRunDll$alkaneAppArchitecture.exe"

        #fixup files
        $dynamicLibraryFixupDll = "DynamicLibraryFixup$alkaneAppArchitecture.dll"
        $fileRedirectionFixupDll = "FileRedirectionFixup$alkaneAppArchitecture.dll"
        $regLegacyFixupDll = "RegLegacyFixups$alkaneAppArchitecture.dll"
        $envVarFixupDll = "EnvVarFixup$alkaneAppArchitecture.dll"
        $mfrFixupDll = "MFRFixup$alkaneAppArchitecture.dll"
        $traceFixupDll = "TraceFixup$alkaneAppArchitecture.dll"

        #copy psfLauncher
        Copy-Item -Path "$psfLocation\$psfLauncherExe" -Destination "$stagingFolder" -Force
        Copy-Item -Path "$psfLocation\$psfLauncherDll" -Destination "$stagingFolder" -Force   
        Copy-Item -Path "$psfLocation\$psfRunExe" -Destination "$stagingFolder" -Force  

        #get a fresh AppxManifest in case we're running this multiple times
        Get-AlkaneAppxManifest -PathToAppx $appxManifest

        #Need to make sure manifest publisher is the same as our certificate
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
        $cert.Import($alkanePathToCertificate,$alkaneCertificatePassword,'DefaultKeySet')
        $certificateSubject = $cert.Subject

        #read from manifest
        [xml]$appinfo = Get-Content -Path $appxManifest

        $identity = $appinfo.Package.Identity
        $identity.Publisher = $certificateSubject

        #get app list for config.json from Appxmanifest
        $applications = $appinfo.Package.Applications.Application
    
        if ($null -ne $applications) {

            #change executable in manifest to point to the PsfLauncher
       
            foreach ($fixupapp in $script:ApplicationsModel) {   
                $foundApp = $false
                foreach ($manifestapp in $applications){
                    if ($manifestapp.id -eq $fixupapp.id) {
                        $foundApp = $true
                        $manifestapp.Executable = "$psfLauncherExe"
                        break
                    }
                }

                if (!$foundApp) {
                    $availableApps = ($applications.id -join " ")
                    write-host "**WARNING** Could not find application ID: $($fixupapp.id). When using Add-AlkanePSFApplication, your app id must be one of: $availableApps" -ForegroundColor DarkYellow             
                }
            }
    
            #save the appxmanifest.xml
            $appinfo.Save($appxManifest)

        }

        #generate config.json
        
        #copy required fixup DLLs, and removed DLLs not required
        $ApplyDynamicLibraryFixup = $false
        $ApplyFileRedirectionFixup = $false
        $ApplyEnvVarFixup = $false
        $ApplyRegLegacyFixup = $false
        $ApplyMFRFixup = $false
        $ApplyTraceFixup = $false

        foreach($fu in $allfixups) {    
             switch ($fu.dll) {
                $dynamicLibraryDllName {$fu.dll="$dynamicLibraryFixupDll";$ApplyDynamicLibraryFixup=$true;}
                $fileRedirectionDllName {$fu.dll="$fileRedirectionFixupDll";$ApplyFileRedirectionFixup=$true;}
                $envVarDllName {$fu.dll="$envVarFixupDll";$ApplyEnvVarFixup=$true;}
                $regLegacyDllName {$fu.dll="$regLegacyFixupDll";$ApplyRegLegacyFixup=$true;}
                $mfrDllName {$fu.dll="$mfrFixupDll";$ApplyMFRFixup=$true;}
                $traceDllName {$fu.dll="$traceFixupDll";$ApplyTraceFixup=$true;}
            } 
        }
        
        if ($ApplyTraceFixup) {            
            write-host "Adding $stagingFolder\$traceFixupDll."
            Copy-Item -Path "$psfLocation\$traceFixupDll" -Destination "$stagingFolder" -Force
        } else {
            write-host "Removing $stagingFolder\$traceFixupDll."
            Remove-Item -Path "$stagingFolder\$traceFixupDll" -Force -Recurse -ErrorAction SilentlyContinue
        }

        if ($ApplyMFRFixup) {            
            write-host "Adding $stagingFolder\$mfrFixupDll."
            Copy-Item -Path "$psfLocation\$mfrFixupDll" -Destination "$stagingFolder" -Force
        } else {
            write-host "Removing $stagingFolder\$mfrFixupDll."
            Remove-Item -Path "$stagingFolder\$mfrFixupDll" -Force -Recurse -ErrorAction SilentlyContinue
        }

        if ($ApplyFileRedirectionFixup) {            
            write-host "Adding $stagingFolder\$fileRedirectionFixupDll."
            Copy-Item -Path "$psfLocation\$fileRedirectionFixupDll" -Destination "$stagingFolder" -Force
        } else {
            write-host "Removing $stagingFolder\$fileRedirectionFixupDll."
            Remove-Item -Path "$stagingFolder\$fileRedirectionFixupDll" -Force -Recurse -ErrorAction SilentlyContinue
        }
       
        if ($ApplyRegLegacyFixup) {          
            write-host "Adding $stagingFolder\$regLegacyFixupDll."      
            Copy-Item -Path "$psfLocation\$regLegacyFixupDll" -Destination "$stagingFolder" -Force
        } else {
            write-host "Removing $stagingFolder\$regLegacyFixupDll."
            Remove-Item -Path "$stagingFolder\$regLegacyFixupDll" -Force -Recurse -ErrorAction SilentlyContinue
        }
    
        if ($ApplyEnvVarFixup) {           
            write-host "Adding $stagingFolder\$EnvVarFixupDll." 
            Copy-Item -Path "$psfLocation\$EnvVarFixupDll" -Destination "$stagingFolder" -Force
        } else {
            write-host "Removing $stagingFolder\$EnvVarFixupDll."
            Remove-Item -Path "$stagingFolder\$EnvVarFixupDll" -Force -Recurse -ErrorAction SilentlyContinue
        }
    
        if ($ApplyDynamicLibraryFixup) {   
            write-host "Adding $stagingFolder\$dynamicLibraryFixupDll."
            Copy-Item -Path "$psfLocation\$dynamicLibraryFixupDll" -Destination "$stagingFolder" -Force
        } else {
            write-host "Removing $stagingFolder\$dynamicLibraryFixupDll."
            Remove-Item -Path "$stagingFolder\$dynamicLibraryFixupDll" -Force -Recurse -ErrorAction SilentlyContinue
        }    
        
        $json = @{
            'applications' = $script:ApplicationsModel
            'processes' = $script:ProcessesModel 
        }   
    
        #delete config.json if exists
        if (test-path $configJSON) {
            write-host "Removing $configJSON."
            Remove-Item $configJSON -Force -Recurse -ErrorAction SilentlyContinue
        }
    
        #write config.json
        $json | ConvertTo-Json -Depth 15 | Out-File -FilePath $configJSON

        if ($FixupOpenConfigJson) {
            Start-Process "notepad.exe" -ArgumentList $configJSON
        }

    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}

#*******************************************
#********************************************
#Function to create and sign a new MSIX
#*******************************************
#********************************************

function New-AlkanePSFMSIX {

    try {
        if (!(test-path $alkanePathToMSIX)) {
            write-host "**WARNING** Could not find $alkanePathToMSIX" -ForegroundColor DarkYellow
            return
        }

        if (!(test-path $alkanePathToCertificate)) {
            write-host "**WARNING** Could not find $alkanePathToCertificate" -ForegroundColor DarkYellow
            return
        }
               
        if (test-path $alkanePathToFixedMSIX) {
            write-host "Removing $alkanePathToFixedMSIX."
            Remove-Item $alkanePathToFixedMSIX -Force -Recurse -ErrorAction SilentlyContinue
        }
    
        $packageName = (get-item $alkanePathToMSIX).BaseName
        $workFolder = (get-item $alkanePathToMSIX).Directory.FullName
        $stagingFolder    = "$workFolder\$($packageName)_Staged"
        
        write-host "Finding MakeAppx.exe."
        $makeAppxPath = Get-WindowsSDKExe -ExeName "makeappx.exe"
        
        if ($makeAppxPath -ne "") {     
            write-host "Found at $makeAppxPath."

            write-host "Finding Signtool.exe."
            $signToolPath = Get-WindowsSDKExe -ExeName "signtool.exe"
            
            if ($signToolPath -ne "") {  
                write-host "Found at $signToolPath."

                write-host "Compiling package to $alkanePathToFixedMSIX."
                $result = (Start-Process -FilePath $makeAppxPath -ArgumentList "pack","/p","$alkanePathToFixedMSIX","/d","$StagingFolder" -Wait -Passthru).ExitCode
                
                if ($result -eq 0) {
                    write-host "Package compiled to $alkanePathToFixedMSIX with exit code $result." 
                } else {
                    write-host "**WARNING** Package compiled to $alkanePathToFixedMSIX with exit code $result." -ForegroundColor DarkYellow  
                }
            
                write-host "Signing $alkanePathToFixedMSIX."
                $result = (Start-Process -FilePath $signToolPath -ArgumentList "sign","/tr","http://timestamp.digicert.com","/v","/fd","sha256","/td","sha256","/f","$alkanePathToCertificate","/p","$alkaneCertificatePassword","$alkanePathToFixedMSIX" -Wait -Passthru).ExitCode
                
                if ($result -eq 0) {
                    write-host "Signed $alkanePathToFixedMSIX with exit code $result."
                } else {
                    write-host "**WARNING** Signed $alkanePathToFixedMSIX with exit code $result." -ForegroundColor DarkYellow  
                }
                    
            } else {
                write-host "Cannot find Signtool.exe."
            }
        } else {
            write-host "Cannot find MakeAppx.exe."
        }
        
    } catch {
        write-host "**ERROR** $($_)" -ForegroundColor Red
    }
}