Classes/psmodule.ps1

class psmodule {
    <#
    .SYNOPSIS
        A class for managing PowerShell modules, including installation, importation, and removal.
 
    .DESCRIPTION
        The psmodule class provides a structured approach to managing PowerShell modules.
        It allows users to check if a module is installed or imported, install new modules
        from the PowerShell Gallery, import existing modules into the current session,
        remove modules from memory, and uninstall modules entirely. The class includes
        features to handle versioning and user scope for module installations.
 
    .EXAMPLES
 
        # Example 1: Create an instance of the class with only the module name.
        $module = [psmodule]::new("Spec.Base.Utilities")
        # Creates a new psmodule object for managing the specified module.
 
        # Example 2: Create an instance of the class with module name and version.
        $moduleWithVersion = [psmodule]::new("Spec.Base.Utilities", "1.0.9")
        # Creates a new psmodule object specifically for version 1.0.9 of the module.
 
        # Example 3: Install the module if not already installed and import it.
        $module.InstallAndImport()
        # Checks if the module is installed; if not, it installs and imports it into the current session.
 
        # Example 4: Check if the module is installed and display the result.
        if ($module.isInstalled()) {
            Write-Host "Module is installed."
        } else {
            Write-Host "Module is not installed."
        }
 
        # Example 5: Remove the module from memory if it is currently imported.
        # This does not uninstall the module; it just removes it from the current session.
        $module.remove()
 
        # Example 6: Report the current status of the module to determine its state.
        $module.reportStatus()
        # This will output whether the module is installed, imported, or both.
 
        # Example 7: Uninstall the module completely, removing all versions if applicable.
        # You can also uninstall a specific version of the module by providing the version
        # during class instantiation. If no version is specified, all versions will be uninstalled.
        $module.Uninstall() # Uninstalls all versions
        # Or for a specific version:
        $specificModule = [psmodule]::new("Spec.Base.Utilities", "1.0.9")
        $specificModule.Uninstall() # Uninstalls only version 1.0.9
 
        # Example 8: Install and import the module with specific scope and license acceptance.
        $specificModule = [psmodule]::new("Spec.Base.Utilities", "1.0.9", "CurrentUser", $true)
        $specificModule.InstallAndImport()
        # Installs version 1.0.9 of the module for the current user, accepting the license. Then imports it.
 
        # Example 9: Check if the module is currently loaded in the session.
        if ($module.isImported()) {
            Write-Host "Module is currently loaded."
        } else {
            Write-Host "Module is not loaded."
        }
 
        # Example 10: Install the latest available version of a module without specifying a version.
        $latestModule = [psmodule]::new("Spec.Base.Utilities")
        $latestModule.InstallAndImport()
        # If the module is not already installed, this will install the latest version available.
        # If a lower version is already installed, it will update to the latest version.
 
        # Example 11: Attempt to install a module that requires a specific version.
        $versionSpecificModule = [psmodule]::new("Spec.Base.Utilities", "1.0.9")
        if (-not $versionSpecificModule.isInstalled()) {
            $versionSpecificModule.InstallAndImport()
        }
        # This ensures that version 1.0.9 is installed and imported if it isn't already.
 
    .NOTES
        See Also: The helper function for instantiating an object: New-specPSModuleObject.
        Author: owen.heaume
        Version: 1.0.0 - initial release
    #>


    [string] $moduleName
    [string] $version
    [ValidateSet('CurrentUser', 'AllUsers')]
    [string] $scope = 'AllUsers'
    [bool] $acceptLicense = $true

    # Constructor with only moduleName
    psmodule([string] $moduleName) {
        $this.moduleName = $moduleName
    }

    # Constructor with only moduleName and version
    psmodule([string] $moduleName, [string]$version) {
        $this.moduleName = $moduleName
        $this.version = $version
    }

    # Constructor with moduleName, version, scope, and acceptLicense
    psmodule([string] $moduleName, [string] $version, [string] $scope = 'AllUsers', [bool] $acceptLicense = $true) {
        $this.moduleName = $moduleName
        $this.version = $version
        $this.scope = $scope
        $this.acceptLicense = $acceptLicense
    }


    # Method to check if the module is installed
    [bool]isInstalled() {
        if ([string]::IsNullOrEmpty($this.version)) {
            # Check if the module exists
            $module = Get-Module -ListAvailable -Name $this.moduleName
        } else {
            # Check if the module exists based on both name and version
            $module = Get-Module -ListAvailable -Name $this.moduleName | Where-Object { $_.Version -eq $this.version }
        }

        # Return true if any module(s) are found, otherwise false
        return @($module).Count -gt 0
    }


    # Method to check if module is currently loaded
    [bool]isImported() {
        # Check if the module is loaded based on name only
        if ([string]::IsNullOrEmpty($this.version)) {
            $module = Get-Module -Name $this.moduleName
        } else {
            # Check if the module is loaded based on both name and version
            $module = Get-Module -Name $this.moduleName | Where-Object { $_.Version -eq $this.version }
        }

        # Return true if any module(s) are found, otherwise false
        return @($module).Count -gt 0
    }

    # method to remove the module (Unload it from memory)
    [void]remove() {
        if ($this.isImported()) {
            Remove-Module -Name $this.moduleName -Force
            Write-Host "Module $($this.moduleName) removed."
        } else {
            Write-Host "Module $($this.moduleName) is not imported."
        }
    }

    # Method to import the module
    [void]import() {
        if ($this.isInstalled()) {
            if (-not ($this.isImported())) {
                # If a version is specified, use the -RequiredVersion parameter
                if ([string]::IsNullOrEmpty($this.version)) {
                    Import-Module -Name $this.moduleName -Force -DisableNameChecking -Global
                    Write-Host "Module $($this.moduleName) imported."
                } else {
                    Import-Module -Name $this.moduleName -RequiredVersion $this.version -Force -DisableNameChecking -Global
                    Write-Host "Module $($this.moduleName) version $($this.version) imported."
                }
            } else {
                # Modified to include version if specified
                if (![string]::IsNullOrEmpty($this.version)) {
                    Write-Host "Module $($this.moduleName) version $($this.version) is already imported."
                } else {
                    Write-Host "Module $($this.moduleName) is already imported."
                }
            }
        } else {
            Write-Host "Module $($this.moduleName) is not installed."
        }
    }


    [void]reportStatus() {
        if ($this.isInstalled() -and $this.isImported()) {
            Write-Host "Module $($this.moduleName) is installed and imported." -ForegroundColor DarkGreen
        } elseif ($this.isInstalled()) {
            Write-Host "Module $($this.moduleName) is installed but not imported." -ForegroundColor DarkYellow
        } else {
            Write-Host "Module $($this.moduleName) is not installed." -ForegroundColor DarkYellow
        }
    }


    [void] install() {
        # Check if the module is installed
        $installedModule = Get-InstalledModule -Name $this.moduleName -ErrorAction SilentlyContinue

        if (-not $installedModule) {
            # Module is not installed, install the latest version
            $installParams = @{
                Name        = $this.moduleName
                Force       = $true
                ErrorAction = 'Stop'
                Scope       = $this.scope
            }

            # Add AcceptLicense only if $this.acceptLicense is true
            if ($this.acceptLicense) {
                $installParams['AcceptLicense'] = $true
            }

            # If a version is specified, add it to the parameters
            if (![string]::IsNullOrEmpty($this.version)) {
                $installParams['RequiredVersion'] = $this.version
            }

            # Install the module
            try {
                Install-Module @installParams
                Write-Host "Module '$($this.moduleName)' has been installed."
            } catch {
                Write-Error "Failed to install module '$($this.moduleName)': $_"
            }
        } elseif ($this.version -and $installedModule.Version -ne $this.version) {
            # If a specific version is provided and it's different, install the specified version
            $installParams = @{
                Name            = $this.moduleName
                RequiredVersion = $this.version
                Force           = $true
            }

            # Add AcceptLicense only if $this.acceptLicense is true
            if ($this.acceptLicense) {
                $installParams['AcceptLicense'] = $true
            }

            try {
                Install-Module @installParams -ea stop
                Write-Host "Module '$($this.moduleName)' version $($this.version) has been installed."
            } catch {
                Write-Error "Failed to install module '$($this.moduleName)' version '$($this.version)': $_"
            }
        } elseif (-not $this.version) {
            # If no version is specified, install the latest version if the installed version is outdated
            $latestModule = Find-Module -Name $this.moduleName
            if ($installedModule.Version -lt $latestModule.Version) {
                $installParams = @{
                    Name  = $this.moduleName
                    Force = $true
                }

                # Add AcceptLicense only if $this.acceptLicense is true
                if ($this.acceptLicense) {
                    $installParams['AcceptLicense'] = $true
                }

                try {
                    Install-Module @installParams -ea stop
                    Write-Host "Module '$($this.moduleName)' has been updated to the latest version $($latestModule.Version)."
                } catch {
                    Write-Error "Failed to update module '$($this.moduleName)': $_"
                }
            } else {
                Write-Host "Module '$($this.moduleName)' is already at the latest version."
            }
        } else {
            Write-Host "Module '$($this.moduleName)' version $($installedModule.Version) is already installed."
        }
    }


    # Uninstall method to remove the module
    [void] Uninstall() {
        # Check if a specific version is provided
        if ([string]::IsNullOrEmpty($this.version)) {
            # No version specified, uninstall all versions
            $installedModules = Get-InstalledModule -Name $this.moduleName -AllVersions -ErrorAction SilentlyContinue

            if ($null -ne $installedModules) {
                # Uninstall all installed versions
                foreach ($module in $installedModules) {
                    try {
                        Uninstall-Module -Name $module.Name -Force -RequiredVersion $module.Version -ErrorAction Stop
                        Write-Host "Module '$($module.Name)' version $($module.Version) has been uninstalled."
                    } catch {
                        # Only warn if the error is not related to not finding the module
                        if ($_.Exception.Message -notlike '*No match was found*') {
                            Write-Warning "Failed to uninstall module '$($module.Name)' version $($module.Version). It may have already been removed."
                        }
                    }
                }
            } else {
                Write-Host "Module '$($this.moduleName)' is not installed."
            }
        } else {
            # A specific version is provided, uninstall only that version
            $installedModule = Get-InstalledModule -Name $this.moduleName -RequiredVersion $this.version -ErrorAction SilentlyContinue

            if ($null -ne $installedModule) {
                try {
                    Uninstall-Module -Name $installedModule.Name -RequiredVersion $this.version -Force -ErrorAction Stop
                    Write-Host "Module '$($installedModule.Name)' version $($this.version) has been uninstalled."
                } catch {
                    # Only warn if the error is not related to not finding the module
                    if ($_.Exception.Message -notlike '*No match was found*') {
                        Write-Warning "Failed to uninstall module '$($installedModule.Name)' version $($this.version)."
                    }
                }
            } else {
                Write-Host "Module '$($this.moduleName)' version $($this.version) is not installed."
            }
        }
    }

    # Method to install the module if required, import the module and report the status
    [void] InstallAndImport() {
        try {
            # Attempt to install the module
            $this.install()

            # Check if the module is installed successfully
            if (-not $this.isInstalled()) {
                Write-Error "Module '$($this.ModuleName)' was not installed successfully." -ea stop
            }

            # Attempt to import the module
            $this.import()

            # Call ReportStatus only if both install and import are successful
            $this.ReportStatus()
        } catch {
            Write-Error "An error occurred: $_"
            $this.ReportStatus()
        }
    }
}