private/Install-SoftwareGitForWindows.ps1

function Install-SoftwareGitForWindows {
    <#
    .SYNOPSIS
        Installs Git for Windows and configures global Git identity
 
    .DESCRIPTION
        Installs Git for Windows with winget when git.exe is unavailable, refreshes the current
        process environment, and configures global user.email and user.name values.
 
        When identity values are omitted, the function prompts for missing values and offers
        to change existing values. NonInteractive suppresses those identity prompts. WhatIf
        and Confirm apply to the installation and global identity changes.
 
    .PARAMETER UserEmail
        Specifies the value for global Git user.email. When omitted, the function may prompt
        for a value or retain the existing setting.
 
    .PARAMETER UserName
        Specifies the value for global Git user.name. When omitted, the function may prompt
        for a value or retain the existing setting.
 
    .PARAMETER ForceIdentityUpdate
        Prompts for omitted identity values even when the corresponding global settings exist.
 
    .PARAMETER NonInteractive
        Suppresses identity prompts. Omitted values remain unchanged unless supplied through
        UserEmail or UserName. Explicit parameter values can still be applied.
 
    .EXAMPLE
        PS> Install-SoftwareGitForWindows
 
        Installs Git when needed and interactively completes global identity configuration.
 
    .EXAMPLE
        PS> Install-SoftwareGitForWindows -UserEmail 'you@example.com' -UserName 'Your Name' -NonInteractive
 
        Installs Git when needed and sets both identity values without identity prompts.
 
    .EXAMPLE
        PS> Install-SoftwareGitForWindows -ForceIdentityUpdate
 
        Prompts for omitted identity values even when global values already exist.
 
    .INPUTS
        None. This function does not accept pipeline input.
 
    .OUTPUTS
        System.String. Returns native winget output when Git is installed.
 
        System.Management.Automation.PSCustomObject. Returns the Git version, installation
        status, final identity values, and winget command path.
 
    .NOTES
        Author: David Segura
        Company: Recast Software
        Version: 1.0.0
        Date: 2026-08-28
 
        Requires Windows and winget. This function persistently changes the current user's
        global Git configuration when identity updates are approved.
 
        Change Summary:
            - Added installation and interactive global identity configuration.
    #>

    [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-SoftwareGitForWindows 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)] 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)] 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)] 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 "[$($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)] 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 "[$($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)] 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)] 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)] "
    Write-Host "[$(Get-Date -Format s)] Your global Git identity is now:" -ForegroundColor Cyan
    Write-Host "[$(Get-Date -Format s)] Name : $finalName" -ForegroundColor White
    Write-Host "[$(Get-Date -Format s)] Email: $finalEmail" -ForegroundColor White

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