FC_Core.psm1

function Set-ScriptSignature {
    <#
    .SYNOPSIS
    Signs the current file in the ISE with the user's code-signing certificate. You
    must have a valid code-signing certificate in your personal certificate store
    for this to work. Prompts for save location if the file has not yet been saved.
    .NOTES
    Author: Matt McNabb
    Date: 8/22/2014
    DISCLAIMER: This script is provided 'AS IS'. It has been tested for personal use, please
    test in a lab environment before using in a production environment.
    #>
 
    if ($host.name -eq 'Windows PowerShell ISE Host') {

    function Get-FileSavePath {
        $SaveDialog = New-Object -TypeName System.Windows.Forms.SaveFileDialog
        $SaveDialog.Filter = 'Powershell Files(*.ps1;*.psm1;*.psd1;*.ps1xml;*.pssc*;*.cdxml)|*.ps1;*.psm1;*.psd1;*.ps1xml;*.pssc*;*.cdxml|All files (*.*)|*.*'
        $SaveDialog.FilterIndex = 1
        $SaveDialog.RestoreDirectory = $true
        $SaveDialog.ShowDialog()
        $SaveDialog.FileName
    }
    
        $File = $psise.CurrentFile
        $Path = $File.FullPath
        $Certificate = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert
        if ($Certificate)
        {
            if ($File.IsUntitled)
            {
                $Path = Get-FileSavePath
                $File.SaveAs($Path,[text.encoding]::utf8)
            }
            if (-not($File.IsSaved)) {$File.Save([text.encoding]::utf8)}
            Add-Content -Path $Path -Value ''
            Set-AuthenticodeSignature -FilePath $Path -Certificate $Certificate | Out-Null
            $psise.CurrentPowerShellTab.Files.Remove($File) | Out-Null
            $psise.CurrentPowerShellTab.Files.Add($Path) | Out-Null
        }
        else {throw 'A valid code-signing certificate could not be found!'} 
    }
}export-modulemember -function Set-ScriptSignature
function Start-MyProcess {
<#
    .Synopsis
      Wraps up a call to execute a program using System.Diagnostics.Process. This allows us to redirect the stdout and stderr streams for better error handling. Specifcially this is used for quite a few MS utilities in our TFS build deploy, as the utilities will usually throw warnings instead of terminating errors.
    .DESCRIPTION
        
    .EXAMPLE
        This example sets up a executable path, and options, then passes them to the function while captureing the returning stdout and stderr streams.
 
        Assume that $dacDinDir, $DestFile, $ConnectionString are all set to sueful values.
 
        $EXEPath = "$dacBinDir\SqlPackage.exe"
        $options = "/Action:Extract /OverwriteFiles:True /tf:$DestFile /scs:$ConnectionString"
 
        $return = Start-MyProcess -EXEPath $EXEPath -options $options
 
        if ($logLevel -eq "Debug"){
            #Only show the stdout stream if we are in debugging logLevel
            $return.stdout
        }
        if ($return.sterr -ne $null){
            Write-Log "$($return.sterr)" Warning
            Write-Log "There was an error of some type. See warning above for more info" Error
        }
    .OUTPUTS
        A object with 2 properties, stdout and stderr. these are text streams that conatian output from the process. Generally if (stderr -eq $null) then there was some sort of error. You need to ensure that you parse these values for any error text you are looking for
        
    #>

[CmdletBinding()]
    param( 
        [Parameter(ValueFromPipeline=$True, Position=0)] [string] $EXEPath
,[string] $options
        )
    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $EXE = $EXEPath.Substring($EXEPath.LastIndexOf("\")+1,$EXEPath.Length-$EXEPath.LastIndexOf("\")-1)
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = "`"$EXEPath`""
    $pinfo.Arguments = "$options"
    $pinfo.UseShellExecute = $false
    $pinfo.CreateNoWindow = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.RedirectStandardError = $true

    # Create a process object using the startup info
    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $pinfo

    Write-Log "Executing the following command" Debug
    Write-Log "$($pinfo.FileName) $($pinfo.Arguments)" Debug
    try{
        $process.Start() | Out-Null
    }
    catch{
        Write-Log "$($_.Exception.ToString())"  Warning
        Write-Log "Error calling $EXE. See previous warning(s) for error text. Try running the script with a lower logLevel variable to collect more troubleshooting information. Aborting script" Error -ErrorAction Stop
        
    }

    if (!$process.HasExited) {
        # Wait a while for the process to exit
        Write-Log "$EXE is not done, let's wait 5 more seconds"
        sleep -Seconds 5
    }
    Write-Log "$EXE has completed."

    # get output from stdout and stderr
    $stdout = $process.StandardOutput.ReadToEnd()
    $stderr = $process.StandardError.ReadToEnd()

    $stdOutput = New-Object -TypeName PSObject
    $stdOutput | Add-Member –MemberType NoteProperty –Name stderr –Value $stderr
    $stdOutput | Add-Member –MemberType NoteProperty –Name stdout –Value $stdout

    return $stdOutput
}export-modulemember -Function Start-MyProcess
function Get-GitBranchesToDelete{
<#
    .Synopsis
      Identifies which local git branches do not have a valid upstream branch. It does this by calling git fetch -p --dry-run
    .DESCRIPTION
      Using the git command: git fetch -p --dry-run, this script reads the output of the git comamnd, and parses out the branches that have been deleted in the upstream
    .PARAMETER logLevel
        explain your parameters here. Create a new .PARAMETER line for each parameter,
    .PARAMETER gitfetchOutputPath
        I have not figured out how to read the output (as an array of strings) from git fetch -p --dry-run directly in Powershell. The hacky workaround is to write stderr to a file, then read the file. This lets me iterate through each line and arse the output. This parameter is the path to the file that will hold this data. It will be created if it does not exist. Default value is C:\temp\gitFetchOutput.txt
    .PARAMETER remoteName
        The remote name that we are looking for when parsing out the response. IE. Which remote are we looking for deleted branches from. default is "origin"
 
    #>

[CmdletBinding(SupportsShouldProcess=$true)] 
param([string]$gitfetchOutputPath = "C:\temp\gitFetchOutput.txt"
,[string] $remoteName = "origin")

Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

if (Test-Path $gitfetchOutputPath){
    Remove-Item $gitfetchOutputPath
    "" | Add-Content -Path $gitfetchOutputPath
}
else{
    New-Item $gitfetchOutputPath -ItemType File | Out-Null
}

$allOutput = & git fetch -p --dry-run 2>&1
$allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] } | Add-Content -Path $gitfetchOutputPath

$branchName = $null
$outBranches = @()
foreach ($line in Get-Content $gitfetchOutputPath){
    if ([string]::IsNullOrEmpty($line)-or ($line -notlike "*$remoteName/*") -or ($line.Substring($line.IndexOf("[")+1,7) -ne "deleted")){continue}
    Write-Log "Parsing the line: $line" Debug
    
    $branchName = $line.Substring($line.IndexOf("$remoteName/")+7)
    $outBranches += $branchName
}

$outBranches
}export-modulemember -Function Get-GitBranchesToDelete
Function Get-GitBranchesComparedToRemote{
<#
    .Synopsis
      WIP, this script is going to check the status of all your local branches and compare it with the origin (TFS server) to see which local branches are out of sync (and by how much) with the origi
    .PARAMETER
        branchName
       if specified, only check the status of this branch. If left as the default, it will compare all branches
    #>

[CmdletBinding(SupportsShouldProcess=$true)] 
param([string] $branchName = $null
,[string] $remoteName = "origin")

Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

if ([string]::IsNullOrEmpty($branchName)){
    $branches = git branch
}
else{
    $branches = $branchName
    
}
$branchesToDelete = Get-GitBranchesToDelete
$outputs = @()

foreach ($branch in $branches){
    
    $branchName = $($($branch.Replace(' ','')).Replace('*',''))
    $output = New-Object -TypeName PSObject
    $output | Add-Member –MemberType NoteProperty –Name branchName –Value $branchName
    $result = git branch -r 
    if (!($result -like "*$branchName*")){
        $output | Add-Member –MemberType NoteProperty –Name hasUpstream –Value $false
        Write-Log "Could not find a branch named $branchName in your remote ref list. Perhaps the branch has not been pushed up to the remote?" Debug
    }
    else {
        $result = $null
        $output | Add-Member –MemberType NoteProperty –Name hasUpstream –Value $true

        if ($branchName -in $branchesToDelete){
            $output | Add-Member –MemberType NoteProperty –Name upstreamRefValid –Value $false
        }
        else{
            $output | Add-Member –MemberType NoteProperty –Name upstreamRefValid –Value $true
        }
        
        Write-log "git rev-list $remoteName/master..$remoteName/$branchName" Debug
        $result = git rev-list $remoteName/master..$remoteName/$branchName
        $output | Add-Member –MemberType NoteProperty –Name aheadRemoteMaster –Value $($result.count)
        Write-Log "Remote branch named '$branchName' is: $($result.count) ahead of remote master" Debug
        $result = $null
        Write-Log "git rev-list $remoteName/$branchName...$remoteName/master" Debug
        $result = git rev-list $remoteName/$branchName...$remoteName/master
        $output | Add-Member –MemberType NoteProperty –Name behindRemoteMaster –Value $($result.count)
        Write-Log "Remote branch named '$branchName' is: $($result.count) behind remote master" Debug
        Write-Log "*******" Debug
        Write-log "git rev-list heads/$branchName...$remoteName/$branchName" Debug
        $result = git rev-list heads/$branchName...$remoteName/$branchName
        $output | Add-Member –MemberType NoteProperty –Name aheadRemote –Value $($result.count)
        Write-Log "Local branch named '$branchName' is: $($result.count) ahead of remote" Debug
        $result = $null
        Write-Log "git rev-list $remoteName/$branchName...heads/$branchName" Debug
        $result = git rev-list $remoteName/$branchName...heads/$branchName
        $output | Add-Member –MemberType NoteProperty –Name behindRemote –Value $($result.count)
        Write-Log "Local branch named '$branchName' is: $($result.count) behind remote" Debug

        $outputs += $output
    }  
}

$outputs
}export-modulemember -Function Get-GitBranchesComparedToRemote
Function Invoke-GitFetchForSubDirectories{
param([Parameter(position=0)][string] $directoryToRecurse = $null
,[Parameter(position=1)][int] $directoryRecurseDepth = 1
,[Parameter(position=2)][string] $remote = $null)

if ([string]::IsNullOrEmpty($directoryToRecurse)){$directoryToRecurse = Get-Location}
$origDirectory = Get-Location

$subDirs = Get-ChildItem $directoryToRecurse -Recurse -Directory -Depth $directoryRecurseDepth
$gitDirs = @()
foreach ($dir in $subDirs){
    Write-Log "Attempting to fetch in $($dir.FullName)" Debug
    cd $($dir.FullName)
    if ([string]::IsNullOrEmpty($remote)){git fetch --all}
    else{git fetch $remote}
    
}

cd $origDirectory
}Export-modulemember -Function Invoke-GitFetchForSubDirectories