Function to Install Application and Its PowerShell Wrapper

# This function checks on whether a particular app has a corresponding PowerShell wrapper. If it does, then the program will attempt to match the installed version of such app with that which is currently available with PowerShell's module.

$appName='batman'  
function includePowerShellWrapper($appName){
    # Ensure that PowerShell is using TLS 1.2 in this session
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    # Uncomment these lines to make TLS1.2 defaults on next reboot
    #Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
    #Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord    

    # Deterministic verification on whether module commands are able to load
    Import-Module $appName -ea SilentlyContinue  
    $moduleIsLoaded=Get-Command -Module $appName -ea SilentlyContinue
    
    # Sanity check: validate module name before proceeding
    $moduleVersion=.{$y=find-module $appName -ea SilentlyContinue;if($y){return $y.Version.ToString()}}
    if(!$moduleVersion){write-warning "'$($appName.ToUpper())' doesn't match PowerShell module";return $false}   
    elseif(!$moduleIsLoaded){
        # Part A) Compare the installed module with its online counter-part; install if necessary
        # Select Major, Minor, and Build - omitting Revision.
        # This is because PowerShell wrapper will deem an installed binary as compatible as long as those values match     
        $installedVersion=.{$x=Get-Module $appName -ea SilentlyContinue;if($x){return $x.Version.ToString()}}
        $targetVersion=.{try{[void]($moduleVersion -match '^(\d+\.\d+\.{0,1}\d+)');$matches[1]}catch{}}
        if($matches){Clear-Variable -name matches}
        write-host "$($appName.ToUpper()) PowerShell wrapper`: Installed $installedVersion v.s Updated version $moduleVersion"
        if($installedVersion -ne $moduleVersion){
            write-host "Attempting to install the module $appName $moduleVersion"
            Remove-Module $appName -ea SilentlyContinue
            try{                
                Install-Module -Name $appName -Force -Confirm:$false;
                Import-Module $appName;
                write-host "$appName PowerShell wrapper module has been installed."
                return $true
                }
            catch{
                if(!('NuGet' -in (get-packageprovider).Name)){    
                    try{
                        Install-PackageProvider -Name NuGet -MinimumVersion '2.8.5.201' -Force -Confirm:$false -ErrorAction Stop;
                        Install-Module -Name $appName -Force -Confirm:$false;
                        Import-Module $appName;
                        write-host "$appName PowerShell wrapper module has been installed."
                        }
                    catch{                  
                        write-host "$error"
                        write-warning "Unable to install $appName module"
                        return $false
                        }            
                    }
                }
            }

        # Part B) Check Installed applications and upgrade/downgrade as necessary
        $installedApps=Get-CimInstance -ClassName win32_InstalledWin32Program -ErrorAction SilentlyContinue|select Name,Version
        $appwizAppExists=.{try{$matchApp=$installedApps|?{$_.Name -like "*$appName*"|select -First 1 }
                            if($matchApp){$matchApp}else{$false}}catch{}}
        $appwizVersion=.{try{[void]($(if($appwizAppExists){$appwizAppExists.Version}else{$false}) -match '^(\d+\.\d+\.{0,1}\d+)');$matches[1]}catch{}}
        if($matches){Clear-Variable -name matches}
        write-host "Application Wizard version being detected: $appwizAppExists"
        $isAppwizSameVersion=$targetVersion -eq $appwizVersion
    
        $chocoInstalledApps=choco list -l
        $chocoAppExists=$chocoInstalledApps|?{$_ -like "*$appName*"}|select -First 1
        $chocoVersion=.{try{[void]($(.{[void]($chocoAppExists -match ' ([\d+\.]+)');return $matches[1]}) -match '^(\d+\.\d+\.{0,1}\d+)');$matches[1]}catch{}}
        if($matches){Clear-Variable -name matches}
        write-host "Choco version being detected: $chocoAppExists"
        $isChocoSameVersion=$targetVersion -eq $chocoVersion
        write-host "Target version: $targetVersion, Appwiz version: $appwizVersion, Choco version: $chocoVersion"
    
        if (!($isAppwizSameVersion -or $isChocoSameVersion)){        
            write-host "Now trying to install $appname..."
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
                try{
                    if(!$chocoAppExists -and !$appwizAppExists){choco install $appName --version=$targetVersion -y}
                    elseif (!$isChocoSameVersion -and !$isAppwizSameVersion){choco install $appName --version=$targetVersion --force -y}
                    write-host "Application $appName $targetVersion is now installed"
                    if($matches){Clear-Variable -name matches}
                    return $true
                }
                catch{
                    write-warning "Unable to install the app name $appName $targetVersion using Chocolatey."
                    if($matches){Clear-Variable -name matches}
                    return $false
                    }
            }
        else{
            write-host "$appName is already installed.";
            if($matches){Clear-Variable -name matches}
            return $true}
    }
    else{write-host "$($appName.ToUpper()) module has loaded successfully.";return $true}
}
includePowerShellWrapper $appName
# This function doesn't work. I'll leave it here for documentation purposes

$appName='uga-uga'
# Import module winSCP as it does not support module autoloading
function includeAppWithPowerShellWrapper($appName){
$ErrorActionPreference = "Stop"
# Install Chocolatey
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
try{
    $appExists=choco list -l|?{$_ -like "*$appName*"}
    if(!$appExists){choco install $appName -y}
    $chocoList=choco list -l
    $appVersion=.{$match=$chocoList|?{$_ -like "$appName*"}|select -first 1
                if($match){[void]($match -match "([\.\d]+)"); return $matches[1]}
                else{return $null}}
    }
catch{
    write-warning "$Error"
    break;
    }

# Install module if it's not already available  
if(!(Get-Module -ListAvailable -Name $appName -ea SilentlyContinue)){
    if(!('MyNuGet' -in (get-packagesource).Name)){    
        try{
            Register-PackageSource -Name MyNuGet -Location https://www.nuget.org/api/v2 -ProviderName NuGet -Force
            }
        catch{
            Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
            Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            Register-PackageSource -Name MyNuGet -Location https://www.nuget.org/api/v2 -ProviderName NuGet -Force
            }            
        }
    Install-Package $appName -RequiredVersion $appVersion -Force -Confirm:$false
    }

# Wrapper installed without the WinSCP software binaries
#Exception calling "Open" with "1" argument(s): "The winscp.exe executable was not found at location of the assembly
#WinSCPnet (C:\Program Files\WindowsPowerShell\Modules\winscp\5.17.5.3\lib), nor in an installation path. You may use
#Session.ExecutablePath property to explicitly set path to winscp.exe."
#At line:1 char:1
#+ $session = (New-Object WinSCP.Session).Open($sessionOptions)
#+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
#    + FullyQualifiedErrorId : SessionLocalException

# Mismatch versions between PowerShell wrapper and Choco installed version
#Exception calling "Open" with "1" argument(s): "The version of C:\Program Files (x86)\WinSCP\winscp.exe (5.17.6.0)
#does not match version of this assembly C:\Program Files\WindowsPowerShell\Modules\winscp\5.17.5.3\lib\WinSCPNet.dll
#(5.17.5.0)."
#At line:1 char:1
#+ $session = (New-Object WinSCP.Session).Open($sessionOptions)
#+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
#    + FullyQualifiedErrorId : SessionLocalException

}
includeAppWithPowerShellWrapper $appName

Leave a Reply

Your email address will not be published. Required fields are marked *