Skip to content

Instantly share code, notes, and snippets.

@chrisconlan
Created June 16, 2018 23:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisconlan/e8088c818c3f395db6dba27dcb91542a to your computer and use it in GitHub Desktop.
Save chrisconlan/e8088c818c3f395db6dba27dcb91542a to your computer and use it in GitHub Desktop.
Commentary and suggestions for Set-AnacondaEnv.ps1 by pldmgg https://gist.github.com/pldmgg/c84e802bcecd6e4c962f65be5b5d316d
[CmdletBinding()]
Param (
# First parameter -AnacondaDirectoryPath, optional, string
[Parameter(Mandatory=$False)]
[string]$AnacondaDirectoryPath,
# Second parameter -HideOutput, optional, switch (bool?)
[Parameter(Mandatory=$False)]
[switch]$HideOutput
)
#region >> Helper Functions
function Get-FilePath {
# Searches an array of directories for a file given a filename with extension
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True)]
[string]$FileNameWExtension,
[Parameter(Mandatory=$True)]
[string[]]$DirectoriesToSearch
)
# Initialize an empty array of results
[System.Collections.ArrayList]$FoundFileItems = @()
# Core loop to search directories in array
# (Chris) I don't understand the -match "\\pkgs\\" part
foreach ($Dir in $DirectoriesToSearch) {
if (Test-Path $Dir) {
Get-ChildItem -Path $Dir -File -Recurse -Filter $FileNameWExtension | Where-Object {
![bool]$($_.FullName -match "\\pkgs\\")
} | foreach {
$null = $FoundFileItems.Add($_)
}
}
else {
Write-Warning "The directory '$Dir' does not exist!"
}
}
# Throw error if unable to find anything.
# (Chris) The function accepts an argument for a search string but prints "Unable to find python.exe" on error.
if ($FoundFileItems.Count -eq 0) {
Write-Error "Unable to find python.exe under the following directories:`n$($DirectoriesToSearch -join "`n")`nHalting!"
$global:FunctionResult = "1"
return
}
# Assign output variable if single item matched
if ($FoundFileItems.Count -eq 1) {
$FinalFilePath = $FoundFileItems[0].FullName
}
# Throw error if multiple items found with search string.
# Prompt the user with choices in this case, write choice to output variable.
# (Chris) Again, this print "python.exe" as part of the error message, but the search string can be anaything
if ($FoundFileItems.Count -gt 1) {
Write-Warning "Multiple python.exe files were found! Available python.exe files are as follows:"
for ($i=0; $i -lt $FoundFileItems.Count; $i++) {
Write-Host "$i) '$($FoundFileItems[$i].FullName)'"
}
$ValidChoiceNumbers = 0..$($FoundFileItems.Count-1)
$ChoiceNumber = Read-Host -Prompt "Please enter the number that corresponds to the python.exe file that you would like to use"
while ($ValidChoiceNumbers -notcontains $ChoiceNumber) {
Write-Host "'$ChoiceNumber' is not a valid choice! Valid choices are $($ValidChoiceNumbers -join ",")"
$ChoiceNumber = Read-Host -Prompt "Please enter the number that corresponds to the python.exe file that you would like to use"
}
$FinalFilePath = $FoundFileItems[$ChoiceNumber].FullName
}
$FinalFilePath
}
# (Chris) The above function checks out.
# Outstanding questions:
# Why does fail message only discuss python.exe when search string is an arg?
# What does "-match \\pkgs\\" do?
# TODO:
# According to the above questions, simplify and extend accordingly.
function Add-ToPath {
# (Chris) It seems there is the option to add to the PSEnvPathOnly or to add to the SystemPathOnly.
# (Chris) My gut feeling is that we are only required to modify the Powershell path, and the users of
# (Chris) the Anaconda prompt expect the SystemPath variables to be untouched by such a program.
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True)]
[string]$PathToAdd,
[Parameter(Mandatory=$False)]
[switch]$SystemPathOnly,
[Parameter(Mandatory=$False)]
[switch]$PSEnvPathOnly
)
# If not PSEnvPathOnly, set system path variables
# (Chris) This seems to be a complicated and dangerous task. Again, let's avoid modifying the system path unnecessarily.
if (!$PSEnvPathOnly) {
$CurrentSystemPath = $(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
$CurrentSystemPathArray = $CurrentSystemPath -split ";"
if ($CurrentSystemPathArray -notcontains $PathToAdd) {
$UpdatedSystemPath = "$PathToAdd;$CurrentSystemPath"
Set-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment" -Name PATH -Value $UpdatedSystemPath
}
}
# If not SystemPathOnly, set env variables for Powershell environment
# (Chris) For this script, I believe this is the only part that should be included.
if (!$SystemPathOnly) {
$CurrentEnvPathArray = $env:Path -split ";"
if ($CurrentEnvPathArray -notcontains $PathToAdd) {
$env:Path = "$PathToAdd;$env:Path"
}
}
}
# (Chris) This function checks out.
# TODO:
# Remove ability to modify system path for safety
# Remove arguments related to PSEnvPathOnly and SystemPathOnly
# Make sure changes reconcile with rest of script
#endregion >> Helper Functions
#region >> Prep
# Set some variables
$OriginalSystemPath = $(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
$OriginalPSEnvPath = $env:Path
$OriginalPYTHONIOENCODING = $env:PYTHONIOENCODING
$OriginalCONDA_EXE = $env:CONDA_EXE
$OriginalCONDA_NEW_ENV = $env:CONDA_NEW_ENV
$OriginalCONDA_PS1_BACKUP = $env:CONDA_PS1_BACKUP
# Assign $AnacondaDirectoryName either with a default, or with the tail of
# the supplied AnacondaDirectoryPath argument
if ($AnacondaDirectoryPath) {
$AnacondaDirectoryName = $AnacondaDirectoryPath | Split-Path -Leaf
}
else {
$AnacondaDirectoryName = "Anaconda3"
}
# This is a ton of code to find the Anaconda3 directory in case it isn't
# supplied as an argument.
# Make sure we can find the Anaconda3 directory
if (!$PSBoundParameters['AnacondaDirectoryPath']) {
# Execute the following if AnacondaDirectoryPath is not a Powershell variable
$PotentialDirectoriesToSearch = @(
"C:\tools\$AnacondaDirectoryName"
"C:\ProgramData\$AnacondaDirectoryName"
"C:\$HOME\$AnacondaDirectoryName" # I think this should be added, this is the case on my system
)
# (Chris) Not sure what this does. Powershell black magic?
$DirectoriesToSearch = $PotentialDirectoriesToSearch | foreach {
if (Test-Path $_) {
$_
}
}
}
else {
# Execute the following if the AnacondaDirectoryPath is A Powershell variable
# Fail if this path to the directory doesn't doesn't exist
if (!$(Test-Path $AnacondaDirectoryPath)) {
Write-Error "The path '$AnacondaDirectoryPath' does not exist! Halting!"
$global:FunctionResult = "1"
return
}
# This will be a search directory for binaries we will locate in a moment
$DirectoriesToSearch = $AnacondaDirectoryPath
}
# Find python.exe
# Get-FilePath is called here and in one other place.
try {
$PythonExePath = Get-FilePath -FileNameWExtension "python.exe" -DirectoriesToSearch $DirectoriesToSearch -ErrorAction Stop
if (!$PythonExePath) {throw "The Get-FilePath function failed! Halting!"}
$PythonParentDir = $PythonExePath | Split-Path -Parent
$PythonCmd = $PythonExePath | Split-Path -Leaf
}
catch {
Write-Error $_
$global:FunctionResult = "1"
return
}
# Add python.exe to System PATH and $env:Path
# (Chris) TODO:
# Change this to only modify Powershell Path
try {
$null = Add-ToPath -PathToAdd $PythonParentDir -ErrorAction Stop
if (![bool]$(Get-Command $PythonCmd -ErrorAction SilentlyContinue)) {
throw "Did not successfully add '$PythonCmd' to System and PowerShell environment paths! Halting!"
}
}
catch {
Write-Error $_
$global:FunctionResult = "1"
return
}
# Set PYTHONIOENCODING PowerShell-Specific Environment Variable
# (Chris) This is black magic to me.
$env:PYTHONIOENCODING = python.exe -c 'import ctypes; print(ctypes.cdll.kernel32.GetACP())'
if (!$env:PYTHONIOENCODING) {
Write-Error "Unable to determine `$env:PYTHONIOENCODING! Halting!"
$global:FunctionResult = "1"
return
}
# Set PYTHONIOENCODING System Environment Variable
[Environment]::SetEnvironmentVariable("PYTHONIOENCODING", $env:PYTHONIOENCODING, "Machine")
# Set CONDA_NEW_ENV PowerShell-Specific Environment Variable
$env:CONDA_NEW_ENV = $PythonParentDir
# Set CONDA_NEW_ENV System Environment Variable
[Environment]::SetEnvironmentVariable("CONDA_NEW_ENV", $env:CONDA_NEW_ENV, "Machine")
# Find conda.exe
# (Chris) If Get-FilePath failed here, the error messages would make no sense
# because "python.exe" is hardcoded as the search string in the error messages
try {
$CondaExePath = Get-FilePath -FileNameWExtension "conda.exe" -DirectoriesToSearch $DirectoriesToSearch -ErrorAction Stop
if (!$CondaExePath) {throw "The Get-FilePath function failed! Halting!"}
$CondaParentDir = $CondaExePath | Split-Path -Parent
$CondaCmd = $CondaExePath | Split-Path -Leaf
}
catch {
Write-Error $_
$global:FunctionResult = "1"
return
}
# Add conda.exe to System PATH and $env:Path
# (Chris) TODO:
# Change this to only modify Powershell Path
try {
$null = Add-ToPath -PathToAdd $CondaParentDir -ErrorAction Stop
if (![bool]$(Get-Command $CondaCmd -ErrorAction SilentlyContinue)) {
throw "Did not successfully add '$CondaCmd' to System and PowerShell environment paths! Halting!"
}
}
catch {
Write-Error $_
$global:FunctionResult = "1"
return
}
# Set CONDA_EXE PowerShell-Specific Environment Variable
$env:CONDA_EXE = $CondaExePath
# Set CONDA_EXE System Environment Variable
[Environment]::SetEnvironmentVariable("CONDA_EXE", $env:CONDA_EXE, "Machine")
# Set CONDA_PS1_BACKUP PowerShell-Specific Environment Variable
$env:CONDA_PS1_BACKUP = $(Get-Location).Path
# Set CONDA_PS1_BACKUP System Environment Variable
[Environment]::SetEnvironmentVariable("CONDA_PS1_BACKUP", $env:CONDA_PS1_BACKUP, "Machine")
$NewSystemPath = $(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
$NewPSEnvPath = $env:Path
$NewPYTHONIOENCODING = $env:PYTHONIOENCODING
$NewCONDA_EXE = $env:CONDA_EXE
$NewCONDA_NEW_ENV = $env:CONDA_NEW_ENV
$NewCONDA_PS1_BACKUP = $env:CONDA_PS1_BACKUP
$FinalNewSystemPath = if ($OriginalSystemPath -ne $NewSystemPath) {$NewSystemPath} else {"NoChange"}
$FinalPSEnvPath = if ($OriginalPSEnvPath -ne $NewPSEnvPath) {$NewPSEnvPath} else {"NoChange"}
$FinalPyEn = if ($OriginalPYTHONIOENCODING -ne $NewPYTHONIOENCODING) {$NewPYTHONIOENCODING} else {"NoChange"}
$FinalCondaExe = if ($OriginalCONDA_EXE -ne $NewCONDA_EXE) {$NewCONDA_EXE} else {"NoChange"}
$FinalCondaNewEnv = if ($OriginalCONDA_NEW_ENV -ne $NewCONDA_NEW_ENV) {$NewCONDA_NEW_ENV} else {"NoChange"}
$FInalCondaPS1 = if ($OriginalCONDA_PS1_BACKUP -ne $NewCONDA_PS1_BACKUP) {$NewCONDA_PS1_BACKUP} else {"NoChange"}
if (!$HideOutput) {
[pscustomobject]@{
SystemPathChanges = @{Original = $OriginalSystemPath; New = $FinalNewSystemPath}
PSEnvPathChanges = @{Original = $OriginalPSEnvPath; New = $FinalPSEnvPath}
PYTHONIOENCODING = @{Original = $OriginalPYTHONIOENCODING; New = $FinalPyEn}
CONDA_EXE = @{Original = $OriginalCONDA_EXE; New = $FinalCondaExe}
CONDA_NEW_ENV = @{Original = $OriginalCONDA_NEW_ENV; New = $FinalCondaNewEnv}
CONDA_PS1_BACKUP = @{Original = $OriginalCONDA_NEW_ENV; New = $FInalCondaPS1}
}
Write-Host "Environment set successfully!" -ForegroundColor Green
}
@pldmgg
Copy link

pldmgg commented Jun 17, 2018

Hi Chris - wanted to address your questions here. I'll be posting updates to my original link (both functions and instructions) a few minutes after I post this.

  • Regarding ![bool]$($_.FullName -match "\\pkgs\\") - The Set-AnacondaEnv function searches for python.exe and conda.exe. However, there are two python.exe binaries and two conda.exe binaries under the Anaconda Directory. We do not want to use ones under the pkgs directory.
  • Regarding the references to python.exe in the Get-FilePath helper function - good catch. Updated.
  • Regarding the ability to modify System Path and System/Machine Environment Variables, see updated instructions for new usage. Basically, the update makes it such that you need to explicitly use the -ChangeMachineEnv switch in the Set-AnacondaEnv function in order to change System/Machine path/environment variables. Again, I'm not too familiar with Anaconda, but I suspect you will need to use the -ChangeMachineEnv switch to get things working, but now the default behavior is to not touch System Environment.
  • Updated with line 158.
  • Regarding line 162, this just loops through $PotentialDirectoriesToSearch and makes sure that they exist. If they don't, then they won't be part of the final $DirectoriesToSearch definition
  • Regarding line 213 - It's black magic to me too :) - I just pulled it directly from the original bat script. I did test it though, and it returns an integer. Not sure what it means though...

Updated Module Link (for convenience):

https://gist.github.com/pldmgg/8940843fe02f886bc6267a6c66a507ff

Updated Instructions Link (for convenience):

https://gist.github.com/pldmgg/c84e802bcecd6e4c962f65be5b5d316d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment