AutomatedLab.Common.psm1


# Get public and private function definition files.
$modulebase =  $PSScriptRoot
# Types first
$typeExists = try { [AutomatedLab.Common.Win32Exception] }catch { }
if (-not $typeExists)
{
    try
    {
        if ($PSEdition -eq 'Core')
        {
            Add-Type -Path $modulebase/lib/core/AutomatedLab.Common.dll -ErrorAction Stop
        }
        else
        {
            Add-Type -Path $modulebase/lib/full/AutomatedLab.Common.dll -ErrorAction Stop
        }
    }
    catch
    {
        Write-Warning -Message "Unable to add AutomatedLab.Common.dll - GPO and PKI functionality might be impaired.`r`nException was: $($_.Exception.Message), $($_.Exception.LoaderExceptions)"
    }
}

try
{
    [ServerCertificateValidationCallback]::Ignore()
}
catch { }

function Add-AccountPrivilege
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string[]]
        $UserName,

        [validateSet('SeNetworkLogonRight', 
            'SeRemoteInteractiveLogonRight', 
            'SeBatchLogonRight', 
            'SeInteractiveLogonRight', 
            'SeServiceLogonRight', 
            'SeDenyNetworkLogonRight', 
            'SeDenyInteractiveLogonRight', 
            'SeDenyBatchLogonRight', 
            'SeDenyServiceLogonRight', 
            'SeDenyRemoteInteractiveLogonRight', 
            'SeTcbPrivilege', 
            'SeMachineAccountPrivilege', 
            'SeIncreaseQuotaPrivilege', 
            'SeBackupPrivilege', 
            'SeChangeNotifyPrivilege', 
            'SeSystemTimePrivilege', 
            'SeCreateTokenPrivilege', 
            'SeCreatePagefilePrivilege', 
            'SeCreateGlobalPrivilege', 
            'SeDebugPrivilege', 
            'SeEnableDelegationPrivilege', 
            'SeRemoteShutdownPrivilege', 
            'SeAuditPrivilege', 
            'SeImpersonatePrivilege', 
            'SeIncreaseBasePriorityPrivilege', 
            'SeLoadDriverPrivilege', 
            'SeLockMemoryPrivilege', 
            'SeSecurityPrivilege', 
            'SeSystemEnvironmentPrivilege', 
            'SeManageVolumePrivilege', 
            'SeProfileSingleProcessPrivilege', 
            'SeSystemProfilePrivilege', 
            'SeUndockPrivilege', 
            'SeAssignPrimaryTokenPrivilege', 
            'SeRestorePrivilege', 
            'SeShutdownPrivilege', 
            'SeSynchAgentPrivilege', 
            'SeTakeOwnershipPrivilege' 
        )]
        [string[]]
        $Privilege
    )    
    
    $lsaWrapper = New-Object -TypeName MyLsaWrapper.LsaWrapper -ErrorAction Stop

    foreach ($User in $UserName)
    {
        foreach ($Priv in $Privilege)
        {
            $lsaWrapper.AddPrivileges($User, $Priv)
            Start-Sleep -Milliseconds 250
            $lsaWrapper.AddPrivileges($User, $Priv)
        }
    }    
}

function Add-FunctionToPSSession
{
    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'None'
    )]

    param
    ( 
        [Parameter(
            HelpMessage    = 'Provide the session(s) to load the functions into', 
            Mandatory    = $true,
            Position    = 0
        )]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Runspaces.PSSession[]] 
        $Session,

        [Parameter( 
            HelpMessage = 'Provide the function info to load into the session(s)', 
            Mandatory = $true, 
            Position = 1, 
            ValueFromPipeline    = $true 
        )]
        [ValidateNotNull()]
        [System.Management.Automation.FunctionInfo]
        $FunctionInfo
    )

    begin 
    {
        $cmdName = (Get-PSCallStack)[0].Command
        Write-Debug "[$cmdName] Entering function"

        $scriptBlock = 
        {
            param([string]$Path, [string]$Definition)
            $null = Set-Item -Path Function:\$Path -Value $Definition
        }
    }

    process
    {
        Invoke-Command -Session $Session -ScriptBlock $scriptBlock -ArgumentList $FunctionInfo.Name, $FunctionInfo.Definition
    }

    end
    {
        Write-Debug "[$cmdName] Exiting function"
    }
}

function Add-StringIncrement
{
    param(
        [Parameter(Mandatory = $true)]
        [string]$String
    )
    
    $testNumberPattern = '^(?<text>.*?) (?<number>\d+)$'
    
    $result = $String -match $testNumberPattern
    
    if ($Matches.Number)
    {
        $String = $String.Substring(0, $String.Length - $Matches.Number.Length) + ([int]$Matches.Number + 1)
    }
    else
    {
        $String = $String + ' 0'
    }
    
    $String
}

function Add-VariableToPSSession
{
    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'None'
    )]

    param
    ( 
        [Parameter(
            HelpMessage    = 'Provide the session(s) to load the functions into', 
            Mandatory    = $true,
            Position    = 0
        )]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Runspaces.PSSession[]] 
        $Session,

        [Parameter( 
            HelpMessage = 'Provide the variable info to load into the session(s)', 
            Mandatory = $true, 
            Position = 1, 
            ValueFromPipeline    = $true 
        )]
        [ValidateNotNull()]
        [System.Management.Automation.PSVariable]
        $PSVariable
    )

    begin 
    {
        $cmdName = (Get-PSCallStack)[0].Command
        Write-Debug "[$cmdName] Entering function"

        $scriptBlock = 
        {
            param([string]$_AL_Path, [object]$Value)
            $null = Set-Item -Path Variable:\$_AL_Path -Value $Value
        }
    }

    process
    {
        if ($PSVariable.Name -eq 'PSBoundParameters')
        {
            Invoke-Command -Session $Session -ScriptBlock $scriptBlock -ArgumentList 'ALBoundParameters', $PSVariable.Value
        }
        else
        {
            Invoke-Command -Session $Session -ScriptBlock $scriptBlock -ArgumentList $PSVariable.Name, $PSVariable.Value
        }
    }

    end
    {
        Write-Debug "[$cmdName] Exiting function"
    }
}

function Get-ConsoleText
{
    [CmdletBinding()]
    param()
    
    # Check the host name and exit if the host is not the Windows PowerShell console host.
    if ($host.Name -eq 'Windows PowerShell ISE Host')
    { 
        $psISE.CurrentPowerShellTab.ConsolePane.Text
    }
    elseif ($host.Name -eq 'ConsoleHost')
    {
        $textBuilderConsole = New-Object System.Text.StringBuilder
        $textBuilderLine = New-Object System.Text.StringBuilder

        # Grab the console screen buffer contents using the Host console API.
        $bufferWidth = $host.UI.RawUI.BufferSize.Width
        $bufferHeight = $host.UI.RawUI.CursorPosition.Y 
        $rec = New-Object System.Management.Automation.Host.Rectangle(0, 0, ($bufferWidth), $bufferHeight)
        $buffer = $host.UI.RawUI.GetBufferContents($rec) 

        # Iterate through the lines in the console buffer.
        for ($i = 0; $i -lt $bufferHeight; $i++) 
        { 
            for ($j = 0; $j -lt $bufferWidth; $j++) 
            { 
                $cell = $buffer[$i, $j] 
                $null = $textBuilderLine.Append($cell.Character)
            }
            $null = $textBuilderConsole.AppendLine($textBuilderLine.ToString().TrimEnd())
            $textBuilderLine = New-Object System.Text.StringBuilder
        }

        $textBuilderConsole.ToString()
        Write-Verbose "$bufferHeight lines have been copied to the clipboard"
    }
}

<#
        Script Name : Get-NetFrameworkVersion.ps1
        Description : This script reports the various .NET Framework versions installed on the local or a remote computer.
        Author : Martin Schvartzman
        Reference : https://msdn.microsoft.com/en-us/library/hh925568
#>

function Get-DotNetFrameworkVersion
{
    [CmdletBinding()]
    param
    (
        [string]$ComputerName = $env:COMPUTERNAME
    )

    $dotNetRegistry = 'SOFTWARE\Microsoft\NET Framework Setup\NDP'
    $dotNet4Registry = 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'
    $dotNet4Builds = @{
        '30319'  = @{ Version = [System.Version]'4.0' }
        '378389' = @{ Version = [System.Version]'4.5' }
        '378675' = @{ Version = [System.Version]'4.5.1'   ; Comment = '(8.1/2012R2)' }
        '378758' = @{ Version = [System.Version]'4.5.1'   ; Comment = '(8/7 SP1/Vista SP2)' }
        '379893' = @{ Version = [System.Version]'4.5.2' }
        '380042' = @{ Version = [System.Version]'4.5'     ; Comment = 'and later with KB3168275 rollup' }
        '393295' = @{ Version = [System.Version]'4.6'     ; Comment = '(Windows 10)' }
        '393297' = @{ Version = [System.Version]'4.6'     ; Comment = '(NON Windows 10)' }
        '394254' = @{ Version = [System.Version]'4.6.1'   ; Comment = '(Windows 10)' }
        '394271' = @{ Version = [System.Version]'4.6.1'   ; Comment = '(NON Windows 10)' }
        '394802' = @{ Version = [System.Version]'4.6.2'   ; Comment = '(Windows 10 1607)' }
        '394806' = @{ Version = [System.Version]'4.6.2'   ; Comment = '(NON Windows 10)' }
        '460798' = @{ Version = [System.Version]'4.7'     ; Comment = '(Windows 10 1703)' }
        '460805' = @{ Version = [System.Version]'4.7'     ; Comment = '(NON Windows 10)' }
        '461308' = @{ Version = [System.Version]'4.7.1'   ; Comment = '(Windows 10 1709)' }
        '461310' = @{ Version = [System.Version]'4.7.1'   ; Comment = '(NON Windows 10)' }
        '461808' = @{ Version = [System.Version]'4.7.2'   ; Comment = '(Windows 10 1803)' }
        '461814' = @{ Version = [System.Version]'4.7.2'   ; Comment = '(NON Windows 10)' }
        '528040' = @{ Version = [System.Version]'4.8'     ; Comment = '(Windows 10 1903)' }
        '528049' = @{ Version = [System.Version]'4.8'     ; Comment = '(NON Windows 10)' }
        '528372' = @{ Version = [System.Version]'4.8'     ; Comment = '(Windows 10 2004)' }
        '528449' = @{ Version = [System.Version]'4.8'     ; Comment = '(Windows 11 / Server 2022)' }
        '533320' = @{ Version = [System.Version]'4.8.1'   ; Comment = '(Windows 11 / Server 2022)' }
        '533325' = @{ Version = [System.Version]'4.8.1'   ; Comment = '(NON Windows 11)' }    
    }

    foreach ($computer in $ComputerName)
    {
        if ($regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computer))
        {
            if ($netRegKey = $regKey.OpenSubKey("$dotNetRegistry"))
            {
                foreach ($versionKeyName in $netRegKey.GetSubKeyNames())
                {
                    if ($versionKeyName -match '^v[123]')
                    {
                        $versionKey = $netRegKey.OpenSubKey($versionKeyName)
                        $version = [System.Version]($versionKey.GetValue('Version', ''))
                        New-Object -TypeName PSObject -Property ([ordered]@{
                                ComputerName = $computer
                                Build        = $version.Build
                                Version      = $version
                                Comment      = ''
                            })
                    }
                }
            }

            if ($net4RegKey = $regKey.OpenSubKey("$dotNet4Registry"))
            {
                if (-not ($net4Release = $net4RegKey.GetValue('Release')))
                {
                    $net4Release = 30319
                }
                New-Object -TypeName PSObject -Property ([ordered]@{
                        ComputerName = $Computer
                        Build        = $net4Release
                        Version      = $dotNet4Builds["$net4Release"].Version
                        Comment      = $dotNet4Builds["$net4Release"].Comment
                    })
            }
        }
    }
}

function Get-FullMesh
{
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [array]$List,

        [switch]$OneWay
    )

    $mesh = New-Object System.Collections.ArrayList

    foreach ($item1 in $List)
    {
        foreach ($item2 in $list)
        {
            if ($item1 -eq $item2)
            { continue }

            if ($mesh.Contains(($item1, $item2)))
            { continue }

            if ($OneWay)
            {
                if ($mesh.Contains(($item2, $item1)))
                { continue }
            }

            $mesh.Add((New-Object (Get-Type -GenericType Mesh.Item -T string) -Property @{ Source = $item1; Destination = $item2 } )) | Out-Null
        }
    }

    $mesh
}

function Get-ModuleDependency
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSModuleInfo]
        $Module,

        [switch]
        $AsModuleInfo
    )

    if ($Module.RequiredModules)
    {
        Write-Verbose "$($Module.Name) has required modules"
        foreach ($moduleName in $Module.RequiredModules)
        {
            $moduleInfo = Get-Module -ListAvailable -Name $moduleName.Name
            if ($moduleName.Version) {$moduleInfo = $moduleInfo | Where-Object Version -eq $moduleName.Version}
            $moduleInfo = $moduleInfo | Sort-Object Version -Descending | Select-Object -First 1
            Write-Verbose "Detecting dependencies for $($moduleInfo.Name)"
            Get-ModuleDependency -Module $moduleInfo -AsModuleInfo:$AsModuleInfo.IsPresent
        }
    }
    
    if ($AsModuleInfo.IsPresent)
    {
        return $Module
    }
    
    $Module.ModuleBase
}

function Get-RunspacePool
{
    [OutputType([System.Management.Automation.Runspaces.RunspacePool[]])]
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [int]
        $ThrottleLimit,

        [Parameter()]
        [System.Threading.ApartmentState]
        $ApartmentState
    )

    $pools = $(Get-Variable -Name ALCommonRunspacePool_* -Scope Script -ErrorAction SilentlyContinue).Value

    if ($ThrottleLimit)
    {
        $pools = $pools.Where({$_.GetMaxRunspaces() -eq $ThrottleLimit})
    }

    if ($ApartmentState)
    {
        $pools = $pools.Where({$_.ApartmentState -eq $ApartmentState})
    }

    $pools
}

function Get-StringSection
{
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$String,

        [Parameter(Mandatory = $true)]
        [int]$SectionSize
    )

    process
    {
        0..($String.Length - 1) | 
            Group-Object -Property { [System.Math]::Truncate($_ / $SectionSize) } |
            ForEach-Object { -join $String[$_.Group] }
    }
}

function Get-Type
{
    param (
        [Parameter(Position = 0, Mandatory = $true)]
        [string] $GenericType,
        
        [Parameter(Position = 1, Mandatory = $true)]
        [string[]] $T
    )
    
    $T = $T -as [type[]]
    
    try
    {
        $generic = [type]($GenericType + '`' + $T.Count)
        $generic.MakeGenericType($T)
    }
    catch [Exception]
    {
        throw New-Object -TypeName System.Exception -ArgumentList ('Cannot create generic type', $_.Exception)
    }
}

function Install-SoftwarePackage
{
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,
        
        [string]$CommandLine,
        
        [bool]$AsScheduledJob,
        
        [bool]$UseShellExecute,

        [string]$WorkingDirectory,

        [int[]]$ExpectedReturnCodes,

        [system.management.automation.pscredential]$Credential
    )    
    
    #region New-InstallProcess
    function New-InstallProcess
    {
        param(
            [Parameter(Mandatory = $true)]
            [string]$Path,

            [string]$CommandLine,
            
            [bool]$UseShellExecute,

            [string]$WorkingDirectory
        )

        $pInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo
        $pInfo.FileName = $Path
        if (-not [string]::IsNullOrWhiteSpace($WorkingDirectory)) { $pInfo.WorkingDirectory = $WorkingDirectory }

        $pInfo.UseShellExecute = $UseShellExecute
        if (-not $UseShellExecute)
        {
            $pInfo.RedirectStandardError = $true
            $pInfo.RedirectStandardOutput = $true
        }
        $pInfo.Arguments = $CommandLine

        $p = New-Object -TypeName System.Diagnostics.Process
        $p.StartInfo = $pInfo
        Write-Verbose -Message "Starting process: $($pInfo.FileName) $($pInfo.Arguments)"
        $p.Start() | Out-Null
        Write-Verbose "The installation process ID is $($p.Id)"
        $p.WaitForExit()
        Write-Verbose -Message 'Process exited. Reading output'

        $params = @{
            Process = $p
            LabSourcesConnectOutput = $labSourcesConnectOutput
        }
        if (-not $UseShellExecute)
        {
            $params.Output = $p.StandardOutput.ReadToEnd()
            $params.Error = $p.StandardError.ReadToEnd()
        }
        New-Object -TypeName PSObject -Property $params
    }
    #endregion New-InstallProcess

    #if the path cannot be found and starts with \\automatedlabsources...
    if ((-not (Test-Path -Path $Path) -and $Path -match '\\automatedlabsources[a-z]{5}\.file\.core\.windows\.net'))
    {
        #we assume, the LabSources share was not mapped correctly and try again by calling 'C:\AL\AzureLabSources.ps1'
        $labSourcesConnectOutput = C:\AL\AzureLabSources.ps1 2> $null
        if ($labSourcesConnectOutput.AlternativeLabSourcesPath)
        {
            $Path = $Path.Replace($labSourcesConnectOutput.LabSourcesPath, $labSourcesConnectOutput.AlternativeLabSourcesPath)
        }
    }

    if (-not (Test-Path -Path $Path -PathType Leaf))
    {
        Write-Error "The file '$Path' could not found"
        return        
    }
        
    $start = Get-Date
    Write-Verbose -Message "Starting setup of '$Path' with the following command"
    Write-Verbose -Message "`t$CommandLine"
    Write-Verbose -Message "The timeout is $Timeout minutes, starting at '$start'"
    
    $installationMethod = [System.IO.Path]::GetExtension($Path)
    $installationFile = [System.IO.Path]::GetFileName($Path)
    
    if ($installationMethod -eq '.msi')
    {        
        [string]$CommandLine = if (-not $CommandLine)
        {
            @(
                "/I `"$Path`"", # Install this MSI
                '/QN', # Quietly, without a UI
                "/L*V `"$([System.IO.Path]::GetTempPath())$([System.IO.Path]::GetFileNameWithoutExtension($Path)).log`""     # Verbose output to this log
            )
        }
        else
        {
            '/I "{0}" {1}' -f $Path, $CommandLine # Install this MSI
        }
        
        Write-Verbose -Message 'Installation arguments for MSI are:'
        Write-Verbose -Message "`tPath: $Path"
        Write-Verbose -Message "`tLog File: '`t$([System.IO.Path]::GetTempPath())$([System.IO.Path]::GetFileNameWithoutExtension($Path)).log'"
        
        $Path = 'msiexec.exe'
    }
    elseif ($installationMethod -eq '.msp')
    {
        [string]$CommandLine = if (-not $CommandLine)
        {
            @(
                "/P `"$Path`"", # Install this MSI
                '/QN', # Quietly, without a UI
                "/L*V `"$([System.IO.Path]::GetTempPath())$([System.IO.Path]::GetFileNameWithoutExtension($Path)).log`""     # Verbose output to this log
            )
        }
        else
        {
            '/P {0} {1}' -f $Path, $CommandLine # Install this MSI
        }
        
        Write-Verbose -Message 'Installation arguments for MSI are:'
        Write-Verbose -Message "`tPath: $Path"
        Write-Verbose -Message "`tLog File: '`t$([System.IO.Path]::GetTempPath())$([System.IO.Path]::GetFileNameWithoutExtension($Path)).log'"
        
        $Path = 'msiexec.exe'
    }
    elseif ($installationMethod -eq '.msu')
    {        
        $tempRemoteFolder = [System.IO.Path]::GetTempFileName()
        Remove-Item -Path $tempRemoteFolder
        New-Item -ItemType Directory -Path $tempRemoteFolder
        expand.exe -F:* $Path $tempRemoteFolder
        $Path = 'dism.exe'
        $CommandLine = "/Online /Add-Package /PackagePath:""$tempRemoteFolder"" /NoRestart /Quiet"
    }
    elseif ($installationMethod -eq '.exe')
    { }
    else
    {
        Write-Error -Message 'The extension of the file to install is unknown'
        return
    }

    Write-Verbose -Message "Starting installation of $installationMethod file"

    if ($AsScheduledJob)
    {
        $jobName = "AL_$([guid]::NewGuid())"
        Write-Verbose "In the AsScheduledJob mode, creating scheduled job named '$jobName'"
            
        if ($PSVersionTable.PSVersion -lt '3.0')
        {
            Write-Verbose "Running SCHTASKS.EXE as PowerShell Version is <2.0"
            $processName = [System.IO.Path]::GetFileNameWithoutExtension($Path)
            $d = "{0:HH:mm}" -f (Get-Date).AddMinutes(1)

            "$Path $CommandLine" | Out-File -FilePath "C:\$jobName.cmd" -Encoding default

            if ($Credential)
            {
                SCHTASKS /Create /SC ONCE /ST $d /TN $jobName /RU "$($Credential.UserName)" /RP "$($Credential.GetNetworkCredential().Password)" /TR "C:\$jobName.cmd" | Out-Null
            }
            else
            {
                SCHTASKS /Create /SC ONCE /ST $d /TN $jobName /TR "C:\$jobName.cmd" /RU "SYSTEM" | Out-Null
            }

            Start-Sleep -Seconds 5 #allow some time to let the scheduled task run
            while (-not ($p))
            {
                Start-Sleep -Milliseconds 500
                $p = Get-Process -Name $processName -ErrorAction SilentlyContinue
            }

            $p.WaitForExit()
            Write-Verbose -Message 'Process exited. Reading output'

            $params = @{ Process = $p }
            $params.Add('Output', "Output cannot be retrieved using AsScheduledJob on PowerShell 2.0")
            $params.Add('Error', "Errors cannot be retrieved using AsScheduledJob on PowerShell 2.0")
            New-Object -TypeName PSObject -Property $params
        }
        else
        {
            Write-Verbose "Running Register-ScheduledJob as PowerShell Version is >=3.0"

            $scheduledJobParams = @{
                Name         = $jobName
                ScriptBlock  = (Get-Command -Name New-InstallProcess).ScriptBlock
                ArgumentList = $Path, $CommandLine, $UseShellExecute
                RunNow       = $true
            }
            if ($WorkingDirectory) { $scheduledJobParams.ArgumentList += $WorkingDirectory }
            if ($Credential) { $scheduledJobParams.Add('Credential', $Credential) }
            $scheduledJob = Register-ScheduledJob @scheduledJobParams
            Write-Verbose "ScheduledJob object registered with the ID $($scheduledJob.Id)"
            Start-Sleep -Seconds 5 #allow some time to let the scheduled task run
            
            while (-not $job)
            {
                Start-Sleep -Milliseconds 500
                $job = Get-Job -Name $jobName -ErrorAction SilentlyContinue
            }        
            $job | Wait-Job | Out-Null
            $result = $job | Receive-Job
        }
    }
    else
    {
        $result = New-InstallProcess -Path $Path -CommandLine $CommandLine -UseShellExecute $UseShellExecute -WorkingDirectory $WorkingDirectory
    }
    
    Start-Sleep -Seconds 5
    
    if ($AsScheduledJob)
    {
        if ($PSVersionTable.PSVersion -lt '3.0')
        {
            schtasks.exe /DELETE /TN $jobName /F | Out-Null
            Remove-Item -Path "C:\$jobName.cmd"
        }
        else
        {
            Write-Verbose "Unregistering scheduled job with ID $($scheduledJob.Id)"
            $scheduledJob | Unregister-ScheduledJob
        }
    }

    if ($installationMethod -eq '.msu')
    {
        Remove-Item -Path $tempRemoteFolder -Recurse -Confirm:$false
    }
        
    Write-Verbose "Exit code of installation process is '$($result.Process.ExitCode)'"
    if ($null -ne $result.Process.ExitCode -and (0, 3010 + $ExpectedReturnCodes) -notcontains $result.Process.ExitCode)
    {
        $onLegacyOs = try
        {
            $type = [AutomatedLab.Common.Win32Exception]
            $false
        }
        catch
        { $true }

        if ($onLegacyOs)
        {
            throw (New-Object System.ComponentModel.Win32Exception($result.Process.ExitCode))
        }

        throw (New-Object AutomatedLab.Common.Win32Exception($result.Process.ExitCode))
    }
    else
    {
        Write-Verbose -Message "Installation of '$installationFile' finished successfully"
        $result.Output
    }
}

function Invoke-Ternary 
{
    param
    (
        [scriptblock]
        $decider,

        [scriptblock]
        $ifTrue,

        [scriptblock]
        $ifFalse
    )

    if (&$decider)
    {
        &$ifTrue
    }
    else
    {
        &$ifFalse
    }
}
Set-Alias -Name ?? -Value Invoke-Ternary -Option AllScope -Description "Ternary Operator like '?' in C#" -Scope Global

function New-RunspacePool
{
    [OutputType([System.Management.Automation.Runspaces.RunspacePool])]
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [int]
        $ThrottleLimit = 10,

        [Parameter()]
        [System.Threading.ApartmentState]
        $ApartmentState = 'Unknown',

        [Parameter()]
        [System.Management.Automation.PSVariable[]]
        $Variable,

        [Parameter()]
        [System.Management.Automation.FunctionInfo[]]
        $Function
    )

    $pool = Get-Variable -Name "ALCommonRunspacePool_$($ThrottleLimit)_$($ApartmentState)" -Scope Script -ErrorAction SilentlyContinue
    $InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()

    foreach ($func in $Function)
    {
        $ssFunc = [System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new($func.Name, $func.ScriptBlock)
        $InitialSessionState.Commands.Add($ssFunc)
    }

    foreach ($var in $Variable)
    {
        $sessionVariable = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new($var.Name, $var.Value, $null)
        $InitialSessionState.Variables.Add($sessionVariable)
    }

    if (-not ($pool))
    {
        Write-Verbose -Message "Creating new runspace pool. Maximum Runspaces: $ThrottleLimit, ApartmentState: $ApartmentState, Variables: $($Variable.Count)"
        $pool = New-Variable -Name "ALCommonRunspacePool_$($ThrottleLimit)_$($ApartmentState)" -Scope Script -Value $([runspacefactory]::CreateRunspacePool($InitialSessionState)) -PassThru
        [void] $($pool.Value.SetMaxRunspaces($ThrottleLimit))

        if ($PSEdition -eq 'Desktop')
        {
            $pool.Value.ApartmentState = $ApartmentState
        }
    }
        
    $pool.Value
}

function Read-Choice
{ 
    param(
        [Parameter(Mandatory = $true)]
        [String[]]$ChoiceList, 

        [Parameter(Mandatory = $true)]
        [String]$Caption,
        
        [String]$Message,

        [int]$Default = 0
    )
    
    if (-not $Message) { $Message = $Caption }

    $choices = New-Object System.Collections.ObjectModel.Collection[System.Management.Automation.Host.ChoiceDescription]

    $choiceList | ForEach-Object { $choices.Add((New-Object "System.Management.Automation.Host.ChoiceDescription" -ArgumentList $_)) }

    $Host.UI.PromptForChoice($Caption, $Message, $choices, $Default) 
}

function Read-HashTable
{ 
    param(
        [Parameter(Mandatory = $true)]
        [String[]]$ChoiceList, 

        [Parameter(Mandatory = $true)]
        [String]$Caption,
        
        [String]$Message,

        [int]$Default = 0
    )
    
    #if (-not $Message) { $Message = $Caption }

    $fields = New-Object System.Collections.ObjectModel.Collection[System.Management.Automation.Host.FieldDescription]

    $choiceList | ForEach-Object { $fields.Add((New-Object System.Management.Automation.Host.FieldDescription -ArgumentList $_)) }

    $Host.UI.Prompt($Caption, $Message, $fields)    
}

function Receive-RunspaceJob
{
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true)]
        [object[]]
        $RunspaceJob
    )

    process
    {
        while ($RunspaceJob.Handle.IsCompleted -contains $false)
        {
            Start-Sleep -Milliseconds 100
        }

        foreach ($job in $RunspaceJob)
        {
            $job.Shell.EndInvoke($job.handle)    
            $job.Shell.Dispose()    
        }
    }
}

function Remove-RunspacePool
{
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param
    (
        [Parameter(ValueFromPipeline = $true)]
        [System.Management.Automation.Runspaces.RunspacePool[]]
        $RunspacePool
    )

    process
    {
        foreach ($pool in $RunspacePool)
        {
            if ($PSCmdlet.ShouldProcess($pool.InstanceId, 'Closing runspace pool'))
            {
                $max = $pool.GetMaxRunspaces()
                $state = if ($null -ne $pool.ApartmentState) { $pool.ApartmentState } else {'Unknown'}

                $pool.Close()
                $pool.Dispose()

                Write-Verbose -Message "Attempting to remove ALCommonRunspacePool_$($max)_$($state)"
                Remove-Variable -Name "ALCommonRunspacePool_$($max)_$($state)" -Scope Script -ErrorAction SilentlyContinue
            }
        }
    }
}

function Send-ModuleToPSSession
{
    [CmdletBinding(  
        RemotingCapability = 'PowerShell', #V3 and above, values documented here: http://msdn.microsoft.com/en-us/library/system.management.automation.remotingcapability(v=vs.85).aspx
        SupportsShouldProcess = $false,
        ConfirmImpact = 'None',
        DefaultParameterSetName = ''
    )]
    
    [OutputType([System.IO.FileInfo])] #OutputType is supported in 3.0 and above
     
    param
    (
        [Parameter(
            HelpMessage = 'Provide the source module info object',
            Position = 0,
            Mandatory = $true, 
            ValueFromPipeline = $true
        )]
        [ValidateNotNullOrEmpty()]
        [PSModuleInfo]
        $Module,

        [Parameter(
            HelpMessage = 'Enter the destination path on the remote computer',
            Position = 1,
            Mandatory = $true, 
            ValueFromPipelineByPropertyName = $true
        )]
        [System.Management.Automation.Runspaces.PSSession[]] 
        $Session,
        
        [ValidateSet('AllUsers', 'CurrentUser')]
        [string]
        $Scope = 'AllUsers',

        [switch]
        $IncludeDependencies,

        [switch]
        $Move,

        [switch]
        $Encrypt,

        [switch]
        $NoWriteBuffer,

        [switch]
        $Verify,

        [switch]
        $Force,

        [switch]
        $NoClobber,

        [ValidateRange(1KB, 7.4MB)] #might be good to have much higher top end as the underlying max is controlled by New-PSSessionOption
        [uint32]
        $MaxBufferSize = 1MB
    )

    begin
    {
        $isCalledRecursivly = (Get-PSCallStack | Where-Object Command -eq $MyInvocation.InvocationName | Measure-Object | Select-Object -ExpandProperty Count) -gt 1
    }
    
    process
    {
        $fileParams = ([hashtable]$PSBoundParameters).Clone()
        [void]$fileParams.Remove('Module')
        [void]$fileParams.Remove('Scope')
        [void]$fileParams.Remove('IncludeDependencies')
        
        if ($Local:Module.ModuleType -eq 'Script' -and ($Local:Module.Path -notmatch '\.psd1$'))
        {
            Write-Error "Cannot send the module '$($Module.Name)' that is not described by a .psd1 file"
            return
        }

        #Remove any sessions where the same or newer module version already exists
        if (-not $Force.IsPresent)
        {
            Write-Verbose 'Filtering out target sessions that do not need the module'
            $Session = foreach ($item in $PSBoundParameters.Session)
            {
                #recursive calls will need to refresh the cached module list because we may have just placed new modules there
                if ($isCalledRecursivly)
                {
                    $modules = Get-Module -PSSession $item -ListAvailable -Name $Local:Module.Name -Refresh
                }
                else
                {
                    $modules = Get-Module -PSSession $item -ListAvailable -Name $Local:Module.Name
                }
                    
                #no version of the module installed, select for sending
                if (-not $modules)
                {
                    $item
                }
                else
                {
                    #determine what versions we have
                    $versions = $modules | ForEach-Object { [System.Version]$_.Version } | Sort-Object -Unique -Descending
                    $highestVersion = $versions | Select-Object -First 1

                    #if the version we are sending is newer than the highest installed version, select for sending
                    if ([System.Version]$Local:Module.Version -gt $highestVersion)
                    {
                        $item
                    }
                    elseif ($highestVersion -gt [System.Version]$Local:Module.Version)
                    {
                        write-Warning "Skipping $($item.ComputerName) which has a higher version $highestVersion of the module installed"
                    }
                    else
                    {
                        write-Verbose  "Skipping $($item.ComputerName) because the same version of the module is installed already"
                    }
                }
            }
        }

        foreach ($s in $Session)
        {
            [version]$sessionVersion = Invoke-Command -Session $s -ScriptBlock {
                if ($PSEdition -eq 'core') {return ('{0}.{1}.{2}' -f $PSVersionTable.PSVersion.Major,$PSVersionTable.PSVersion.Minor,$PSVersionTable.PSVersion.Patch)}
                $PSVersionTable.PSVersion   
            }

            if ($Local:Module.PowerShellVersion -gt $sessionVersion)
            {
                Write-Warning -Message "Module $($Local:Module.Name) requires PS Version $($Local:Module.PowerShellVersion). We only found $($sessionVersion) on $($s.ComputerName). Skipping."
                continue
            }

            $destination = if ($Scope -eq 'AllUsers')
            {
                Invoke-Command -Session $s -ScriptBlock {
                    $destination = if (-not $IsLinux -and -not $IsMacOs)
                    {
                        if ($PSVersionTable.PSVersion.Major -ge 4)
                        {
                            Join-Path -Path ([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath WindowsPowerShell\Modules
                        }
                        else
                        {
                            Join-Path -Path ([System.Environment]::GetFolderPath('System')) -ChildPath WindowsPowerShell\v1.0\Modules
                        }
                    }
                    else
                    {
                        '/usr/local/share/powershell/Modules'
                    }

                    if (-not (Test-Path -Path $destination))
                    {
                        New-Item -ItemType Directory -Path $destination -Force | Out-Null
                    }

                    $destination
                }
            }
            else
            {
                Invoke-Command -Session $s -ScriptBlock { 
                    $destination = if (-not $IsLinux -and -not $IsMacOs)
                    {
                        Join-Path -Path ([System.Environment]::GetFolderPath('MyDocuments')) -ChildPath WindowsPowerShell\Modules
                    }
                    else
                    {
                        '~/.local/share/powershell/Modules'
                    }

                    if (-not (Test-Path -Path $destination))
                    {
                        New-Item -ItemType Directory -Path $destination -Force | Out-Null
                    }
                    $destination
                }
            }

            Write-Verbose "Sending psd1 manifest module in directory $($Local:Module.ModuleBase)"

            if (($Local:Module.ModuleBase -match '\d{1,4}\.\d{1,4}\.\d{1,4}\.\d{1,4}$' -or $Local:Module.ModuleBase -match '\d{1,4}\.\d{1,4}\.\d{1,4}$') -and $sessionVersion -ge ([version]::new(5,0)))
            {
                #parent folder contains a specific version. In order to copy the module right, the parent of this parent is required
                $Local:moduleParentFolder = Split-Path -Path $Local:Module.ModuleBase -Parent
            }
            else
            {
                $Local:moduleParentFolder = $Local:Module.ModuleBase
            }
            
            Send-Directory -SourceFolderPath $Local:moduleParentFolder -DestinationFolderPath $destination -Session $s

            if ($PSBoundParameters.IncludeDependencies -and ($Local:Module.RequiredAssemblies -or $Local:Module.RequiredModules))
            {
                foreach ($requiredModule in $Module.RequiredModules)
                {
                    $requiredModule = Get-Module -ListAvailable $requiredModule | Sort-Object Version -Descending | Select-Object -First 1
                    $params = ([hashtable]$PSBoundParameters).Clone()
                    [void]$params.Remove('Module')
                    Send-ModuleToPSSession -Module $requiredModule @params
                }

                foreach ($requiredAssembly in $Local:Module.RequiredAssemblies)
                {
                    if (Test-Path -Path $requiredAssembly)
                    {
                        Send-FileToPSSession -Source (Get-Item -Path $requiredAssembly -Force).FullName @fileParams
                    }
                    else
                    {
                        write-Warning "Sending required assemblies that do not have the full path information is not currently supported, $requiredAssembly not sent"
                    }
                }
            }
        }
    }
    
    end
    {
    }
}

function Split-Array
{
    param(
        [Parameter(Mandatory = $true)]
        [System.Collections.IEnumerable]$List,

        [Parameter(Mandatory = $true, ParameterSetName = 'MaxChunkSize')]
        [Alias('ChunkSize')]
        [int]$MaxChunkSize,
        
        [ValidateRange(2, [long]::MaxValue)]
        [Parameter(Mandatory = $true, ParameterSetName = 'ChunkCount')]
        [int]$ChunkCount,
        
        [switch]$AllowEmptyChunks
    )
    
    if (-not $AllowEmptyChunks -and ($list.Count -lt $ChunkCount))
    {
        Write-Error "List count ($($List.Count)) is smaller than ChunkCount ($ChunkCount).)"
        return
    }
    
    if ($PSCmdlet.ParameterSetName -eq 'MaxChunkSize')
    {        
        $ChunkCount = [Math]::Ceiling($List.Count / $MaxChunkSize)
    }
    $containers = foreach ($i in 1..$ChunkCount)
    {
        New-Object System.Collections.Generic.List[object]
    }
        
    $iContainer = 0
    foreach ($item in $List)
    {
        $containers[$iContainer].Add($item)
        $iContainer++
        if ($iContainer -ge $ChunkCount) {
            $iContainer = 0
        }
    }
        
    $containers
}

function Start-RunspaceJob
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ScriptBlock]
        $ScriptBlock,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.Runspaces.RunspacePool]
        $RunspacePool,

        [Parameter()]
        [Object[]]
        $Argument
    )

    if ($RunspacePool.RunspacePoolStateInfo.State -eq 'Closed')
    {
        Write-Error -Message "Runspace pool $($RunspacePool.InstanceId) is already closed. Cannot queue job."
        return
    }

    if ($RunspacePool.RunspacePoolStateInfo.State -ne 'Opened')
    {
        $RunspacePool.Open()
    }

    $shell = [powershell]::Create()
    $shell.RunspacePool = $RunspacePool
    [void] $($shell.AddScript($ScriptBlock, $true))

    foreach ($arg in $Argument)
    {
        [void] $($shell.AddArgument($arg))
    }

    [PSCustomObject]@{
        Shell  = $shell
        Handle = $shell.BeginInvoke()
    }
}

function Sync-Parameter
{
    [Cmdletbinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript( {
                $_ -is [System.Management.Automation.FunctionInfo] -or $_ -is [System.Management.Automation.CmdletInfo] -or $_ -is [System.Management.Automation.ExternalScriptInfo]
            })]
        [object]$Command,
        
        [hashtable]$Parameters,

        [switch]$ConvertValue
    )
    
    if (-not $PSBoundParameters.ContainsKey('Parameters'))
    {
        $Parameters = ([hashtable]$ALBoundParameters).Clone()
    }
    else
    {
        $Parameters = ([hashtable]$Parameters).Clone()
    }
    
    $commonParameters = [System.Management.Automation.Internal.CommonParameters].GetProperties().Name
    $commandParameterKeys = $Command.Parameters.Keys.GetEnumerator() | ForEach-Object { $_ }
    $parameterKeys = $Parameters.Keys.GetEnumerator() | ForEach-Object { $_ }
    
    $keysToRemove = Compare-Object -ReferenceObject $commandParameterKeys -DifferenceObject $parameterKeys |
        Select-Object -ExpandProperty InputObject

    $keysToRemove = $keysToRemove + $commonParameters | Select-Object -Unique #remove the common parameters
    
    foreach ($key in $keysToRemove)
    {
        $Parameters.Remove($key)
    }

    if ($ConvertValue.IsPresent)
    {
        $keysToUpdate = @{}
        foreach ($kvp in $Parameters.GetEnumerator())
        {
            if (-not $kvp.Value) # $null or empty string will not trip up conversion
            {
                continue
            }

            $targetType = $Command.Parameters[$kvp.Key].ParameterType
            $sourceType = $kvp.Value.GetType()
            $targetValue = $kvp.Value -as $targetType

            if (-not $targetValue -and $targetType.ImplementedInterfaces -contains [Collections.IList])
            {
                $targetValue = $targetType::new()
                foreach ($v in $kvp.Value)
                {
                    $targetValue.Add($v)
                }
            }

            if (-not $targetValue -and $targetType.ImplementedInterfaces -contains [Collections.IDictionary] )
            {
                $targetValue = $targetType::new()
                foreach ($k in $kvp.Value.GetEnumerator())
                {
                    $targetValue.Add($k.Key, $k.Value)
                }
            }

            if (-not $targetValue -and ($sourceType.ImplementedInterfaces -contains [Collections.IList] -and $targetType.ImplementedInterfaces -notcontains [Collections.IList]))
            {
                Write-Verbose -Message "Value of source parameter $($kvp.Key) is a collection, but target parameter is not. Selecting first object"
                $targetValue = $kvp.Value | Select-Object -First 1
            }

            if (-not $targetValue)
            {
                Write-Error -Message "Conversion of source parameter $($kvp.Key) (Type: $($sourceType.FullName)) to type $($targetType.FullName) was impossible"
                return
            }

            $keysToUpdate[$kvp.Key] = $targetValue
        }
    }

    if ($keysToUpdate)
    {
        foreach ($kvp in $keysToUpdate.GetEnumerator())
        {
            $Parameters[$kvp.Key] = $kvp.Value
        }
    }
    
    if ($PSBoundParameters.ContainsKey('Parameters'))
    {
        $Parameters
    }
    else
    {
        $global:ALBoundParameters = $Parameters
    }
}

function Test-HashtableKeys
{
    param (
        [Parameter(Mandatory = $true)]
        [hashtable]$Hashtable,

        [string[]]$MandatoryKeys,

        [string[]]$ValidKeys,

        [switch]$Quiet
    )

    $result = $true
    
    if ($ValidKeys)
    {
        $compareResult = Compare-Object -ReferenceObject $ValidKeys -DifferenceObject ([array]$Hashtable.Keys) | Where-Object SideIndicator -eq '=>'
        if ($compareResult -and -not $Quiet)
        {
            Write-Error "The keys '$($compareResult.InputObject -join ', ')' are not valid"
        }

        $result = -not $compareResult
    }

    if ($MandatoryKeys)
    {
        $compareResult = Compare-Object -ReferenceObject $MandatoryKeys -DifferenceObject ([array]$Hashtable.Keys) | Where-Object SideIndicator -eq '<='
        if ($compareResult -and -not $Quiet)
        {
            Write-Error "The keys '$($compareResult.InputObject -join ', ')' are mandatory and not defined"
        }

        $result = -not $compareResult
    }

    $result
}

function Test-IsAdministrator
{
    
    [CmdletBinding()]
    param ()
    
    if ($IsLinux -or $IsMacOS)
    {
        # If sudo-ing or logged on as root, returns user ID 0
        $idCmd = (Get-Command -Name id).Source
        [int64] $idResult = & $idCmd -u
        $idResult -eq 0
    }
    else
    {
        $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
        (New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList $currentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
    }
}

function Wait-RunspaceJob
{
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true)]
        [object[]]
        $RunspaceJob,

        [Parameter()]
        [switch]
        $PassThru
    )

    begin
    {
        $jobs = @()
    }

    process
    {
        $jobs += $RunspaceJob
    }

    end
    {
        while ($jobs.Handle.IsCompleted -contains $false)
        {
            Start-Sleep -Milliseconds 100
        }

        if ($PassThru) { $jobs }
    }
}

function Get-DscConfigurationImportedResource
{
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ByFile')]
        [string]$FilePath,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'ByConfiguration')]
        [System.Management.Automation.ConfigurationInfo]$Configuration
    )
    
    $modules = New-Object System.Collections.ArrayList

    if ($Configuration)
    {
        $ast = $Configuration.ScriptBlock.Ast
        $FilePath = $ast.FindAll( { $args[0] -is [System.Management.Automation.Language.ScriptBlockAst] }, $true)[0].Extent.File
        if (-not $FilePath)
        {
            Write-Error "The configuration '$Name' could not be found in a file. Please put the configuration into a file and try again."
            return
        }
    }
    
    $ast = [scriptblock]::Create((Get-Content -Path $FilePath -Raw)).Ast
    
    $configurations = $ast.FindAll( { $args[0] -is [System.Management.Automation.Language.ConfigurationDefinitionAst] }, $true)
    Write-Verbose "Script knwos about $($configurations.Count) configurations"
    foreach ($c in $configurations)
    {
        $importCmds = $c.Body.ScriptBlock.FindAll( { $args[0].Value -eq 'Import-DscResource' -and $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] }, $true)
        Write-Verbose "Configuration $($c.InstanceName) knows about $($importCmds.Count) Import-DscResource commands"
    
        foreach ($importCmd in $importCmds)
        {
            $commandElements = $importCmd.Parent.CommandElements | Select-Object -Skip 1 | Where-Object {$_ -is [System.Management.Automation.Language.ArrayLiteralAst] -or $_ -is [System.Management.Automation.Language.StringConstantExpressionAst] }     
            
            $moduleNames = $commandElements.SafeGetValue()
            if ($moduleNames.GetType().IsArray)
            {
                $modules.AddRange($moduleNames)
            }
            else
            {
                [void]$modules.Add($moduleNames)
            }
        }
    }
    
    $compositeResources = $modules | Where-Object { $_ -ne 'PSDesiredStateConfiguration' } | ForEach-Object { Get-DscResource -Module $_ } | Where-Object { $_.ImplementedAs -eq 'Composite' }
    foreach ($compositeResource in $compositeResources)
    {
        $modulesInResource = Get-DscConfigurationImportedResource -FilePath $compositeResource.Path
        if ($modulesInResource)
        {
            if ($modulesInResource.GetType().IsArray)
            {
                $modules.AddRange($modulesInResource)
            }
            else
            {
                [void]$modules.Add($modulesInResource)
            }
        }
    }
    
    $modules | Select-Object -Unique
}

#author Iain Brighton, from here: https://gist.github.com/iainbrighton/9d3dd03630225ee44126769c5d9c50a9
function Get-RequiredModulesFromMOF
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [System.String] $Path
    )
    process
    {

        $modules = @{ }
        $moduleName = $null
        $moduleVersion = $null

        Get-Content -Path $Path -Encoding Unicode | ForEach-Object {
    
            $line = $_;
            if ($line -match '^\s?Instance of')
            {
                ## We have a new instance so write the existing one
                if (($null -ne $moduleName) -and ($null -ne $moduleVersion))
                {
            
                    $modules[$moduleName] = $moduleVersion;
                    $moduleName = $null
                    $moduleVersion = $null
                    Write-Verbose "Module Instance found: $moduleName $moduleVersion"
                }
            }
            elseif ($line -match '(?<=^\s?ModuleName\s?=\s?")\S+(?=";)')
            {

                ## Ignore the default PSDesiredStateConfiguration module
                if ($Matches[0] -notmatch 'PSDesiredStateConfiguration')
                {
                    $moduleName = $Matches[0]
                    Write-Verbose "Found Module Name $modulename"
                }
                else
                {
                    Write-Verbose 'Excluding PSDesiredStateConfiguration module'
                }
            }
            elseif ($line -match '(?<=^\s?ModuleVersion\s?=\s?")\S+(?=";)')
            {
                $moduleVersion = $Matches[0] -as [System.Version]
                Write-Verbose "Module version = $moduleVersion"
            }
        }

        Write-Output -InputObject $modules
    } #end process
}

Function ConvertTo-BinaryIP
{
    
    
    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
        [Net.IPAddress]$IPAddress
    )
    
    Process
    {
        Return [String]::Join('.', $($IPAddress.GetAddressBytes() |
                    ForEach-Object -Process {
                    [Convert]::ToString($_, 2).PadLeft(8, '0')
                }
            ))
    }
}

Function ConvertTo-DecimalIP
{
    
    
    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
        [Net.IPAddress]$IPAddress
    )
    
    Process
    {
        $i = 3
        $decimalIP = 0
        $IPAddress.GetAddressBytes() | ForEach-Object -Process {
            $decimalIP += $_ * [Math]::Pow(256, $i)
            $i--
        }
        
        Return [UInt32]$decimalIP
    }
}

Function ConvertTo-DottedDecimalIP
{
    
    
    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
        [String]$IPAddress
    )
    
    process
    {
        switch -RegEx ($IPAddress)
        {
            '([01]{8}\.){3}[01]{8}'
            {
                return [String]::Join('.', $($IPAddress.Split('.') | ForEach-Object -Process {
                            [Convert]::ToUInt32($_, 2)
                        }
                    ))
            }
            {$_ -as [Uint32]}
            {
                $IPAddress = [UInt32]$IPAddress
                $dottedIP = $(For ($i = 3; $i -gt -1; $i--)
                    {
                        $remainder = $IPAddress % [Math]::Pow(256, $i)
                        ($IPAddress - $remainder) / [Math]::Pow(256, $i)
                        $IPAddress = $remainder
                    }
                )
                
                return [String]::Join('.', $dottedIP)
            }
            default
            {
                throw 'Cannot convert this format'
            }
        }
    }
}

Function ConvertTo-Mask
{
    
    
    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
        [Alias('Length')]
        [ValidateRange(0, 32)]
        $MaskLength
    )
    
    Process
    {
        Return ConvertTo-DottedDecimalIP ([Convert]::ToUInt32($(('1' * $MaskLength).PadRight(32, '0')), 2))
    }
}

Function ConvertTo-MaskLength
{
    
    
    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
        [Alias('Mask')]
        [Net.IPAddress]$SubnetMask
    )
    
    Process
    {
        $Bits = "$( $SubnetMask.GetAddressBytes() | ForEach-Object -Process { [Convert]::ToString($_, 2)
    } )"

        $Bitsx = $Bits -Replace '[\s0]'
        
        Return $Bitsx.Length
    }
}

Function Get-BroadcastAddress
{
    
    
    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
        [Net.IPAddress]$IPAddress,
        
        [Parameter(Mandatory = $True, Position = 1)]
        [Alias('Mask')]
        [Net.IPAddress]$SubnetMask
    )
    
    process
    {
        return ConvertTo-DottedDecimalIP $((ConvertTo-DecimalIP $IPAddress) -BOr `
            ((-bnot (ConvertTo-DecimalIP $SubnetMask)) -band [UInt32]::MaxValue))
    }
}

Function Get-NetworkAddress
{
    
    
    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
        [Net.IPAddress]$IPAddress,
        
        [Parameter(Mandatory = $True, Position = 1)]
        [Alias('Mask')]
        [Net.IPAddress]$SubnetMask
    )
    
    Process
    {
        Return ConvertTo-DottedDecimalIP ((ConvertTo-DecimalIP $IPAddress) -BAnd (ConvertTo-DecimalIP $SubnetMask))
    }
}

function Get-NetworkRange
{
    [CmdletBinding()]
    param 
    (
        [string]$IPAddress,
        [string]$SubnetMask
    )
    
    if ($IPAddress.Contains('/'))
    {
        $temp = $IPAddress.Split('/')
        $IPAddress = $temp[0]
        $SubnetMask = $temp[1]
    }
    
    If (-not $SubnetMask.Contains('.'))
    {
        $SubnetMask = ConvertTo-Mask -MaskLength $SubnetMask
    }
    
    $decimalIP = ConvertTo-DecimalIP -IPAddress $IPAddress
    $decimalMask = ConvertTo-DecimalIP -IPAddress $SubnetMask
    
    $network = $decimalIP -band $decimalMask
    $broadcast = $decimalIP -bor ((-bnot $decimalMask) -band [UInt32]::MaxValue)
    
    for ($i = $($network + 1); $i -lt $broadcast; $i++)
    {
        ConvertTo-DottedDecimalIP -IPAddress $i
    }
}

function Get-NetworkSummary
{
    param (
        [Parameter(Mandatory = $true)]
        [String]$IPAddress,
        [Parameter(Mandatory = $true)]
        [String]$SubnetMask
    )
    If ($IPAddress.Contains('/'))
    {
        $temp = $IP.Split('/')
        $IPAddress = $temp[0]
        $SubnetMask = $temp[1]
    }
    
    If (!$SubnetMask.Contains('.'))
    {
        $SubnetMask = ConvertTo-Mask $SubnetMask
    }
    
    $decimalIP = ConvertTo-DecimalIP $IPAddress
    $decimalMask = ConvertTo-DecimalIP $SubnetMask
    
    $network = $decimalIP -BAnd $decimalMask
    $broadcast = $decimalIP -BOr
    ((-BNot $decimalMask) -BAnd [UInt32]::MaxValue)
    $networkAddress = ConvertTo-DottedDecimalIP $network
    $rangeStart = ConvertTo-DottedDecimalIP ($network + 1)
    $rangeEnd = ConvertTo-DottedDecimalIP ($broadcast - 1)
    $broadcastAddress = ConvertTo-DottedDecimalIP $broadcast
    $MaskLength = ConvertTo-MaskLength $SubnetMask
    
    $binaryIP = ConvertTo-BinaryIP $IPAddress
    $private = $false
    
    switch -RegEx ($binaryIP)
    {
        '^1111'
        {
            $class = 'E'
            $subnetBitMap = '1111'
        }
        '^1110'
        {
            $class = 'D'
            $subnetBitMap = '1110'
        }
        '^110'
        {
            $class = 'C'
            If ($binaryIP -Match '^11000000.10101000')
            {
                $private = $True
            }
        }
        '^10'
        {
            $class = 'B'
            If ($binaryIP -Match '^10101100.0001')
            {
                $private = $True
            }
        }
        '^0'
        {
            $class = 'A'
            If ($binaryIP -Match '^00001010')
            {
                $private = $True
            }
        }
    }
    
    $netInfo = New-Object -TypeName Object
    Add-Member -MemberType NoteProperty -Name 'Network' -InputObject $netInfo -Value $networkAddress
    Add-Member -MemberType NoteProperty -Name 'Broadcast' -InputObject $netInfo -Value $broadcastAddress
    Add-Member -MemberType NoteProperty -Name 'Range' -InputObject $netInfo `
        -Value "$rangeStart - $rangeEnd"
    Add-Member -MemberType NoteProperty -Name 'Mask' -InputObject $netInfo -Value $SubnetMask
    Add-Member -MemberType NoteProperty -Name 'MaskLength' -InputObject $netInfo -Value $MaskLength
    Add-Member -MemberType NoteProperty -Name 'Hosts' -InputObject $netInfo `
        -Value $($broadcast - $network - 1)
    Add-Member -MemberType NoteProperty -Name 'Class' -InputObject $netInfo -Value $class
    Add-Member -MemberType NoteProperty -Name 'IsPrivate' -InputObject $netInfo -Value $private
    
    return $netInfo
}

function Get-PublicIpAddress
{
    [CmdletBinding()]
    param
    ()

    $ipProviderUris = @(
        'https://api.ipify.org?format=json'
        'https://ip.seeip.org/jsonip?'
        'https://api.myip.com'
    )

    foreach ($uri in $ipProviderUris)
    {
        $ip = (Invoke-RestMethod -Method Get -UseBasicParsing -Uri $uri -ErrorAction SilentlyContinue).Ip

        if ($ip)
        {
            return $ip
        }
    }
}

function Test-Port
{  
    [Cmdletbinding()]
    Param(  
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]$ComputerName,

        [Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)]
        [int]$Port,

        [int]$Count = 1,

        [int]$Delay = 500,
        
        [int]$TcpTimeout = 1000,
        [int]$UdpTimeout = 1000,
        [switch]$Tcp,
        [switch]$Udp
    )

    begin
    {  
        if (-not $Tcp -and -not $Udp)
        {
            $Tcp = $true
        }
        #Typically you never do this, but in this case I felt it was for the benefit of the function
        #as any errors will be noted in the output of the report
        $ErrorActionPreference = 'SilentlyContinue'
        $report = @()

        $sw = New-Object System.Diagnostics.Stopwatch
    }

    process
    {
        foreach ($c in $ComputerName)
        {
            for ($i = 0; $i -lt $Count; $i++) 
            {
                $result = New-Object PSObject | Select-Object Server, Port, TypePort, Open, Notes, ResponseTime
                $result.Server = $c
                $result.Port = $Port
                $result.TypePort = 'TCP'

                if ($Tcp)
                {
                    $tcpClient = New-Object System.Net.Sockets.TcpClient
                    $sw.Start()
                    $connect = $tcpClient.BeginConnect($c, $Port, $null, $null)
                    $wait = $connect.AsyncWaitHandle.WaitOne($TcpTimeout, $false)
                    
                    if (-not $wait)
                    {
                        $tcpClient.Close()
                        $sw.Stop()

                        $result.Open = $false
                        $result.Notes = 'Connection to Port Timed Out'
                        $result.ResponseTime = $sw.ElapsedMilliseconds
                    }
                    else
                    {
                        try
                        {
                            [void]$tcpClient.EndConnect($connect)
                            $tcpClient.Close()
                            $sw.Stop()

                            $result.Open = $true
                        }
                        catch
                        {
                            $result.Open = $false
                        }
                    }

                    $result.ResponseTime = $sw.ElapsedMilliseconds
                }
                if ($Udp)
                {
                    $udpClient = New-Object System.Net.Sockets.UdpClient
                    $udpClient.Client.ReceiveTimeout = $UdpTimeout

                    $a = New-Object System.Text.ASCIIEncoding
                    $byte = $a.GetBytes("$(Get-Date)")

                    $result.Server = $c
                    $result.Port = $Port
                    $result.TypePort = 'UDP'

                    Write-Verbose 'Making UDP connection to remote server'
                    $sw.Start()
                    $udpClient.Connect($c, $Port)
                    Write-Verbose 'Sending message to remote host'
                    [void]$udpClient.Send($byte, $byte.Length)
                    Write-Verbose 'Creating remote endpoint'
                    $remoteEndpoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)

                    try
                    {
                        Write-Verbose 'Waiting for message return'
                        $receiveBytes = $udpClient.Receive([ref]$remoteEndpoint)
                        $sw.Stop()
                        [string]$returnedData = $a.GetString($receiveBytes)
                        
                        Write-Verbose 'Connection Successful'
                            
                        $result.Open = $true
                        $result.Notes = $returnedData
                    }
                    catch
                    {
                        Write-Verbose 'Host maybe unavailable'
                        $result.Open = $false
                        $result.Notes = 'Unable to verify if port is open or if host is unavailable.'
                    }
                   finally
                    {
                        $udpClient.Close()
                        $result.ResponseTime = $sw.ElapsedMilliseconds
                    }
                }

                $sw.Reset()
                $report += $result

                Start-Sleep -Milliseconds $Delay
            }
        }
    }

    end
    {
        $report 
    }
} 

function Get-PerformanceCounterID
{
    param
    (
        [Parameter(Mandatory = $true)]
        $Name
    )
 
    if ($script:perfHash -eq $null)
    {
        Write-Progress -Activity 'Retrieving PerfIDs' -Status 'Working'
 
        $key = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage'
        $counters = (Get-ItemProperty -Path $key -Name Counter).Counter
        $script:perfHash = @{}
        $all = $counters.Count
 
        for($i = 0; $i -lt $all; $i += 2)
        {
            Write-Progress -Activity 'Retrieving PerfIDs' -Status 'Working' -PercentComplete ($i * 100 / $all)
            $script:perfHash.$($counters[$i + 1]) = $counters[$i]
        }
    }
 
    $script:perfHash.$Name
}
function Get-PerformanceCounterLocalName
{
    param
    (
        [Parameter(Mandatory = $true)]
        [UInt32]$ID,
        
        [string]$ComputerName = $env:COMPUTERNAME
    )
 
    $code = @'
    [DllImport("pdh.dll", SetLastError=true, CharSet=CharSet.Unicode)]
    public static extern UInt32 PdhLookupPerfNameByIndex(string szMachineName, uint dwNameIndex, System.Text.StringBuilder szNameBuffer, ref uint pcchNameBufferSize);
'@

 
    $buffer = New-Object System.Text.StringBuilder(1024)
    [UInt32]$bufferSize = $buffer.Capacity
 
    $t = Add-Type -MemberDefinition $code -PassThru -Name PerfCounter -Namespace Utility
    $rv = $t::PdhLookupPerfNameByIndex($ComputerName, $id, $buffer, [Ref]$bufferSize)
 
    if ($rv -eq 0)
    {
        $buffer.ToString().Substring(0, $bufferSize - 1)
    }
    else
    {
        throw 'Get-PerformanceCounterLocalName : Unable to retrieve localized name. Check computer name and performance counter ID.'
    }
}
function Get-PerformanceDataCollectorSet
{
    Param(
        [Parameter(Mandatory = $true)]
        [string]$CollectorSetName,
        
        [string]$ComputerName = 'localhost'
    )

    $collectorSet = New-Object -ComObject Pla.DataCollectorSet
    
    try
    {
        $collectorSet.Query($CollectorSetName, $ComputerName)
        return $collectorSet
    }
    catch
    {
        Write-Error -Message "Could not query data collector set. The error was: $($_.Exception.Message)" -Exception $_.Exception
    }
}
function New-PerformanceDataCollectorSet
{
    [CmdletBinding(DefaultParameterSetName = 'Counter')]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$CollectorSetName,

        [datetime]$StartDate,

        [Parameter(ParameterSetName = 'Counters')]
        [string[]]$Counters,

        [Parameter(ParameterSetName = 'Xml')]
        [string[]]$XmlTemplatePath,
        
        [string]$ComputerName = 'localhost'
    )
    
    if ((Get-PerformanceDataCollectorSet -CollectorSetName $CollectorSetName -ComputerName $ComputerName -ErrorAction SilentlyContinue))
    {
        Write-Error "There is already a data collector set named '$CollectorSetName' on machine '$ComputerName'"
        return
    }

    $collectorSet = New-Object -COM Pla.DataCollectorSet
    if ($XmlTemplatePath)
    {
        if (-not (Test-Path -Path $XmlTemplatePath -PathType Leaf))
        {
            Write-Error "The file '$XmlTemplatePath' could not be found."
            return
        }
        $xml = Get-Content -Path $XmlTemplatePath
        $collectorSet.SetXml($xml)
    }
    else
    {
        $collectorSet.DisplayName = $CollectorSetName
        $collectorSet.Duration = 50400 
        $collectorSet.SubdirectoryFormat = 1 
        $collectorSet.SubdirectoryFormatPattern = 'yyyy\-MM'
        $collectorSet.RootPath = "%systemdrive%\PerfLogs\Admin\$CollectorSetName"

        $collector = $collectorSet.DataCollectors.CreateDataCollector(0) 
        $collector.FileName = $CollectorSetName + '_'
        $collector.FileNameFormat = 0x1
        $collector.FileNameFormatPattern = 'yyyy\-MM\-dd'
        $collector.SampleInterval = 15
        $collector.LogAppend = $true

        if (-not $Counters)
        {
            $Counters = @(
                '\PhysicalDisk\Avg. Disk Sec/Read',
                '\PhysicalDisk\Avg. Disk Sec/Write',
                '\PhysicalDisk\Avg. Disk Queue Length',
                '\Memory\Available MBytes', 
                '\Processor(_Total)\% Processor Time', 
                '\System\Processor Queue Length'
            )
        }

        $collector.PerformanceCounters = $Counters
        $collectorSet.DataCollectors.Add($collector)

        if ($StartDate)
        {
            $newSchedule = $collectorSet.Schedules.CreateSchedule()
            $newSchedule.Days = 127
            $newSchedule.StartDate = $StartDate
            $newSchedule.StartTime = $StartDate
    
            $collectorSet.Schedules.Add($newSchedule)
        }        
    }

    try
    {
        $collectorSet.Commit($CollectorSetName, $ComputerName, 3) | Out-Null #3 = CreateOrModify
    }
    catch
    { 
        Write-Host 'Exception Caught: ' $_.Exception -ForegroundColor Red 
        return 
    }
}
function Remove-PerformanceDataCollectorSet
{
    Param(
        [Parameter(Mandatory = $true)]
        [string]$CollectorSetName,
        
        [string]$ComputerName = 'localhost'
    )
    
    $collectorSet = Get-PerformanceDataCollectorSet -CollectorSetName $CollectorSetName -ComputerName $ComputerName -ErrorAction SilentlyContinue
    if (-not $collectorSet)
    {
        Write-Error "The data collector set '$CollectorSetName' could not be found on '$ComputerName'"
        return
    }
    
    try
    {
        $collectorSet.Delete()
    }
    catch
    {
        Write-Error -Message "Could not remove data collector set. The error was: $($_.Exception.Message)" -Exception $_.Exception
    }
}
function Start-PerformanceDataCollectorSet
{
    Param(
        [Parameter(Mandatory = $true)]
        [string]$CollectorSetName,
        
        [string]$ComputerName = 'localhost'
    )
    
    $collectorSet = Get-PerformanceDataCollectorSet -CollectorSetName $CollectorSetName -ComputerName $ComputerName -ErrorAction SilentlyContinue
    if (-not $collectorSet)
    {
        Write-Error "The data collector set '$CollectorSetName' could not be found on '$ComputerName'"
        return
    }
    
    try
    {
        $collectorSet.Start($false)
    }
    catch
    {
        Write-Error -Message "Could not start data collector set. The error was: $($_.Exception.Message)" -Exception $_.Exception
    }
}
function Stop-PerformanceDataCollectorSet
{
    Param(
        [Parameter(Mandatory = $true)]
        [string]$CollectorSetName,
        
        [string]$ComputerName = 'localhost'
    )
    
    $collectorSet = Get-PerformanceDataCollectorSet -CollectorSetName $CollectorSetName -ComputerName $ComputerName -ErrorAction SilentlyContinue
    if (-not $collectorSet)
    {
        Write-Error "The data collector set '$CollectorSetName' could not be found on '$ComputerName'"
        return
    }
    
    try
    {
        $collectorSet.Stop($false)
    }
    catch
    {
        Write-Error -Message "Could not start data collector set. The error was: $($_.Exception.Message)" -Exception $_.Exception
    }
}
function Add-CATemplateStandardPermission
{
    [cmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TemplateName,
        
        [Parameter(Mandatory = $true)]
        [string[]]$SamAccountName
    )
    
    $configNc = ([adsi]'LDAP://RootDSE').configurationNamingContext
    $templateContainer = [adsi]"LDAP://CN=Certificate Templates,CN=Public Key Services,CN=Services,$configNc"
    Write-Verbose "Template container is '$templateContainer'"

    $template = $templateContainer.Children | Where-Object Name -eq $TemplateName
    if (-not $template)
    {
        Write-Error "The template '$TemplateName' could not be found"
        return
    }
   
    foreach ($name in $SamAccountName)
    {
        try
        {
            $sid = ([System.Security.Principal.NTAccount]$name).Translate([System.Security.Principal.SecurityIdentifier])
            $name = $sid.Translate([System.Security.Principal.NTAccount])

            dsacls $template.DistinguishedName /G "$($name):GR"
            dsacls $template.DistinguishedName /G "$($name):CA;Enroll"
            dsacls $template.DistinguishedName /G "$($name):CA;AutoEnrollment"
        }
        catch
        {
            Write-Error "The principal '$name' could not be found"
        }
    }
}

function Add-Certificate2
{
    [cmdletBinding(DefaultParameterSetName = 'ByteArray')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'File')]
        [string]$Path,
        
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByteArray')]
        [byte[]]$RawContentBytes,
        
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Store,
        
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [System.Security.Cryptography.X509Certificates.CertStoreLocation]$Location,
        
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$ServiceName,
        
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('CER', 'PFX')]
        [string]$CertificateType = 'CER',
        
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$Password
    )
    
    process
    {
        if ($Location -eq 'CERT_SYSTEM_STORE_SERVICES' -and (-not $ServiceName))
        {
            Write-Error "Please specify a ServiceName if the Location is set to 'CERT_SYSTEM_STORE_SERVICES'"
            return
        }
    
        $storePath = $Store
        
        if ($Path -and -not (Test-Path -Path $Path))
        {
            Write-Error "The path '$Path' does not exist."
            continue
        }
        
        if ($ServiceName)
        {
            if (-not (Get-Service -Name $ServiceName))
            {
                Write-Error "The service '$ServiceName' could not be found."
                return
            }
            else
            {
                $storePath = "$ServiceName\$Store"
            }
        }
    
        $storeProvider = [System.Security.Cryptography.X509Certificates.CertStoreProvider]::CERT_STORE_PROV_SYSTEM_REGISTRY

        $Location = $Location -bor [System.Security.Cryptography.X509Certificates.CertOpenStoreFlags]::CERT_STORE_MAXIMUM_ALLOWED_FLAG
    
        $storePtr = [System.Security.Cryptography.X509Certificates.Win32]::CertOpenStore($storeProvider, 0, 0, $Location, $storePath)
        if ($storePtr -eq [System.IntPtr]::Zero)
        {
            Write-Error "Store '$Store' in location '$Location' could not be opened."
            return
        }
        $s = New-Object System.Security.Cryptography.X509Certificates.X509Store($storePtr)
        
        if ($Path)
        {
            $RawContentBytes = [System.IO.File]::ReadAllBytes($Path)
        }
        
        try
        {
            if ($Password)
            {
                $securePassword = $Password | ConvertTo-SecureString -AsPlainText -Force
            }
            $certInfo = if ([System.Security.Cryptography.X509Certificates.X509Certificate2]::GetCertContentType($RawContentBytes) -eq 'Pfx')
            {
                New-Object Pki.Certificates.CertificateInfo($RawContentBytes, $securePassword)
            }
            else
            {
                New-Object Pki.Certificates.CertificateInfo(,$RawContentBytes)
            }
        }
        catch
        {
            Write-Error -ErrorRecord $_
            return
        }

        Write-Verbose "Store '$Store' in location '$Location' knowns about $($s.Certificates.Count) certificates before import."
        
        $s.Add($certInfo.Certificate)
        
        Write-Verbose "Store '$Store' in location '$Location' knowns about $($s.Certificates.Count) certificates after import."

        [void][System.Security.Cryptography.X509Certificates.Win32]::CertCloseStore($storePtr, 0)
    }
}

function Enable-AutoEnrollment
{
    param
    (
        [switch]$Computer,
        [switch]$UserOrCodeSigning
    )
    
    Write-Verbose -Message "Computer: '$Computer'"
    Write-Verbose -Message "Computer: '$UserOrCodeSigning'"

    if ($PSEdition -eq 'Core') 
    { 
        Write-Warning -Message 'Cannot execute Enable-AutoEnrollment on PowerShell Core!'
        return 
    }
    
    if ($Computer)
    {
        Write-Verbose -Message 'Configuring for computer auto enrollment'
        [GPO.Helper]::SetGroupPolicy($true, 'Software\Policies\Microsoft\Cryptography\AutoEnrollment', 'AEPolicy', 7)
        [GPO.Helper]::SetGroupPolicy($true, 'Software\Policies\Microsoft\Cryptography\AutoEnrollment', 'OfflineExpirationPercent', 10)
        [GPO.Helper]::SetGroupPolicy($true, 'Software\Policies\Microsoft\Cryptography\AutoEnrollment', 'OfflineExpirationStoreNames', 'MY')
    }
    if ($UserOrCodeSigning)
    {
        Write-Verbose -Message 'Configuring for user auto enrollment'
        [GPO.Helper]::SetGroupPolicy($false, 'Software\Policies\Microsoft\Cryptography\AutoEnrollment', 'AEPolicy', 7)
        [GPO.Helper]::SetGroupPolicy($false, 'Software\Policies\Microsoft\Cryptography\AutoEnrollment', 'OfflineExpirationPercent', 10)
        [GPO.Helper]::SetGroupPolicy($false, 'Software\Policies\Microsoft\Cryptography\AutoEnrollment', 'OfflineExpirationStoreNames', 'MY')
    }
    
    1..3 | ForEach-Object { gpupdate.exe /force; certutil.exe -pulse; Start-Sleep -Seconds 1 }
}

function Find-CertificateAuthority
{
    [cmdletBinding()]
    param(
        [string]$DomainName
    )

    Add-Type -AssemblyName System.DirectoryServices.AccountManagement

    try
    {
        $ctx = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('Domain', $DomainName)
    }
    catch
    {
        Write-Error "The domain '$DomainName' could not be contacted"
        return
    }
    
    try
    {
        $configDn = ([ADSI]'LDAP://RootDSE').configurationNamingContext
        $cdpContainer = [ADSI]"LDAP://CN=CDP,CN=Public Key Services,CN=Services,$configDn"

        if (-not $cdpContainer)
        {
            Write-Error 'Could not connect to CDP container' -ErrorAction Stop
        }
    }
    catch
    {
        Write-Error "The domain '$DomainName' could not be contacted" -TargetObject $DomainName
        return
    }
                
    $caFound = $false
    foreach ($item in $cdpContainer.Children)
    {
        if (-not $caFound)
        {
            $machine = ($item.distinguishedName -split '=|,')[1]
            $caName = ($item.Children.distinguishedName -split '=|,')[1]

            if ($DomainName)
            {
                $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($ctx, 'Cert Publishers')
                $machine = $group.Members | Where-Object Name -eq $machine
                if ($machine.Context.Name -ne $DomainName)
                {
                    continue
                }
            }
                        
            $certificateAuthority = "$machine\$caName"
                        
            $result = certutil.exe -ping $certificateAuthority
            if ($result -match 'interface is alive*' )
            {
                $caFound = $true
            }
        }
    }
    
    if ($caFound)
    {
        $certificateAuthority
    }
    else
    {
        Write-Error "No Certificate Authority could be found in domain '$DomainName'"
    }
}
function Get-CATemplate
{
    [cmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TemplateName
    )
    
    $configNc = ([adsi]'LDAP://RootDSE').configurationNamingContext
    $templateContainer = [adsi]"LDAP://CN=Certificate Templates,CN=Public Key Services,CN=Services,$configNc"
    Write-Verbose "Template container is '$($templateContainer.distinguishedName)'"

    $templateContainer.Children | Where-Object Name -eq $TemplateName
}

function Get-Certificate2
{
    [cmdletBinding(DefaultParameterSetName = 'FindCer')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'FindCer')]
        [Parameter(Mandatory = $true, ParameterSetName = 'FindPfx')]
        [string]$SearchString,

        [Parameter(Mandatory = $true, ParameterSetName = 'FindCer')]
        [Parameter(Mandatory = $true, ParameterSetName = 'FindPfx')]        
        [System.Security.Cryptography.X509Certificates.X509FindType]$FindType,
        
        [Parameter(ParameterSetName = 'AllCer')]
        [Parameter(ParameterSetName = 'AllPfx')]
        [Parameter(ParameterSetName = 'FindCer')]
        [Parameter(ParameterSetName = 'FindPfx')]
        [System.Security.Cryptography.X509Certificates.CertStoreLocation]$Location,
        
        [Parameter(ParameterSetName = 'AllCer')]
        [Parameter(ParameterSetName = 'AllPfx')]
        [Parameter(ParameterSetName = 'FindCer')]
        [Parameter(ParameterSetName = 'FindPfx')]
        [string]$Store,
        
        [Parameter(ParameterSetName = 'AllCer')]
        [Parameter(ParameterSetName = 'AllPfx')]
        [Parameter(ParameterSetName = 'FindCer')]
        [Parameter(ParameterSetName = 'FindPfx')]
        [string]$ServiceName,

        [Parameter(Mandatory = $true, ParameterSetName = 'AllCer')]
        [Parameter(Mandatory = $true, ParameterSetName = 'AllPfx')]
        [switch]$All,

        [Parameter(ParameterSetName = 'AllCer')]
        [Parameter(ParameterSetName = 'AllPfx')]
        [switch]$IncludeServices,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'FindPfx')]
        [Parameter(Mandatory = $true, ParameterSetName = 'AllPfx')]
        [securestring]$Password,
        
        [Parameter(ParameterSetName = 'FindPfx')]
        [Parameter(ParameterSetName = 'AllPfx')]
        [switch]$ExportPrivateKey
    )
    
    $services = Get-Service
    
    if ($ServiceName -and $Location -ne 'CERT_SYSTEM_STORE_SERVICES')
    {
        $Location = 'CERT_SYSTEM_STORE_SERVICES'
    }
    
    if ($ServiceName -and $ServiceName -notin $services.Name)
    {
        Write-Error "The service '$ServiceName' could not be found."
        return
    }
    
    $storeProvider = [System.Security.Cryptography.X509Certificates.CertStoreProvider]::CERT_STORE_PROV_SYSTEM
    
    $certs = foreach ($currentLocation in [Enum]::GetNames([System.Security.Cryptography.X509Certificates.CertStoreLocation]))
    {
        if ($Location -and $Location -ne $currentLocation)
        {
            Write-Verbose "Skipping location '$currentLocation'"
            continue
        }
        Write-Verbose "Enumerating stores location '$currentLocation'"

        $internalLocation = [System.Security.Cryptography.X509Certificates.CertStoreLocation]$currentLocation -bor [System.Security.Cryptography.X509Certificates.CertOpenStoreFlags]::CERT_STORE_READONLY_FLAG
    
        $availableStores = if ($ServiceName)
        {
            [System.Security.Cryptography.X509Certificates.Win32]::GetServiceCertificateStores($ServiceName)
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_SERVICES)
        {
            $services = Get-Service
            foreach ($Service in $services)
            {
                [System.Security.Cryptography.X509Certificates.Win32]::GetServiceCertificateStores($service.Name)
            }
        }
        else
        {
            [System.Security.Cryptography.X509Certificates.Win32]::GetCertificateStores()
        }
        
        $availableStores = if ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_CURRENT_USER)
        {
            $availableStores | Where-Object Location -eq CurrentUser
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_LOCAL_MACHINE)
        {
            $availableStores | Where-Object Location -eq LocalMachine
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_LOCAL_MACHINE)
        {
            $availableStores | Where-Object Location -eq LocalMachine
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_SERVICES)
        {
            $availableStores | Where-Object Location -eq Services
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_USERS)
        {
            $availableStores | Where-Object Location -eq Users
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY)
        {
            $availableStores | Where-Object Location -eq CurrentUserGroupPolicy
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY)
        {
            $availableStores | Where-Object Location -eq LocalMachineGroupPolicy
        }
        elseif ($Location -eq [System.Security.Cryptography.X509Certificates.CertStoreLocation]::CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE)
        {
            $availableStores | Where-Object Location -eq LocalMachineEnterprise
        }
        else
        {
            $availableStores
        }
        
        if ($Store)
        {
            if ($ServiceName)
            {
                if ("$ServiceName\$Store" -notin $availableStores.Name)
                {
                    Write-Error "The store '$Store' does not exist for location '$currentLocation' for service '$ServiceName'"
                    continue
                }
                else
                {
                    $availableStores = $availableStores | Where-Object Name -eq "$ServiceName\$Store"
                }
                
            }
            else
            {
                if ($Store -notin $availableStores.Name)
                {
                    Write-Error "The store '$Store' does not exist for location '$currentLocation'"
                    continue
                }
                else
                {
                    $availableStores = $availableStores | Where-Object Name -eq $Store
                }
            }
        }
            
        foreach ($storePath in $availableStores)
        {
            Write-Verbose "Enumerating certificates in store '$storePath' in location '$currentLocation'"
                
            $storePtr = [System.Security.Cryptography.X509Certificates.Win32]::CertOpenStore($storeProvider, 0, 0, $internalLocation, $storePath.Name)
            if ($storePtr -eq [System.IntPtr]::Zero)
            {
                Write-Verbose "Store '$storePath' in location '$currentLocation' could not be opened."
                continue
            }
            
            $s = New-Object System.Security.Cryptography.X509Certificates.X509Store($storePtr)
            $result = if ($All)
            {
                $s.Certificates
            }
            else
            {
                $s.Certificates.Find($FindType, $SearchString, $false)
            }
                
            foreach ($item in $result)
            {
                $item | Add-Member -MemberType NoteProperty -Name Location -Value $currentLocation
                $item | Add-Member -MemberType NoteProperty -Name Store -Value $storePath
                $item | Add-Member -MemberType NoteProperty -Name Password -Value $plainPassword
                    
                if ($Location -eq 'CERT_SYSTEM_STORE_SERVICES')
                {
                    $item | Add-Member -MemberType NoteProperty -Name ServiceName -Value ($storePath -split '\\')[0]
                    $item | Add-Member -MemberType NoteProperty -Name Store -Value ($storePath -split '\\')[1] -Force
                }
                    
                $item
            }

            [void][System.Security.Cryptography.X509Certificates.Win32]::CertCloseStore($storePtr, 0)
        }
    }

    Write-Verbose "Found $($certs.Count) certificates"
    
    if ($SearchString -and $certs.Count -eq 0)
    {
        Write-Error "No certificate found applying search string '$SearchString' and looking for '$FindType'"
        return
    }
    
    foreach ($cert in $certs)
    {
        $tempFile = [System.IO.Path]::GetTempFileName()
        Remove-Item -Path $tempFile

        Write-Verbose "Current certificate is $($cert.Thumbprint)"

        try
        {
            if ($cert.HasPrivateKey -and $ExportPrivateKey)
            {
                Write-Verbose 'Calling Export-PfxCertificate'
                Export-PfxCertificate -Cert $cert -FilePath $tempFile -Password $Password -ErrorAction Stop | Out-Null
            }
            else
            {
                Write-Verbose 'Calling Export-Certificate'
                Export-Certificate -Cert $cert -FilePath $tempFile -ErrorAction Stop | Out-Null
            }
            Write-Verbose 'Export finished'
        }
        catch
        {
            if ($SearchString) #A specific cert is desired so an error is written as not in list mode
            {
                Write-Error $_
            }
            continue
        }

        $certInfo = if ($ExportPrivateKey)
        {
            New-Object Pki.Certificates.CertificateInfo($tempFile, $Password)
        }
        else
        {
            New-Object Pki.Certificates.CertificateInfo($tempFile)
        }
        Remove-Item -Path $tempFile
        
        $certInfo.ComputerName = $env:COMPUTERNAME
        $certInfo.Location = $cert.Location
        $certInfo.Store = $cert.Store.Name
        $certInfo.ServiceName = $cert.ServiceName
        
        $certInfo
    }
}

function Get-NextOid
{
    param(
        [Parameter(Mandatory = $true)]
        [string]$Oid
    )
    
    $oidRange = $Oid.Substring(0, $Oid.LastIndexOf('.'))
    $lastNumber = $Oid.Substring($Oid.LastIndexOf('.') + 1)
    '{0}.{1}' -f $oidRange, ([int]$lastNumber + 1)
}

$ApplicationPolicies = @{
    # Remote Desktop
    'Remote Desktop'                            = '1.3.6.1.4.1.311.54.1.2'
    # Windows Update
    'Windows Update'                            = '1.3.6.1.4.1.311.76.6.1'
    # Windows Third Party Applicaiton Component
    'Windows Third Party Application Component' = '1.3.6.1.4.1.311.10.3.25'
    # Windows TCB Component
    'Windows TCB Component'                     = '1.3.6.1.4.1.311.10.3.23'
    # Windows Store
    'Windows Store'                             = '1.3.6.1.4.1.311.76.3.1'
    # Windows Software Extension verification
    ' Windows Software Extension Verification'  = '1.3.6.1.4.1.311.10.3.26'
    # Windows RT Verification
    'Windows RT Verification'                   = '1.3.6.1.4.1.311.10.3.21'
    # Windows Kits Component
    'Windows Kits Component'                    = '1.3.6.1.4.1.311.10.3.20'
    # ROOT_PROGRAM_NO_OCSP_FAILOVER_TO_CRL
    'No OCSP Failover to CRL'                   = '1.3.6.1.4.1.311.60.3.3'
    # ROOT_PROGRAM_AUTO_UPDATE_END_REVOCATION
    'Auto Update End Revocation'                = '1.3.6.1.4.1.311.60.3.2'
    # ROOT_PROGRAM_AUTO_UPDATE_CA_REVOCATION
    'Auto Update CA Revocation'                 = '1.3.6.1.4.1.311.60.3.1'
    # Revoked List Signer
    'Revoked List Signer'                       = '1.3.6.1.4.1.311.10.3.19'
    # Protected Process Verification
    'Protected Process Verification'            = '1.3.6.1.4.1.311.10.3.24'
    # Protected Process Light Verification
    'Protected Process Light Verification'      = '1.3.6.1.4.1.311.10.3.22'
    # Platform Certificate
    'Platform Certificate'                      = '2.23.133.8.2'
    # Microsoft Publisher
    'Microsoft Publisher'                       = '1.3.6.1.4.1.311.76.8.1'
    # Kernel Mode Code Signing
    'Kernel Mode Code Signing'                  = '1.3.6.1.4.1.311.6.1.1'
    # HAL Extension
    'HAL Extension'                             = '1.3.6.1.4.1.311.61.5.1'
    # Endorsement Key Certificate
    'Endorsement Key Certificate'               = '2.23.133.8.1'
    # Early Launch Antimalware Driver
    'Early Launch Antimalware Driver'           = '1.3.6.1.4.1.311.61.4.1'
    # Dynamic Code Generator
    'Dynamic Code Generator'                    = '1.3.6.1.4.1.311.76.5.1'
    # Domain Name System (DNS) Server Trust
    'DNS Server Trust'                          = '1.3.6.1.4.1.311.64.1.1'
    # Document Encryption
    'Document Encryption'                       = '1.3.6.1.4.1.311.80.1'
    # Disallowed List
    'Disallowed List'                           = '1.3.6.1.4.1.10.3.30'
    # Attestation Identity Key Certificate
    # System Health Authentication
    'System Health Authentication'              = '1.3.6.1.4.1.311.47.1.1'
    # Smartcard Logon
    'IdMsKpScLogon'                             = '1.3.6.1.4.1.311.20.2.2'
    # Certificate Request Agent
    'ENROLLMENT_AGENT'                          = '1.3.6.1.4.1.311.20.2.1'
    # CTL Usage
    'AUTO_ENROLL_CTL_USAGE'                     = '1.3.6.1.4.1.311.20.1'
    # Private Key Archival
    'KP_CA_EXCHANGE'                            = '1.3.6.1.4.1.311.21.5'
    # Key Recovery Agent
    'KP_KEY_RECOVERY_AGENT'                     = '1.3.6.1.4.1.311.21.6'
    # Secure Email
    'PKIX_KP_EMAIL_PROTECTION'                  = '1.3.6.1.5.5.7.3.4'
    # IP Security End System
    'PKIX_KP_IPSEC_END_SYSTEM'                  = '1.3.6.1.5.5.7.3.5'
    # IP Security Tunnel Termination
    'PKIX_KP_IPSEC_TUNNEL'                      = '1.3.6.1.5.5.7.3.6'
    # IP Security User
    'PKIX_KP_IPSEC_USER'                        = '1.3.6.1.5.5.7.3.7'
    # Time Stamping
    'PKIX_KP_TIMESTAMP_SIGNING'                 = '1.3.6.1.5.5.7.3.8'
    # OCSP Signing
    'KP_OCSP_SIGNING'                           = '1.3.6.1.5.5.7.3.9'
    # IP security IKE intermediate
    'IPSEC_KP_IKE_INTERMEDIATE'                 = '1.3.6.1.5.5.8.2.2'
    # Microsoft Trust List Signing
    'KP_CTL_USAGE_SIGNING'                      = '1.3.6.1.4.1.311.10.3.1'
    # Microsoft Time Stamping
    'KP_TIME_STAMP_SIGNING'                     = '1.3.6.1.4.1.311.10.3.2'
    # Windows Hardware Driver Verification
    'WHQL_CRYPTO'                               = '1.3.6.1.4.1.311.10.3.5'
    # Windows System Component Verification
    'NT5_CRYPTO'                                = '1.3.6.1.4.1.311.10.3.6'
    # OEM Windows System Component Verification
    'OEM_WHQL_CRYPTO'                           = '1.3.6.1.4.1.311.10.3.7'
    # Embedded Windows System Component Verification
    'EMBEDDED_NT_CRYPTO'                        = '1.3.6.1.4.1.311.10.3.8'
    # Root List Signer
    'ROOT_LIST_SIGNER'                          = '1.3.6.1.4.1.311.10.3.9'
    # Qualified Subordination
    'KP_QUALIFIED_SUBORDINATION'                = '1.3.6.1.4.1.311.10.3.10'
    # Key Recovery
    'KP_KEY_RECOVERY'                           = '1.3.6.1.4.1.311.10.3.11'
    # Document Signing
    'KP_DOCUMENT_SIGNING'                       = '1.3.6.1.4.1.311.10.3.12'
    # Lifetime Signing
    'KP_LIFETIME_SIGNING'                       = '1.3.6.1.4.1.311.10.3.13'
    'DRM'                                       = '1.3.6.1.4.1.311.10.5.1'
    'DRM_INDIVIDUALIZATION'                     = '1.3.6.1.4.1.311.10.5.2'
    # Key Pack Licenses
    'LICENSES'                                  = '1.3.6.1.4.1.311.10.6.1'
    # License Server Verification
    'LICENSE_SERVER'                            = '1.3.6.1.4.1.311.10.6.2'
    'Server Authentication'                     = '1.3.6.1.5.5.7.3.1' #The certificate can be used for OCSP authentication.
    KP_IPSEC_USER                               = '1.3.6.1.5.5.7.3.7' #The certificate can be used for an IPSEC user.
    'Code Signing'                              = '1.3.6.1.5.5.7.3.3' #The certificate can be used for signing code.
    'Client Authentication'                     = '1.3.6.1.5.5.7.3.2' #The certificate can be used for authenticating a client.
    KP_EFS                                      = '1.3.6.1.4.1.311.10.3.4' #The certificate can be used to encrypt files by using the Encrypting File System.
    EFS_RECOVERY                                = '1.3.6.1.4.1.311.10.3.4.1' #The certificate can be used for recovery of documents protected by using Encrypting File System (EFS).
    DS_EMAIL_REPLICATION                        = '1.3.6.1.4.1.311.21.19' #The certificate can be used for Directory Service email replication.
    ANY_APPLICATION_POLICY                      = '1.3.6.1.4.1.311.10.12.1' #The applications that can use the certificate are not restricted.
}
function New-CATemplate
{
    [cmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TemplateName,
        
        [string]$DisplayName,
        
        [Parameter(Mandatory = $true)]
        [string]$SourceTemplateName,
        
        [ValidateSet('EFS_RECOVERY', 'Auto Update CA Revocation', 'No OCSP Failover to CRL', 'OEM_WHQL_CRYPTO', 'Windows TCB Component', 'DNS Server Trust', 'Windows Third Party Application Component', 'ANY_APPLICATION_POLICY', 'KP_LIFETIME_SIGNING', 'Disallowed List', 'DS_EMAIL_REPLICATION', 'LICENSE_SERVER', 'KP_KEY_RECOVERY', 'Windows Kits Component', 'AUTO_ENROLL_CTL_USAGE', 'PKIX_KP_TIMESTAMP_SIGNING', 'Windows Update', 'Document Encryption', 'KP_CTL_USAGE_SIGNING', 'IPSEC_KP_IKE_INTERMEDIATE', 'PKIX_KP_IPSEC_TUNNEL', 'Code Signing', 'KP_KEY_RECOVERY_AGENT', 'KP_QUALIFIED_SUBORDINATION', 'Early Launch Antimalware Driver', 'Remote Desktop', 'WHQL_CRYPTO', 'EMBEDDED_NT_CRYPTO', 'System Health Authentication', 'DRM', 'PKIX_KP_EMAIL_PROTECTION', 'KP_TIME_STAMP_SIGNING', 'Protected Process Light Verification', 'Endorsement Key Certificate', 'KP_IPSEC_USER', 'PKIX_KP_IPSEC_END_SYSTEM', 'LICENSES', 'Protected Process Verification', 'IdMsKpScLogon', 'HAL Extension', 'KP_OCSP_SIGNING', 'Server Authentication', 'Auto Update End Revocation', 'KP_EFS', 'KP_DOCUMENT_SIGNING', 'Windows Store', 'Kernel Mode Code Signing', 'ENROLLMENT_AGENT', 'ROOT_LIST_SIGNER', 'Windows RT Verification', 'NT5_CRYPTO', 'Revoked List Signer', 'Microsoft Publisher', 'Platform Certificate', ' Windows Software Extension Verification', 'KP_CA_EXCHANGE', 'PKIX_KP_IPSEC_USER', 'Dynamic Code Generator', 'Client Authentication', 'DRM_INDIVIDUALIZATION')]
        [string[]]$ApplicationPolicy,

        [Pki.CATemplate.EnrollmentFlags]$EnrollmentFlags = 'None',

        [Pki.CATemplate.PrivateKeyFlags]$PrivateKeyFlags = 0,

        [Pki.CATemplate.KeyUsage]$KeyUsage = 0,
        
        [int]$Version,

        [timespan]$ValidityPeriod,
        
        [timespan]$RenewalPeriod
    )

    $configNc = ([adsi]'LDAP://RootDSE').ConfigurationNamingContext
    $templateContainer = [adsi]"LDAP://CN=Certificate Templates,CN=Public Key Services,CN=Services,$configNc"
    Write-Verbose "Template container is '$templateContainer'"

    $sourceTemplate = $templateContainer.Children | Where-Object Name -eq $SourceTemplateName
    if (-not $sourceTemplate)
    {
        Write-Error "The source template '$SourceTemplateName' could not be found"
        return
    }

    if (($templateContainer.Children | Where-Object Name -eq $TemplateName))
    {
        Write-Error "The template '$TemplateName' does aleady exist"
        return
    }
    
    if (-not $DisplayName) { $DisplayName = $TemplateName }
    
    $newCertTemplate = $templateContainer.Create('pKICertificateTemplate', "CN=$TemplateName") 
    $newCertTemplate.put('distinguishedName', "CN=$TemplateName,CN=Certificate Templates,CN=Public Key Services,CN=Services,$configNc")

    $lastOid = $templateContainer.Children | 
        Sort-Object -Property { [int]($_.'msPKI-Cert-Template-OID' -split '\.')[-1] } | 
        Select-Object -Last 1 -ExpandProperty msPKI-Cert-Template-OID
    $oid = Get-NextOid -Oid $lastOid
    
    $flags = $sourceTemplate.flags.Value
    $flags = $flags -bor [Pki.CATemplate.Flags]::IsModified -bxor [Pki.CATemplate.Flags]::IsDefault
    
    $newCertTemplate.put('flags', $flags)
    $newCertTemplate.put('displayName', $DisplayName)
    $newCertTemplate.put('revision', '100')
    $newCertTemplate.put('pKIDefaultKeySpec', $sourceTemplate.pKIDefaultKeySpec.Value)

    $newCertTemplate.put('pKIMaxIssuingDepth', $sourceTemplate.pKIMaxIssuingDepth.Value)
    $newCertTemplate.put('pKICriticalExtensions', $sourceTemplate.pKICriticalExtensions.Value)
    
    $eku = @($sourceTemplate.pKIExtendedKeyUsage.Value)
    $newCertTemplate.put('pKIExtendedKeyUsage', $eku)
    
    #$newCertTemplate.put('pKIDefaultCSPs','2,Microsoft Base Cryptographic Provider v1.0, 1,Microsoft Enhanced Cryptographic Provider v1.0')
    $newCertTemplate.put('msPKI-RA-Signature', '0')
    $newCertTemplate.put('msPKI-Enrollment-Flag', $EnrollmentFlags)
    $newCertTemplate.put('msPKI-Private-Key-Flag', $PrivateKeyFlags)
    $newCertTemplate.put('msPKI-Certificate-Name-Flag', $sourceTemplate.'msPKI-Certificate-Name-Flag'.Value)
    $newCertTemplate.put('msPKI-Minimal-Key-Size', $sourceTemplate.'msPKI-Minimal-Key-Size'.Value)
    
    if (-not $Version)
    {
        $Version = $sourceTemplate.'msPKI-Template-Schema-Version'.Value
    }
    $newCertTemplate.put('msPKI-Template-Schema-Version', $Version)
    $newCertTemplate.put('msPKI-Template-Minor-Revision', '1')
                   
    $newCertTemplate.put('msPKI-Cert-Template-OID', $oid)
    
    if (-not $ApplicationPolicy)
    {
        #V2 template
        $ap = $sourceTemplate.'msPKI-Certificate-Application-Policy'.Value
        if (-not $ap)
        {
            #V1 template
            $ap = $sourceTemplate.pKIExtendedKeyUsage.Value
        }
    }
    else
    {
        $ap = $ApplicationPolicy | ForEach-Object { $ApplicationPolicies[$_] }
    }
    
    if ($ap)
    {
        $newCertTemplate.put('msPKI-Certificate-Application-Policy', $ap)
    }
    
    $newCertTemplate.SetInfo()

    if ($KeyUsage)
    {
        $newCertTemplate.pKIKeyUsage = $KeyUsage
    }
    else
    {
        $newCertTemplate.pKIKeyUsage = $sourceTemplate.pKIKeyUsage
    }
    
    if ($ValidityPeriod)
    {
        $newCertTemplate.pKIExpirationPeriod.Value = [Pki.Period]::ToByteArray($ValidityPeriod)
    }
    else
    {
        $newCertTemplate.pKIExpirationPeriod = $sourceTemplate.pKIExpirationPeriod
    }
    
    if ($RenewalPeriod)
    {
        $newCertTemplate.pKIOverlapPeriod.Value = [Pki.Period]::ToByteArray($RenewalPeriod)
    }
    else
    {
        $newCertTemplate.pKIOverlapPeriod = $sourceTemplate.pKIOverlapPeriod
    }    
    $newCertTemplate.SetInfo()
}

function Publish-CaTemplate
{
    [cmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TemplateName
    )
    
    $ca = Find-CertificateAuthority
    $caInfo = certutil.exe -CAInfo -Config $ca
    if ($caInfo -like '*No local Certification Authority*')
    {
        Write-Error 'No issuing CA found in the machines domain'
        return
    }
    $computerName = $ca.Split('\')[0]

    $start = Get-Date
    $done = $false
    $i = 0
    do
    {
        Write-Verbose -Message "Trying to publish '$TemplateName' on '$ca' at ($(Get-Date)), retry count $i"
        certutil.exe -Config $ca -SetCAtemplates "+$TemplateName" | Out-Null
        if (-not $LASTEXITCODE)
        {
            $done = $true
        }
        else
        {
            if ($i % 5 -eq 0)
            {
                Get-Service -Name CertSvc -ComputerName $computerName | Restart-Service
            }

            $ex = New-Object System.ComponentModel.Win32Exception($LASTEXITCODE)
            Write-Verbose -Message "Publishing the template '$TemplateName' failed: $($ex.Message)"

            Start-Sleep -Seconds 10
            $i++
        }
    }
    until ($done -or ((Get-Date) - $start).TotalMinutes -ge 10)
    Write-Verbose -Message "Certificate templete '$TemplateName' published successfully"

    if ($LASTEXITCODE)
    {
        $ex = New-Object System.ComponentModel.Win32Exception($LASTEXITCODE)
        Write-Error -Message "Publishing the template '$TemplateName' failed: $($ex.Message)" -Exception $ex
        return
    }

    Write-Verbose "Successfully published template '$TemplateName'"
}

function Request-Certificate
{
    [cmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = 'Please enter the subject beginning with CN=')]
        [ValidatePattern('CN=')]
        [string]$Subject,

        [Parameter(HelpMessage = 'Please enter the SAN domains as a comma separated list')]
        [string[]]$SAN,

        [Parameter(HelpMessage = 'Please enter the Online Certificate Authority')]
        [string]$OnlineCA,

        [Parameter(Mandatory = $true, HelpMessage = 'Please enter the Online Certificate Authority')]
        [string]$TemplateName
    )

    $infFile = [System.IO.Path]::GetTempFileName()
    $requestFile = [System.IO.Path]::GetTempFileName()
    $certFile = [System.IO.Path]::GetTempFileName()
    $rspFile = [System.IO.Path]::GetTempFileName()

    ### INI file generation
    $iniContent = @'
[Version]
Signature="$Windows NT$"

[NewRequest]
Subject="{0}"
Exportable=TRUE
KeyLength=2048
KeySpec=1
KeyUsage=0xA0
MachineKeySet=True
ProviderName="Microsoft RSA SChannel Cryptographic Provider"
ProviderType=12
SMIME=FALSE
RequestType=PKCS10
[Strings]
szOID_ENHANCED_KEY_USAGE = "2.5.29.37"
szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"
szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"
'@


    $iniContent = $iniContent -f $Subject

    Add-Content -Path $infFile -Value $iniContent
    Write-Verbose "ini file created '$infFile'"
 
    if ($SAN)
    {
        Write-Verbose 'Assing SAN section'
        Add-Content -Path $infFile -Value 'szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"'
        Add-Content -Path $infFile -Value '[Extensions]'
        Add-Content -Path $infFile -Value '2.5.29.17 = "{text}"'
 
        foreach ($s in $SAN)
        {
            Write-Verbose "`t $s"
            $temp = '_continue_ = "dns={0}&"' -f $s
            Add-Content -Path $infFile -Value $temp
        }
    }
 
    ### Certificate request generation
    Remove-Item -Path $requestFile
    Write-Verbose "Calling 'certreq.exe -new $infFile $requestFile | Out-Null'"
    certreq.exe -new $infFile $requestFile | Out-Null
 
    ### Online certificate request and import
    if (-not $OnlineCA)
    {
        Write-Verbose 'No CA given, trying to find one...'
        $OnlineCA = Find-CertificateAuthority -ErrorAction Stop
        Write-Verbose "Found CA '$OnlineCA'"
    }
    
    if (-not $OnlineCA)
    {
        Write-Error "No OnlineCA given and no one could be found in the machine's domain"
        return
    }
       
    Remove-Item -Path $certFile
    Write-Verbose "Calling 'certreq.exe -q -submit -attrib CertificateTemplate:$TemplateName -config $OnlineCA $requestFile $certFile | Out-Null'"
    certreq.exe -submit -q -attrib "CertificateTemplate:$TemplateName" -config $OnlineCA $requestFile $certFile | Out-Null

    if ($LASTEXITCODE)
    {
        $ex = New-Object System.ComponentModel.Win32Exception($LASTEXITCODE)
        Write-Error -Message "Submitting the certificate request failed: $($ex.Message)" -Exception $ex 
        return
    }
 
    Write-Verbose "Calling 'certreq.exe -accept $certFile'"
    certreq.exe -q -accept $certFile
    if ($LASTEXITCODE)
    {
        $ex = New-Object System.ComponentModel.Win32Exception($LASTEXITCODE)
        Write-Error -Message "Accepting the certificate failed: $($ex.Message)" -Exception $ex
        return
    }

    Copy-Item -Path $certFile -Destination c:\cert.cer -Force
    Copy-Item -Path $infFile -Destination c:\request.inf -Force

    $certPrint = [System.Security.Cryptography.X509Certificates.X509Certificate2][System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromCertFile('C:\cert.cer')
    $certPrint

    Remove-Item -Path $infFile, $requestFile, $certFile, $rspFile, 'C:\cert.cer' -Force
}

function Test-CATemplate
{
    [cmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TemplateName
    )
    
    $tempates = certutil.exe -Template | Select-String -Pattern TemplatePropCommonName

    $template = $tempates -like "*$TemplateName"

    return [bool]$template
}

function Add-TfsAgentUserCapability
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [Parameter(Mandatory = $true)]
        [string]
        $PoolName = '*',

        [Parameter(Mandatory = $true, ParameterSetName = 'CredId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatId')]
        [uint16]
        $AgentId,

        [Parameter(Mandatory = $true, ParameterSetName = 'CredObject')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatObject')]
        [object]
        $Agent,

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '5.1',

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'CredId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'CredObject')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'PatId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatObject')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $Capability
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $poolParam = Sync-Parameter -Command (Get-Command Get-TfsAgentPool) -Parameter $PSBoundParameters
    $pool = Get-TfsAgentPool @poolParam

    if (-not $pool)
    {
        Write-Error -Message "Pool $PoolName could not be found!"
        return
    }

    if ($AgentId)
    {
        $agtParam = Sync-Parameter -Command (Get-Command Get-TfsAgent) -Parameter $PSBoundParameters
        $Agent = Get-TfsAgent @agtParam -Filter {$_.id -eq $AgentId}
    }

    if (-not $Agent)
    {
        Write-Error -Message "Agent could not be found!"
        return
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port  -gt 0)
    {
        '{0}{1}/{2}/_apis/distributedtask/pools/{3}/agents/{4}/usercapabilities' -f $InstanceName, ":$Port", $CollectionName, $pool.id, $Agent.Id
    }
    else
    {
        '{0}/{1}/_apis/distributedtask/pools/{2}/agents/{3}/usercapabilities' -f $InstanceName, $CollectionName, $pool.id, $Agent.Id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $settableCapabilities = @{ }
    foreach ($prop in $Agent.usercapabilities.psobject.properties)
    {
        $settableCapabilities[$prop.Name] = $prop.Value
    }

    foreach ($kvp in $Capability.GetEnumerator())
    {
        $settableCapabilities[$kvp.Key] = $kvp.Value
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Put'
        ContentType     = 'application/json'
        Body            = ($settableCapabilities | ConvertTo-Json)
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    if ($result.value)
    {
        return $result.value
    }
    elseif ($result)
    {
        return $result
    }
}

function Get-TfsAccessTokenString
{
    [CmdletBinding()]
    [OutputType([String])]
    param
    (
        [Parameter(Mandatory = $True)]
        [String] $PersonalAccessToken
    )

    $tokenString = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f '',$PersonalAccessToken)))
    return ("Basic {0}" -f $tokenString)
}

function Get-TfsAgent
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [Parameter(Mandatory = $true)]
        [string]
        $PoolName,

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck,

        [scriptblock]
        $Filter = { $true }
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $poolParam = Sync-Parameter -Command (Get-Command Get-TfsAgentPool) -Parameter $PSBoundParameters
    $pool = Get-TfsAgentPool @poolParam

    if (-not $pool)
    {
        Write-Error -Message "Pool $PoolName could not be found!"
        return
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port  -gt 0)
    {
        '{0}{1}/{2}/_apis/distributedtask/pools/{3}/agents?includeCapabilities=true' -f $InstanceName, ":$Port", $CollectionName, $pool.id
    }
    else
    {
        '{0}/{1}/_apis/distributedtask/pools/{2}/agents?includeCapabilities=true' -f $InstanceName, $CollectionName, $pool.id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '&api-version={0}' -f $ApiVersion
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Get'
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    if ($result.value)
    {
        return $result.value | Where-Object -FilterScript $Filter
    }
    elseif ($result)
    {
        return $result | Where-Object -FilterScript $Filter
    }
}

function Get-TfsAgentPool
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [Parameter()]
        [string]
        $PoolName = '*',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.3-preview.1',

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port  -gt 0)
    {
        '{0}{1}/{2}/_apis/distributedtask/pools' -f $InstanceName, ":$Port", $CollectionName
    }
    else
    {
        '{0}/{1}/_apis/distributedtask/pools' -f $InstanceName, $CollectionName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Get'
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    if ($result.value)
    {
        return $result.value | Where-Object -Property Name -like $PoolName
    }
    elseif ($result)
    {
        return $result | Where-Object -Property Name -like $PoolName
    }
}

function Get-TfsAgentQueue
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '3.0-preview',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [string]
        $QueueName,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/distributedtask/queues' -f $InstanceName, ":$Port", $CollectionName, $ProjectName
    }
    else
    {
        '{0}/{1}/{2}/_apis/distributedtask/queues' -f $InstanceName, $CollectionName, $ProjectName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    if ($QueueName)
    {
        $requestUrl += '&queueName={0}' -f $QueueName
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Get'
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    return $result.value
}

function Get-TfsBuildDefinition
{
    
    [CmdletBinding(DefaultParameterSetName = 'Cred')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [string]
        $QueueName,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }
    
    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/build/definitions' -f $InstanceName, ":$Port", $CollectionName, $ProjectName
    }
    else
    {
        '{0}/{1}/{2}/_apis/build/definitions' -f $InstanceName, $CollectionName, $ProjectName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Get'
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        if ($_.ErrorDetails.Message)
        {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorDetails.typeKey -eq 'ProjectDoesNotExistWithNameException')
            {
                return $null
            }
        }
        
        Write-Error -ErrorRecord $_
    }
    
    return $result.value
}

function Get-TfsBuildDefinitionTemplate
{
    
    [CmdletBinding(DefaultParameterSetName = 'Cred')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port  -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/build/definitions/templates' -f $InstanceName, ":$Port", $CollectionName, $ProjectName
    }
    else
    {
        '{0}/{1}/{2}/_apis/build/definitions/templates' -f $InstanceName, $CollectionName, $ProjectName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Get'
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    if ($result.value)
    {
        return $result.value
    }
    elseif ($result)
    {
        return $result
    }
}

function Get-TfsBuildStep
{
    [CmdletBinding(DefaultParameterSetName = 'Tfs')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [Parameter(Mandatory = $true, ParameterSetName = 'TfsName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsName')]
        [SupportsWildcards()]
        [string]
        $FriendlyName,

        [Parameter(Mandatory = $true, ParameterSetName = 'TfsHashtable')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsHashtable')]
        [hashtable]
        $FilterHashtable,

        [Parameter(Mandatory = $true, ParameterSetName = 'TfsScript')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsScript')]
        [scriptblock]
        $FilterScript,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Tfs')]
        [Parameter(Mandatory = $true, ParameterSetName = 'TfsName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'TfsHashtable')]
        [Parameter(Mandatory = $true, ParameterSetName = 'TfsScript')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Vsts')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsHashtable')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsScript')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/_apis/distributedtask/tasks' -f $InstanceName, ":$Port", $CollectionName
    }
    else
    {
        '{0}/_apis/distributedtask/tasks' -f $InstanceName, $CollectionName
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Get'
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters -UseBasicParsing
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    $steps = if ($result.value)
    {
        $result.value
    }
    elseif ($result)
    {
        ($result | ConvertFrom-JsonNewtonsoft).Value
    }

    if ($FriendlyName)
    {
        $steps = if ($FriendlyName -match '(\?|\*)')
        {
            $steps | Where-Object -Property friendlyName -like $FriendlyName
        }
        else
        {
            $steps | Where-Object -Property friendlyName -eq $FriendlyName
        }
    }

    if ($FilterHashtable)
    {
        $steps = foreach ( $kvp in $FilterHashtable.GetEnumerator())
        {
            if ($kvp.Value -match '(\?|\*)')
            {
                $steps | Where-Object -Property $kvp.Key -like $kvp.Value
            }
            else
            {
                $steps | Where-Object -Property $kvp.Key -eq $kvp.Value    
            }            
        }
    }

    if ($FilterScript)
    {
        $steps = $steps | Where-Object -FilterScript $FilterScript
    }

    '@('
    foreach ($step in $steps)
    {
        "
        @{
            enabled = $true
            continueOnError = $false
            alwaysRun = $false
            displayName = 'YOUR OWN DISPLAY NAME HERE' # e.g. $($step.instanceNameFormat) or $($step.friendlyName)
            task = @{
                id = '$($step.id)'
                versionSpec = '*'
            }
            inputs = @{"

        foreach ($input in $step.inputs)
        {
            $required = if ($input.required) {$true}else {$false}
            "`t`t`t`t{0} = 'VALUE' # Type: {1}, Default: {2}, Mandatory: {3}" -f $input.name, $input.type, $input.defaultValue, $required
        }
        '
            }
        }
        '

    }
    ')'
}

function Get-TfsFeed
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,
 
        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',
 
        [ValidateRange(1, 65535)]
        [uint32]
        $Port,
 
        [string]
        $ApiVersion = '1.0',
 
        [string]
        $FeedName,
 
        [switch]
        $UseSsl,
 
        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
         
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }
 
    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ($Port -gt 0)
    {
        '{0}{1}/{2}/_apis/packaging/feeds' -f $InstanceName, ":$Port", $CollectionName
    }
    else
    {
        '{0}/{1}/_apis/packaging/feeds' -f $InstanceName, $CollectionName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }
 
    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Get'
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }
 
    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }
 
    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        if ($_.ErrorDetails.Message)
        {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorDetails.typeKey -eq 'ProjectDoesNotExistWithNameException')
            {
                return $null
            }
        }
        Write-Error -ErrorRecord $_
    }
     
    $data = if ($result.value)
    {
        $result.value
    }
    elseif ($result)
    {
        $result
    }

    if ($FeedName)
    {
        $data | Where-Object name -eq $FeedName
    }
    else
    {
        $data
    }
}

function Get-TfsFeedPermission
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,
 
        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',
 
        [ValidateRange(1, 65535)]
        [uint32]
        $Port,
 
        [string]
        $ApiVersion = '1.0',
 
        [string]
        $FeedName,
 
        [switch]
        $UseSsl,
 
        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
         
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $feed = Get-TfsFeed @PSBoundParameters
    if (-not $feed)
    {
        Write-Warning "The feed '$FeedName' does not exist."
        return
    }
 
    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ($Port -gt 0)
    {
        '{0}{1}/{2}/_apis/packaging/feeds/{3}/permissions' -f $InstanceName, ":$Port", $CollectionName, $feed.id
    }
    else
    {
        '{0}/{1}/_apis/packaging/feeds/{2}/permissions' -f $InstanceName, $CollectionName, $feed.id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }
 
    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Get'
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }
 
    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }
 
    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        if ($_.ErrorDetails.Message)
        {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorDetails.typeKey -eq 'ProjectDoesNotExistWithNameException')
            {
                return $null
            }
        }
        Write-Error -ErrorRecord $_
    }
     
    if ($result.value)
    {
        $result.value
    }
    elseif ($result)
    {
        $result
    }
}

function Get-TfsGitRepository
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter(Mandatory = $true)]
        [string]
        $CollectionName,

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '1.0',

        [string]
        $ProjectName,

        [switch]
        $UseSsl,

        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
        
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/git/repositories' -f $InstanceName, ":$Port", $CollectionName, $ProjectName, $ApiVersion
    }
    else
    {
        '{0}/{1}/{2}/_apis/git/repositories' -f $InstanceName, $CollectionName, $ProjectName, $ApiVersion
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Get'
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        if ($_.ErrorDetails.Message)
        {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorDetails.typeKey -eq 'ProjectDoesNotExistWithNameException')
            {
                return $null
            }
        }
        
        Write-Error -ErrorRecord $_
    }
    
    return $result.value
}

function Get-TfsProcessTemplate
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '1.0',

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ($Port -gt 0)
    {
        '{0}{1}/{2}/_apis/process/processes' -f $InstanceName, ":$Port", $CollectionName
    }
    else
    {
        '{0}/{1}/_apis/process/processes' -f $InstanceName, $CollectionName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }
    
    $requestParameters = @{
        Uri    = $requestUrl
        Method = 'Get'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ( $Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }

    if ($result.value)
    {
        return $result.value
    }
    elseif ($result)
    {
        return $result
    }
}

function Get-TfsProject
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,
 
        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',
 
        [ValidateRange(1, 65535)]
        [uint32]
        $Port,
 
        [string]
        $ApiVersion = '1.0',
 
        [string]
        $ProjectName,
 
        [switch]
        $UseSsl,
 
        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
         
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }
 
    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/_apis/projects{3}' -f $InstanceName, ":$Port", $CollectionName, "/$ProjectName"
    }
    else
    {
        '{0}/{1}/_apis/projects{2}' -f $InstanceName, $CollectionName, "/$ProjectName"
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }
 
    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Get'
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }
 
    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }
 
    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        if ($_.ErrorDetails.Message)
        {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorDetails.typeKey -eq 'ProjectDoesNotExistWithNameException')
            {
                return $null
            }
        }
        Write-Error -ErrorRecord $_
    }
     
    if ($result.value)
    {
        return $result.value
    }
    elseif ($result)
    {
        return $result
    }
}

function Get-TfsReleaseDefinition
{
    [CmdletBinding(DefaultParameterSetName = 'Cred')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion,

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/release/definitions' -f $InstanceName, ":$Port", $CollectionName, $ProjectName
    }
    else
    {
        '{0}/{1}/{2}/_apis/release/definitions' -f $InstanceName, $CollectionName, $ProjectName
    }

    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Get'
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        if ($_.ErrorDetails.Message)
        {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorDetails.typeKey -eq 'ProjectDoesNotExistWithNameException')
            {
                return $null
            }
        }
        
        Write-Error -ErrorRecord $_
    }
    
    return $result.value
}

function Get-TfsReleaseStep
{
    [CmdletBinding(DefaultParameterSetName = 'Tfs')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [Parameter(Mandatory = $true, ParameterSetName = 'TfsName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsName')]
        [SupportsWildcards()]
        [string]
        $FriendlyName,

        [Parameter(Mandatory = $true, ParameterSetName = 'TfsHashtable')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsHashtable')]
        [hashtable]
        $FilterHashtable,

        [Parameter(Mandatory = $true, ParameterSetName = 'TfsScript')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsScript')]
        [scriptblock]
        $FilterScript,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Tfs')]
        [Parameter(Mandatory = $true, ParameterSetName = 'TfsName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'TfsHashtable')]
        [Parameter(Mandatory = $true, ParameterSetName = 'TfsScript')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Vsts')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsHashtable')]
        [Parameter(Mandatory = $true, ParameterSetName = 'VstsScript')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/_apis/distributedtask/tasks' -f $InstanceName, ":$Port", $CollectionName
    }
    else
    {
        '{0}/{1}/_apis/distributedtask/tasks' -f $InstanceName, $CollectionName
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Get'
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    $steps = if ($result.value)
    {
        $result.value | Where-Object -Property visibility -Contains 'Release'
    }
    elseif ($result)
    {
        $result | Where-Object -Property visibility -Contains 'Release'
    }

    if ($FriendlyName)
    {
        $steps = if ($FriendlyName -match '(\?|\*)')
        {
            $steps | Where-Object -Property friendlyName -like $FriendlyName
        }
        else
        {
            $steps | Where-Object -Property friendlyName -eq $FriendlyName
        }
    }

    if ($FilterHashtable)
    {
        $steps = foreach ( $kvp in $FilterHashtable.GetEnumerator())
        {
            if ($kvp.Value -match '(\?|\*)')
            {
                $steps | Where-Object -Property $kvp.Key -like $kvp.Value
            }
            else
            {
                $steps | Where-Object -Property $kvp.Key -eq $kvp.Value    
            }            
        }
    }

    if ($FilterScript)
    {
        $steps = $steps | Where-Object -FilterScript $FilterScript
    }

    '@('
    foreach ($step in $steps)
    {
        "
        @{
            enabled = `$true
            continueOnError = `$false
            alwaysRun = `$false
            timeoutInMinutes = 0
            definitionType = 'task'
            version = '*'
            name = 'YOUR OWN DISPLAY NAME HERE' # e.g. $($step.instanceNameFormat) or $($step.friendlyName)
            taskid = '$($step.id)'
            inputs = @{"

        foreach ($input in $step.inputs)
        {
            $required = if ($input.required) {$true}else {$false}
            "`t`t`t`t{0} = 'VALUE' # Type: {1}, Default: {2}, Mandatory: {3}" -f $input.name, $input.type, $input.defaultValue, $required
        }
        '
            }
        }
        '

    }
    ')'
}

function New-TfsAgentQueue
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '3.0-preview.1',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [switch]
        $UseSsl,

        [string]
        $QueueName,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $existingQueue = Get-TfsAgentQueue @PSBoundParameters
    if ($existingQueue) { return $existingQueue }
    
    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/distributedTask/queues' -f $InstanceName, ":$Port", $CollectionName, $ProjectName
    }
    else
    {
        '{0}/{1}/{2}/_apis/distributedTask/queues' -f $InstanceName, $CollectionName, $ProjectName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $poolParameter = Sync-Parameter -Command (Get-Command Get-TfsAgentPool) -Parameters $PSBoundParameters
    $pools = Get-TfsAgentPool @poolParameter

    $useablePool = $pools | Where-Object -Property size -gt 0 | Select-Object -First 1
    if (-not $useablePool) { $useablePool = $pools | Select-Object -First 1}
    if (-not $useablePool) { Write-Error -Message 'No agent pools available to form queue'; return}

    $payload = [ordered]@{
        "name" = $QueueName
        "pool" = @{
            "id" = $useablePool.id
        }
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Post'
        ContentType = 'application/json'
        Body        = ($payload | ConvertTo-Json)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
}

function New-TfsBuildDefinition
{
    
    [CmdletBinding(DefaultParameterSetName = 'Cred')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionName,

        [string]
        $QueueName,

        [hashtable[]]
        $BuildTasks, # Not very nice and needs to be replaced as soon as I find out how to retrieve all build step guids

        [hashtable[]]
        $Phases,

        [string[]]
        $CiTriggerRefs,

        [hashtable]
        $Variables,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,
        
        [switch]$Clean,
        
        [int]$CleanOptions = 0,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ($Port -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/build/definitions' -f $InstanceName, ":$Port", $CollectionName, $ProjectName
    }
    else
    {
        '{0}/{1}/{2}/_apis/build/definitions' -f $InstanceName, $CollectionName, $ProjectName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $exBuildParam = Sync-Parameter -Command (Get-Command Get-TfsBuildDefinition) -Parameters $PSBoundParameters
    $exBuildParam.Remove('Version')
    $existingBuild = Get-TfsBuildDefinition @exBuildParam
    if ($existingBuild | Where-Object name -eq $DefinitionName)
    { 
        Write-Warning -Message ('Build definition {0} in {1} already exists.' -f $DefinitionName, $ProjectName)
        return 
    }

    $qparameters = Sync-Parameter -Command (Get-Command Get-TfsAgentQueue) -Parameters $PSBoundParameters
    $qparameters.Remove('ApiVersion') # preview-API is called
    $qparameters.ErrorAction = 'SilentlyContinue'
    $queue = Get-TfsAgentQueue @qparameters | Select-Object -First 1

    if (-not $queue)
    {
        Write-Verbose -Message ('No existing queue found for project {0}. Creating new queue.' -f $ProjectName)
        $parameters = Sync-Parameter -Command (Get-Command New-TfsAgentQueue) -Parameters $PSBoundParameters
        $parameters.Remove('ApiVersion') # preview-API is called
        $parameters.ErrorAction = 'Stop'
        $qparameters.ErrorAction = 'Stop'
        try
        {
            New-TfsAgentQueue @parameters
            $queue = Get-TfsAgentQueue @qparameters | Select-Object -First 1
        }
        catch
        {
            Write-Error -ErrorRecord $_
        }
    }

    $projectParameters = Sync-Parameter -Command (Get-Command Get-TfsProject) -Parameters $PSBoundParameters
    $projectParameters.ErrorAction = 'Stop'
    
    try
    {
        $project = Get-TfsProject @projectParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }

    $repoParameters = Sync-Parameter -Command (Get-Command Get-TfsGitRepository) -Parameters $PSBoundParameters
    $repoParameters.ErrorAction = 'Stop'

    try
    {
        $repo = Get-TfsGitRepository @repoParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }

    $buildDefinition = if ($ApiVersion -gt '4.0')
    {
        @{
            name       = $DefinitionName
            type       = "build"
            quality    = "definition"
            queue      = @{
                id = $queue.id
            }
            process      = @{ }
            repository = @{
                id            = $repo.id
                type          = "TfsGit"
                name          = $repo.name
                defaultBranch = "refs/heads/master"
                url           = $repo.remoteUrl                
                clean         = $Clean.ToBool()
                properties = @{
                    cleanOptions = "$CleanOptions"
                }
            }
            options    = @(
                @{
                    enabled    = $true
                    definition = @{
                        id = (New-Guid).Guid
                    }
                    inputs     = @{
                        parallel  = $false
                        multipliers = '["config","platform"]'
                    }
                }
            )
            variables  = @{
                forceClean = @{
                    value         = $false
                    allowOverride = $true
                }
                config     = @{
                    value         = "debug, release"
                    allowOverride = $true
                }
                platform   = @{
                    value         = "any cpu"
                    allowOverride = $true
                }
            }
        }
    }
    else
    {
        @{
            name       = $DefinitionName
            type       = "build"
            quality    = "definition"
            queue      = @{
                id = $queue.id
            }
            build      = $BuildTasks
            repository = @{
                id            = $repo.id
                type          = "TfsGit"
                name          = $repo.name
                defaultBranch = "refs/heads/master"
                url           = $repo.remoteUrl
                clean         = $false
            }
            options    = @(
                @{
                    enabled    = $true
                    definition = @{
                        id = (New-Guid).Guid
                    }
                    inputs     = @{
                        parallel  = $false
                        multipliers = '["config","platform"]'
                    }
                }
            )
            variables  = @{
                forceClean = @{
                    value         = $false
                    allowOverride = $true
                }
                config     = @{
                    value         = "debug, release"
                    allowOverride = $true
                }
                platform   = @{
                    value         = "any cpu"
                    allowOverride = $true
                }
            }
        }
    }

    if (-not $Phases -and $ApiVersion -ge '4.0')
    {
        $Phases =  @(
            @{
                name = 'Phase 1'
                condition = 'succeeded()'
            }
        )
        $buildDefinition.process.Add('phases', $Phases)

        if ($BuildTasks)
        {
            $buildDefinition.process.phases[0].Add('steps', $BuildTasks)
        }
    }

    $refs = @()
    if ($CiTriggerRefs)
    {
        foreach ($ref in $CiTriggerRefs)
        {
            $refs += "+$ref"
        }
        $trigger = @{
            branchFilters = $refs
            maxConcurrentBuildsPerBranch = 1
            pollingInterval = 0
            triggerType = 2
        }

        $buildDefinition.triggers = @($trigger)
    }

    if ($Variables)
    {
        foreach ($variable in $Variables.GetEnumerator())
        {
            $variableContent = @{
                value = $variable.Value
                allowOverrise = $true
            }
            $buildDefinition.variables.Add($variable.Key, $variableContent)
        }
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Post'
        ContentType = 'application/json'
        Body        = ($buildDefinition | ConvertTo-Json -Depth 42)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
        Write-Verbose -Message ('New build definition {0} created for project {1}' -f $DefinitionName, $ProjectName)
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
}

function New-TfsFeed
{
    
    [CmdletBinding(DefaultParameterSetName = 'NameCred')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $FeedName,

        [string]
        $Description,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'GuidCred')]
        [Parameter(Mandatory = $true, ParameterSetName = 'NameCred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'NamePat')]
        [Parameter(Mandatory = $true, ParameterSetName = 'GuidPat')]
        [string]
        $PersonalAccessToken,

        [timespan]
        $Timeout = (New-TimeSpan -Seconds 30),

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/_apis/packaging/feeds' -f $InstanceName, ":$Port", $CollectionName
    }
    else
    {
        '{0}/{1}/_apis/packaging/feeds' -f $InstanceName, $CollectionName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $feedParameters = Sync-Parameter -Command (Get-Command -Name Get-TfsFeed) -Parameters $PSBoundParameters
    $feedParameters.ErrorAction = 'SilentlyContinue'
    if (Get-TfsFeed @feedParameters)
    {
        Write-Error -Message "The Feed '$FeedName' already exists"
        return
    }

    $payload = @{
        name         = $FeedName
        description  = $Description
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Post'
        ContentType = 'application/json'
        Body        = ($payload | ConvertTo-Json)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
}

function New-TfsProject
{
    
    [CmdletBinding(DefaultParameterSetName = 'NameCred')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [string]
        $ProjectDescription,

        [ValidateSet('Git', 'Tfvc')]
        $SourceControlType = 'Git',

        [Parameter(Mandatory = $true, ParameterSetName = 'GuidPat')]
        [Parameter(Mandatory = $true, ParameterSetName = 'GuidCred')]
        [guid]
        $TemplateGuid,

        [Parameter(Mandatory = $true, ParameterSetName = 'NamePat')]
        [Parameter(Mandatory = $true, ParameterSetName = 'NameCred')]
        [string]
        $TemplateName,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'GuidCred')]
        [Parameter(Mandatory = $true, ParameterSetName = 'NameCred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'NamePat')]
        [Parameter(Mandatory = $true, ParameterSetName = 'GuidPat')]
        [string]
        $PersonalAccessToken,

        [timespan]
        $Timeout = (New-TimeSpan -Seconds 30),

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/_apis/projects' -f $InstanceName, ":$Port", $CollectionName
    }
    else
    {
        '{0}/{1}/_apis/projects' -f $InstanceName, $CollectionName
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    if ($PSCmdlet.ParameterSetName -like 'Name*')
    {
        $parameters = Sync-Parameter -Command (Get-Command Get-TfsProcessTemplate) -Parameters $PSBoundParameters
        $TemplateGuid = (Get-TfsProcessTemplate @parameters | Where-Object -Property name -eq $TemplateName).id
        if (-not $TemplateGuid) {Write-Error -Message "Could not locate $TemplateName. Try Get-TfsProcessTemplate to see all available templates"; return}
    }

    $projectParameters = Sync-Parameter -Command (Get-Command Get-TfsProject) -Parameters $PSBoundParameters
    $projectParameters.ErrorAction = 'SilentlyContinue'
    if (Get-TfsProject @projectParameters)
    {
        Write-Verbose -Message ('Project {0} already exists' -f $ProjectName)
        return
    }

    $payload = @{
        name         = $ProjectName
        description  = $ProjectDescription
        capabilities = @{
            versioncontrol  = @{
                sourceControlType = $SourceControlType                
            }
            processTemplate = @{
                templateTypeId = $TemplateGuid.Guid
            }
        }
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Post'
        ContentType = 'application/json'
        Body        = ($payload | ConvertTo-Json)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }

    $start = Get-Date
    $projectStatus = Get-TfsProject @projectParameters

    while ($projectStatus.State -ne 'wellFormed' -and ((Get-Date) - $start -lt $Timeout))
    {
        Write-Verbose -Message ('Waiting {0} for {1} to enter status wellFormed' -f $Timeout, $ProjectName)
        Start-Sleep -Seconds 1
        $projectStatus = Get-TfsProject @projectParameters
    }

    if (-not $projectStatus.State -eq 'wellFormed')
    {
        Write-Error -Message ('Unable to create new project in {0}' -f $Timeout) -TargetObject $ProjectName
        return
    }

    return $projectStatus
}

function New-TfsReleaseDefinition
{
    [CmdletBinding(DefaultParameterSetName = 'Cred')]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '3.0-preview.3',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectName,

        [Parameter(Mandatory = $true)]
        [string]
        $ReleaseName,

        [hashtable[]]
        $ReleaseTasks,

        [hashtable[]]
        $Environments,

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'Cred')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Pat')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/{3}/_apis/release/definitions' -f $InstanceName, ":$Port", $CollectionName, $ProjectName
    }
    else
    {
        '{0}/{1}/{2}/_apis/release/definitions' -f $InstanceName, $CollectionName, $ProjectName
    }

    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $exReleaseParam = Sync-Parameter -Command (Get-Command Get-TfsReleaseDefinition) -Parameters $PSBoundParameters
    $exReleaseParam.Remove('ApiVersion')
    $existingRelease = Get-TfsReleaseDefinition @exReleaseParam
    if ($existingRelease | Where-Object name -eq $ReleaseName)
    {
        Write-Verbose -Message ('Release definition {0} in {1} already exists.' -f $ReleaseName, $ProjectName);
        return 
    }

    $qparameters = Sync-Parameter -Command (Get-Command Get-TfsAgentQueue) -Parameters $PSBoundParameters
    $qparameters.Remove('ApiVersion') # preview-API is called
    $qparameters.ErrorAction = 'SilentlyContinue'
    $queue = Get-TfsAgentQueue @qparameters | Select-Object -First 1

    if (-not $queue)
    {
        $parameters = Sync-Parameter -Command (Get-Command New-TfsAgentQueue) -Parameters $PSBoundParameters
        $parameters.Remove('ApiVersion') # preview-API is called
        $parameters.ErrorAction = 'Stop'
        $qparameters.ErrorAction = 'Stop'
        try
        {
            New-TfsAgentQueue @parameters
            $queue = Get-TfsAgentQueue @qparameters | Select-Object -First 1
        }
        catch
        {
            Write-Error -ErrorRecord $_
        }
    }

    $projectParameters = Sync-Parameter -Command (Get-Command Get-TfsProject) -Parameters $PSBoundParameters
    $projectParameters.ErrorAction = 'Stop'
    
    try
    {
        $project = Get-TfsProject @projectParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }

    $buildParameters = Sync-Parameter -Command (Get-Command Get-TfsBuildDefinition) -Parameters $PSBoundParameters
    $buildParameters.ErrorAction = 'Stop'

    try
    {
        $build = Get-TfsBuildDefinition @buildParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }

    if (-not $Environments)
    {
        $Environments = @(
            @{
                "id"                      = 1 
                "name"                    = "Environment" 
                "rank"                    = 1 
                "deployStep"              = @{
                    "id"    = 0 
                    "tasks" = @()
                } 
                "deployPhases"            = @(
                    @{
                        "name"            = "Run on agent" 
                        "phaseType"       = 1 
                        "rank"            = 1 
                        "workflowTasks"   = $ReleaseTasks
                        "deploymentInput" = @{
                            "demands"               = @() 
                            "queueId"               = $queue.id 
                            "enableAccessToken"     = $false 
                            "skipArtifactsDownload" = $false 
                            "timeoutInMinutes"      = 0
                        } 
                        "controlOptions"  = @{
                            "alwaysRun"       = $false 
                            "continueOnError" = $false 
                            "enabled"         = $true
                        }
                    }
                ) 
                "queueId"                 = $queue.id 
                "demands"                 = @() 
                "conditions"              = @(
                    @{
                        "name"          = "ReleaseStarted" 
                        "conditionType" = 1 
                        "value"         = ""
                    }
                ) 
                "environmentOptions"      = @{
                    "emailNotificationType" = "OnlyOnFailure" 
                    "emailRecipients"       = "release.environment.owner;release.creator" 
                    "skipArtifactsDownload" = $false 
                    "timeoutInMinutes"      = 0 
                    "enableAccessToken"     = $false
                } 
                "executionPolicy"         = @{
                    "concurrencyCount" = 0 
                    "queueDepthCount"  = 0
                } 
                "releaseId"               = $null 
                "definitionEnvironmentId" = $null 
                "preDeployApprovals"      = @{
                    "approvals"       = @(
                        @{
                            "rank"             = 1 
                            "isAutomated"      = $true
                            "isNotificationOn" = $false 
                            "id"               = 0
                        }
                    ) 
                    "approvalOptions" = $null
                } 
                "postDeployApprovals"     = @{
                    "approvals"       = @(
                        @{
                            "rank"             = 1 
                            "isAutomated"      = $true 
                            "isNotificationOn" = $false 
                            "id"               = 0
                        }
                    ) 
                    "approvalOptions" = $null
                } 
                "schedules"               = @() 
                "retentionPolicy"         = @{
                    "daysToKeep"     = 30 
                    "releasesToKeep" = 3 
                    "retainBuild"    = $true
                }
            }
        )
    }

    $payload = @{
        "id"                = 0 
        "name"              = $ReleaseName
        "comment"           = $null 
        "createdOn"         = (Get-Date).ToString('yyyy-MM-ddThh:mm:ss.fffZ')
        "createdBy"         = $null 
        "modifiedBy"        = $null 
        "modifiedOn"        = $null 
        "environments"      = $Environments
        "artifacts"         = @(
            @{
                "id"                  = 0 
                "definitionReference" = @{
                    "project"    = @{
                        "id"   = $project.id
                        "name" = $project.name
                    } 
                    "definition" = @{
                        "id"   = $build.id
                        "name" = $build.name
                    }
                } 
                "alias"               = $build.name
                "type"                = "Build" 
                "artifactTypeName"    = "Build" 
                "sourceId"            = "" 
                "isPrimary"           = $true
            }
        )  
        "triggers"          = @(
            @{
                "triggerType"   = 1 
                "artifactAlias" = $build.name
            }
        ) 
        "releaseNameFormat" = 'Release-$(rev:r)'
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Post'
        ContentType = 'application/json'
        Body        = ($payload | ConvertTo-Json -Depth 42)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
        Write-Verbose -Message ('New release definition {0} created for project {1}' -f $ReleaseName, $ProjectName)
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
}

function Remove-TfsAgentUserCapability
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [Parameter(Mandatory = $true)]
        [string]
        $PoolName = '*',

        [Parameter(Mandatory = $true, ParameterSetName = 'CredId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatId')]
        [uint16]
        $AgentId,

        [Parameter(Mandatory = $true, ParameterSetName = 'CredObject')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatObject')]
        [object]
        $Agent,

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '5.1',

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'CredId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'CredObject')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'PatId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatObject')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck,

        [Parameter(Mandatory = $true)]
        [string[]]
        $Capability
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $poolParam = Sync-Parameter -Command (Get-Command Get-TfsAgentPool) -Parameter $PSBoundParameters
    $pool = Get-TfsAgentPool @poolParam

    if (-not $pool)
    {
        Write-Error -Message "Pool $PoolName could not be found!"
        return
    }

    if ($AgentId)
    {
        $agtParam = Sync-Parameter -Command (Get-Command Get-TfsAgent) -Parameter $PSBoundParameters
        $Agent = Get-TfsAgent @agtParam -Filter {$_.id -eq $AgentId}
    }

    if (-not $Agent)
    {
        Write-Error -Message "Agent could not be found!"
        return
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port  -gt 0)
    {
        '{0}{1}/{2}/_apis/distributedtask/pools/{3}/agents/{4}/usercapabilities' -f $InstanceName, ":$Port", $CollectionName, $pool.id, $Agent.Id
    }
    else
    {
        '{0}/{1}/_apis/distributedtask/pools/{2}/agents/{3}/usercapabilities' -f $InstanceName, $CollectionName, $pool.id, $Agent.Id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $settableCapabilities = @{ }
    foreach ($prop in $Agent.usercapabilities.psobject.properties)
    {
        if ($prop.Name -in $Capability) { continue }
        $settableCapabilities[$prop.Name] = $prop.Value
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Put'
        ContentType     = 'application/json'
        Body            = ($settableCapabilities | ConvertTo-Json)
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    if ($result.value)
    {
        return $result.value
    }
    elseif ($result)
    {
        return $result
    }
}

function Remove-TfsFeed
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,
 
        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',
 
        [ValidateRange(1, 65535)]
        [uint32]
        $Port,
 
        [string]
        $ApiVersion = '1.0',
 
        [Parameter(Mandatory = $true)]
        [string]
        $FeedName,
 
        [switch]
        $UseSsl,
 
        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
         
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $feed = Get-TfsFeed @PSBoundParameters
    if (-not $feed)
    {
        Write-Warning "The feed '$FeedName' does not exist."
        return
    }
 
    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ($Port -gt 0)
    {
        '{0}{1}/{2}/_apis/packaging/feeds/{3}' -f $InstanceName, ":$Port", $CollectionName, $feed.id
    }
    else
    {
        '{0}/{1}/_apis/packaging/feeds/{2}' -f $InstanceName, $CollectionName, $feed.id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }
 
    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Delete'
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }
 
    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }
 
    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        if ($_.ErrorDetails.Message)
        {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorDetails.typeKey -eq 'ProjectDoesNotExistWithNameException')
            {
                return $null
            }
        }
        Write-Error -ErrorRecord $_
    }
     
    $data = if ($result.value)
    {
        $result.value
    }
    elseif ($result)
    {
        $result
    }

    if ($FeedName)
    {
        $data | Where-Object name -eq $FeedName
    }
    else
    {
        $data
    }
}

function Set-TfsAgentUserCapability
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [Parameter(Mandatory = $true)]
        [string]
        $PoolName = '*',

        [Parameter(Mandatory = $true, ParameterSetName = 'CredId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatId')]
        [uint16]
        $AgentId,

        [Parameter(Mandatory = $true, ParameterSetName = 'CredObject')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatObject')]
        [object]
        $Agent,

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '5.1',

        [switch]
        $UseSsl,

        [Parameter(Mandatory = $true, ParameterSetName = 'CredId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'CredObject')]
        [pscredential]
        $Credential,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'PatId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PatObject')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $Capability
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $poolParam = Sync-Parameter -Command (Get-Command Get-TfsAgentPool) -Parameter $PSBoundParameters
    $pool = Get-TfsAgentPool @poolParam

    if (-not $pool)
    {
        Write-Error -Message "Pool $PoolName could not be found!"
        return
    }

    if ($AgentId)
    {
        $agtParam = Sync-Parameter -Command (Get-Command Get-TfsAgent) -Parameter $PSBoundParameters
        $Agent = Get-TfsAgent @agtParam -Filter {$_.id -eq $AgentId}
    }

    if (-not $Agent)
    {
        Write-Error -Message "Agent could not be found!"
        return
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port  -gt 0)
    {
        '{0}{1}/{2}/_apis/distributedtask/pools/{3}/agents/{4}/usercapabilities' -f $InstanceName, ":$Port", $CollectionName, $pool.id, $Agent.Id
    }
    else
    {
        '{0}/{1}/_apis/distributedtask/pools/{2}/agents/{3}/usercapabilities' -f $InstanceName, $CollectionName, $pool.id, $Agent.Id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $requestParameters = @{
        Uri             = $requestUrl
        Method          = 'Put'
        ContentType     = 'application/json'
        Body            = ($Capability | ConvertTo-Json)
        ErrorAction     = 'Stop'
        UseBasicParsing = $true
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
    
    if ($result.value)
    {
        return $result.value
    }
    elseif ($result)
    {
        return $result
    }
}

function Set-TfsFeedPermission
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $FeedName,

        [Parameter(Mandatory = $true)]
        [object[]]
        $Permissions,

        [switch]
        $UseSsl,

        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
        
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }
        
    $feedParameters = Sync-Parameter -Command (Get-Command -Name Get-TfsFeed) -Parameters $PSBoundParameters
    $feedParameters.ErrorAction = 'SilentlyContinue'
    $feed = Get-TfsFeed @feedParameters
    if (-not $feed)
    {
        Write-Error -Message "The Feed '$FeedName' does not exist"
        return
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/_apis/packaging/Feeds/{3}/permissions' -f $InstanceName, ":$Port", $CollectionName, $feed.id
    }
    else
    {
        '{0}/{1}/_apis/packaging/Feeds/{2}/permissions' -f $InstanceName, $CollectionName, $feed.id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $payload = @{
        body        = $Permissions
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Patch'
        ContentType = 'application/json'
        Body        = ($Permissions | ConvertTo-Json)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
}

function Set-TfsFeedPermissions
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $FeedName,

        [Parameter(Mandatory = $true)]
        [object[]]
        $Permissions,

        [switch]
        $UseSsl,

        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
        
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }
        
    $feedParameters = Sync-Parameter -Command (Get-Command -Name Get-TfsFeed) -Parameters $PSBoundParameters
    $feedParameters.ErrorAction = 'SilentlyContinue'
    $feed = Get-TfsFeed @feedParameters
    if (-not $feed)
    {
        Write-Error -Message "The Feed '$FeedName' does not exist"
        return
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/_apis/packaging/Feeds/{3}/permissions' -f $InstanceName, ":$Port", $CollectionName, $feed.id
    }
    else
    {
        '{0}/{1}/_apis/packaging/Feeds/{2}/permissions' -f $InstanceName, $CollectionName, $feed.id
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $payload = @{
        body        = $Permissions
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Patch'
        ContentType = 'application/json'
        Body        = ($Permissions | ConvertTo-Json)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
}

function Set-TfsProject
{
    
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InstanceName,

        [Parameter()]
        [string]
        $CollectionName = 'DefaultCollection',

        [ValidateRange(1, 65535)]
        [uint32]
        $Port,

        [string]
        $ApiVersion = '2.0',

        [Parameter(Mandatory = $true)]
        [string]
        $ProjectGuid,

        [string]
        $NewName,

        [string]
        $NewDescription,

        [switch]
        $UseSsl,

        [Parameter(ParameterSetName = 'Tfs')]
        [pscredential]
        $Credential,
        
        [Parameter(ParameterSetName = 'Vsts')]
        [string]
        $PersonalAccessToken,

        [switch]
        $SkipCertificateCheck
    )

    if ($SkipCertificateCheck.IsPresent)
    {
        $null = [ServerCertificateValidationCallback]::Ignore()
    }

    $requestUrl = if ($UseSsl) {'https://' } else {'http://'}
    $requestUrl += if ( $Port -gt 0)
    {
        '{0}{1}/{2}/_apis/projects/{3}' -f $InstanceName, ":$Port", $CollectionName, $ProjectGuid
    }
    else
    {
        '{0}/{1}/_apis/projects/{2}' -f $InstanceName, $CollectionName, $ProjectGuid
    }
    
    if ($ApiVersion)
    {
        $requestUrl += '?api-version={0}' -f $ApiVersion
    }

    $payload = @{
        name        = $NewName
        description = $NewDescription
    }

    $requestParameters = @{
        Uri         = $requestUrl
        Method      = 'Patch'
        ContentType = 'application/json'
        Body        = ($payload | ConvertTo-Json)
        ErrorAction = 'Stop'
    }

    if ($PSEdition -eq 'Core' -and (Get-Command -Name Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck'))
    {
        $requestParameters.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
    }

    if ($Credential)
    {
        $requestParameters.Credential = $Credential
    }
    else
    {
        $requestParameters.Headers = @{ Authorization = Get-TfsAccessTokenString -PersonalAccessToken $PersonalAccessToken }
    }

    try
    {
        $result = Invoke-RestMethod @requestParameters
        Write-Verbose ('Project {0} renamed to {1}' -f $ProjectGuid, $NewName)
    }
    catch
    {
        Write-Error -ErrorRecord $_
    }
}

Export-ModuleMember -Function Add-AccountPrivilege,Add-FunctionToPSSession,Add-StringIncrement,Add-VariableToPSSession,Get-ConsoleText,Get-DotNetFrameworkVersion,Get-FullMesh,Get-ModuleDependency,Get-RunspacePool,Get-StringSection,Get-Type,Install-SoftwarePackage,Invoke-Ternary,New-RunspacePool,Read-Choice,Read-HashTable,Receive-RunspaceJob,Remove-RunspacePool,Send-ModuleToPsSession,Split-Array,Start-RunspaceJob,Sync-Parameter,Test-HashtableKeys,Test-IsAdministrator,Wait-RunspaceJob,Get-DscConfigurationImportedResource,Get-RequiredModulesFromMOF,ConvertTo-BinaryIp,ConvertTo-DecimalIp,ConvertTo-DottedDecimalIp,ConvertTo-Mask,ConvertTo-MaskLength,Get-BroadcastAddress,Get-NetworkAddress,Get-NetworkRange,Get-NetworkSummary,Get-PublicIpAddress,Test-Port,Get-PerformanceCounterID,Get-PerformanceCounterLocalName,Get-PerformanceDataCollectorSet,New-PerformanceDataCollectorSet,Remove-PerformanceDataCollectorSet,Start-PerformanceDataCollectorSet,Stop-PerformanceDataCollectorSet,Add-CATemplateStandardPermission,Add-Certificate2,Enable-AutoEnrollment,Find-CertificateAuthority,Get-CaTemplate,Get-Certificate2,Get-NextOid,New-CaTemplate,Publish-CaTemplate,Request-Certificate,Test-CaTemplate,Add-TfsAgentUserCapability,Get-TfsAccessTokenString,Get-TfsAgent,Get-TfsAgentPool,Get-TfsAgentQueue,Get-TfsBuildDefinition,Get-TfsBuildDefinitionTemplate,Get-TfsBuildStep,Get-TfsFeed,Get-TfsFeedPermission,Get-TfsGitRepository,Get-TfsProcessTemplate,Get-TfsProject,Get-TfsReleaseDefinition,Get-TfsReleaseStep,New-TfsAgentQueue,New-TfsBuildDefinition,New-TfsFeed,New-TfsProject,New-TfsReleaseDefinition,Remove-TfsAgentUserCapability,Remove-TfsFeed,Set-TfsAgentUserCapability,Set-TfsFeedPermission,Set-TfsFeedPermissions,Set-TfsProject