7-Zip Auto Install Latest /w Intune detectionScript

Hi,

I just want to share my solution for an 7-Zip Installer which automatically installs the latest 7-Zip Version. I have created it for Intune, so it hat 2 parts.

The Installation Script ‘Invoke-AppDeployToolkit.ps1’
AND
The detection Script ‘detectionScript.ps1’

If you have any sugestions, feel free.

I just share the relevant parts of the scripts:

    ##================================================
    ## MARK: Pre-Install
    ##================================================
    $adtSession.InstallPhase = "Pre-$($adtSession.DeploymentType)"

    ## Show Welcome Message, close Internet Explorer if required, allow up to 3 deferrals, verify there is enough disk space to complete the install, and persist the prompt.
    Show-ADTInstallationWelcome -CloseProcesses 7zFM, 7zG -CheckDiskSpace -RequiredDiskSpace 150 -ForceCloseProcessesCountdown 180  -PersistPrompt

    ## Show Progress Message (with the default message).
    Show-ADTInstallationProgress

    ## <Perform Pre-Installation tasks here>


    ##================================================
    ## MARK: Install
    ##================================================
    $adtSession.InstallPhase = $adtSession.DeploymentType

    ## <Perform Installation tasks here>
    
    #Get the latest 7z Version, Download it, returns the Full path of the downloaded File
    function Get-Latest7zVersion {
        # Define the GitHub repository
        $repo = "ip7z/7zip"

        # GitHub API URL for releases
        $apiUrl = "https://api.github.com/repos/$repo/releases/latest"

        # Send a request to the GitHub API
        $response = Invoke-RestMethod -Uri $apiUrl -Headers @{ "User-Agent" = "PowerShell" }

        # Extract the latest version tag
        $latestVersion = $response.tag_name

        # Find the 64-bit exe asset in the release assets
        $asset = $response.assets | Where-Object { $_.name -like "*x64.exe" }

        if ($asset) {
            # Define the download URL and target path
            $downloadUrl = $asset.browser_download_url
            $targetPath = "$env:TEMP\$($asset.name)"

            # Download the file
            Invoke-WebRequest -Uri $downloadUrl -OutFile $targetPath -Headers @{ "User-Agent" = "PowerShell" }

            #Write-Output "Downloaded $($asset.name) to $targetPath"
            return $targetPath
        } else {
            Close-ADTSession -ExitCode 1
        }

    }
    
    [String]$7zSetupEXE = Get-Latest7zVersion

    Start-ADTProcess -FilePath $7zSetupEXE -ArgumentList '/S'

    ##================================================
    ## MARK: Post-Install
    ##================================================
    $adtSession.InstallPhase = "Post-$($adtSession.DeploymentType)"

    ## <Perform Post-Installation tasks here>

    Show-ADTInstallationPrompt -Message 'Installation of latest 7-Zip completed' -Title '7-Zip install completed' -Subtitle 'None' -ButtonRightText 'OK'
    ##================================================
    ## MARK: Pre-Uninstall
    ##================================================
    $adtSession.InstallPhase = "Pre-$($adtSession.DeploymentType)"

    ## Show Welcome Message, close Internet Explorer with a 60 second countdown before automatically closing.
    Show-ADTInstallationWelcome -CloseProcesses 7zFM, 7zG -CheckDiskSpace -RequiredDiskSpace 150 -ForceCloseProcessesCountdown 180  -PersistPrompt

    ## Show Progress Message (with the default message).
    Show-ADTInstallationProgress

    ## <Perform Pre-Uninstallation tasks here>


    ##================================================
    ## MARK: Uninstall
    ##================================================
    $adtSession.InstallPhase = $adtSession.DeploymentType

    ## <Perform Uninstallation tasks here>

    $7ZipPathUninstaller = $((Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip).UninstallString) -replace "[`"\`']", ''

    Start-ADTProcess -FilePath $7ZipPathUninstaller -ArgumentList '/S'


    ##================================================
    ## MARK: Post-Uninstallation
    ##================================================
    $adtSession.InstallPhase = "Post-$($adtSession.DeploymentType)"

    Show-ADTInstallationPrompt -Message 'Uninstall of 7-Zip completed' -Title '7-Zip uninstall completed' -Subtitle 'None' -ButtonRightText 'OK'

    ## <Perform Post-Uninstallation tasks here>

Detection Script for Intune:

# Get Current Version of 7z
$InstalledVersion = $((Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip).DisplayVersion)

# Define the GitHub repository
$repo = "ip7z/7zip"

# GitHub API URL for releases
$apiUrl = "https://api.github.com/repos/$repo/releases/latest"

# Send a request to the GitHub API
$response = Invoke-RestMethod -Uri $apiUrl -Headers @{ "User-Agent" = "PowerShell" } -UseBasicParsing

# Extract the latest version tag
$LatestVersion = $response.tag_name

# Variables for the installed version and latest version
[Version]$InstalledVersion = $InstalledVersion
[Version]$LatestVersion = $LatestVersion

# Compare versions
if ($InstalledVersion -ge $LatestVersion) {
    # Installed version matches or is newer than the latest version
    Write-Output "Installed version ($InstalledVersion) is up-to-date or newer than the latest version ($LatestVersion)."
    exit 0
} elseif ($InstalledVersion -lt $LatestVersion) {
    # Installed version is older than the latest version
    Write-Output "Installed version ($InstalledVersion) is older than the latest version ($LatestVersion).Install/Update 7z"
    exit 1
} else {
    # If something goes wrong (e.g., invalid version formats)
    Write-Output "Version comparison failed due to unexpected input. Install/Update 7z"
    exit 1
}
1 Like

We had this function in our build system at one point in time:

If there’s sufficient interest, I could do a PSAppDeployToolkit.WebUtilities extension with this, along with some other download functions to grease the wheels for people?

1 Like

that would be a awsom if you share this or implement this.

I was doing some research yesterday and came across evergreen (and I don’t mean the ship from the Panama Canal :stuck_out_tongue: ) I mean the module from aaronparker.

Evergreen is a simple PowerShell module that retrieves the latest version numbers and download URLs for various software products directly from the manufacturer’s source.

Before we reinvent the wheel, I wonder whether we can create synergies here. In terms of licenses, PSADT and Evergreen can be combined without any problems.

What do you think of the idea?

People have been combining PSAppDeployToolkit and Evergreen for some time now: GitHub - durrante/PSADT. I think it’s a great idea, it should be done as a PSADT extension like what I’ve done with WinGet (that is, wrap Evergreen’s functions to provide logging within PSADT, etc)

The repository of GitHub - durrante/PSADT is very nice, I didn’t know that yet.

durrante installs the module on the system. I have integrated it as a test extension.

Ultimately, only 3 steps are required.

  • Copy the module into the directory of Invoke-AppDeployToolkit.exe/ps1
  • Rename the module PSAppDeployToolkit. before the module name (here: PSAppDeployToolkit.Evergreen)
  • Rename .psd1 and place PSAppDeployToolkit. in front of it (here: PSAppDeployToolkit.Evergreen.psd1)

This way you can use the commands directly and do not have to install the module first. In my opinion, this is also not necessary on the client target system.

Do I need to make any other changes to achieve optimum integration in PSADTv4? If that was all, you can also update here very easily without much effort. But I’m not quite sure about this yet, as I haven’t yet understood the whole extension thing.

However, the following could be derived as a feature request: A function that downloads a module and adds it to a package as an extension.

Below you will find a first draft that should work.

# DO NOT USE
# BAD IDEA, see Mitch post below.
function Add-ADTModuleExtension {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ModuleName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Destination = $ExecutionContext.SessionState.Path.CurrentLocation.Path
    )

    # Ensure the destination path exists
    if (-not (Test-Path -Path $Destination)) {
        throw "Now PSADT Template created or Wrong path specified: $Destination"
    }

    # Save the module to the destination path
    Save-Module -Name $ModuleName -Path $Destination

    # Locate the module folder
    $moduleFolder = Get-ChildItem -Path $Destination | Where-Object { $_.PsIsContainer -and $_.Name -eq $ModuleName }
    if (-not $moduleFolder) {
        throw "Module folder not found after saving."
    }

    # Define the new folder name and path
    $newFolderName = "PSAppDeployToolkit.$ModuleName"
    $newFolderPath = Join-Path -Path $Destination -ChildPath $newFolderName

    # Rename the folder
    Rename-Item -Path $moduleFolder.FullName -NewName $newFolderName

    # Find the .psd1 file in the module folder
    $psd1File = Get-ChildItem -Path $newFolderPath -Filter "*.psd1" -Recurse | Select-Object -First 1
    if (-not $psd1File) {
        throw ".psd1 file not found in the module folder."
    }

    # Define the new .psd1 file name
    $newPsd1Name = "PSAppDeployToolkit.$ModuleName.psd1"
    $newPsd1Path = Join-Path -Path $newFolderPath -ChildPath $newPsd1Name

    # Rename the .psd1 file
    Rename-Item -Path $psd1File.FullName -NewName $newPsd1Name

    Write-Host "Module '$ModuleName' saved and added to ADT Package" -ForegroundColor Green
}

I was suggesting in my previous post that you don’t do this. You should simply be adding Import-Module -Name $PSScriptRoot\Evergreen to your Invoke-AppDeployToolkit.ps1 script.

By doing what you’re doing, you’re misrepresenting the Evergreen author’s work and implying it’s a properly built PSAppDeployToolkit module. You’re also going to make it harder for someone to do a genuine PSAppDeployToolkit.Evergreen integration module, should that ever occur, because users could run into conflicts between a proper module and your solution.

I still think developing a proper module that wraps Evergreen’s functions to integrate it better with PSADT’s logging, etc, would be preferable. I understand and appreciate that this is a significant undertaking though.

Please don’t do this.

This is a bad idea. As said, you’re taking someone elses work and misrepresenting it. The user when they have issues will report it to the original project’s maintainer, only for them to decipher it’s some PSADT frankenmodule.

It absolutely does not. Developing a fully fledged PSADT module requires a lot more than this. Using Update-ADTDesktop as a simple example:

function Update-ADTDesktop
{
    <#
    .SYNOPSIS
        Refresh the Windows Explorer Shell, which causes the desktop icons and the environment variables to be reloaded.

    .DESCRIPTION
        This function refreshes the Windows Explorer Shell, causing the desktop icons and environment variables to be reloaded. This can be useful after making changes that affect the desktop or environment variables, ensuring that the changes are reflected immediately.

    .INPUTS
        None

        You cannot pipe objects to this function.

    .OUTPUTS
        None

        This function does not return any objects.

    .EXAMPLE
        Update-ADTDesktop

        Refreshes the Windows Explorer Shell, reloading the desktop icons and environment variables.

    .NOTES
        An active ADT session is NOT required to use this function.

        Tags: psadt
        Website: https://psappdeploytoolkit.com
        Copyright: (C) 2024 PSAppDeployToolkit Team (Sean Lillis, Dan Cunningham, Muhammad Mashwani, Mitch Richters, Dan Gough).
        License: https://opensource.org/license/lgpl-3-0

    .LINK
        https://psappdeploytoolkit.com
    #>

    [CmdletBinding()]
    param
    (
    )

    begin
    {
        Initialize-ADTFunction -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process
    {
        Write-ADTLogEntry -Message 'Refreshing the Desktop and the Windows Explorer environment process block.'
        try
        {
            try
            {
                [PSADT.GUI.Explorer]::RefreshDesktopAndEnvironmentVariables()
            }
            catch
            {
                Write-Error -ErrorRecord $_
            }
        }
        catch
        {
            Invoke-ADTFunctionErrorHandler -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -ErrorRecord $_ -LogMessage "Failed to refresh the Desktop and the Windows Explorer environment process block."
        }
    }

    end
    {
        Complete-ADTFunction -Cmdlet $PSCmdlet
    }
}
  • The Initialize-ADTFunction call ensures the functions $ErrorActionPreference is appropriately configured.
  • The Write-ADTLogEntry call announces the function’s start, as people are accustomed to all PSAppDeployToolkit functions logging their actions.
  • The Write-Error -ErrorRecord $_ line rewrites any caught ErrorRecord object to ensure the PositionMessage matches the caller’s position, not some position inside the module.
  • The Invoke-ADTFunctionErrorHandler call handles the ErrorRecord in a consistent manner by updating the logs and throwing via the calling function’s $PSCmdlet object.

That’s perfectly fine! I appreciate the enthusiasm but there’s just a better way to go about this. Doing what you’re doing above, privately, in your own business, etc, is perfectly fine. I just can’t have the community thinking this is OK as I think there’s both ethical and technical problems with it.

At least I still have the enthusiasm. :woozy_face:

I will take the approach you suggested with import modules.

The last thing I’d want to do is stifle anyone’s enthusiasm as I want to encourage it at all costs, however I can’t encourage things I think could cause issues for our users or our project, or possibly be considered in poor taste (such as renaming someone else’s work).

I’m interested to see what you can develop here because a lot of people would appreciate always up to date installation packages.

1 Like

Don’t worry, my enthusiasm remains intact, even though it tends to come and go in waves depending on how much time the real world allows me to stay in the Matrix.

Admittedly, I felt a bit crumpled when I first read it. But I’d much rather have a few direct words that I can learn from than blindly walk into a trap.

So, thank you for the honest words!

Hi @mjr4077au ,
I thought again about what you said and also looked at how the auto import of the PSAppDeployToolkit.Extensions works.
Following the extension import, I have extended the invocation part.

I have 2 questions:

  1. would this meet technical and ethical requirements?
  2. if yes, would this be a useful addition for PSADT v4? if no, why? :wink:
##================================================
## MARK: Invocation
##================================================

try
{
    # Importing PSAppDeployToolkit Extensions into the Session
    Get-Item -Path $PSScriptRoot\PSAppDeployToolkit.* | & {
        process
        {
            Get-ChildItem -LiteralPath $_.FullName -Recurse -File | Unblock-File -ErrorAction Ignore
            Import-Module -Name $_.FullName -Force
        }
    }
    # Importing Modules into the Session
    $ImportedModules = @()
    Get-Item -Path $PSScriptRoot\Modules\* | & {
        process
        {
            $ImportedModules += $_.Name
            Get-ChildItem -LiteralPath $_.FullName -Recurse -File | Unblock-File -ErrorAction Ignore
            Import-Module -Name $_.FullName -Force
        }
    }

    & "$($adtSession.DeploymentType)-ADTDeployment"
    Close-ADTSession
}
catch
{
    Write-ADTLogEntry -Message ($mainErrorMessage = Resolve-ADTErrorRecord -ErrorRecord $_) -Severity 3
    Show-ADTDialogBox -Text $mainErrorMessage -Icon Stop | Out-Null
    Close-ADTSession -ExitCode 60001
}
finally
{
    Remove-Module -Name PSAppDeployToolkit* -Force
    $ImportedModules | Remove-Module -Force
}

1 Like

This is the way for sure and is definitely better than renaming modules.

This definitely eliminates my prior concerns, and its easier on users than having to rename things etc.

It’s a useful demonstration on how others could achieve such a thing, but at this time I don’t believe it’s appropriate we have some kind of auto-loading mechanism for non-PSAppDeployToolkit extension modules. We have a wide any drastically varying demographic using PSAppDeployToolkit, and there’s people within that who will be of the impression just because a module is auto-loaded that it logs to the active session’s log, etc.

Which mechanisms? Can you explain/show?

You kind of cut off what I said there. To be clearer, I don’t want to add support for auto-loading non-PSAppDeployToolkit extension modules at this time as I believe extensions should be PSADT-specific. I’ll discuss this with the team as it’s not a decision for one person (me) to make.

You’re right, I misunderstood