private/Software/Install-GitForWindows.ps1

<#
.SYNOPSIS
Installs Git for Windows and configures global Git identity.
 
.DESCRIPTION
Ensures Git for Windows is available by installing it with winget when needed,
then configures global user.email and user.name values. If identity values are
not provided as parameters, the function prompts interactively based on current
git config values.
 
.PARAMETER UserEmail
The value to set for git global user.email. If omitted, the function prompts
when required.
 
.PARAMETER UserName
The value to set for git global user.name. If omitted, the function prompts
when required.
 
.PARAMETER ForceIdentityUpdate
Forces prompting for identity values even when existing global values are set.
 
.OUTPUTS
System.Management.Automation.PSCustomObject
 
.EXAMPLE
Install-GitForWindows
 
Installs Git for Windows if not present and walks through interactive identity
configuration.
 
.EXAMPLE
Install-GitForWindows -UserEmail 'you@example.com' -UserName 'Your Name'
 
Installs Git for Windows if needed and sets global identity non-interactively.
 
.EXAMPLE
Install-GitForWindows -ForceIdentityUpdate
 
Prompts for identity values even when user.email and user.name already exist.
 
.NOTES
Author: David Segura
Company: Recast Software
This function is supported only on Windows and requires winget.
Change Summary:
#>

function Install-GitForWindows {
    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([pscustomobject])]
    param (
        [Parameter()]
        [string]$UserEmail,

        [Parameter()]
        [string]$UserName,

        [Parameter()]
        [switch]$ForceIdentityUpdate,

        [Parameter()]
        [switch]$NonInteractive
    )

    if (-not $IsWindows) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Install-GitForWindows is supported only on Windows."
    }

    $winget = Get-Command -Name 'winget' -ErrorAction SilentlyContinue
    if (-not $winget) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] winget is required but was not found. Install App Installer from Microsoft Store and try again."
    }

    $gitCommand = Get-Command -Name 'git' -ErrorAction SilentlyContinue
    $wasInstalled = $false

    if (-not $gitCommand) {
        if (-not $PSCmdlet.ShouldProcess('Git for Windows', 'Install with winget')) {
            return [pscustomobject]@{
                GitVersion    = $null
                WasInstalled  = $false
                UserEmail     = $null
                UserName      = $null
                WingetCommand = $winget.Source
            }
        }

        Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Git for Windows is not installed. Installing with winget..." -ForegroundColor DarkGray

        & $winget.Source install --id 'Git.Git' -e -h --accept-source-agreements --accept-package-agreements
        if ($LASTEXITCODE -ne 0) {
            throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Git for Windows installation failed with exit code $LASTEXITCODE."
        }

        Update-OSDeploySessionEnvironment

        $gitCommand = Get-Command -Name 'git' -ErrorAction SilentlyContinue
        if (-not $gitCommand) {
            $fallbackGitPath = Join-Path -Path ${env:ProgramFiles} -ChildPath 'Git\cmd\git.exe'
            if (Test-Path -Path $fallbackGitPath) {
                $gitCommand = Get-Command -Name $fallbackGitPath -ErrorAction SilentlyContinue
            }
        }

        if (-not $gitCommand) {
            throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Git was installed but git.exe was not found in PATH. Open a new PowerShell session and run the command again."
        }

        $wasInstalled = $true
    }
    else {
        $installedVersion = & $gitCommand.Source --version
        Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Git is already installed: $installedVersion" -ForegroundColor Green
    }

    $currentEmail = ((& $gitCommand.Source config --global user.email 2>$null) -join '').Trim()
    $currentName = ((& $gitCommand.Source config --global user.name 2>$null) -join '').Trim()

    if ($WhatIfPreference) {
        $finalVersion = (& $gitCommand.Source --version).Trim()
        return [pscustomobject]@{
            GitVersion    = $finalVersion
            WasInstalled  = $wasInstalled
            UserEmail     = $currentEmail
            UserName      = $currentName
            WingetCommand = $winget.Source
        }
    }

    if (-not $PSCmdlet.ShouldProcess('global Git identity', 'Configure user.email and user.name')) {
        $finalVersion = (& $gitCommand.Source --version).Trim()
        return [pscustomobject]@{
            GitVersion    = $finalVersion
            WasInstalled  = $wasInstalled
            UserEmail     = $currentEmail
            UserName      = $currentName
            WingetCommand = $winget.Source
        }
    }

    if (-not $UserEmail) {
        if ([string]::IsNullOrWhiteSpace($currentEmail) -or $ForceIdentityUpdate) {
            $UserEmail = Read-Host 'Enter your Git email address'
        }
        elseif (-not $NonInteractive) {
            Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Current git user.email: $currentEmail" -ForegroundColor DarkGray
            $changeEmail = Read-Host 'Do you want to change it? (y/n)'
            if ($changeEmail -match '^(y|yes)$') {
                $UserEmail = Read-Host 'Enter your new Git email address'
            }
        }
        else {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] NonInteractive: keeping existing git user.email: $currentEmail"
        }
    }

    if (-not $UserName) {
        if ([string]::IsNullOrWhiteSpace($currentName) -or $ForceIdentityUpdate) {
            $UserName = Read-Host 'Enter your Git user name'
        }
        elseif (-not $NonInteractive) {
            Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Current git user.name: $currentName" -ForegroundColor DarkGray
            $changeName = Read-Host 'Do you want to change it? (y/n)'
            if ($changeName -match '^(y|yes)$') {
                $UserName = Read-Host 'Enter your new Git user name'
            }
        }
        else {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] NonInteractive: keeping existing git user.name: $currentName"
        }
    }

    if (-not [string]::IsNullOrWhiteSpace($UserEmail)) {
        if ($PSCmdlet.ShouldProcess('git global user.email', "Set to $UserEmail")) {
            & $gitCommand.Source config --global user.email "$UserEmail"
            Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Set git user.email to $UserEmail" -ForegroundColor Green
        }
    }

    if (-not [string]::IsNullOrWhiteSpace($UserName)) {
        if ($PSCmdlet.ShouldProcess('git global user.name', "Set to $UserName")) {
            & $gitCommand.Source config --global user.name "$UserName"
            Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Set git user.name to $UserName" -ForegroundColor Green
        }
    }

    $finalEmail = (& $gitCommand.Source config --global user.email).Trim()
    $finalName = (& $gitCommand.Source config --global user.name).Trim()
    $finalVersion = (& $gitCommand.Source --version).Trim()

    Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] "
    Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Your global Git identity is now:" -ForegroundColor Cyan
    Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Name : $finalName" -ForegroundColor White
    Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Email: $finalEmail" -ForegroundColor White

    [pscustomobject]@{
        GitVersion    = $finalVersion
        WasInstalled  = $wasInstalled
        UserEmail     = $finalEmail
        UserName      = $finalName
        WingetCommand = $winget.Source
    }
}