InvokeAbilityHelpers.psm1

# Implement your module commands in this script.

if (-not $DependenciesPath ) {
    $DependenciesPath = (Join-Path $PSScriptRoot Dependencies)
}

$defaultDependencyTags = @("dev")
$defaultPsDependVersion = '0.1.62'


$defaultPsDependConfiguration = @{
    PSDependOptions = @{
        AddToPath = $true
    }
    InvokeBuild     = @{
        Version        = '4.1.0'
        Tags           = 'dev', 'prod'
        DependencyType = 'PSGalleryModule'
    }
}
Write-Verbose ("DependenciesPath [{0}]" -f $DependenciesPath) -Verbose
if (-not(Get-InstalledModule PSDepend -RequiredVersion $defaultPsDependVersion -ErrorAction SilentlyContinue)) {
    Install-Module PSDepend -RequiredVersion $defaultPsDependVersion -Force -Scope CurrentUser
}
# -ProjectPath (Get-Location).Path
    
Write-Verbose ("Import or Install Dependencies") -Verbose
Import-Module PSDepend -RequiredVersion $defaultPsDependVersion
Invoke-PSDepend -InputObject $defaultPsDependConfiguration  -Install -Import -Force `
    -Target $DependenciesPath -Tags $defaultDependencyTags -ErrorAction Stop -Verbose

# Export only the functions using PowerShell standard verb-noun naming.
# Be sure to list each exported functions in the FunctionsToExport field of the module manifest file.
# This improves performance of command discovery in PowerShell.

function Update-Project {
    param (
        [Parameter(Mandatory = $false)]
        $ProjectPath,

        [Parameter(Mandatory = $false)]
        $OutputPath
    )

    if (-not $ProjectPath ) {
        $ProjectPath = Resolve-Path  (Get-Location).Path
    }
    if (-not $OutputPath ) {
        $OutputPath = $ProjectPath
    }

    $MetadataObjectAsJson = @{}
    $CurrentTime = Get-Date -Format "MM-dd-yyyy-hh-mm-ss"

    Set-Location $ProjectPath

    $GitVersionExe = (Join-Path $PSScriptRoot "\Dependencies\GitVersion_3.6.5\GitVersion.exe")

    $MetadataFile = (Join-Path $ProjectPath ".metadata.json")
    $GitVersionFile = (Join-Path $ProjectPath GitVersion.yml)
    

    # NOTE: Generate GitVersion file
    Write-Verbose "Generate GitVersion file" -Verbose
    Set-Content $GitVersionFile @"
branches:
    master:`
      mode: ContinuousDeployment
      tag: stable
      increment: None
      prevent-increment-of-merged-branch-version: true
      track-merge-target: false
    release:
      mode: ContinuousDeployment
      tag: release
      increment: None
      prevent-increment-of-merged-branch-version: true
      track-merge-target: false
    (pull|pull\-requests|pr)[/-]:
      mode: ContinuousDeployment
      tag: pr
      increment: None
      prevent-increment-of-merged-branch-version: false
      tag-number-pattern: '[/-](?<number>\d+)[-/]'
      track-merge-target: false
    hotfix(es)?[/-]:
      mode: ContinuousDeployment
      tag: hotfix
      increment: None
      prevent-increment-of-merged-branch-version: false
      track-merge-target: false
    support[/-]:
      mode: ContinuousDeployment
      tag: support
      increment: None
      prevent-increment-of-merged-branch-version: true
      track-merge-target: false
    develop:
      mode: ContinuousDeployment
      tag: develop
      increment: None
      prevent-increment-of-merged-branch-version: false
      track-merge-target: true
"@



    # NOTE: Generate metadata
    Write-Verbose "Generate metadata" -Verbose

    $MetadataFileObject = @{}
    $GitVersionObject = @{}

    #Metadata
    try {
        (ConvertFrom-Json ((Get-Content $MetadataFile -ErrorAction Stop ) | Out-String)).psobject.properties | 
            ForEach-Object { $MetadataFileObject[$_.Name] = $_.Value }
    }
    catch {
        Write-Warning ("Not Found metadata file [{0}]... will be created" -f $MetadataFile)
    }

    #GitVersion
    try {
        (ConvertFrom-Json ( & $GitVersionExe | Out-String)).psobject.properties | 
            ForEach-Object { $GitVersionObject[$_.Name] = $_.Value }     
    }
    catch {
        Write-Error "Cannot load information about version"
        return
    }

    $MetadataObject = @{
        Version   = if ($GitVersionObject.FullSemVer) {$GitVersionObject.FullSemVer} else {"none"};
        
        Build     = @{
            Time              = $CurrentTime
            BuildId           = if ($env:BUILD_BUILDID) {$env:BUILD_BUILDID} else {"none"};
            DefinitionName    = if ($env:BUILD_DEFINITIONNAME) {$env:BUILD_DEFINITIONNAME} else {"none"};
            BuildNumber       = if ($env:BUILD_BUILDNUMBER) {$env:BUILD_BUILDNUMBER} else {"none"};
            DefinitionVersion = if ($env:BUILD_DEFINITIONVERSION) {$env:BUILD_DEFINITIONVERSION} else {"none"};
            QueuedBy          = if ($env:BUILD_QUEUEDBY) {$env:BUILD_QUEUEDBY} else {"none"};
            RepositoryUri     = if ($env:BUILD_REPOSITORY_URI) {$env:BUILD_REPOSITORY_URI} else {"none"};
            TeamProject       = if ($env:SYSTEM_TEAMPROJECT) {$env:SYSTEM_TEAMPROJECT} else {"none"};
            BuildReason       = if ($env:RELEASE_ENVIRONMENTNAME) {$env:RELEASE_ENVIRONMENTNAME} else {"none"};
            AgentName         = if ($env:AGENT_NAME) {$env:AGENT_NAME} else {"none"};
        }
        SemVer    = $GitVersionObject 
        UpdatedAt = $CurrentTime
    }

    $MetadataObjectAsJson = (Merge-Hashtables -primary $MetadataObject -secondary $MetadataFileObject ) `
        | ConvertTo-Json -depth 100 

    Write-Verbose ("MetadataObjectAsJson: {0}" -f $MetadataObjectAsJson) -Verbose    
    $MetadataObjectAsJson | Out-File $MetadataFile -Force

    # NOTE: Update Version element in each csproj
    Write-Verbose "Update Version element in each csproj" -Verbose
    Get-ChildItem -Path $ProjectPath -Recurse -Filter *.csproj -File |
        ForEach-Object {
        Write-Verbose ("Update [{0}]" -f $_.FullName) -Verbose
        [xml]$CsProjectName = Get-Content $_.FullName
        $Version = $CsProjectName.SelectSingleNode('//Version[last()]')
        if ($Version) {
            $Version.InnerText = $MetadataObject.Version
            $CsProjectName.Save($_.FullName)
        }
    }
    #VSTS variables
    Write-Host "##vso[build.updatebuildnumber]$($GitVersionObject.FullSemVer)"
    Write-Host "##vso[task.setvariable variable=ChangelogFile;]$ChangelogFile"
    Write-Host "##vso[task.setvariable variable=MetadataFile;]$MetadataFile"
    
    Write-Verbose ("MetadataPath: {0}" -f $MetadataFile) -Verbose
    Write-Verbose ("Metadata saved to: [{0}]" -f $MetadataFile) -Verbose
    Write-Verbose ("ChangelogFile saved to: [{0}]" -f $ChangelogFile) -Verbose
    Write-Verbose ("GitVersionExe: [{0}]" -f $GitVersionExe) -Verbose

}

Export-ModuleMember -Function Update-Project

Function ConvertTo-FlattenObject {
    # Version 00.02.12, by iRon
    [CmdletBinding()]Param (
        [Parameter(ValueFromPipeLine = $True)][Object[]]$Objects,
        [String]$Separator = ".", [ValidateSet("", 0, 1)]$Base = 1, [Int]$Depth = 5, [Int]$Uncut = 1,
        [String[]]$ToString = ([String], [DateTime], [TimeSpan]), [String[]]$Path = @()
    )
    $PipeLine = $Input | ForEach {$_}; If ($PipeLine) {$Objects = $PipeLine}
    If (@(Get-PSCallStack)[1].Command -eq $MyInvocation.MyCommand.Name -or @(Get-PSCallStack)[1].Command -eq "<position>") {
        $Object = @($Objects)[0]; $Iterate = New-Object System.Collections.Specialized.OrderedDictionary
        If ($ToString | Where {$Object -is $_}) {$Object = $Object.ToString()}
        ElseIf ($Depth) {
            $Depth--
            If ($Object.GetEnumerator.OverloadDefinitions -match "[\W]IDictionaryEnumerator[\W]") {
                $Iterate = $Object
            }
            ElseIf ($Object.GetEnumerator.OverloadDefinitions -match "[\W]IEnumerator[\W]") {
                $Object.GetEnumerator() | ForEach -Begin {$i = $Base} {$Iterate.($i) = $_; $i += 1}
            }
            Else {
                $Names = If ($Uncut) {$Uncut--} Else {$Object.PSStandardMembers.DefaultDisplayPropertySet.ReferencedPropertyNames}
                If (!$Names) {$Names = $Object.PSObject.Properties | Where {$_.IsGettable} | Select -Expand Name}
                If ($Names) {$Names | ForEach {$Iterate.$_ = $Object.$_}}
            }
        }
        If (@($Iterate.Keys).Count) {
            $Iterate.Keys | ForEach {
                ConvertTo-FlattenObject @(, $Iterate.$_) $Separator $Base $Depth $Uncut $ToString ($Path + $_)
            }
        }
        Else {$Property.(($Path | Where {$_}) -Join $Separator) = $Object}
    }
    ElseIf ($Objects -ne $Null) {
        @($Objects) | ForEach -Begin {$Output = @(); $Names = @()} {
            New-Variable -Force -Option AllScope -Name Property -Value (New-Object System.Collections.Specialized.OrderedDictionary)
            ConvertTo-FlattenObject @(, $_) $Separator $Base $Depth $Uncut $ToString $Path
            $Output += New-Object PSObject -Property $Property
            $Names += $Output[-1].PSObject.Properties | Select -Expand Name
        }
        $Output | Select ([String[]]($Names | Select -Unique))
    }
}; 
Export-ModuleMember -Function ConvertTo-FlattenObject

function Merge-Hashtables {
    [cmdletbinding()]
    Param (
        [hashtable]
        # First hashtable to merge, this will have priority
        $primary,

        [hashtable]
        # second hashtable to merge
        $secondary
    )

    # If in debug mode, show the function currently in
    #Write-Log -IfDebug -Message $("***** {0} *****" -f $MyInvocation.MyCommand)

    # Craete an array of types that can be merged.
    # Hashtables and Dictionaries can be merged
    $types = @(
        "Hashtable"
        "Dictionary``2"
    )

    #check for any duplicate keys
    $duplicates = $primary.keys | where {$secondary.ContainsKey($_)}

    if ($duplicates) {
        foreach ($key in $duplicates) {

            # if the item is a hashtable then call this function again
            if ($types -contains $primary.$key.gettype().name -and
                $types -contains $secondary.$key.gettype().name) {

                # set the argument hashtable
                $splat = @{
                    primary   = $primary.$key
                    secondary = $secondary.$key
                }

                $Primary.$key = Merge-Hashtables @splat
            }

            # if the key is an array merge the two items
            if ($primary.$key.GetType().Name -eq "Object[]" -and $secondary.$key.GetType().name -eq "Object[]") {

                $result = @()

                # Because an array can contain many different types, need to be careful how this information is merged
                # This means that the normal additional functions and the Unique parameter of Select will not work properly
                # so iterate around each of the two arrays and add to a result array
                foreach ($arr in @($primary.$key, $secondary.$key)) {

                    # analyse each item in the arr
                    foreach ($item in $arr) {

                        # Switch on the type of the item to determine how to add the information
                        switch ($item.GetType().Name) {
                            "Object[]" {
                                $result += , $item
                            }

                            # If the type is a string make sure that the array does not already
                            # contain the same string
                            "String" {
                                if ($result -notcontains $item) {
                                    $result += $item
                                }
                            }

                            # For everything else add it in
                            default {
                                $result += $item
                            }
                        }
                    }
                }

                # Now assign the result back to the primary array
                $primary.$key = $result
            }

            #force primary key, so remove secondary conflict
            $Secondary.Remove($key)

        }
    }

    #join the two hash tables and return to the calling function
    $Primary + $Secondary

}
Export-ModuleMember -Function Merge-Hashtables

function ConvertTo-PsCustomObjectFromHashtable { 
    param ( 
        [Parameter(  
            Position = 0,   
            Mandatory = $true,   
            ValueFromPipeline = $true,  
            ValueFromPipelineByPropertyName = $true  
        )] [object[]]$hashtable 
    ); 
    
    begin { $i = 0; } 
    
    process { 
        foreach ($myHashtable in $hashtable) { 
            if ($myHashtable.GetType().Name -eq 'hashtable') { 
                $output = New-Object -TypeName PsObject; 
                Add-Member -InputObject $output -MemberType ScriptMethod -Name AddNote -Value {  
                    Add-Member -InputObject $this -MemberType NoteProperty -Name $args[0] -Value $args[1]; 
                }; 
                $myHashtable.Keys | Sort-Object | % {  
                    $output.AddNote($_, $myHashtable.$_);  
                } 
                $output; 
            }
            else { 
                Write-Warning "Index $i is not of type [hashtable]"; 
            } 
            $i += 1;  
        } 
    } 
}
Export-ModuleMember -Function ConvertTo-PsCustomObjectFromHashtable
function ConvertTo-HashtableFromPsCustomObject { 
    param ( 
        [Parameter(  
            Position = 0,   
            Mandatory = $true,   
            ValueFromPipeline = $true,  
            ValueFromPipelineByPropertyName = $true  
        )] [object[]]$psCustomObject 
    ); 
    
    process { 
        foreach ($myPsObject in $psObject) { 
            $output = @{}; 
            $myPsObject | Get-Member -MemberType *Property | % { 
                $output.($_.name) = $myPsObject.($_.name); 
            } 
            $output; 
        } 
    } 
}
Export-ModuleMember -Function ConvertTo-HashtableFromPsCustomObject