DSCResources/cChocoPackageInstall/cChocoPackageInstall.psm1
# Copyright (c) 2017 Chocolatey Software, Inc. # Copyright (c) 2013 - 2017 Lawrence Gripper & original authors/contributors from https://github.com/chocolatey/cChoco # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function Get-TargetResource { [OutputType([hashtable])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "MinimumVersion")] param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name, [ValidateNotNullOrEmpty()] [string] $Params, [ValidateNotNullOrEmpty()] [string] $Version, [ValidateNotNull()] [string] $MinimumVersion, [ValidateNotNullOrEmpty()] [string] $Source ) Write-Verbose -Message 'Start Get-TargetResource' if (-Not (Test-ChocoInstalled)) { throw "cChocoPackageInstall requires Chocolatey to be installed, consider using cChocoInstaller with 'dependson' in dsc config" } #Needs to return a hashtable that returns the current #status of the configuration component $Configuration = @{ Name = $Name Params = $Params Version = $Version Source = $Source } return $Configuration } function Set-TargetResource { [CmdletBinding(SupportsShouldProcess)] param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name, [ValidateSet('Present','Absent')] [string] $Ensure='Present', [ValidateNotNullOrEmpty()] [string] $Params, [ValidateNotNullOrEmpty()] [string] $Version, [ValidateNotNull()] [string] $MinimumVersion, [string] $Source, [String] $chocoParams, [bool] $AutoUpgrade = $false ) Write-Verbose -Message 'Start Set-TargetResource' $isVersionPresent = $PSBoundParameters.ContainsKey('Version') $isMinimumVersionPresent = $PSBoundParameters.ContainsKey('MinimumVersion') if ($isVersionPresent -and $isMinimumVersionPresent ) { throw "Cannot specify 'Version' and 'MinimumVersion' in the same configuration" } if (-Not (Test-ChocoInstalled)) { throw "cChocoPackageInstall requires Chocolatey to be installed, consider using cChocoInstaller with 'dependson' in dsc config" } $isInstalled = IsPackageInstalled -pName $Name #Determine the correct package version to use get to desired state if ($isVersionPresent -or $isMinimumVersionPresent) { if ($isVersionPresent) { $versionToInstall = $PSBoundParameters['Version'] } else { $versionToInstall = $PSBoundParameters['MinimumVersion'] } } #Uninstall if Ensure is set to absent and the package is installed if ($isInstalled) { if ($Ensure -eq 'Absent') { $whatIfShouldProcess = $pscmdlet.ShouldProcess("$Name", 'Remove Chocolatey package') if ($whatIfShouldProcess) { Write-Verbose -Message "Removing $Name as ensure is set to absent" UninstallPackage -pName $Name -pParams $Params } } else { $whatIfShouldProcess = $pscmdlet.ShouldProcess("$Name", 'Installing / upgrading package from Chocolatey') if ($whatIfShouldProcess) { if ($Version) { Write-Verbose -Message "Uninstalling $Name due to version mis-match" UninstallPackage -pName $Name -pParams $Params Write-Verbose -Message "Re-Installing $Name with correct version $versionToInstall" InstallPackage -pName $Name -pParams $Params -pVersion $versionToInstall -pSource $Source -cParams $chocoParams } elseif ($MinimumVersion) { Write-Verbose -Message "Upgrading $Name because installed version is lower that the specified minimum" $chocoParams += " --version='$versionToInstall'" Upgrade-Package -pName $Name -pParams $Params -pSource $Source -cParams $chocoParams } elseif ($AutoUpgrade) { Write-Verbose -Message "Upgrading $Name due to version mis-match" Upgrade-Package -pName $Name -pParams $Params -pSource $Source -cParams $chocoParams } } } } else { $whatIfShouldProcess = $pscmdlet.ShouldProcess("$Name", 'Install package from Chocolatey') if ($whatIfShouldProcess) { InstallPackage -pName $Name -pParams $Params -pVersion $versionToInstall -pSource $Source -cParams $chocoParams } } } function Test-TargetResource { [CmdletBinding(SupportsShouldProcess)] [OutputType([bool])] param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name, [ValidateSet('Present','Absent')] [string] $Ensure='Present', [ValidateNotNullOrEmpty()] [string] $Params, [ValidateNotNullOrEmpty()] [string] $Version, [ValidateNotNull()] [string] $MinimumVersion, [string] $Source, [ValidateNotNullOrEmpty()] [String] $chocoParams, [bool] $AutoUpgrade = $false ) Write-Verbose -Message 'Start Test-TargetResource' $isVersionPresent = $PSBoundParameters.ContainsKey('Version') $isMinimumVersionPresent = $PSBoundParameters.ContainsKey('MinimumVersion') if ($isVersionPresent -and $isMinimumVersionPresent ) { throw "Cannot specify 'Version' and 'MinimumVersion' in the same configuration" } if (-Not (Test-ChocoInstalled)) { return $false } $isInstalled = IsPackageInstalled -pName $Name if ($ensure -eq 'Absent') { if ($isInstalled -eq $false) { return $true } else { return $false } } if ($version) { Write-Verbose -Message "Checking if $Name is installed and if version matches $version" $result = IsPackageInstalled -pName $Name -pVersion $Version } elseif ($MinimumVersion) { Write-Verbose -Message "Checking if $Name is installed and version is $MinimumVersion or higher" $result = IsPackageInstalled -pName $Name -pMinimumVersion $MinimumVersion } else { Write-Verbose -Message "Checking if $Name is installed" if ($AutoUpgrade -and $isInstalled) { $testParams = @{ pName = $Name } if ($Source){ $testParams.pSource = $Source } $result = Test-LatestVersionInstalled @testParams } else { $result = $isInstalled } } Return $result } function Test-ChocoInstalled { Write-Verbose -Message 'Test-ChocoInstalled' $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') Write-Verbose -Message "Env:Path contains: $env:Path" if (Test-Command -command choco) { Write-Verbose -Message 'YES - Choco is Installed' return $true } Write-Verbose -Message 'NO - Choco is not Installed' return $false } Function Test-Command { [CmdletBinding()] [OutputType([bool])] Param ( [string]$command = 'choco' ) Write-Verbose -Message "Test-Command $command" if (Get-Command -Name $command -ErrorAction SilentlyContinue) { Write-Verbose -Message "$command exists" return $true } else { Write-Verbose -Message "$command does NOT exist" return $false } } function InstallPackage { [Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingInvokeExpression','')] param( [Parameter(Position=0,Mandatory)] [string]$pName, [Parameter(Position=1)] [string]$pParams, [Parameter(Position=2)] [string]$pVersion, [Parameter(Position=3)] [string]$pSource, [Parameter(Position=4)] [string]$cParams ) $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') [string]$chocoParams = '-y' if ($pParams) { $chocoParams += " --params=`"$pParams`"" } if ($pVersion) { $chocoParams += " --version=`"$pVersion`"" } if ($pSource) { $chocoParams += " --source=`"$pSource`"" } if ($cParams) { $chocoParams += " $cParams" } # Check if Chocolatey version is Greater than 0.10.4, and add --no-progress if ((Get-ChocoVersion) -ge [System.Version]('0.10.4')){ $chocoParams += " --no-progress" } $cmd = "choco install $pName $chocoParams" Write-Verbose -Message "Install command: '$cmd'" $packageInstallOuput = Invoke-Expression -Command $cmd Write-Verbose -Message "Package output $packageInstallOuput" # Clear Package Cache Get-ChocoInstalledPackage -Purge #refresh path varaible in powershell, as choco doesn"t, to pull in git $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') } function UninstallPackage { [Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingInvokeExpression','')] param( [Parameter(Position=0,Mandatory)] [string]$pName, [Parameter(Position=1)] [string]$pParams ) $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') [string]$chocoParams = "-y" if ($pParams) { $chocoParams += " --params=`"$pParams`"" } if ($pVersion) { $chocoParams += " --version=`"$pVersion`"" } # Check if Chocolatey version is Greater than 0.10.4, and add --no-progress if ((Get-ChocoVersion) -ge [System.Version]('0.10.4')){ $chocoParams += " --no-progress" } $cmd = "choco uninstall $pName $chocoParams" Write-Verbose -Message "Uninstalling $pName with: '$cmd'" $packageUninstallOuput = Invoke-Expression -Command $cmd Write-Verbose -Message "Package uninstall output $packageUninstallOuput " # Clear Package Cache Get-ChocoInstalledPackage -Purge #refresh path varaible in powershell, as choco doesn"t, to pull in git $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') } function IsPackageInstalled { [CmdletBinding(DefaultParameterSetName = 'RequiredVersion')] [OutputType([Boolean])] param( [Parameter(Position=0, Mandatory)] [string]$pName, [Parameter(ParameterSetName = 'RequiredVersion')] [string]$pVersion, [Parameter(ParameterSetName = 'MinimumVersion')] [string]$pMinimumVersion ) Write-Verbose -Message "Start IsPackageInstalled $pName" $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') Write-Verbose -Message "Path variables: $($env:Path)" $installedPackages = Get-ChocoInstalledPackage if ($pVersion) { Write-Verbose 'Comparing required version' $installedPackages = $installedPackages | Where-Object { $_.Name -eq $pName -and $_.Version -eq $pVersion} } elseif ($pMinimumVersion) { Write-Verbose 'Comparing minimum version' # version comparison can be done with [System.Version] but this lacks the ability to compare pre-release versions # because of this limitation MinimumVersion cannot be used in conjuction with pre-release packages $pre = ($pMinimumVersion -split "-")[1] if ($pre) { throw "MinimumVersion does not support comparing pre-releases, please use Version parameter instead" } $comparablePackages = $installedPackages | Where-Object { $_.Name -eq $pName} | ForEach-Object { # as mentioned above we cant convert prerelease versions to [Sytem.Version] so we ignore anything after "-" # leaving just the . seperated numeric version. this is loosely equivalent to "rounding down" $parseableVersion = ($_.Version -split "-")[0] $v = [System.Version]($parseableVersion) $_ | Add-Member -MemberType NoteProperty -Name ComparableVersion -Value $v -PassThru } $installedPackages = $comparablePackages | Where-Object {$_.ComparableVersion -ge $pMinimumVersion} } else { Write-Verbose "Finding packages -eq $pName" $installedPackages = $installedPackages | Where-Object { $_.Name -eq $pName} } $count = @($installedPackages).Count Write-Verbose "Found $Count matching packages" if ($Count -gt 0) { $installedPackages | ForEach-Object {Write-Verbose -Message "Found: $($_.Name) with version $($_.Version)"} return $true } return $false } Function Test-LatestVersionInstalled { [Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingInvokeExpression','')] param( [Parameter(Mandatory)] [string]$pName, [string]$pSource ) Write-Verbose -Message "Testing if $pName can be upgraded" [string]$chocoParams = '--noop' if ($pSource) { $chocoParams += " --source=`"$pSource`"" } $cmd = "choco upgrade $pName $chocoParams" Write-Verbose -Message "Testing if $pName can be upgraded: '$cmd'" $packageUpgradeOuput = Invoke-Expression -Command $cmd $packageUpgradeOuput | ForEach-Object {Write-Verbose -Message $_} if ($packageUpgradeOuput -match "$pName.*is the latest version available based on your source") { return $true } return $false } ##region - chocolately installer work arounds. Main issue is use of write-host ##attempting to work around the issues with Chocolatey calling Write-host in its scripts. function global:Write-Host { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalFunctions", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "NoNewLine")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "ForegroundColor")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "BackgroundColor")] param( [Parameter(Mandatory, Position = 0)] [Object] $Object, [Switch] $NoNewLine, [ConsoleColor] $ForegroundColor, [ConsoleColor] $BackgroundColor ) #Override default Write-Host... Write-Verbose -Message $Object } Function Upgrade-Package { [Diagnostics.CodeAnalysis.SuppressMessage('PSUseApprovedVerbs','')] [Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingInvokeExpression','')] param( [Parameter(Position=0,Mandatory)] [string]$pName, [Parameter(Position=1)] [string]$pParams, [Parameter(Position=2)] [string]$pSource, [Parameter(Position=3)] [string]$cParams ) $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') Write-Verbose -Message "Path variables: $($env:Path)" [string]$chocoParams = '-dv -y' if ($pParams) { $chocoParams += " --params=`"$pParams`"" } if ($pSource) { $chocoParams += " --source=`"$pSource`"" } if ($cParams) { $chocoParams += " $cParams" } # Check if Chocolatey version is Greater than 0.10.4, and add --no-progress if ((Get-ChocoVersion) -ge [System.Version]('0.10.4')){ $chocoParams += " --no-progress" } $cmd = "choco upgrade $pName $chocoParams" Write-Verbose -Message "Upgrade command: '$cmd'" if (-not (IsPackageInstalled -pName $pName)) { throw "$pName is not installed, you cannot upgrade" } $packageUpgradeOuput = Invoke-Expression -Command $cmd $packageUpgradeOuput | ForEach-Object { Write-Verbose -Message $_ } # Clear Package Cache Get-ChocoInstalledPackage -Purge } function Get-ChocoInstalledPackage { [CmdletBinding()] param ( [switch]$Purge, [switch]$NoCache ) $ChocoInstallLP = Join-Path -Path $env:ChocolateyInstall -ChildPath 'cache' if ( -not (Test-Path $ChocoInstallLP)){ New-Item -Name 'cache' -Path $env:ChocolateyInstall -ItemType Directory | Out-Null } $ChocoInstallList = Join-Path -Path $ChocoInstallLP -ChildPath 'ChocoInstalled.xml' if ($Purge.IsPresent) { Remove-Item $ChocoInstallList -Force $res = $true } else { $PackageCacheSec = (Get-Date).AddSeconds('-60') if ( $PackageCacheSec -lt (Get-Item $ChocoInstallList -ErrorAction SilentlyContinue).LastWriteTime ) { $res = Import-Clixml $ChocoInstallList } else { $res = choco list -lo -r | ConvertFrom-Csv -Header 'Name', 'Version' -Delimiter "|" if ( -not $NoCache){ $res | Export-Clixml -Path $ChocoInstallList } } } Return $res } function Get-ChocoVersion { [OutputType([System.Version])] [CmdletBinding()] param ( [switch]$Purge, [switch]$NoCache ) $chocoInstallCache = Join-Path -Path $env:ChocolateyInstall -ChildPath 'cache' if ( -not (Test-Path $chocoInstallCache)){ New-Item -Name 'cache' -Path $env:ChocolateyInstall -ItemType Directory | Out-Null } $chocoVersion = Join-Path -Path $chocoInstallCache -ChildPath 'ChocoVersion.xml' if ($Purge.IsPresent) { Remove-Item $chocoVersion -Force $res = $true } else { $cacheSec = (Get-Date).AddSeconds('-60') if ( $cacheSec -lt (Get-Item $chocoVersion -ErrorAction SilentlyContinue).LastWriteTime ) { $res = Import-Clixml $chocoVersion } else { $cmd = choco -v $res = [System.Version]($cmd.Split('-')[0]) if (-not $NoCache) { $res | Export-Clixml -Path $chocoVersion } } } return $res } Export-ModuleMember -Function *-TargetResource |