Functions/ModuleManagement/Install-FpsTools.ps1
<#
.SYNOPSIS Installs or updates the 4PS Powershell Tools from Azure DevOps on local system for the current user, and creates a shortcut on the desktop to the destination folder. .DESCRIPTION 1. Create the destination folder if not exists. (i) Except if SkipScriptInstallation is set. 2. Test if files in the destination folder are locked or not. (i) Except if SkipScriptInstallation is set. 3. Obtains Azure DevOps PAT token. (i) Automatically or through users input. 4. (Re)installs the PowerShell repository/feed. 5. Installs or updates the PowerShell modules (i) If ModuleFilter is set only the modules in the filter are installed. 6. Copies the module script folder into the DestinationPath (i) Except if SkipScriptInstallation is set. a. Creates a backup of the module folder in DestinationPath. b. Clears the module folder in DestinationPath. c. Clears the DestinationPath (i) Only if ClearDestination is set. d. Copies the scripts from the PS Module folder to the DestinationPath. e. Creates a shortcut to the users desktop (i) Except if SkipShortcut is set. .EXAMPLE Install-FpsTools ` -AzDoPatMethod 'CredentialManager' ` -ModuleFilter @('FpsBcGeneral', 'FpsDevelopment', 'FpsNavInternal') ` -AzDoFeedName 'fpstools_internal' ` -AzDoProjectName '4PS Tools' .EXAMPLE Install-FpsTools ` -DestinationPath (Join-Path -Path ([Environment]::GetFolderPath("mydocuments")) -ChildPath 'FpsNavDevelopmentTools') -ModuleFilter @('FpsDevelopment') -AzDoPatMethod 'CredentialManager' ` -AzDoFeedName 'fpstools_internal' ` -AzDoProjectName '4PS Tools' ` -ClearDestination ` -ErrorAction Stop .EXAMPLE Install-FpsTools ` -AzDoPatMethod 'CredentialManager' ` -AzDoFeedName 'fpstools_internal' ` -AzDoProjectName '4PS Tools' ` -SkipScriptInstallation #> function Install-FpsTools { [CmdletBinding()] param ( # Destination Path for the PowerShell Scripts to be installed in. Default is the '4PS PowerShell Tools' folder in your documents. [string] $DestinationPath = (Join-Path ` -Path ([Environment]::GetFolderPath("mydocuments")) ` -ChildPath '4PS PowerShell Tools'), # Option CredentialManager stores a manual suplied PAT token in Windows Credential Manager. Option Az.Accounts generates a temporary PAT token. [ValidateSet('CredentialManager', 'Az.Accounts')] [string] $AzDoPatMethod = 'CredentialManager', # Filter on the modules to install. Default installs all modules from feed. [string[]] $ModuleFilter, # Azure DevOps Artifact Feed name [string] $AzDoFeedName = 'fpstools_internal', # Azure DevOps organization name [string] $AzDoOrganisation = '4psnl', # Azure DevOps project name [string] $AzDoProjectName = '4PS Tools', # For a clean installation enable ClearDestination. This will purge the content of $DestinationPath before installing the modules. [switch] $ClearDestination, # Disables the creation of the shortcut on the desktop to the destionation folder [switch] $SkipShortcut, # Disables the installation of the PowerShell scripts to the destination folder. [switch] $SkipScriptInstallation ) if($SkipScriptInstallation){$SkipShortcut = $true} $TaskStartTime = Write-StartProcessLine -StartLogText ('Check if folder ''{0}'' exists and is editable.' -f (Split-Path -Path $destinationPath -Leaf)) if ($SkipScriptInstallation -eq $false) { # Create destination folder if not exists if (-not (Test-Path $DestinationPath)) { 'Folder created: {0}' -f $DestinationPath | Write-Host New-Item -Path $DestinationPath -ItemType Directory -Force | Out-Null } # Test if items in destination folder are not locked by another process $lockedFiles = Test-ReadWriteAccessFile -Path $destinationPath if($lockedFiles -ne $false){ $msg = @() $msg += 'One or more files in folder {0} are still locked by another process. Please close other processes before continuing' -f (Split-Path -Path $destinationPath -Leaf) | Write-Host $msg += 'The following files are still locked: {0}' -f $($LockedFiles | Out-String) Write-Error ($msg | Out-String) } else { 'Items in folder ''{0}'' are not locked by other processes' -f (Split-Path -Path $destinationPath -Leaf) | Write-Host } } Write-EndProcessLine -TaskStartTime $TaskStartTime $TaskStartTime = Write-StartProcessLine -StartLogText ('Retrieving Personal Access Token (PAT) with method: {0}' -f $AzDoPatMethod) # Get Azure DevOps PAT token $personalAccessToken = Get-FpsAzDoPat -method $AzDoPatMethod # Test if Azure DevOps API is accessible with PAT $result = Test-FpsAzDoPat ` -PersonalAccessToken $personalAccessToken ` -AzDoOrganisation $AzDoOrganisation if ($result -eq $false){ switch ($AzDoPatMethod){ 'Az.Accounts' { $msg = @() $msg += 'Personal Access Token is not valid.' $msg += 'Please validate if you have access to the artifact feed ''{0}'' in project ''{1}'' on Azure DevOps' -f $AzDoFeedName, $AzDoProjectName $msg += 'Or try to use the alternative Azure DevOps PAT method ''CredentialManager''.' Write-Error ($msg | Out-String) } 'CredentialManager' { Set-FpsAzDoPat $personalAccessToken = Get-FpsAzDoPat -method $AzDoPatMethod } } } # Test if Azure DevOps Feed is accessible $result = Test-FpsAzDoPatFeedAccess ` -PersonalAccessToken $personalAccessToken ` -AzDoOrganisation $AzDoOrganisation ` -AzDoProjectName $AzDoProjectName ` -AzDoFeedName $AzDoFeedName ` -ErrorAction stop if ($result -eq $false){ $msg = 'Please validate if you have access to the artifact feed ''{0}'' in project ''{1}'' on Azure DevOps' -f $AzDoFeedName, $AzDoProjectName Write-Error ($msg | Out-String) } # Create credential object $feedUsername = 'dummy@4ps.nl' $feedPassword = ConvertTo-SecureString -String $personalAccessToken -AsPlainText -Force $feedCredential = New-Object System.Management.Automation.PSCredential ($feedUsername, $feedPassword) Write-EndProcessLine -TaskStartTime $TaskStartTime $TaskStartTime = Write-StartProcessLine -StartLogText 'Install the Azure DevOps Artifact feed URL (Nuget)' Install-FpsRepository ` -AzDoOrganisation $AzDoOrganisation ` -AzDoProjectName $AzDoProjectName ` -AzDoFeedName $AzDoFeedName ` -Credential $feedCredential Write-EndProcessLine -TaskStartTime $TaskStartTime $TaskStartTime = Write-StartProcessLine -StartLogText 'Install the PowerShell modules from Artifact feed.' # Get modules $modulesInFeed = Find-Module -Repository $AzDoFeedName -Credential $feedCredential # Apply module filter if([string]::IsNullOrEmpty($ModuleFilter)){ $modules = $modulesInFeed } else { $modules = $modulesInFeed | Where-Object -Property Name -in $ModuleFilter } "The following modules are found in feed {0}: `n{1}" -f $AzDoFeedName, ($modules.Name | Out-String) | Write-Host # Install and import modules Import-FpsModule ` -ModuleNames $modules.Name ` -Repository $AzDoFeedName ` -feedCredential $feedCredential ` -Force Write-EndProcessLine -TaskStartTime $TaskStartTime $TaskStartTime = Write-StartProcessLine -StartLogText 'Creating a backup of local scripts and clear folder' if ($SkipScriptInstallation -eq $false) { # Backup and clear installed module script folders $backupDestination = Join-Path -Path $env:LOCALAPPDATA -ChildPath ('4ps\fpstools\backup\{0}' -f (Get-Date).ToString('yyyy-MM-dd_HH.mm.ss')) 'Creating a backup of the PowerShell script folder to ''{0}''' -f $backupDestination | Write-Host foreach ($moduleName in $modules.Name){ $scriptPath = Join-Path -Path $DestinationPath -ChildPath $moduleName if((Test-Path $scriptPath) -eq $false){ continue } # Create backup folder if not exists if((Test-Path $backupDestination) -eq $false){ New-Item -Path $backupDestination -ItemType Directory -Force | Out-Null } ' Create backup for scripts from module ''{0}''' -f $moduleName | Write-Host Copy-Item -Path $scriptPath -Destination $backupDestination -Recurse -Force -ErrorAction Stop ' Clearing PowerShell scripts from ''{0}''' -f $scriptPath | Write-Host Remove-Item (Join-Path $scriptPath '*') -Recurse -Force } if($ClearDestination){ 'ClearDestination enabled, clearing folder ''{0}''' -f $DestinationPath | Write-Host Remove-Item (Join-Path $DestinationPath '*') -Recurse -Force } # Remove older backups (keep max 6 backups) if((Test-Path $backupDestination) -eq $true){ $backups = Get-ChildItem (Split-Path -Parent $backupDestination) if($Backups.Count -gt 6){ $exclude = $backups | Sort-Object -Property CreationTime -Descending | Select-Object -First 6 Get-ChildItem (Split-Path -Parent $backupDestination) -Exclude $exclude | Remove-Item -Recurse -Force } } } Write-EndProcessLine -TaskStartTime $TaskStartTime $TaskStartTime = Write-StartProcessLine -StartLogText ('Copying script folder(s) to destination ''{0}''' -f (Split-Path -Path $destinationPath -Leaf)) if ($SkipScriptInstallation -eq $false) { foreach ($moduleName in $modules.Name){ # Get module script folder $moduleBase = (Get-Module -Name $moduleName -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1).ModuleBase $moduleScriptPath = Join-Path -Path $moduleBase -ChildPath 'Scripts' if(Test-Path $moduleScriptPath){ 'Module {0} contains a script folder.' -f $moduleName | Write-Host $scriptDestionationPath = Join-Path -Path $DestinationPath -ChildPath $moduleName if((Test-Path $scriptDestionationPath) -eq $false){ New-Item -Path $scriptDestionationPath -ItemType Directory -Force | Out-Null } "Copying scripts for module {0}`n Source: '{1}'`n Target: '{2}'" -f $moduleName, $moduleScriptPath, $scriptDestionationPath | Write-Host Get-ChildItem $moduleScriptPath | ForEach-Object { Copy-Item -Path $_.FullName -Destination $scriptDestionationPath -Recurse -Force | Out-Null } continue } 'Module {0} does not contain a script folder.' -f $moduleName | Write-Host } } else { 'Installation of the PowerShell scripts to the destionation folder is skipped.' | Write-Host } # Create shortcut to the destination folder if ($SkipShortcut -eq $false) { $linkPath = Join-Path ` -Path ([Environment]::GetFolderPath('Desktop')) ` -ChildPath ('{0}.lnk' -f (Split-Path $DestinationPath -Leaf)) if (Test-Path $linkPath) { Remove-Item $linkPath } $wshShell = New-Object -comObject WScript.Shell $shortcut = $wshShell.CreateShortcut($linkPath) $shortcut.TargetPath = $DestinationPath $shortcut.Save() 'A hyperlink to the PowerShell scripts is created on your desktop' | Write-Host } else { 'Creation of the hyperlink on the desktop is skipped.' | Write-Host } Write-EndProcessLine -TaskStartTime $TaskStartTime } Export-ModuleMember -Function Install-FpsTools |