Artifacts/windows-clone-git-repo/GitEnlister.ps1 (597 lines of code) (raw):
<##################################################################################################
Description
===========
- This script does the following -
- installs chocolatey
- installs git
- clones the specified git repo.
- Creates url and lnk shortcuts to the git repo and local repo respectively.
- The following logs are generated on the machine -
- Chocolatey's log : %ALLUSERSPROFILE%\chocolatey\logs folder.
- This script's log : $PSScriptRoot\GitEnslister-{TimeStamp}\Logs folder.
Usage examples
==============
- Powershell -executionpolicy bypass -file GitEnlister.ps1 -GitRepoLocation "your repo URI" -PersonalAccessToken "access-token"
- Powershell -executionpolicy bypass -file GitEnlister.ps1 -GitRepoLocation "your repo URI" -PersonalAccessToken "access-token" -GitBranch "branch to check out"
- Powershell -executionpolicy bypass -file GitEnlister.ps1 -GitRepoLocation "your repo URI" -PersonalAccessToken "access-token" -GitLocalRepoLocation "local folder location"
Prerequisites
=============
- Please ensure that this script is run elevated.
- Please ensure that the powershell execution policy is set to unrestricted or bypass.
Known issues / Caveats
======================
- The 'git clone -b <branch-name>' command treats the branch-name as case-sensitive. Need
to investigate further and resolve this.
Coming soon / planned work
==========================
- We're currently installing git version 1.9.5.20150320 (last known good). See whether we
can roll forward to a newer version.
##################################################################################################>
#
# Arguments to this script file.
#
#
$GitRepoLocation = $args[0]
#
$GitLocalRepoLocation = $args[1]
#
$GitBranch = $args[2]
#
$PersonalAccessToken = $args[3]
# The location where this script resides.
# Note: We cannot use $PSScriptRoot or $MyInvocation inside a script block. Hence passing
# the location explicitly.
$ScriptRoot = $args[4]
##################################################################################################
#
# Powershell Configurations
#
# Note: Because the $ErrorActionPreference is "Stop", this script will stop on first failure.
$ErrorActionPreference = "stop"
# Ensure we force use of TLS 1.2 for all downloads.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Ensure that current process can run scripts.
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
###################################################################################################
#
# Custom Configurations
#
$GitEnlisterFolder = Join-Path $ScriptRoot -ChildPath $("GitEnlister-" + [System.DateTime]::Now.ToString("yyyy-MM-dd-HH-mm-ss"))
# Location of the log files
$ScriptLogFolder = Join-Path $GitEnlisterFolder -ChildPath "Logs"
$ScriptLog = Join-Path -Path $ScriptLogFolder -ChildPath "GitEnlister.log"
$ChocolateyInstallLog = Join-Path -Path $ScriptLogFolder -ChildPath "ChocolateyInstall.log"
$GitCloneStdOut = Join-Path -Path $ScriptLogFolder -ChildPath "GitClone.log"
$GitCloneStdErr = Join-Path -Path $ScriptLogFolder -ChildPath "GitClone.err"
$GitConfigStdOut = Join-Path -Path $ScriptLogFolder -ChildPath "GitConfig.log"
$GitConfigStdErr = Join-Path -Path $ScriptLogFolder -ChildPath "GitConfig.err"
##################################################################################################
#
# Description:
# - Displays the script argument values (default or user-supplied).
#
# Parameters:
# - N/A.
#
# Return:
# - N/A.
#
# Notes:
# - Please ensure that the Initialize() method has been called at least once before this
# method. Else this method can only write to console and not to log files.
#
function DisplayArgValues
{
WriteLog "========== Configuration =========="
WriteLog $("-GitRepoLocation : " + $GitRepoLocation)
WriteLog $("-GitLocalRepoLocation : " + $GitLocalRepoLocation)
WriteLog $("-GitBranch : " + $GitBranch)
WriteLog $("-PersonalAccessToken : " + $PersonalAccessToken)
WriteLog "========== Configuration =========="
}
##################################################################################################
#
# Description:
# - Creates the folder structure which'll be used for dumping logs generated by this script and
# the logon task.
#
# Parameters:
# - N/A.
#
# Return:
# - N/A.
#
# Notes:
# - N/A.
#
function InitializeFolders
{
if ($false -eq (Test-Path -Path $GitEnlisterFolder))
{
New-Item -Path $GitEnlisterFolder -ItemType directory | Out-Null
}
if ($false -eq (Test-Path -Path $ScriptLogFolder))
{
New-Item -Path $ScriptLogFolder -ItemType directory | Out-Null
}
}
##################################################################################################
#
# Description:
# - Writes specified string to the console as well as to the script log (indicated by $ScriptLog).
#
# Parameters:
# - $message: The string to write.
#
# Return:
# - N/A.
#
# Notes:
# - N/A.
#
function WriteLog
{
Param(
<# Can be null or empty #> $message
)
$timestampedMessage = $("[" + [System.DateTime]::Now + "] " + $message) | % {
Out-File -InputObject $_ -FilePath $ScriptLog -Append
}
}
##################################################################################################
#
# Description:
# - Installs the chocolatey package manager.
#
# Parameters:
# - N/A.
#
# Return:
# - If installation is successful, then nothing is returned.
# - Else a detailed terminating error is thrown.
#
# Notes:
# - @TODO: Write to $chocolateyInstallLog log file.
# - @TODO: Currently no errors are being written to the gitenlister.log. This needs to be fixed.
#
function InstallChocolatey
{
Param(
[ValidateNotNullOrEmpty()] $chocolateyInstallLog
)
WriteLog "Installing Chocolatey..."
Invoke-Expression ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) | Out-Null
WriteLog "Success."
}
##################################################################################################
#
# Description:
# - Returns the The 'leaf' name of git repo.
#
# Parameters:
# - $gitRepoLocation: The HTTPS url of the git repository.
#
# Return:
# - If installation is successful, then the 'leaf' name of the git repo is returned.
# - Else a detailed terminating error is thrown.
#
# Notes:
# - N/A.
#
function GetGitRepoLeaf
{
Param(
[ValidateNotNullOrEmpty()] $gitRepoLocation
)
$gitRepoLeaf = Split-Path -Path $gitRepoLocation -Leaf
# replace any occurrences of '%20' with whitespace.
if ($gitRepoLeaf.Contains("%20"))
{
$gitRepoLeaf = $gitRepoLeaf.Replace("%20", " ")
}
# If appended by ".git", strip that out
if ($gitRepoLeaf -like "*.git")
{
$gitRepoLeaf = $gitRepoLeaf -replace "\.git$", ""
}
return $gitRepoLeaf
}
##################################################################################################
#
# Description:
# - Installs git client on the machine.
# - Adds location of git.exe to the path.
#
# Parameters:
# - $gitVersion: The specific version of git to install.
#
# Return:
# - If successful, then the location of git install folder is returned.
# - Else a detailed terminating error is thrown.
#
# Notes:
# - N/A.
#
function InstallGit
{
WriteLog $("Installing Git latest stable version ...")
# install git via chocolatey
choco install git --force --yes --acceptlicense --verbose | Out-Null
if ($? -eq $false)
{
$errMsg = $("Error! Installation failed. Please see the chocolatey logs in %ALLUSERSPROFILE%\chocolatey\logs folder for details.")
WriteLog $errMsg
Write-Error $errMsg
}
WriteLog "Success."
# now find the location of git
WriteLog "Finding location of Git.exe..."
$gitInstallFolder = $null
$gitBinariesFolder = $null
$gitExeLocation = $null
# Construct possible known paths to git.exe. We'll probe all these.
$pathsToProbe = @(
$(${env:ProgramFiles(x86)}),
$(${env:ProgramFiles})
)
# Start probing.
foreach($path in $pathsToProbe)
{
if ((Test-Path $($path + "\git")) -and (Test-Path $($path + "\git\bin")) -and (Test-Path $($path + "\git\bin\git.exe")))
{
$gitInstallFolder = $path + "\git"
$gitBinariesFolder = $path + "\git\bin"
$gitExeLocation = $path + "\git\bin\git.exe"
break
}
}
# bail out if git.exe was not found
if ($null -eq $gitExeLocation)
{
$errMsg = $("Error! Git.exe could not be detected on this machine.")
WriteLog $errMsg
Write-Error $errMsg -Category ObjectNotFound
}
WriteLog $("Success. Git.exe located at - " + $gitExeLocation)
# now add the git binaries folder to the path
WriteLog "Adding Git folder location to the PATH"
# Check if the git binaries folder is already on the path
if ($false -eq ($env:Path).Contains($gitBinariesFolder))
{
# Add it to the path.
[Environment]::SetEnvironmentVariable("path", $($env:Path + ";" + $gitBinariesFolder), [System.EnvironmentVariableTarget]::Machine)
WriteLog "Success."
}
else
{
WriteLog "Git binaries folder location already exists on the PATH."
}
return $gitInstallFolder
}
##################################################################################################
#
# Description:
# - Clones the specified git repo into specified local folder.
#
# Parameters:
# - $gitExeLocation: Location of git.exe.
# - $gitRepoLocation: The HTTPS clone url of the git repository.
# - $gitLocalRepoLocation: The local folder into which the git repository will be cloned.
# - $gitBranch: The branch that will be checked out.
# - $stdOutLogfile: The log file to which the operation's stdout will be redirected.
# - $stdErrLogfile: The log file to which the operation's stderr will be redirected.
# - $personalAccessToken: The personal access token for authenticating to the git repo.
#
# Return:
# - If git clone is successful, then nothing is returned.
# - Else a detailed terminating error is thrown.
#
# Notes:
# - N/A.
#
function CloneGitRepo
{
Param(
[ValidateNotNullOrEmpty()] $gitExeLocation,
[ValidateNotNullOrEmpty()] $gitRepoLocation,
[ValidateNotNullOrEmpty()] $gitLocalRepoLocation,
[ValidateNotNullOrEmpty()] $gitBranch,
[ValidateNotNullOrEmpty()] $stdOutLogfile,
[ValidateNotNullOrEmpty()] $stdErrLogfile,
[ValidateNotNullOrEmpty()] $personalAccessToken
)
# pre-condition checks
if ($false -eq $gitRepoLocation.ToLowerInvariant().StartsWith("https://"))
{
$errMsg = $("Error! The specified Git repo url is not a valid HTTPS clone url : " + $gitRepoLocation)
WriteLog $errMsg
Write-Error $errMsg
}
if ($false -eq $gitRepoLocation.Length -gt 8)
{
$errMsg = $("Error! The specified Git repo url is not valid : " + $gitRepoLocation)
WriteLog $errMsg
Write-Error $errMsg
}
# Using specified credentials, create the actual repo url to clone from.
if ([string]::IsNullOrEmpty($personalAccessToken))
{
$gitRepoToUse = $gitRepoLocation
}
else
{
$protocolPrefix = "https://"
$gitRepoNameSuffix = $gitRepoLocation.Substring(8)
# HACK: If a personal access token is used in the clone URI, then the username is ignored. So
# any username can be used.
$anyUserName = "AnyUserName"
$gitRepoToUse = $("$protocolPrefix" + $anyUserName + ":" + $personalAccessToken + "@" + $gitRepoNameSuffix)
}
# Prep to start git.exe
$args = $("clone -b " + $gitBranch + " " + $gitRepoToUse + " `"" + $gitLocalRepoLocation + "`"")
WriteLog $("Cloning the git repo...")
WriteLog $($gitExeLocation + " " + $args)
# Run the git clone operation
$p = Start-Process -FilePath $gitExeLocation -ArgumentList $args -RedirectStandardOutput $stdOutLogfile -RedirectStandardError $stdErrLogfile -PassThru -Wait
# Was the clone operation successful?
if ($p.ExitCode -ne 0)
{
$errMsg = $("Error! Git clone failed with exit code " + $p.ExitCode + ". Please see the log file: " + $stdErrLogfile)
WriteLog $errMsg
Write-Error $errMsg
}
WriteLog $("Success. Git repo cloned at '" + $gitLocalRepoLocation + "'")
}
##################################################################################################
#
# Description:
# - Runs some commonly used 'git config' commands
# - Resets the git remote.
#
# Parameters:
# - $gitExeLocation: Location of git.exe.
# - $bIgnoreGitExitCode : Set to $true to ignore the git.exe's exitcode.
# - $gitRepoLocation: The HTTPS clone url of the git repository.
# - $gitLocalRepoLocation: The local folder into which the git repository has been cloned.
# - $stdOutLogfile: The log file to which the operation's stdout will be redirected.
# - $stdErrLogfile: The log file to which the operation's stderr will be redirected.
#
# Return:
# - If successful, then nothing is returned.
# - Else a detailed terminating error is thrown.
#
# Notes:
# - N/A.
#
function ConfigureGit
{
Param(
[ValidateNotNullOrEmpty()] $gitExeLocation,
[ValidateNotNullOrEmpty()] $bIgnoreGitExitCode,
[ValidateNotNullOrEmpty()] $gitRepoLocation,
[ValidateNotNullOrEmpty()] $gitLocalRepoLocation,
[ValidateNotNullOrEmpty()] $stdOutLogfile,
[ValidateNotNullOrEmpty()] $stdErrLogfile
)
# list of git config commands
$gitConfigCmds = @(
"config --system core.safecrlf true"
"config --system push.default simple"
"config --system core.preloadindex true"
"config --system core.fscache true"
"config --system credential.helper wincred"
)
# execute each git config command
foreach ($gitConfigCmd in $gitConfigCmds)
{
# Prep to start git.exe
WriteLog $("Running git config...")
WriteLog $($gitExeLocation + " " + $gitConfigCmd)
# Run the git config gitConfigCmd
$p = Start-Process -FilePath $gitExeLocation -ArgumentList $gitConfigCmd -PassThru -Wait
# Was the config operation successful?
if ($p.ExitCode -ne 0)
{
if ($true -eq $bIgnoreGitExitCode)
{
WriteLog $("Git.exe returned with exit code " + $p.ExitCode + ", which is being ignored as requested.")
}
else
{
$errMsg = $("Error! Git config failed with exit code " + $p.ExitCode + ". Please see the log file: " + $stdErrLogfile)
WriteLog $errMsg
Write-Error $errMsg
}
}
else
{
WriteLog "Success."
}
}
# reset the git remote
$gitResetRemoteCmd = $("remote set-url origin " + $gitRepoLocation)
# Prep to start git.exe
WriteLog $("Running git remote...")
WriteLog $($gitExeLocation + " " + $gitResetRemoteCmd)
# Run the git config gitConfigCmd.
# Ensure that the working directory is set to the local repo (since the 'git remote' command needs to be run from a local repo).
$p = Start-Process -FilePath $gitExeLocation -ArgumentList $gitResetRemoteCmd -WorkingDirectory $gitLocalRepoLocation -RedirectStandardOutput $stdOutLogfile -RedirectStandardError $stdErrLogfile -PassThru -Wait
# Was the config operation successful?
if ($p.ExitCode -ne 0)
{
if ($true -eq $bIgnoreGitExitCode)
{
WriteLog $("Git.exe returned with exit code " + $p.ExitCode + ", which is being ignored as requested.")
}
else
{
$errMsg = $("Error! Git config failed with exit code " + $p.ExitCode + ". Please see the log file: " + $stdErrLogfile)
WriteLog $errMsg
Write-Error $errMsg
}
}
else
{
WriteLog "Success."
}
}
##################################################################################################
#
# Description:
# - Creates a desktop url shortcut to the git repo's project page (on VSO or Github etc).
#
# Parameters:
# - $gitRepoLocation: The HTTPS url of the git repository.
# - $gitRepoLeaf: The 'leaf' name of git repo.
#
# Return:
# - If successful, then nothing is returned.
# - Else a detailed terminating error is thrown.
#
# Notes:
# - Shortcuts are created on the public desktop (c:\users\public\desktop) in order to be
# accessible to all users.
# - Naming convention for the shortcuts:
# - "GitHub - {Repo short-name}" (if hosted on github. E.g. "GitHub - CoreFx").
# - "VS Online - {Repo short-name}" (if hosted on visual studio online. E.g. "VS Online - CloudExplorer").
# - "Project Page - {Repo short-name}" (if hosted elsewhere).
#
function CreateUrlDesktopShortcut
{
Param(
[ValidateNotNullOrEmpty()] $gitRepoLocation,
[ValidateNotNullOrEmpty()] $gitRepoLeaf
)
$shell = New-Object -ComObject wscript.shell
$desktopFolder = [System.Environment]::GetFolderPath("CommonDesktopDirectory")
$urlShortcutName = $("Project Page - " + $gitRepoLeaf)
if ($gitRepoLocation -like "*visualstudio.com*")
{
$urlShortcutName = $("VS Online - " + $gitRepoLeaf)
}
elseif ($gitRepoLocation -like "*github.com*")
{
$urlShortcutName = $("GitHub - " + $gitRepoLeaf)
}
# prep the url shortcut to the git repo.
$urlShortcutPath = $($desktopFolder + "\" + $urlShortcutName + ".url")
# create the shortcut only if it doesn't already exist.
if ($false -eq (Test-Path -Path $urlShortcutPath))
{
$gitRepoLocationShortcut = $shell.CreateShortcut($urlShortcutPath)
$gitRepoLocationShortcut.TargetPath = $gitRepoLocation
# save the shortcut
WriteLog $("Creating url shortcut to git repo...")
WriteLog $("Shortcut file: '" + $urlShortcutPath + "'")
WriteLog $("Shortcut target: '" + $gitRepoLocationShortcut.TargetPath + "'")
WriteLog $("Shortcut args: '" + $gitRepoLocationShortcut.Arguments + "'")
$gitRepoLocationShortcut.Save()
WriteLog "Success."
}
else
{
WriteLog "Url shortcut to git repo already exists."
}
}
##################################################################################################
#
# Description:
# - Creates a .lnk desktop shortcut to the local repo (opens in file explorer).
#
# Parameters:
# - $gitLocalRepoLocation: The local folder into which the git repository has been cloned.
# - $gitRepoLeaf: The 'leaf' name of git repo.
#
# Return:
# - If successful, then nothing is returned.
# - Else a detailed terminating error is thrown.
#
# Notes:
# - Shortcuts are created on the public desktop (c:\users\public\desktop) in order to be
# accessible to all users.
#
function CreateFileExplorerDesktopShortcut
{
Param(
[ValidateNotNullOrEmpty()] $gitLocalRepoLocation,
[ValidateNotNullOrEmpty()] $gitRepoLeaf
)
$shell = New-Object -ComObject wscript.shell
$desktopFolder = [System.Environment]::GetFolderPath("CommonDesktopDirectory")
# now prep the lnk shortcut to the local repo (opens in file explorer)
$lnkFileExplorerShortcutPath = $($desktopFolder + "\" + $gitRepoLeaf + ".lnk")
# create the shortcut only if it doesn't already exist.
if ($false -eq (Test-Path -Path $lnkFileExplorerShortcutPath))
{
$gitLocalRepoLocationFileExplorerShortcut = $shell.CreateShortcut($lnkFileExplorerShortcutPath)
$gitLocalRepoLocationFileExplorerShortcut.TargetPath = $gitLocalRepoLocation
$gitLocalRepoLocationFileExplorerShortcut.Description = $gitRepoLeaf
$gitLocalRepoLocationFileExplorerShortcut.WindowStyle = 3
# save the shortcut
WriteLog $("Creating lnk file explorer shortcut to git local repo...")
WriteLog $("Shortcut file: '" + $lnkFileExplorerShortcutPath + "'")
WriteLog $("Shortcut target: '" + $gitLocalRepoLocationFileExplorerShortcut.TargetPath + "'")
WriteLog $("Shortcut args: '" + $gitLocalRepoLocationFileExplorerShortcut.Arguments + "'")
$gitLocalRepoLocationFileExplorerShortcut.Save()
WriteLog "Success."
}
else
{
WriteLog "Lnk file explorer shortcut to git local repo already exists."
}
}
##################################################################################################
#
#
#
try
{
InitializeFolders
# extract the leaf node/name of the git repo url.
$gitRepoLeaf = GetGitRepoLeaf -gitRepoLocation $GitRepoLocation
# We don't need to fully url-encode the repo url. However we should replace whitespaces with '%20'.
if ($GitRepoLocation.Contains(" "))
{
$GitRepoLocation = $GitRepoLocation.Replace(" ", "%20")
}
# ensure that the git repo leaf is appended to the local repo path (e.g. c:\Repos\coreclr).
$GitLocalRepoLocation = Join-Path -Path $GitLocalRepoLocation -ChildPath $gitRepoLeaf
#
DisplayArgValues
# install the chocolatey package manager
InstallChocolatey -chocolateyInstallLog $ChocolateyInstallLog
# install the git client
$GitInstallFolder = InstallGit
$GitExeLocation = Join-Path -Path $GitInstallFolder -ChildPath "\bin\git.exe"
# clone the repo
CloneGitRepo -gitExeLocation $GitExeLocation -gitRepoLocation $GitRepoLocation -gitLocalRepoLocation $GitLocalRepoLocation -gitBranch $GitBranch -stdOutLogfile $GitCloneStdOut -stdErrLogfile $GitCloneStdErr -personalAccessToken $PersonalAccessToken
# run some commonly used git config commands
ConfigureGit -gitExeLocation $GitExeLocation -bIgnoreGitExitCode $false -gitRepoLocation $GitRepoLocation -gitLocalRepoLocation $GitLocalRepoLocation -stdOutLogfile $GitConfigStdOut -stdErrLogfile $GitConfigStdErr
# Create a desktop url shortcut to the git repo.
CreateUrlDesktopShortcut -gitRepoLocation $GitRepoLocation -gitRepoLeaf $gitRepoLeaf
# Create a .lnk desktop shortcut to the local repo (opens in file explorer).
CreateFileExplorerDesktopShortcut -gitLocalRepoLocation $GitLocalRepoLocation -gitRepoLeaf $gitRepoLeaf
# all done. Let's return will exit code 0.
return 0
}
catch
{
try
{
if (($null -ne $Error[0]) -and ($null -ne $Error[0].Exception) -and ($null -ne $Error[0].Exception.Message))
{
$errMsg = $Error[0].Exception.Message
WriteLog $errMsg
Write-Host $errMsg
}
# Important note: Throwing a terminating error (using $ErrorActionPreference = "stop") still returns exit
# code zero from the powershell script. The workaround is to use try/catch blocks and return a non-zero
# exit code from the catch block.
return -1
}
catch
{
# Trying to log a message threw an exception
return -2
}
}