modules/Utilities/public/Install-SdnDiagnostics.ps1
# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. function Install-SdnDiagnostics { <# .SYNOPSIS Install SdnDiagnostic Module to remote computers if not installed or version mismatch. .PARAMETER ComputerName Type the NetBIOS name, an IP address, or a fully qualified domain name of one or more remote computers. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the password. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String[]]$ComputerName, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false)] [switch]$Force ) try { [System.IO.FileInfo]$moduleRootDir = "C:\Program Files\WindowsPowerShell\Modules" $filteredComputerName = [System.Collections.ArrayList]::new() $installNodes = [System.Collections.ArrayList]::new() # if we have multiple modules installed on the current workstation, # abort the operation because side by side modules can cause some interop issues to the remote nodes $localModule = Get-Module -Name 'SdnDiagnostics' if ($localModule.Count -gt 1) { throw New-Object System.ArgumentOutOfRangeException("Detected more than one module version of SdnDiagnostics. Remove existing modules and restart your PowerShell session.") } # since we may not know where the module was imported from we cannot accurately assume the $localModule.ModuleBase is correct # manually generate the destination path we want the module to be installed on remote nodes if ($localModule.ModuleBase -ilike "*$($localModule.Version.ToString())") { [System.IO.FileInfo]$destinationPathDir = "{0}\{1}\{2}" -f $moduleRootDir.FullName, 'SdnDiagnostics', $localModule.Version.ToString() } else { [System.IO.FileInfo]$destinationPathDir = "{0}\{1}" -f $moduleRootDir.FullName, 'SdnDiagnostics' } "Current version of SdnDiagnostics is {0}" -f $localModule.Version.ToString() | Trace-Output # make sure that in instances where we might be on a node within the sdn dataplane, # that we do not remove the module locally foreach ($computer in $ComputerName) { if (Test-ComputerNameIsLocal -ComputerName $computer) { "Detected that {0} is local machine. Skipping update operation for {0}." -f $computer | Trace-Output -Level:Warning continue } [void]$filteredComputerName.Add($computer) } # check to see if the current version is already present on the remote computers # else if we -Force defined, we can just move forward if ($Force) { "{0} will be installed on all computers" -f $localModule.Version.ToString() | Trace-Output $installNodes = $filteredComputerName } else { "Getting current installed version of SdnDiagnostics on remote nodes" | Trace-Output $remoteModuleVersion = Invoke-PSRemoteCommand -ComputerName $filteredComputerName -Credential $Credential -ScriptBlock { try { # Get the latest version of SdnDiagnostics Module installed $version = (Get-Module -Name SdnDiagnostics -ListAvailable -ErrorAction SilentlyContinue | Sort-Object Version -Descending)[0].Version.ToString() } catch { # in some instances, the module will not be available and as such we want to skip the noise and return # a string back to the remote call command which we can do proper comparison against $version = '0.0.0.0' } return $version } # enumerate the versions returned for each computer and compare with current module version to determine if we should perform an update foreach ($computer in ($remoteModuleVersion.PSComputerName | Sort-Object -Unique)) { $remoteComputerModuleVersions = $remoteModuleVersion | Where-Object {$_.PSComputerName -ieq $computer} "{0} is currently using version(s): {1}" -f $computer, ($remoteComputerModuleVersions.ToString() -join ' | ') | Trace-Output -Level:Verbose $updateRequired = $true foreach ($version in $remoteComputerModuleVersions) { if ([version]$version -ge [version]$localModule.Version) { $updateRequired = $false # if we found a version that is greater or equal to current version, break out of current foreach loop for the versions # and move to the next computer as update is not required break } else { $updateRequired = $true } } if ($updateRequired) { "{0} will be updated to {1}" -f $computer, $localModule.Version.ToString() | Trace-Output [void]$installNodes.Add($computer) } } } if (-NOT $installNodes) { "All computers are up to date with version {0}. No update required" -f $localModule.Version.ToString() | Trace-Output return } # clean up the module directory on remote computers "Cleaning up SdnDiagnostics in remote Windows PowerShell Module directory" | Trace-Output Invoke-PSRemoteCommand -ComputerName $installNodes -Credential $Credential -ScriptBlock { $modulePath = 'C:\Program Files\WindowsPowerShell\Modules\SdnDiagnostics' if (Test-Path -Path $modulePath -PathType Container) { Remove-Item -Path $modulePath -Recurse -Force } } # copy the module base directory to the remote computers # currently hardcoded to machine's module path. Use the discussion at https://github.com/microsoft/SdnDiagnostics/discussions/68 to get requirements and improvement Copy-FileToRemoteComputer -Path $localModule.ModuleBase -ComputerName $installNodes -Destination $destinationPathDir.FullName -Credential $Credential -Recurse -Force # ensure that we destroy the current pssessions for the computer to prevent any caching issues # we want to target all the original computers, as may be possible that we running on a node within the sdn fabric # and have existing PSSession to itself from previous execution run Remove-PSRemotingSession -ComputerName $ComputerName } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } |