Skip to content
This repository was archived by the owner on Feb 19, 2019. It is now read-only.

Commit

Permalink
Merge branch 'per-machine-install-v6' into stable
Browse files Browse the repository at this point in the history
* per-machine-install-v6:
  tests: add environment manipulation mocking facility
  helpers: add environment variable management abstractions
  rename Test-AdminRights to Test-ProcessAdminRights
  setup tests: fix formatting
  setup tests: do not vary by UAC status
  when running with administrative rights, set environment variables at Machine scope
  setup tests: with admin rights, variables should be set at Machine level regardless of UAC
  setup tests: add simulated standard user scenarios
  enable mocking Test-AdminRights for setup tests
  Test-AdminRights: request WindowsIdentity with minimal access necessary
  add Test-AdminRights helper
  setup tests: mock UAC status
  setup tests: conditionally test default installation
  add tests for Chocolatey setup (Initialize-Chocolatey)
  tests: add environment variable handling facilities
  • Loading branch information
ferventcoder committed Jun 25, 2014
2 parents 4fd0ad1 + 4c28671 commit 02eccc9
Show file tree
Hide file tree
Showing 19 changed files with 1,109 additions and 81 deletions.
12 changes: 4 additions & 8 deletions nuget/tools/chocolateysetup.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,8 @@ param(
[string]$folder
)
$environmentTarget = [System.EnvironmentVariableTarget]::User
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$UACEnabled = Get-UACEnabled
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -and !$UACEnabled) {
Write-Debug "Administrator installing with UAC disabled so using Machine environment variable target instead of User."
if (Test-ProcessAdminRights) {
Write-Debug "Administrator installing so using Machine environment variable target instead of User."
$environmentTarget = [System.EnvironmentVariableTarget]::Machine
}
Write-Host "Creating $chocInstallVariableName as an Environment variable (targeting `'$environmentTarget`') and setting it to `'$folder`'"
Expand Down Expand Up @@ -156,10 +154,8 @@ param(
)

$environmentTarget = [System.EnvironmentVariableTarget]::User
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$UACEnabled = Get-UACEnabled
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -and !$UACEnabled) {
Write-Debug "Administrator installing with UAC disabled so using Machine environment variable target instead of User."
if (Test-ProcessAdminRights) {
Write-Debug "Administrator installing so using Machine environment variable target instead of User."
$environmentTarget = [System.EnvironmentVariableTarget]::Machine
}

Expand Down
2 changes: 1 addition & 1 deletion src/functions/Chocolatey-Python.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ param(
Chocolatey-InstallIfMissing 'python'

if ($($env:Path).ToLower().Contains("python") -eq $false) {
$env:Path = [Environment]::GetEnvironmentVariable('Path',[System.EnvironmentVariableTarget]::Machine);
$env:Path = Get-EnvironmentVariable -Name 'Path' -Scope Machine
}

Chocolatey-InstallIfMissing 'easy.install'
Expand Down
2 changes: 1 addition & 1 deletion src/functions/Chocolatey-RubyGem.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ param(
Chocolatey-InstallIfMissing 'ruby'

if ($($env:Path).ToLower().Contains("ruby") -eq $false) {
$env:Path = [Environment]::GetEnvironmentVariable('Path',[System.EnvironmentVariableTarget]::Machine);
$env:Path = Get-EnvironmentVariable -Name 'Path' -Scope Machine
}


Expand Down
6 changes: 5 additions & 1 deletion src/helpers/chocolateyInstaller.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ Export-ModuleMember -Function `
Write-ChocolateyFailure,`
Write-Host,`Write-Debug,`Write-Error,`
Start-ChocolateyProcessAsAdmin,`
Test-ProcessAdminRights,`
Uninstall-ChocolateyPackage,`
Update-SessionEnvironment
Update-SessionEnvironment,`
Get-EnvironmentVariableNames,`
Get-EnvironmentVariable,`
Set-EnvironmentVariable
2 changes: 1 addition & 1 deletion src/helpers/functions/Get-BinRoot.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function Get-BinRoot {

# Now that we figured out the binRoot, let's store it as per proposal #3 line #7
if (-not($env:ChocolateyBinRoot -eq $binRoot)) {
[Environment]::SetEnvironmentVariable("ChocolateyBinRoot", "$binRoot", "User")
Set-EnvironmentVariable -Name "ChocolateyBinRoot" -Value $binRoot -Scope User
# Note that user variables pose a problem when there are two admins on one computer. But this is what was decided upon.
}

Expand Down
3 changes: 3 additions & 0 deletions src/helpers/functions/Get-EnvironmentVariable.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function Get-EnvironmentVariable([string] $Name, [System.EnvironmentVariableTarget] $Scope) {
[Environment]::GetEnvironmentVariable($Name, $Scope)
}
8 changes: 8 additions & 0 deletions src/helpers/functions/Get-EnvironmentVariableNames.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function Get-EnvironmentVariableNames([System.EnvironmentVariableTarget] $Scope) {
switch ($Scope) {
'User' { Get-Item 'HKCU:\Environment' | Select-Object -ExpandProperty Property }
'Machine' { Get-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' | Select-Object -ExpandProperty Property }
'Process' { Get-ChildItem Env:\ | Select-Object -ExpandProperty Key }
default { throw "Unsupported environment scope: $Scope" }
}
}
10 changes: 4 additions & 6 deletions src/helpers/functions/Install-ChocolateyEnvironmentVariable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,14 @@ param(
Write-Debug "Running 'Install-ChocolateyEnvironmentVariable' with variableName:`'$variableName`' and variableValue:`'$variableValue`'";

if ($variableType -eq [System.EnvironmentVariableTarget]::Machine) {
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$UACEnabled = Get-UACEnabled
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -and !$UACEnabled) {
[Environment]::SetEnvironmentVariable($variableName, $variableValue, $variableType)
if (Test-ProcessAdminRights) {
Set-EnvironmentVariable -Name $variableName -Value $variableValue -Scope $variableType
} else {
$psArgs = "[Environment]::SetEnvironmentVariable(`'$variableName`',`'$variableValue`', `'$variableType`')"
$psArgs = "Install-ChocolateyEnvironmentVariable -variableName `'$variableName`' -variableValue `'$variableValue`' -variableType `'$variableType`'"
Start-ChocolateyProcessAsAdmin "$psArgs"
}
} else {
[Environment]::SetEnvironmentVariable($variableName, $variableValue, $variableType)
Set-EnvironmentVariable -Name $variableName -Value $variableValue -Scope $variableType
}

Set-Content env:\$variableName $variableValue
Expand Down
14 changes: 6 additions & 8 deletions src/helpers/functions/Install-ChocolateyPath.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ param(
[System.EnvironmentVariableTarget] $pathType = [System.EnvironmentVariableTarget]::User
)
Write-Debug "Running 'Install-ChocolateyPath' with pathToInstall:`'$pathToInstall`'";
$originalPathToInstall = $pathToInstall

#get the PATH variable
$envPath = $env:PATH
#$envPath = [Environment]::GetEnvironmentVariable('Path', $pathType)
if (!$envPath.ToLower().Contains($pathToInstall.ToLower()))
{
Write-Host "PATH environment variable does not have $pathToInstall in it. Adding..."
$actualPath = [Environment]::GetEnvironmentVariable('Path', $pathType)
$actualPath = Get-EnvironmentVariable -Name 'Path' -Scope $pathType

$statementTerminator = ";"
#does the path end in ';'?
Expand All @@ -22,16 +22,14 @@ param(
$actualPath = $actualPath + $pathToInstall

if ($pathType -eq [System.EnvironmentVariableTarget]::Machine) {
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$UACEnabled = Get-UACEnabled
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -and !$UACEnabled) {
[Environment]::SetEnvironmentVariable('Path', $actualPath, $pathType)
if (Test-ProcessAdminRights) {
Set-EnvironmentVariable -Name 'Path' -Value $actualPath -Scope $pathType
} else {
$psArgs = "[Environment]::SetEnvironmentVariable('Path',`'$actualPath`', `'$pathType`')"
$psArgs = "Install-ChocolateyPath -pathToInstall `'$originalPathToInstall`' -pathType `'$pathType`'"
Start-ChocolateyProcessAsAdmin "$psArgs"
}
} else {
[Environment]::SetEnvironmentVariable('Path', $actualPath, $pathType)
Set-EnvironmentVariable -Name 'Path' -Value $actualPath -Scope $pathType
}

#add it to the local path as well so users will be off and running
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/functions/Set-EnvironmentVariable.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function Set-EnvironmentVariable([string] $Name, [string] $Value, [System.EnvironmentVariableTarget] $Scope) {
[Environment]::SetEnvironmentVariable($Name, $Value, $Scope)
}
25 changes: 25 additions & 0 deletions src/helpers/functions/Test-ProcessAdminRights.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function Test-ProcessAdminRights {
<#
.SYNOPSIS
Tests whether the current process is running with administrative rights.
.DESCRIPTION
This function checks whether the current process has administrative rights
by checking if the current user identity is a member of the Administrators group.
It returns $true if the current process is running with administrative rights,
$false otherwise.
On Windows Vista and later, with UAC enabled, the returned value represents the
actual rights available to the process, i.e. if it returns $true, the process is
running elevated.
.OUTPUTS
System.Boolean
#>

$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent([Security.Principal.TokenAccessLevels]'Query,Duplicate'))
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
Write-Debug "Test-ProcessAdminRights: returning $isAdmin"
return $isAdmin
}
18 changes: 5 additions & 13 deletions src/helpers/functions/Update-SessionEnvironment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,21 @@ powershell session with all environment settings possibly performed by
chocolatey package installs.
#>
$user = 'HKCU:\Environment'
$machine ='HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
#ordering is important here, $user comes after so we can override $machine
$machine, $user |
Get-Item |
'Machine', 'User' |
% {
$regPath = $_.PSPath
$_ |
Select -ExpandProperty Property |
$scope = $_
Get-EnvironmentVariableNames -Scope $scope |
% {
Set-Item "Env:$($_)" -Value (Get-ItemProperty $regPath -Name $_).$_
Set-Item "Env:$($_)" -Value (Get-EnvironmentVariable -Scope $scope -Name $_)
}
}

#Path gets special treatment b/c it munges the two together
$paths = 'Machine', 'User' |
% {
(Get-EnvironmentVar 'PATH' $_) -split ';'
(Get-EnvironmentVariable -Name 'PATH' -Scope $_) -split ';'
} |
Select -Unique
$Env:PATH = $paths -join ';'
}

function Get-EnvironmentVar($key, $scope) {
[Environment]::GetEnvironmentVariable($key, $scope)
}
2 changes: 2 additions & 0 deletions tests/_Common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ $src = Join-Path (Split-Path $here) 'src'
$script = Join-Path $src 'chocolatey.ps1'
$setup = Join-Path $here '_Setup.ps1'

. (Join-Path $here '_TestHelpers.ps1')

Get-Module ChocolateyInstaller -All | Remove-Module
Get-Module chocolatey | Remove-Module
Import-Module $script
Expand Down
182 changes: 182 additions & 0 deletions tests/_TestHelpers.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
$here = Split-Path -Parent $MyInvocation.MyCommand.Definition

Get-ChildItem "$here\mocks" -Filter *.ps1 -Recurse | ForEach-Object { Write-Debug "Importing $($_.FullName)"; . $_.FullName }

function Get-EnvironmentSnapshot()
{
Write-Debug 'Obtaining snapshot of the environment'
$machineEnv = @{}
$key = Get-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
$key.GetValueNames() | ForEach-Object { $machineEnv[$_] = $key.GetValue($_) }

$userEnv = @{}
$key = Get-Item 'HKCU:\Environment'
$key.GetValueNames() | ForEach-Object { $userEnv[$_] = $key.GetValue($_) }

$processEnv = @{}
Get-ChildItem Env:\ | ForEach-Object { $processEnv[$_.Key] = $_.Value }

return New-Object PSCustomObject -Property @{ machine = $machineEnv; user = $userEnv; process = $processEnv }
}

function Restore-Environment($state)
{
Write-Debug 'Restoring the environment'
$state.machine.GetEnumerator() | ForEach-Object {
$current = [Environment]::GetEnvironmentVariable($_.Key, 'Machine')
if ($current -ne $_.Value) {
Write-Warning "Restoring value of environment variable $($_.Key) at Machine scope. The need to do that means that some code did not use the environment manipulation functions *-EnvironmentVariable*."
[Environment]::SetEnvironmentVariable($_.Key, $_.Value, 'Machine')
}
}

$key = Get-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
$key.GetValueNames() | Where-Object { -not $state.machine.ContainsKey($_) } | ForEach-Object {
Write-Warning "Deleting environment variable $_ at Machine scope. The need to do that means that some code did not use the environment manipulation functions *-EnvironmentVariable*."
[Environment]::SetEnvironmentVariable($_, $null, 'Machine')
}

$state.user.GetEnumerator() | ForEach-Object {
$current = [Environment]::GetEnvironmentVariable($_.Key, 'User')
if ($current -ne $_.Value) {
Write-Warning "Restoring value of environment variable $($_.Key) at User scope. The need to do that means that some code did not use the environment manipulation functions *-EnvironmentVariable*."
[Environment]::SetEnvironmentVariable($_.Key, $_.Value, 'User')
}
}

$key = Get-Item 'HKCU:\Environment'
$key.GetValueNames() | Where-Object { -not $state.user.ContainsKey($_) } | ForEach-Object {
Write-Warning "Deleting environment variable $_ at User scope. The need to do that means that some code did not use the environment manipulation functions *-EnvironmentVariable*."
[Environment]::SetEnvironmentVariable($_, $null, 'User')
}

$state.process.GetEnumerator() | ForEach-Object {
$current = [Environment]::GetEnvironmentVariable($_.Key, 'Process')
if ($current -ne $_.Value) {
Write-Debug "Restoring value of environment variable $($_.Key) at Process scope"
[Environment]::SetEnvironmentVariable($_.Key, $_.Value, 'Process')
}
}

Get-ChildItem Env:\ | Select-Object -ExpandProperty Name | Where-Object { -not $state.process.ContainsKey($_) } | ForEach-Object {
Write-Debug "Deleting environment variable $_ at Process scope"
[Environment]::SetEnvironmentVariable($_, $null, 'Process')
}
}

function Setup-EnvironmentMockup
{
$global:ChocolateyTestEnvironmentVariables = Get-EnvironmentSnapshot
}

function Cleanup-EnvironmentMockup
{
$global:ChocolateyTestEnvironmentVariables = $null
}

function Execute-WithEnvironmentProtection($scriptBlock)
{
$savedEnvironment = Get-EnvironmentSnapshot
try
{
Setup-EnvironmentMockup
try
{
& $scriptBlock
}
finally
{
Cleanup-EnvironmentMockup
}
}
finally
{
Restore-Environment $savedEnvironment
}
}

function Add-EnvironmentVariable($name, $value, $targetScope)
{
Write-Debug "Setting $name to $value at $targetScope scope"
Set-EnvironmentVariable -Name $name -Value $Value -Scope $targetScope
if ($targetScope -eq 'Process') {
Write-Debug "Current $name value is '$value' (from Process scope)"
return
}
# find lowest scope with $name set and use that value as current
foreach ($currentScope in @('User', 'Machine')) {
$valueAtCurrentScope = Get-EnvironmentVariable -Name $name -Scope $currentScope
if ($valueAtCurrentScope -ne $null) {
Write-Debug "Current $name value is '$valueAtCurrentScope' (from $currentScope scope)"
Set-EnvironmentVariable -Name $name -Value $valueAtCurrentScope -Scope Process
break
}
}
}

function Remove-EnvironmentVariable($name)
{
Write-Debug "Ensuring environment variable $name is not set at any scope"
'Machine','User','Process' | ForEach-Object {
if (-not ([String]::IsNullOrEmpty((Get-EnvironmentVariable -Name $name -Scope $_)))) {
Write-Debug "Deleting environment variable $name at $_ scope"
Set-EnvironmentVariable -Name $name -Value $null -Scope $_
}
}
}

function Add-DirectoryToPath($directory, $scope)
{
$curPath = Get-EnvironmentVariable -Name 'PATH' -Scope $scope
$newPath = ($curPath -split ';' | Where-Object { $_.TrimEnd('\') -ne $directory.TrimEnd('\') }) -join ';'
if ($newPath -ne $curPath) {
Write-Debug "Directory $directory is already on PATH at $scope scope"
} else {
Write-Debug "Adding directory $directory to PATH at $scope scope"
if ([String]::IsNullOrEmpty($newPath)) {
Set-EnvironmentVariable -Name 'PATH' -Value $directory -Scope $scope
} else {
Set-EnvironmentVariable -Name 'PATH' -Value "$($newPath.TrimEnd(';'));$directory" -Scope $scope
}
}
if ($scope -ne 'Process') {
$curPath = Get-EnvironmentVariable -Name 'PATH' -Scope Process
$newPath = ($curPath -split ';' | Where-Object { $_.TrimEnd('\') -ne $directory.TrimEnd('\') }) -join ';'
if ($newPath -eq $curPath) {
Write-Debug "Adding directory $directory to PATH at Process scope"
if ([String]::IsNullOrEmpty($newPath)) {
Set-EnvironmentVariable -Name 'PATH' -Value $directory -Scope Process
} else {
Set-EnvironmentVariable -Name 'PATH' -Value "$($newPath.TrimEnd(';'));$directory" -Scope Process
}
}
}
}

function Remove-DirectoryFromPath($directory)
{
Write-Debug "Ensuring directory $directory is not on PATH at any scope"
'Machine','User','Process' | ForEach-Object {
$scope = $_
$curPath = Get-EnvironmentVariable -Name 'PATH' -Scope $scope
$newPath = ($curPath -split ';' | Where-Object { $_.TrimEnd('\') -ne $directory.TrimEnd('\') }) -join ';'
if ($newPath -ne $curPath) {
Write-Debug "Removing directory $directory from PATH at $scope scope"
Set-EnvironmentVariable -Name 'PATH' -Value $newPath -Scope $scope
}
}
}

function Assert-OnPath($directory, $pathScope)
{
$path = Get-EnvironmentVariable -Name 'PATH' -Scope $pathScope
$dirInPath = $path -split ';' | Where-Object { $_ -eq $directory }
"$dirInPath" | Should not BeNullOrEmpty
}

function Assert-NotOnPath($directory, $pathScope)
{
$path = Get-EnvironmentVariable -Name 'PATH' -Scope $pathScope
$dirInPath = $path -split ';' | Where-Object { $_ -eq $directory }
"$dirInPath" | Should BeNullOrEmpty
}
Loading

0 comments on commit 02eccc9

Please sign in to comment.