commands.ps1


<#
    .SYNOPSIS
        Clear the imported AX 2012 PowerShell modules
         
    .DESCRIPTION
        Removes the different AX 2012 PowerShell modules that has been loaded into the session.
         
    .EXAMPLE
        PS C:\> Clear-Ax2012StandardPowershellModule
         
        This will remove all the known AX 2012 PowerShell modules that have been loaded.
        It is connected to the use of Import-Module $Script:AxPowerShellModule
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Clear-Ax2012StandardPowershellModule {
    [CmdletBinding()]
    param ( )

    Remove-Module @("AxUtilLib", "AxUtilLib.PowerShell", "Microsoft.Dynamics.Administration", "Microsoft.Dynamics.AX.Framework.Management", "Microsoft.Dynamics.ManagementUtilities")
}


<#
    .SYNOPSIS
        Convert HashTable into an array
         
    .DESCRIPTION
        Convert HashTable into an array of Key and Value
         
    .PARAMETER InputObject
        The HashTable object that you want to work against
         
    .PARAMETER KeyPrefix
        The prefix that you want to append to the key of the HashTable
         
        The default value is "-"
         
    .PARAMETER ValuePrefix
        The prefix that you want to append to the value of the HashTable
         
        The default value is " "
         
    .EXAMPLE
        PS C:\> $params = @{DatabaseServer = "Localhost"; DatabaseName = "MicrosoftDynamicsAx_model"}
        PS C:\> $arguments = Convert-HashToArgString -Inputs $params
         
        This will convert the $params into an array of strings, each with the Key and Value.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Convert-HashToArgString {
    [CmdletBinding()]
    param (
        [HashTable] $InputObject,

        [string] $KeyPrefix = "-",

        [string] $ValuePrefix = " "
    )

    $InputObject.Keys | ForEach-Object { "$KeyPrefix$($_)$ValuePrefix`"$($InputObject.Item($_))`""}
}


<#
    .SYNOPSIS
        Convert HashTable into an array
         
    .DESCRIPTION
        Convert HashTable with switches inside into an array of Key:Value
         
    .PARAMETER InputObject
        The HashTable object that you want to work against
         
        Shold only contain Key / Vaule, where value is $true or $false
         
    .PARAMETER KeyPrefix
        The prefix that you want to append to the key of the HashTable
         
        The default value is "-"
         
    .PARAMETER ValuePrefix
        The prefix that you want to append to the value of the HashTable
         
        The default value is ":"
         
    .EXAMPLE
        PS C:\> $params = @{NoPrompt = $true; CreateParents = $false}
        PS C:\> $arguments = Convert-HashToArgStringSwitch -Inputs $params
         
        This will convert the $params into an array of strings, each with the Key:Value.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Convert-HashToArgStringSwitch {
    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        [HashTable] $InputObject,

        [string] $KeyPrefix = "-",

        [string] $ValuePrefix = ":"
    )

    foreach ($key in $InputObject.Keys) {
        $value = "`${0}" -f $InputObject.Item($key).ToString().ToLower()
        "$KeyPrefix$($key)$ValuePrefix$($value)"
    }
}


<#
    .SYNOPSIS
        Get details about an AX 2012 AOS instance
         
    .DESCRIPTION
        Get all the technical details about an AX 2012 AOS instance
         
    .PARAMETER RegistryPath
        Path to the registry for the specific AX 2012 AOS instance
         
    .EXAMPLE
        PS C:\> Get-AxAosInstanceDetails -RegistryPath "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Dynamics Server\6.0\01"
         
        This will traverse all the details about the first installed AX 2012 AOS instance in the registry.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-AxAosInstanceDetails {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding()]
    param(
        [string] $RegistryPath
    )

    Write-PSFMessage -Level Verbose -Message "Working against $RegistryPath" -Target $RegistryPath

    $RegKey = Get-Item -Path $RegistryPath.Replace("HKEY_LOCAL_MACHINE", "HKLM:")
    $RegOuter = Get-ItemProperty -Path ($RegKey.Name).Replace("HKEY_LOCAL_MACHINE", "HKLM:")
    $RegInner = Get-ItemProperty -Path (Join-Path $RegKey.Name $RegOuter.Current).Replace("HKEY_LOCAL_MACHINE", "HKLM:")
    $BuildNumbers = Get-FileVersion -Path $(Join-Path $RegInner.bindir "Ax32Serv.exe")

    $InstanceDetail = [Ordered]@{}
    
    $InstanceDetail.InstanceName = $RegOuter.InstanceName
    $InstanceDetail.ConfigurationName = $RegOuter.Current
    $InstanceDetail.BinDirectory = $RegInner.bindir
    $InstanceDetail.ExecutablePath = Join-Path $RegInner.bindir "Ax32Serv.exe"

    $InstanceDetail.FileVersion = $BuildNumbers.FileVersion
    $InstanceDetail.ProductVersion = $BuildNumbers.ProductVersion
    $InstanceDetail.FileVersionUpdated = $BuildNumbers.FileVersionUpdated
    $InstanceDetail.ProductVersionUpdated = $BuildNumbers.ProductVersionUpdated

    $InstanceDetail.DatabaseServer = $RegInner.dbserver
    $InstanceDetail.DatabaseName = $RegInner.database
    $InstanceDetail.ModelstoreDatabase = "$($RegInner.database)_model"

    $InstanceDetail.AosPort = $RegInner.port
    $InstanceDetail.WsdlPort = $RegInner.WSDLPort
    $InstanceDetail.NetTcpPort = $RegInner.NetTCPPort
    
    $InstanceDetail.RegistryKeyPath = $RegKey.Name
    $InstanceDetail.InstanceNumber = Split-Path -Path $RegKey.Name -Leaf
    $InstanceDetail.ComputerName = "$env:computername"

    [PSCustomObject] $InstanceDetail
}


<#
    .SYNOPSIS
        Get the AX 2012 Client bin directory
         
    .DESCRIPTION
        Get the AX 2012 Client bin directory from the registry
         
    .EXAMPLE
        PS C:\> Get-ClientBinDir
         
        This will get the full path for the AX 2012 client bin directory
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-ClientBinDir {
    [CmdletBinding()]
    [OutputType('System.String')]
    param ( )

    $RegKey = Get-Item -Path $Script:RegistryClient -ErrorAction SilentlyContinue
    
    if(-not ($null -eq $RegKey)) {
        $RegOuter = Get-ItemProperty -Path $($RegKey.Name.Replace("HKEY_CURRENT_USER", "HKCU:"))

        $RegInner = Get-ItemProperty -Path (Join-Path $RegKey.Name $RegOuter.Current).Replace("HKEY_CURRENT_USER", "HKCU:")
    
        $RegInner.bindir
    }
}


<#
    .SYNOPSIS
        Clone a hashtable
         
    .DESCRIPTION
        Create a deep clone of a hashtable for you to work on it without updating the original object
         
    .PARAMETER InputObject
        The hashtable you want to clone
         
    .EXAMPLE
        PS C:\> Get-DeepClone -InputObject $HashTable
         
        This will clone the $HashTable variable into a new object and return it to you.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-DeepClone {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true)]
        $InputObject
    )
    process {
        if ($InputObject -is [hashtable]) {
            $clone = @{}
            foreach ($key in $InputObject.keys) {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            $clone
        }
        else {
            $InputObject
        }
    }
}


<#
    .SYNOPSIS
        Get the file version details
         
    .DESCRIPTION
        Get the file version details for any given file
         
    .PARAMETER Path
        Path to the file that you want to extract the file version details from
         
    .EXAMPLE
        PS C:\> Get-FileVersion -Path "C:\Program Files\Microsoft Dynamics AX\60\Server\MicrosoftDynamicsAX\Bin\AxServ32.exe"
         
        This will get the file version details for the AX AOS executable (AxServ32.exe).
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
        Inspired by https://blogs.technet.microsoft.com/askpfeplat/2014/12/07/how-to-correctly-check-file-versions-with-powershell/
         
#>

function Get-FileVersion {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string] $Path
    )

    if (-not (Test-PathExists -Path $Path -Type Leaf)) { return }

    Write-PSFMessage -Level Verbose -Message "Extracting the file properties for: $Path" -Target $Path
    
    $Filepath = Get-Item -Path $Path

    [PSCustomObject]@{
        FileVersion           = $Filepath.VersionInfo.FileVersion
        ProductVersion        = $Filepath.VersionInfo.ProductVersion
        FileVersionUpdated    = "$($Filepath.VersionInfo.FileMajorPart).$($Filepath.VersionInfo.FileMinorPart).$($Filepath.VersionInfo.FileBuildPart).$($Filepath.VersionInfo.FilePrivatePart)"
        ProductVersionUpdated = "$($Filepath.VersionInfo.ProductMajorPart).$($Filepath.VersionInfo.ProductMinorPart).$($Filepath.VersionInfo.ProductBuildPart).$($Filepath.VersionInfo.ProductPrivatePart)"
    }
}


<#
    .SYNOPSIS
        Get key from HashTable
         
    .DESCRIPTION
        Get specific key(s) from a HashTable returned as a HashTable
         
    .PARAMETER InputObject
        The HashTable that you want to extract key(s) from
         
    .PARAMETER Keys
        Names of the key(s) that you want to to extract from the HashTable
         
    .EXAMPLE
        PS C:\> $params = @{NoPrompt = $true; CreateParent = $false}
         
        Get-HashtableKey -InputObject $params -Keys "NoPrompt"
         
        This will return a new HashTable only containing the "NoPrompt" entry.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-HashtableKey {
    [CmdletBinding()]
    [OutputType([HashTable])]
    param (
        [parameter(Mandatory = $true, Position = 1)]
        [HashTable] $InputObject,

        [Parameter(Mandatory = $true, Position = 2)]
        [string[]] $Keys
        
    )

    $var = Get-DeepClone $InputObject
    $res = @{}

    foreach ($key in $var.Keys) {
        Write-PSFMessage -Level Verbose -Message "Working on key: $key" -Target $key
        
        if($Keys.Contains($key)) {
            Write-PSFMessage -Level Verbose -Message "Found key: $key" -Target $key
            $null = $res.Add($key, $var.Item($key))
        }
    }

    $res
}


<#
    .SYNOPSIS
        Get service list from HashTable
         
    .DESCRIPTION
        Extract the services from the list of entries in the HashTable
         
    .PARAMETER AllAxServices
        Switch to instruct the cmdlet to return all services
         
    .PARAMETER Aos
        Switch to instruct the cmdlet to return AOS
         
    .PARAMETER ManagementReporter
        Switch to instruct the cmdlet to return ManagementReporter
         
    .PARAMETER DIXF
        Switch to instruct the cmdlet to return DIXF
         
    .EXAMPLE
        PS C:\> Get-ServiceList -All
         
        This will return all services that the cmdlet knows about.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

Function Get-ServiceList {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'Default', Position = 1 )]
        [switch] $AllAxServices,

        [Parameter(Mandatory = $false, ParameterSetName = 'Specific', Position = 2 )]
        [switch] $Aos,

        [Parameter(Mandatory = $false, ParameterSetName = 'Specific', Position = 3 )]
        [switch] $ManagementReporter,

        [Parameter(Mandatory = $false, ParameterSetName = 'Specific', Position = 4 )]
        [switch] $DIXF
    )

    if ($PSCmdlet.ParameterSetName -eq "Specific") {
        $AllAxServices = $false
    }

    Write-PSFMessage -Level Verbose -Message "The PSBoundParameters was" -Target ($PSBoundParameters.Keys -Join "," )
    
    $MRService = "MR2012ProcessService"
    $dixfname = "Microsoft.Dynamics.AX.Framework.Tools.DMF.SSISHelperService.exe"

    [System.Collections.ArrayList]$Services = New-Object -TypeName "System.Collections.ArrayList"

    if ($AllAxServices) {
        for ($i = 1; $i -lt 100; $i++) {
            $null = $Services.Add("AOS60`$$($i.ToString("00"))")
        }

        $null = $Services.AddRange(@($MRService, $dixfname))
    }
    else {
        if ($Aos) {
            for ($i = 1; $i -lt 100; $i++) {
                $null = $Services.Add("AOS60`$$($i.ToString("00"))")
            }
        }

        if ($ManagementReporter) {
            $null = $Services.Add($ManagementReporter)
        }

        if ($DIXF) {
            $null = $Services.Add($dixfname)
        }
    }

    $Services.ToArray()
}


<#
    .SYNOPSIS
        Get a SqlCommand object
         
    .DESCRIPTION
        Get a SqlCommand object initialized with the passed parameters
         
    .PARAMETER DatabaseServer
        The name of the database server
         
    .PARAMETER DatabaseName
        The name of the database
         
    .PARAMETER SqlUser
        The login name for the SQL Server instance
         
    .PARAMETER SqlPwd
        The password for the SQL Server user.
         
    .PARAMETER TrustedConnection
        Should the connection use a Trusted Connection or not
         
    .EXAMPLE
        PS C:\> Get-SqlCommand -DatabaseServer localhost -DatabaseName MicrosoftDynamicsAx_model -SqlUser User123 -SqlPwd "Password123" -TrustedConnection $false
         
        This will initialize a new SqlCommand object (.NET type) with localhost as the server name, AxDB as the database and the User123 sql credentials.
         
    .EXAMPLE
        PS C:\> Get-SqlCommand -DatabaseServer localhost -DatabaseName MicrosoftDynamicsAx_model -TrustedConnection $true
         
        This will initialize a new SqlCommand object (.NET type) with localhost as the server name, AxDB as the database and use the current windows credentials (trusted connection).
         
    .NOTES
        Author: Rasmus Andersen (@ITRasmus)
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-SQLCommand {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $DatabaseServer,

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

        [Parameter(Mandatory = $false)]
        [string] $SqlUser,

        [Parameter(Mandatory = $false)]
        [string] $SqlPwd,

        [Parameter(Mandatory = $false)]
        [boolean] $TrustedConnection
    )

    Write-PSFMessage -Level Debug -Message "Writing the bound parameters" -Target $PsBoundParameters
    [System.Collections.ArrayList]$Params = New-Object -TypeName "System.Collections.ArrayList"

    $null = $Params.Add("Server='$DatabaseServer';")
    $null = $Params.Add("Database='$DatabaseName';")

    if ($null -eq $TrustedConnection -or (-not $TrustedConnection)) {
        $null = $Params.Add("User='$SqlUser';")
        $null = $Params.Add("Password='$SqlPwd';")
    }
    else {
        $null = $Params.Add("Integrated Security='SSPI';")
    }

    $null = $Params.Add("Application Name='ax2012.tools'")
    
    Write-PSFMessage -Level Verbose -Message "Building the SQL connection string." -Target ($Params.ToArray() -join ",")
    $sqlConnection = New-Object System.Data.SqlClient.SqlConnection

    try {
        $sqlConnection.ConnectionString = ($Params -join "")

        $sqlCommand = New-Object System.Data.SqlClient.SqlCommand
        $sqlCommand.Connection = $sqlConnection
        $sqlCommand.CommandTimeout = 0
    }
    catch {
        Write-PSFMessage -Level Host -Message "Something went wrong while working with the sql server connection objects" -Exception $PSItem.Exception
        Stop-PSFFunction -Message "Stopping because of errors"
        return
    }
    
    $sqlCommand
}


<#
    .SYNOPSIS
        Get the size from the parameter
         
    .DESCRIPTION
        Get the size from the parameter based on its datatype and value
         
    .PARAMETER SqlParameter
        The SqlParameter object that you want to get the size from
         
    .EXAMPLE
        PS C:\> $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
        PS C:\> $SqlCmd.Parameters.AddWithValue("@Parm1", "1234")
        PS C:\> Get-SqlParameterSize -SqlParameter $SqlCmd.Parameters[0]
         
        This will extract the size from the first parameter from the SqlCommand object and return it as a formatted string.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-SqlParameterSize {
    [CmdletBinding()]
    [OutputType('System.String')]
    param (
        [System.Data.SqlClient.SqlParameter] $SqlParameter
    )

    $res = ""

    $stringSizeTypes = @(
        [System.Data.SqlDbType]::Char,
        [System.Data.SqlDbType]::NChar,
        [System.Data.SqlDbType]::NText,
        [System.Data.SqlDbType]::NVarChar,
        [System.Data.SqlDbType]::Text,
        [System.Data.SqlDbType]::VarChar
    )

    if ( $stringSizeTypes -contains $SqlParameter.SqlDbType) {
        $res = "($($SqlParameter.Size))"
    }

    $res
}


<#
    .SYNOPSIS
        Get the value from the parameter
         
    .DESCRIPTION
        Get the value that is assigned to the SqlParameter object
         
    .PARAMETER SqlParameter
        The SqlParameter object that you want to work against
         
    .EXAMPLE
        PS C:\> $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
        PS C:\> $SqlCmd.Parameters.AddWithValue("@Parm1", "1234")
        PS C:\> Get-SqlParameterValue -SqlParameter $SqlCmd.Parameters[0]
         
        This will extract the value from the first parameter from the SqlCommand object.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-SqlParameterValue {
    [CmdletBinding()]
    [OutputType('System.String')]
    param (
        [System.Data.SqlClient.SqlParameter] $SqlParameter
    )

    $result = $null

    $stringEscaped = @(
        [System.Data.SqlDbType]::Char,
        [System.Data.SqlDbType]::DateTime,
        [System.Data.SqlDbType]::NChar,
        [System.Data.SqlDbType]::NText,
        [System.Data.SqlDbType]::NVarChar,
        [System.Data.SqlDbType]::Text,
        [System.Data.SqlDbType]::VarChar,
        [System.Data.SqlDbType]::Xml,
        [System.Data.SqlDbType]::Date,
        [System.Data.SqlDbType]::Time,
        [System.Data.SqlDbType]::DateTime2,
        [System.Data.SqlDbType]::DateTimeOffset
    )
    
    $stringNumbers = @([System.Data.SqlDbType]::Float, [System.Data.SqlDbType]::Decimal)
    
    switch ($SqlParameter.SqlDbType) {
        { $stringEscaped -contains $_ } {
            $result = "'{0}'" -f $SqlParameter.Value.ToString().Replace("'", "''")
            break
        }

        { [System.Data.SqlDbType]::Bit } {
            if ((ConvertTo-BooleanOrDefault -Object $SqlParameter.Value.ToString() -Default $true)) {
                $result = '1'
            }
            else {
                $result = '0'
            }
                        
            break
        }
        
        { $stringNumbers -contains $_ } {
            $SqlParameter.Value
            $result = ([System.Double]$SqlParameter.Value).ToString([System.Globalization.CultureInfo]::InvariantCulture).Replace("'", "''")
            break
        }

        default {
            $result = $SqlParameter.Value.ToString().Replace("'", "''")
            break
        }
    }

    $result
}


<#
    .SYNOPSIS
        Get an executable string from a SqlCommand object
         
    .DESCRIPTION
        Get an formatted and valid string from a SqlCommand object that contains all variables
         
    .PARAMETER SqlCommand
        The SqlCommand object that you want to retrieve the string from
         
    .EXAMPLE
        PS C:\> $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
        PS C:\> $SqlCmd.CommandText = "SELECT * FROM Table WHERE Column = @Parm1"
        PS C:\> $SqlCmd.Parameters.AddWithValue("@Parm1", "1234")
        PS C:\> Get-SqlString -SqlCommand $SqlCmd
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-SqlString {
    [CmdletBinding()]
    [OutputType('System.String')]
    param (
        [System.Data.SqlClient.SqlCommand] $SqlCommand
    )

    $sbDeclare = [System.Text.StringBuilder]::new()
    $sbAssignment = [System.Text.StringBuilder]::new()
    $sbRes = [System.Text.StringBuilder]::new()

    if ($SqlCommand.CommandType -eq [System.Data.CommandType]::Text) {
        if (-not ($null -eq $SqlCommand.Connection)) {
            $null = $sbDeclare.Append("USE ").AppendLine($SqlCommand.Connection.Database)
        }

        foreach ($parameter in $SqlCommand.Parameters) {
            if ($parameter.Direction -eq [System.Data.ParameterDirection]::Input) {
                $null = $sbDeclare.Append("DECLARE ").Append($parameter.ParameterName).Append("`t")
                $null = $sbDeclare.Append($parameter.SqlDbType.ToString().ToUpper())
                $null = $sbDeclare.AppendLine((Get-SqlParameterSize -SqlParameter $parameter))

                $null = $sbAssignment.Append("SET ").Append($parameter.ParameterName).Append(" = ").AppendLine((Get-SqlParameterValue -SqlParameter $parameter))
            }
        }
        
        $null = $sbRes.AppendLine($sbDeclare.ToString())
        $null = $sbRes.AppendLine($sbAssignment.ToString())
        $null = $sbRes.AppendLine($SqlCommand.CommandText)
    }

    $sbRes.ToString()
}


<#
    .SYNOPSIS
        Get WMDP details from the IIS
         
    .DESCRIPTION
        Get all the necessary details from the IIS about the WMDP installation
         
    .EXAMPLE
        PS C:\> Get-WMDPDetailsFromIIS
         
        This will get details from all the WMDP installations on the server.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-WMDPDetailsFromIIS {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding()]
    param ( )

    if ((Get-Module -ListAvailable -Name "IISAdministration").Count -lt 1) {
        Write-PSFMessage -Level Host -Message "It seems that you didn't have the <c='em'>extended</c> powershell administration tools for IIS installed. For Windows Server 2012 and 2012 R2 it is required that you install this module directly."
        Write-PSFMessage -Level Host -Message "Install-Module IISAdministration -Force -Confirm:`$false"
        Stop-PSFFunction -Message "Stopping because of missing parameters" -StepsUpward 1
        return
    } else {
        $null = Import-Module -name "IISAdministration" -Force
    }

    $sites = Get-IISSite | Where-Object {$_.Applications.VirtualDirectories.PhysicalPath -like "*AX*warehouse*portal*"}

    foreach ($site in $sites) {
        $res = [Ordered]@{SiteId = $site.Id; SiteName = $site.Name;
                SiteStatus = $site.State; SiteBindings = $site.Bindings;
                Path = $site.Applications.VirtualDirectories.PhysicalPath
                MvcViewPath = Join-Path $site.Applications.VirtualDirectories.PhysicalPath "Views\Execute"
                CssPath = Join-Path $site.Applications.VirtualDirectories.PhysicalPath "Content\CSS\RFCSS"
            }
        
        $appPool = Get-IISAppPool | Where-Object Name -eq $site.Applications.ApplicationPoolName
        $res.AppPoolName = $appPool.Name
        $res.AppPoolStatus = $appPool.Status
        $res.AppPoolIdentity = $appPool.ProcessModel.Username

        [PSCustomObject]$res
    }
}


<#
    .SYNOPSIS
        Invoke timing logic
         
    .DESCRIPTION
        Invoke timing logic that keeps track of the time spend inside a function
         
    .PARAMETER Start
        Switch to instruct the cmdlet that the starting of measurement
         
    .PARAMETER End
        Switch to instruct the cmdlet that the ending of measurement
         
    .EXAMPLE
        PS C:\> Invoke-TimeSignal -Start
         
        This will start the timing measurement.
         
    .EXAMPLE
        PS C:\> Invoke-TimeSignal -End
         
        This will end the timing measurement and have the cmdlet write the details into the verbose log.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Invoke-TimeSignal {
    [CmdletBinding(DefaultParameterSetName = 'Start')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Start', Position = 1 )]
        [switch] $Start,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'End', Position = 2 )]
        [switch] $End
    )
    $Time = (Get-Date)

    $Command = (Get-PSCallStack)[1].Command

    if($Start.IsPresent) {
        if($Script:TimeSignals.ContainsKey($Command)) {
            Write-PSFMessage -Level Verbose -Message "The command '$Command' was already taking part in time measurement. The entry has been update with current date and time."
            $Script:TimeSignals[$Command] = $Time
        }
        else{
            $Script:TimeSignals.Add($Command, $Time)
        }
    }
    else{
        if($Script:TimeSignals.ContainsKey($Command)) {
            $TimeSpan = New-TimeSpan -End $Time -Start (($Script:TimeSignals)[$Command])

            Write-PSFMessage -Level Verbose -Message "Total time spent inside the function was $TimeSpan" -Target $TimeSpan -FunctionName $Command -Tag "TimeSignal"
            $Script:TimeSignals.Remove($Command)
        }
        else {
            Write-PSFMessage -Level Verbose -Message "The command '$Command' was never started to take part in time measurement."
        }
    }
}


<#
    .SYNOPSIS
        Create a new folder with datetime in its name
         
    .DESCRIPTION
        Create a new folder with current date and time as part of its name
         
    .PARAMETER Path
        Path to the parent folder where you want the new folder created
         
    .PARAMETER NoCreate
        Switch to instruct the cmdlet not to create the folder
         
    .EXAMPLE
        PS C:\> New-FolderWithDateTime -Path "c:\temp\ax2012.tools"
         
        This will create a new folder with the current date and time as a child to the "c:\temp\ax2012.tools" folder.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

Function New-FolderWithDateTime {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 1)]
        [string] $Path,

        [Parameter(Mandatory = $false, Position = 2)]
        [switch] $NoCreate
    )

    $dateString = (Get-Date).ToString("yyyy-MM-dd_HH.mm.ss")
    
    $backUpPath = Join-Path $Path $dateString
    
    Write-PSFMessage -Level Verbose -Message "Creating the new directory: $backUpPath" -Target $backUpPath
    
    if (-not ($NoCreate)) {
        $null = New-Item -Path $backUpPath -ItemType Directory -Force
    }
    
    $backUpPath
}


<#
    .SYNOPSIS
        Test accessible to the configuration storage
         
    .DESCRIPTION
        Test if the desired configuration storage is accessible with the current user context
         
    .PARAMETER ConfigStorageLocation
        Parameter used to instruct where to store the configuration objects
         
        The default value is "User" and this will store all configuration for the active user
         
        Valid options are:
        "User"
        "System"
         
        "System" will store the configuration so all users can access the configuration objects
         
    .EXAMPLE
        PS C:\> Test-ConfigStorageLocation -ConfigStorageLocation "System"
         
        This will test if the current executing user has enough privileges to save to the system wide configuration storage.
        The system wide configuration storage requires administrator rights.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Test-ConfigStorageLocation {
    [CmdletBinding()]
    [OutputType('System.String')]
    param (
        [ValidateSet('User', 'System')]
        [string] $ConfigStorageLocation = "User"
    )
    
    $configScope = "UserDefault"

    if ($ConfigStorageLocation -eq "System") {
        if ($Script:IsAdminRuntime) {
            $configScope = "SystemDefault"
        }
        else {
            Write-PSFMessage -Level Host -Message "Unable to locate save the <c='em'>configuration objects</c> in the <c='em'>system wide configuration store</c> on the machine. Please start an elevated session and run the cmdlet again."
            Stop-PSFFunction -Message "Elevated permissions needed. Please start an elevated session and run the cmdlet again." -StepsUpward 1
            return
        }
    }

    $configScope
}


<#
    .SYNOPSIS
        Test multiple paths
         
    .DESCRIPTION
        Easy way to test multiple paths for public functions and have the same error handling
         
    .PARAMETER Path
        Array of paths you want to test
         
        They have to be the same type, either file/leaf or folder/container
         
    .PARAMETER Type
        Type of path you want to test
         
        Valid options are:
        "Leaf"
        "Container"
         
    .PARAMETER Create
        Switch to instruct the cmdlet to create the folder
         
    .EXAMPLE
        PS C:\> Test-PathExists "c:\temp","c:\temp\dir" -Type Container
         
        This will test if the mentioned paths (folders) exists and the current context has enough permission.
         
    .NOTES
        Author: M�tz Jensen (@splaxi)
         
#>

function Test-PathExists {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param (
        [Parameter(Mandatory = $True, Position = 1 )]
        [string[]] $Path,

        [ValidateSet('Leaf', 'Container')]
        [Parameter(Mandatory = $True, Position = 2 )]
        [string] $Type,

        [switch] $Create
    )
    
    $res = $false

    $arrList = New-Object -TypeName "System.Collections.ArrayList"
         
    foreach ($item in $Path) {
        Write-PSFMessage -Level Verbose -Message "Testing the path: $item" -Target $item
        $temp = Test-Path -Path $item -Type $Type

        if ((!$temp) -and ($Create) -and ($Type -eq "Container")) {
            Write-PSFMessage -Level Verbose -Message "Creating the path: $item" -Target $item
            New-Item -Path $item -ItemType Directory -Force -ErrorAction Stop
            $temp = $true
        }
        elseif (!$temp) {
            Write-PSFMessage -Level Host -Message "The <c='em'>$item</c> path wasn't found. Please ensure the path <c='em'>exists</c> and you have enough <c='em'>permission</c> to access the path."
        }
        
        $null = $arrList.Add($temp)
    }

    if ($arrList.Contains($false)) {
        Stop-PSFFunction -Message "Stopping because of missing paths." -StepsUpward 1
    }
    else {
        $res = $true
    }

    $res
}


<#
    .SYNOPSIS
        Test if a given registry key exists or not
         
    .DESCRIPTION
        Test if a given registry key exists in the path specified
         
    .PARAMETER Path
        Path to the registry hive and sub directories you want to work against
         
    .PARAMETER Name
        Name of the registry key that you want to test for
         
    .EXAMPLE
        PS C:\> Test-RegistryValue -Path "HKLM:\SOFTWARE\Microsoft\Dynamics\Deployment\" -Name "InstallationInfoDirectory"
         
        This will query the LocalMachine hive and the sub directories "HKLM:\SOFTWARE\Microsoft\Dynamics\Deployment\" for a registry key with the name of "InstallationInfoDirectory".
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

Function Test-RegistryValue {
    [OutputType('System.Boolean')]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,
        
        [Parameter(Mandatory = $true)]
        [string]$Name
    )

    if (Test-Path -Path $Path -PathType Any) {
        $null -ne (Get-ItemProperty $Path).$Name
    }
    else {
        $false
    }
}


<#
    .SYNOPSIS
        Test PSBoundParameters whether or not to support TrustedConnection
         
    .DESCRIPTION
        Test callers PSBoundParameters (HashTable) for details that determines whether or not a SQL Server connection should support TrustedConnection or not
         
    .PARAMETER Inputs
        HashTable ($PSBoundParameters) with the parameters from the callers invocation
         
    .EXAMPLE
        PS C:\> $UseTrustedConnection = Test-TrustedConnection $PSBoundParameters
         
        This will send the entire HashTable from the callers invocation, containing all explicit defined parameters to be analyzed whether or not the SQL Server connection should support TrustedConnection or not.
         
    .NOTES
        Author: M�tz Jensen (@splaxi)
         
#>

function Test-TrustedConnection {
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param (
        [HashTable] $Inputs
    )

    if (($Inputs.ContainsKey("SqlUser")) -or ($Inputs.ContainsKey("SqlPwd"))) {
        Write-PSFMessage -Level Verbose -Message "Not capable of using Trusted Connection based on supplied SQL login details."
        $false
    }
    elseif ($Inputs.ContainsKey("TrustedConnection")) {
        Write-PSFMessage -Level Verbose -Message "The script was calling with TrustedConnection directly. This overrides all other logic in respect that the caller should know what it is doing. Value was: $($Inputs.TrustedConnection)" -Tag $Inputs.TrustedConnection
        $Inputs.TrustedConnection
    }
    else {
        $false
    }
}


<#
    .SYNOPSIS
        Update all the active variables
         
    .DESCRIPTION
        All PSF configuration entries that are names *.active* will be extracted to their corresponding $script:VARIABLENAMe
         
    .EXAMPLE
        PS C:\> Update-ActiveVariables
         
        This will update all the variables that maps directly to a PSF configuration value in the *.active* part of the configuration store.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
#>

function Update-ActiveVariables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param ()
    
    foreach ($item in (Get-PSFConfig -FullName ax2012.tools.active*)) {
        $nameTemp = $item.FullName -replace "^ax2012.tools.", ""
        $name = ($nameTemp -Split "\." | ForEach-Object { (Get-Culture).TextInfo.ToTitleCase($_) } ) -Join ""
        
        New-Variable -Name $name -Value $item.Value -Scope Script -Force
    }
    
    if ([System.String]::IsNullOrEmpty(($Script:ActiveAosDatabaseserver -replace "null", "" ))) {
        Write-PSFMessage -Level Verbose -Message "ActiveAosDatabaseserver was empty. Defaulting to `"localhost`""
        $Script:ActiveAosDatabaseserver = "localhost"
    }
    
    if ([System.String]::IsNullOrEmpty(($Script:ActiveAosModelstoredatabase -replace "null", "" ))) {
        Write-PSFMessage -Level Verbose -Message "ActiveAosModelstoredatabase was empty. Defaulting to `"MicrosoftDynamicsAx_model`""
        $Script:ActiveAosModelstoredatabase = "MicrosoftDynamicsAx_model"
    }
    
    if ([System.String]::IsNullOrEmpty(($Script:ActiveAosDatabase -replace "null", "" ))) {
        Write-PSFMessage -Level Verbose -Message "ActiveAosDatabase was empty. Defaulting to `"MicrosoftDynamicsAx`""
        $Script:ActiveAosDatabase = "MicrosoftDynamicsAx"
    }
}


<#
    .SYNOPSIS
        Export an AX 2012 modelstore file
         
    .DESCRIPTION
        Export an AX 2012 modelstore file from the modelstore database
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER ModelstoreDatabase
        Name of the modelstore database
         
        Default value is: "MicrosoftDynamicsAx_model"
         
        Note: From AX 2012 R2 and upwards you need to provide the full name for the modelstore database. E.g. "AX2012R3_PROD_model"
         
    .PARAMETER InstanceName
        Name of the instance that you are working against
         
        If not supplied the cmdlet will take the name of the database and use that
         
    .PARAMETER Suffix
        A suffix text value that you want to add to the name of the file while it is exported
         
        The default value is: (Get-Date).ToString("yyyyMMdd")
         
        This will always name you file with the current date
         
    .PARAMETER Path
        Path to the location where you want the file to be exported
         
        Default value is: "c:\temp\ax2012.tools"
         
    .PARAMETER GenerateScript
        Switch to instruct the cmdlet to only generate the needed command and not execute it
         
    .EXAMPLE
        PS C:\> Export-AxModelStoreV2
         
        This will execute the cmdlet with all the default values.
        This will work against the SQL server that is on localhost.
        The database is expected to be "MicrosoftDynamicsAx_model".
        The path where the exported modelstore file will be saved is: "c:\temp\ax2012.tools".
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

Function Export-AxModelStoreV2 {
    [CmdletBinding()]
    [OutputType('System.String')]
    Param(
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase,

        [string] $InstanceName = $Script:ActiveAosInstancename,

        [string] $Suffix = $((Get-Date).ToString("yyyyMMdd")),
        
        [string] $Path = $Script:DefaultTempPath,

        [switch] $GenerateScript
    )

    Invoke-TimeSignal -Start

    if (-not (Test-PathExists -Path $Path -Type Container -Create)) { return }

    if (-not (Test-PathExists -Path $Script:AxPowerShellModule -Type Leaf)) { return }
        
    if ([System.String]::IsNullOrEmpty($InstanceName)) {
        $InstanceName = "{0}" -f $ModelstoreDatabase.Replace("_model", "")
    }

    if (-not ([system.string]::IsNullOrEmpty($Suffix))) {
        $ExportPath = Join-Path $Path "$($InstanceName)_$($Suffix).axmodelstore"
    }
    else {
        $ExportPath = Join-Path $Path "$InstanceName.axmodelstore"
    }

    $params = @{
        Server   = $DatabaseServer
        Database = $ModelstoreDatabase
        File     = $ExportPath
    }

    if ($GenerateScript) {
        $arguments = Convert-HashToArgString -InputObject $params

        "Export-AxModelStore $($arguments -join ' ')"
    }
    else {
        Write-PSFMessage -Level Verbose -Message "Starting the export of the model store"
        
        $null = Import-Module $Script:AxPowerShellModule

        Export-AxModelStore @params

        Clear-Ax2012StandardPowershellModule
    }

    Invoke-TimeSignal -End
}


<#
    .SYNOPSIS
        Export AX 2012 model
         
    .DESCRIPTION
        Export AX 2012 model from the AX 2012 model store
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER ModelstoreDatabase
        Name of the modelstore database
         
        Default value is: "MicrosoftDynamicsAx_model"
         
        Note: From AX 2012 R2 and upwards you need to provide the full name for the modelstore database. E.g. "AX2012R3_PROD_model"
         
    .PARAMETER Path
        Path to the location where you want the file to be exported
         
        Default value is: "c:\temp\ax2012.tools"
         
    .PARAMETER Name
        Name of the AX 2012 model that you are looking for
         
        Accepts wildcards for searching. E.g. -Name "ISV*MODULE*"
         
        Default value is "*" which will search for all models
         
    .PARAMETER Id
        Id of the AX 2012 model that you are looking for
         
        Accepts wildcards for searching. E.g. -Id "2*"
         
        Default value is "*" which will search for all models
         
    .PARAMETER Layer
        Layer where the AX 2012 model that you are looking for should reside
         
        Accepts wildcards for searching. E.g. -Layer "IS*"
         
        Default value is "*" which will search for models in all layers
         
    .PARAMETER GenerateScript
        Switch to instruct the cmdlet to output the script to execute the command in hand
         
    .EXAMPLE
        PS C:\> Get-AxAosInstance | Export-AxModelV2
         
        This will fetch all the AX 2012 AOS instances that are configured on the machine.
        Foreach of the instances it will export all AX 2012 Models into a sub folder to "c:\temp\ax2012.tools".
         
    .EXAMPLE
        PS C:\> Export-AxModelV2 -DatabaseServer localhost -ModelstoreDatabase MicrosoftDynamicsAx_model -Name *CUS*
         
        This will fetch all the AX 2012 AOS instances that are configured on the machine.
        Foreach of the instances it will export all AX 2012 Models into a sub folder to "c:\temp\ax2012.tools".
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

Function Export-AxModelV2 {
    [CmdletBinding()]
    [OutputType([System.String], ParameterSetName="Generate")]
    Param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 1)]
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 2)]
        [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase,
        
        [Parameter(Mandatory = $false, Position = 3)]
        [string] $Path = $Script:DefaultTempPath,

        [Parameter(Mandatory = $false, Position = 4)]
        [string] $Name = "*",

        [Parameter(Mandatory = $false, Position = 5)]
        [string] $Id = "*",

        [Parameter(Mandatory = $false, Position = 6)]
        [string] $Layer = "*",

        [Parameter(ParameterSetName = "Generate")]
        [switch] $GenerateScript
    )

    BEGIN {
        if (-not ($GenerateScript)) {
            if (-not (Test-PathExists -Path $Path -Type Container -Create)) { return }
            $backupFilePath = New-FolderWithDateTime -Path $Path
        }
        else {
            $backupFilePath = New-FolderWithDateTime -Path $Path -NoCreate
        }

        $null = Import-Module $Script:AxPowerShellModule
    }

    PROCESS {
        Invoke-TimeSignal -Start

        $xml = (Get-AXModel -Server $DatabaseServer -Database $ModelstoreDatabase | ConvertTo-Xml )
        $nodes = $xml.SelectNodes("Objects/Object")

        foreach ($obj in $nodes) {
            $filenameAxModel = ""
            $modelId = ""
            $modelLayer = ""
            $modelName = ""

            # Loop all properties
            foreach ($property in $obj.SelectNodes("Property")) {
                if ($property.GetAttribute("Name").Equals( "Name" )) {
                    $modelName = $property.InnerText
                }
        
                if ($property.GetAttribute("Name").Equals( "Layer" )) {
                    $modelLayer = $property.InnerText
                }
        
                if ($property.GetAttribute("Name").Equals( "ModelId" )) {
                    $modelId = $property.InnerText
                }
            }
    
            $filenameAxModel = $modelId + "_" + $modelName + ".axmodel"

            Write-PSFMessage -Level Verbose -Message "Testing that ModelName, ModelId and Layer matches the search criteria"
            if ($modelName -NotLike $Name) { continue }
            if ($modelId -NotLike $Id) { continue }
            if ($modelLayer -NotLike $Layer) { continue }

            if ($Script:LayerDictionary.ContainsKey($modelLayer.ToUpper()) -and $filenameAxModel -ne "") {
                $localLayer = $Script:LayerDictionary.Get_Item($modelLayer.ToUpper()) + $modelLayer.ToUpper()
                $tempPath = Join-Path $backupFilePath $localLayer
        
                $filenameAxModel = Join-Path $tempPath $filenameAxModel

                $params = @{Model = $modelName; File = $filenameAxModel;
                    Server = $DatabaseServer; Database = $ModelstoreDatabase
                }

                if ($GenerateScript) {
                    $arguments = Convert-HashToArgString -InputObject $params

                    "Export-AXModel $($arguments -join ' ')"
                }
                else {
                    if (-not (Test-PathExists -Path $tempPath -Type Container -Create)) { return }

                    Write-PSFMessage -Level Verbose -Message "Starting the export of the ax model file"
                    $null = Export-AXModel @params
                }
            }
            else {
                Write-PSFMessage -Level Verbose -Message "Skipping $filenameAxModel in layer $modelLayer"
            }
        }

        Invoke-TimeSignal -End
    }

    END {
        if (-not ($GenerateScript)) {
            [PSCustomObject]@{
                Path = $backupFilePath
            }
        }

        Clear-Ax2012StandardPowershellModule
    }
}


<#
    .SYNOPSIS
        Get the active stored AX 2012 AOS configuration
         
    .DESCRIPTION
        Get the active AX 2012 AOS configuration from the configuration storage
         
    .EXAMPLE
        PS C:\> Get-AxActiveAosConfiguration
         
        This will export all the stored details saved into the configuration storage.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

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

    $res = [Ordered]@{}

    $res.InstanceName = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.instancename'
    $res.BinDirectory = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.bindirectory'

    $res.DatabaseServer = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.databaseserver'
    $res.DatabaseName = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.database'
    $res.ModelstoreDatabase = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.modelstoredatabase'

    $res.AosPort = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.aos.port'
    $res.WsdlPort = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.wsdl.port'
    $res.NetTcpPort = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.nettcp.port'
    
    $res.InstanceNumber = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.instance.number'
    $res.ComputerName = Get-PSFConfigValue -FullName 'ax2012.tools.active.aos.computername'

    [PSCustomObject] $res
}


<#
    .SYNOPSIS
        Get AX 2012 AOS Instance
         
    .DESCRIPTION
        Get AX 2012 AOS Instance details from the local machine
         
    .PARAMETER Name
        The search string to filter the AOS instance that you're looking for
         
        The parameter supports wildcards. E.g. -Name "*DEV*"
         
        Default value is "*" and will give you all the instances
         
    .PARAMETER InstanceNo
        The search string to filter the AOS instance that you're looking for
         
        The parameter supports wildcards. E.g. -InstanceNo "*1*"
         
        Default value is "*" and will give you all the instances
         
    .EXAMPLE
        PS C:\> Get-AxAosInstance
         
        This will get you all the installed AX 2012 AOS instances on the machine
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-AxAosInstance {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [string] $Name = "*",
        
        [Parameter()]
        [string] $InstanceNo = "*"
    )
    $Instances = Get-ChildItem -Path $Script:RegistryAos
        
    $res = New-Object System.Collections.ArrayList
    
    $Instances | ForEach-Object {
        $null = $res.Add((Get-AxAosInstanceDetails $_.Name))
    }

    foreach ($obj in $res) {
        if ($obj.InstanceName -NotLike $Name) { continue }
        if ($obj.InstanceNumber -NotLike $InstanceNo) { continue }

        $obj
    }
}


<#
    .SYNOPSIS
        Get AOS server details from the SQL Server database
         
    .DESCRIPTION
        Traverse the SysServerConfig table from the AX 2012 database and get all registered AOS Servers
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER DatabaseName
        Name of the database
         
        Default value is: "MicrosoftDynamicsAx"
         
    .PARAMETER SqlUser
        User name of the SQL Server credential that you want to use when working against the database
         
    .PARAMETER SqlPwd
        Password of the SQL Server credential that you want to use when working against the database
         
    .EXAMPLE
        PS C:\> Get-AxAosServerFromDB
         
        This will query the "MicrosoftDynamicsAx" hosted on the localhost server.
        It will extract all AOS Servers from the SysServerConfig table.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-AxAosServerFromDB {
    [CmdletBinding()]
    #[OutputType()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 1)]
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 2)]
        [string] $DatabaseName = $Script:ActiveAosDatabase,

        [Parameter(Mandatory = $false, Position = 3)]
        [string] $SqlUser,

        [Parameter(Mandatory = $false, Position = 4)]
        [string] $SqlPwd
    )

    Invoke-TimeSignal -Start

    $baseParams = Get-DeepClone $PSBoundParameters
    $baseParams.Add("TrustedConnection", $true)

    $UseTrustedConnection = Test-TrustedConnection $baseParams

    $SqlParams = @{ DatabaseServer = $DatabaseServer; DatabaseName = $DatabaseName;
        SqlUser = $SqlUser; SqlPwd = $SqlPwd
    }

    $sqlCommand = Get-SqlCommand @SqlParams -TrustedConnection $UseTrustedConnection

    $sqlCommand.CommandText = (Get-Content "$script:ModuleRoot\internal\sql\get-aosserversfromdatabase.sql") -join [Environment]::NewLine

    try {
        Write-PSFMessage -Level InternalComment -Message "Executing a script against the database." -Target (Get-SqlString $sqlCommand)

        $sqlCommand.Connection.Open()

        $reader = $sqlCommand.ExecuteReader()

        while ($reader.Read() -eq $true) {
            $rawServer = "$($reader.GetString($($reader.GetOrdinal("SERVERID"))))"
            
            [PSCustomObject]@{
                RawServerName  = $rawServer
                InstanceNumber = $rawServer.ToString().Split("@")[0]
                ServerName     = $rawServer.ToString().Split("@")[1]
                IsBatchEnabled = [bool][int]"$($reader.GetInt32($($reader.GetOrdinal("IsBatch"))))"
            }
        }
    }
    catch {
        Write-PSFMessage -Level Host -Message "Something went wrong while working against the database" -Exception $PSItem.Exception
        Stop-PSFFunction -Message "Stopping because of errors"
        return
    }
    finally {
        if ($sqlCommand.Connection.State -ne [System.Data.ConnectionState]::Closed) {
            $sqlCommand.Connection.Close()
        }
    
        $sqlCommand.Dispose()
    }

    Invoke-TimeSignal -End
}


<#
    .SYNOPSIS
        Get the build numbers
         
    .DESCRIPTION
        Get the build numbers for the AX 2012 client
         
    .PARAMETER Path
        The path to the Ax32.exe file you want to work against
         
        The default path is read from the registry
         
    .EXAMPLE
        PS C:\> Get-AxClientBuild
         
        This will get the executable path and the build numbers for the client.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-AxClientBuild {
    [CmdletBinding()]
    Param(
        [string] $Path = $(Join-Path $Script:ClientBin "Ax32.exe")
    )
    
    $BuildNumbers = Get-FileVersion -Path $Path

    $clientDetails = [Ordered]@{}

    $clientDetails.ExecutablePath = $Path

    $clientDetails.FileVersion = $BuildNumbers.FileVersion
    $clientDetails.ProductVersion = $BuildNumbers.ProductVersion
    $clientDetails.FileVersionUpdated = $BuildNumbers.FileVersionUpdated
    $clientDetails.ProductVersionUpdated = $BuildNumbers.ProductVersionUpdated

    [PSCustomObject] $clientDetails
}


<#
    .SYNOPSIS
        Get the AX 2012 client configuration
         
    .DESCRIPTION
        Get the AX 2012 client configuration from the registry
         
    .PARAMETER Name
        Name of the configuration that you are looking for
         
        The parameter supports wildcards. E.g. -Name "*DEV*"
         
    .EXAMPLE
        PS C:\> Get-AxClientConfig
         
        This will get all available client configurations from the registry and display them.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-AxClientConfig {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [SupportsWildcards()]
        [string] $Name = "*"
    )
    
    $configs = Get-ChildItem -Path $Script:RegistryClient

    foreach ($item in $configs) {
        $RegistryPath = Get-Item -Path $($item.Name.Replace("HKEY_CURRENT_USER", "HKCU:"))
        $configName = Split-Path -Path $RegistryPath.Name -Leaf
        
        if ($configName -NotLike $Name) { continue }

        $RegInner = Get-ItemProperty -Path $($RegistryPath.Name.Replace("HKEY_CURRENT_USER", "HKCU:"))
        $res = [Ordered]@{ConfigName = $configName}
        
        $res.AosServer = ($RegInner.Aos2).Split(":")[0]
        $res.AosPort = ($RegInner.Aos2).Split(":")[1]
        
        if ($RegInner.Aos2 -like "*@*") {
            $res.InstanceName = ($RegInner.Aos2).Split("@")[0]
        }

        $res.AosTrafficEncrypted = $RegInner.AosEncryption

        $res.BinDir = $RegInner.BinDir
        $res.ClientDirectory = $RegInner.Directory
        $res.Aol = $RegInner.Aol
        $res.AolCode = $RegInner.AolCode
        
        [PSCustomObject]$res
    }
}


<#
    .SYNOPSIS
        Get the status of an AX 2012 environment
         
    .DESCRIPTION
        Get the status of AX 2012 services in your environment
         
    .PARAMETER ComputerName
        Name of the computer(s) that you want to work against
         
    .PARAMETER AllAxServices
        Switch to instruct the cmdlet to include all known AX 2012 services
         
    .PARAMETER AosInstanceName
        Name of the AOS instance that you are looking for
         
        Accepts wildcards for searching. E.g. -AosInstanceName "*DEV*"
         
        If AxActiveAosConfiguration has been configured, the default value is the name of the instance registered
         
        Default value is otherwise "*" which will search for all AOS instances
         
    .PARAMETER Aos
        Switch to instruct the cmdlet to include the AOS service
         
    .PARAMETER ManagementReporter
        Switch to instruct the cmdlet to include the ManagementReporter service
         
    .PARAMETER DIXF
        Switch to instruct the cmdlet to include the DIXF service
         
    .PARAMETER ScanAllAosServices
        Parameter description
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment
         
        This will get the status for all the default services from your environment.
        If AxActiveAosConfiguration has been configured, it will work against the ComputerName and AosInstanceName registered.
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment -ScanAllAosServices
         
        This will scan for all available AOS Services.
        If AxActiveAosConfiguration has been configured, it will work against the ComputerName registered otherwise localhost is used.
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment -ComputerName TEST-AOS-01 -Aos
         
        This will get all AOS instances from the server named "TEST-AOS-01".
        If AxActiveAosConfiguration has been configured, it will work against the AosInstanceName registered otherwise it will find all.
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment -ComputerName TEST-AOS-01 -Aos -AosInstanceName *DEV*
         
        This will get all AOS instances that match the search pattern "*DEV*" from the server named "TEST-AOS-01".
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment -ComputerName TEST-AOS-01 -Aos | Start-AxEnvironment -ShowOutput
         
        This will scan the "TEST-AOS-01" server for all AOS instances and start them.
        It will show the status for the service(s) on the server afterwards.
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment -ComputerName TEST-AOS-01 -Aos | Stop-AxEnvironment -ShowOutput
         
        This will scan the "TEST-AOS-01" server for all AOS instances and stop them.
        It will show the status for the service(s) on the server afterwards.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-AxEnvironment {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Alias('Server')]
        [string[]] $ComputerName = $Script:ActiveAosComputername,

        [Parameter(Mandatory = $false, ParameterSetName = 'Default', Position = 2 )]
        [switch] $AllAxServices = [switch]::Present,
        
        [Alias('InstanceName')]
        [string] $AosInstanceName = $(if (-not ([System.String]::IsNullOrEmpty($Script:ActiveAosInstancename))) { "*$Script:ActiveAosInstancename" } else { "*" }),

        [Parameter(Mandatory = $false, ParameterSetName = 'Specific', Position = 2 )]
        [switch] $Aos,

        [Parameter(Mandatory = $false, ParameterSetName = 'Specific', Position = 3 )]
        [switch] $ManagementReporter,

        [Parameter(Mandatory = $false, ParameterSetName = 'Specific', Position = 4 )]
        [switch] $DIXF,

        [switch] $ScanAllAosServices
    )

    if ($PSCmdlet.ParameterSetName -eq "Specific") {
        $AllAxServices = ![switch]::Present
    }

    if (!$AllAxServices -and !$Aos -and !$ManagementReporter -and !$DIXF) {
        Write-PSFMessage -Level Host -Message "You have to use at least one switch when running this cmdlet. Please run the cmdlet again."
        Stop-PSFFunction -Message "Stopping because of missing parameters"
        return
    }

    $baseParams = Get-DeepClone $PSBoundParameters

    $params = @{}
    $includeParamNames = @("ManagementReporter", "DIXF")

    foreach ($key in $baseParams.Keys) {
        Write-PSFMessage -Level Verbose -Message "Working on key: $key" -Target $key
        if ($includeParamNames -notlike $key ) {continue}
        
        $null = $params.Add($key, $baseParams.Item($key).ToString())
    }
    
    if ($params.Count -eq 0) {
        if ($AllAxServices) {
            $params.AllAxServices = $true

            $Services = Get-ServiceList @params
        }
    }
    else {
        $Services = Get-ServiceList @params
    }

    if ($PSBoundParameters.ContainsKey("Aos")) {
        Write-PSFMessage -Level Verbose -Message "Aos seems to be bound" -Target $key
        
        $searchServicesAos = Get-ServiceList -Aos
    }

    $res = New-Object System.Collections.ArrayList
    
    foreach ($server in $ComputerName) {
        Write-PSFMessage -Level Verbose -Message "Working against: $server - listing services" -Target ($Services -Join ",")
        Write-PSFMessage -Level Verbose -Message "Working against: $server - listing Aos services" -Target ($searchServicesAos -Join ",")
        
        if (-not ($null -eq $searchServicesAos)) {

            $colAosServices = Get-Service -ComputerName $server -Name $searchServicesAos -ErrorAction SilentlyContinue | Select-Object @{Name = "Server"; Expression = {$Server}}, Name, Status, DisplayName

            foreach ($service in $colAosServices) {
                if ((-not $ScanAllAosServices) -and ($service.DisplayName -NotLike $AosInstanceName)) { continue }

                $null = $res.Add($service)
            }
        }

        if (-not ($null -eq $Services)) {
            $axServices = Get-Service -ComputerName $server -Name $Services -ErrorAction SilentlyContinue | Select-Object @{Name = "Server"; Expression = {$Server}}, Name, Status, DisplayName
    
            foreach ($service in $axServices) {
                if ($service.DisplayName -like "*AX Object Server*" ) {
                    if ((-not $ScanAllAosServices) -and ($service.DisplayName -NotLike $AosInstanceName)) { continue }
                }

                $null = $res.Add($service)
            }
        }
    }
        
    $res.ToArray() | Select-Object Server, DisplayName, Status, Name
}


<#
    .SYNOPSIS
        Get the registered details for Azure Logic App
         
    .DESCRIPTION
        Get the details that are stored for the module when
        it has to invoke the Azure Logic App
         
    .EXAMPLE
        PS C:\> Get-AxLogicAppConfig
         
        This will fetch the current registered Azure Logic App details on the machine.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

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

    [PSCustomObject][ordered]@{
        Email = (Get-PSFConfigValue -Fullname 'ax2012.tools.active.logicapp.email')
        URL = (Get-PSFConfigValue -Fullname 'ax2012.tools.active.logicapp.url')
        Subject = (Get-PSFConfigValue -Fullname 'ax2012.tools.active.logicapp.subject')
    }
}


<#
    .SYNOPSIS
        Get the SSRS configuration
         
    .DESCRIPTION
        Get all the SSRS configuration from the AX 2012 database
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER DatabaseName
        Name of the database
         
        Default value is: "MicrosoftDynamicsAx"
         
    .PARAMETER SqlUser
        User name of the SQL Server credential that you want to use when working against the database
         
    .PARAMETER SqlPwd
        Password of the SQL Server credential that you want to use when working against the database
         
    .EXAMPLE
        PS C:\> Get-AxSsrsConfig
         
        This will get all the stored SSRS configuration entries from the default DatabaseServer and the default Database.
         
    .EXAMPLE
        PS C:\> Get-AxAosInstance | Get-AxSsrsConfig
         
        This will get all AOS Instance from the local machine and pipe them to the Get-AxSsrsConfig cmdlet.
        The Get-AxSsrsConfig will the traverse every AOS Instance and their corresponding database for all SSRS configuration.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
#>

function Get-AxSsrsConfig {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 1)]
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 2)]
        [string] $DatabaseName = $Script:ActiveAosDatabase,

        [Parameter(Mandatory = $false, Position = 3)]
        [string] $SqlUser,

        [Parameter(Mandatory = $false, Position = 4)]
        [string] $SqlPwd
    )

    begin {
        Invoke-TimeSignal -Start
    }

    process {

        $baseParams = Get-DeepClone $PSBoundParameters
        $baseParams.Add("TrustedConnection", $true)

        $UseTrustedConnection = Test-TrustedConnection $baseParams

        $SqlParams = @{ DatabaseServer = $DatabaseServer; DatabaseName = $DatabaseName;
            SqlUser = $SqlUser; SqlPwd = $SqlPwd
        }

        $sqlCommand = Get-SqlCommand @SqlParams -TrustedConnection $UseTrustedConnection

        $sqlCommand.CommandText = (Get-Content "$script:ModuleRoot\internal\sql\get-ssrsconfig.sql") -join [Environment]::NewLine

        try {
            Write-PSFMessage -Level InternalComment -Message "Executing a script against the database." -Target (Get-SqlString $sqlCommand)

            $sqlCommand.Connection.Open()

            $reader = $sqlCommand.ExecuteReader()

            while ($reader.Read() -eq $true) {
                [PSCustomObject][ordered]@{
                    ConfigurationId            = "$($reader.GetString($($reader.GetOrdinal("CONFIGURATIONID"))))"
                    Description                = "$($reader.GetString($($reader.GetOrdinal("DESCRIPTION"))))"
                    IsDefaultReportServer      = [bool][int]"$($reader.GetInt32($($reader.GetOrdinal("ISDEFAULTREPORTLIBRARYSERVER"))))"
                    SsrsServerName             = "$($reader.GetString($($reader.GetOrdinal("SERVERID"))))"
                    SsrsServerInstance         = "$($reader.GetString($($reader.GetOrdinal("SERVERINSTANCE"))))"
                    ReportManagerUrl           = "$($reader.GetString($($reader.GetOrdinal("REPORTMANAGERURL"))))"
                    ServerUrl                  = "$($reader.GetString($($reader.GetOrdinal("SERVERURL"))))"
                    IsSharepointIntegrated     = [bool][int]"$($reader.GetInt32($($reader.GetOrdinal("ISSHAREPOINTINTEGRATED"))))"
                    ReportFolder         = "$($reader.GetString($($reader.GetOrdinal("AXAPTAREPORTFOLDER"))))"
                    IsDefaultReportmodelServer = [bool][int]"$($reader.GetInt32($($reader.GetOrdinal("ISDEFAULTREPORTMODELSERVER"))))"
                    DataSourceName             = "$($reader.GetString($($reader.GetOrdinal("DATASOURCENAME"))))"
                    AosId                      = "$($reader.GetString($($reader.GetOrdinal("AOSID"))))"
                }
            }
        }
        catch {
            Write-PSFMessage -Level Host -Message "Something went wrong while working against the database" -Exception $PSItem.Exception
            Stop-PSFFunction -Message "Stopping because of errors"
            return
        }
        finally {
            if ($sqlCommand.Connection.State -ne [System.Data.ConnectionState]::Closed) {
                $sqlCommand.Connection.Close()
            }
    
            $sqlCommand.Dispose()
        }
    }
    end {
        Invoke-TimeSignal -End

    }
}


<#
 
InstanceName : MicrosoftDynamicsAX
ConfigurationName : DEBUG
BinDirectory : C:\Program Files\Microsoft Dynamics AX\60\Server\MicrosoftDynamicsAX\Bin\
ExecutablePath : C:\Program Files\Microsoft Dynamics AX\60\Server\MicrosoftDynamicsAX\Bin\Ax32Serv.exe
FileVersion : 6.3.6000.7046
ProductVersion : 6.3.6000.7046
FileVersionUpdated : 6.3.6000.7046
ProductVersionUpdated : 6.3.6000.7046
DatabaseServer : GJ-AX2012CU13
DatabaseName : MicrosoftDynamicsAX
ModelstoreDatabase : MicrosoftDynamicsAX_model
AosPort : 2712
WsdlPort : 8101
NetTcpPort : 8201
RegistryKeyPath : HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Dynamics Server\6.0\01
InstanceNumber : 01
ComputerName : GJ-AX2012CU13
 
 
SERVERID ISDEFAULTREPORTMODELSERVER SERVERURL ISDEFAULTREPORTLIBRARYSERVER AXAPTAREPORTFOLDER DESCRIPTION DATASOURCENAME REPORTMANAGERURL SERVERINSTANCE AOSID CONFIGURATIONID ISSHAREPOINTINTEGRATED RECVERSION RECID
ESADM01 0 http://esadm01:81/ReportServer 1 DynamicsAX http://esadm01:81/Reports MSSQLSERVER 01@ESADM01 SSRS_LIVE 0 546259976 5637145176
 
#>



<#
    .SYNOPSIS
        Get WMDP details
         
    .DESCRIPTION
        Get the most relevant WMDP details from your AX 2012 environment
         
    .EXAMPLE
        PS C:\> Get-AxWMDPDetails
         
        This will get all the relevant WMDP details from the AX 2012 environment
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Get-AxWMDPDetails {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding()]
    param ( )

    Get-WMDPDetailsFromIIS
}


<#
    .SYNOPSIS
        Import an AX 2012 modelstore file
         
    .DESCRIPTION
        Import an AX 2012 modelstore file into the modelstore database
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER ModelstoreDatabase
        Name of the modelstore database
         
        Default value is: "MicrosoftDynamicsAx_model"
         
        Note: From AX 2012 R2 and upwards you need to provide the full name for the modelstore database. E.g. "AX2012R3_PROD_model"
         
    .PARAMETER SchemaName
        Name of the schema to import the modelstore into
         
        Default value is: "TempSchema"
         
    .PARAMETER Path
        Path to the location where you want the file to be exported
         
        Default value is: "c:\temp\ax2012.tools"
         
    .PARAMETER IdConflictMode
        Parameter to instruct how the import should handle ID conflicts if it hits any during the import
         
        Valid options:
        "Reject"
        "Push"
        "Overwrite"
         
    .PARAMETER Apply
        Switch to instruct the cmdlet to switch modelstore with the SchemaName in as the current code
         
    .PARAMETER GenerateScript
        Switch to instruct the cmdlet to only generate the needed command and not execute it
         
    .EXAMPLE
        PS C:\> Import-AXModelStoreV2 -SchemaName TempSchema -Path C:\Temp\ax2012.tools\MicrosoftDynamicsAx.axmodelstore
         
        This will execute the cmdlet with some of the default values.
        This will work against the SQL server that is on localhost.
        The database is expected to be "MicrosoftDynamicsAx_model".
        The import will import the modelstore into the "TempSchema".
        The path where the modelstore file you want to import must exists is: "c:\temp\ax2012.tools\MicrosoftDynamicsAx.axmodelstore".
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Import-AXModelStoreV2 {
    [CmdletBinding(DefaultParameterSetName = "ImportModelstore")]
    [OutputType([System.String], ParameterSetName = "Generate")]
    Param(
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase,

        [Parameter(ParameterSetName = "ImportModelstore")]
        [Parameter(ParameterSetName = "ApplyModelstore")]
        [string] $SchemaName = "TempSchema",

        [Parameter(ParameterSetName = "ImportModelstore")]
        [string] $Path = (Join-Path $Script:DefaultTempPath "MicrosoftDynamicsAx.axmodelstore"),

        [ValidateSet("Reject", "Push", "Overwrite")]
        [Parameter(ParameterSetName = "ImportModelstore")]
        [string] $IdConflictMode,
        
        [Parameter(ParameterSetName = "ApplyModelstore")]
        [switch] $Apply,

        [switch] $GenerateScript
    )

    Invoke-TimeSignal -Start

    if (-not (Test-PathExists -Path $Path -Type Container -Create)) { return }

    if (-not (Test-PathExists -Path $Script:AxPowerShellModule -Type Leaf)) { return }

    $params = @{
        Server   = $DatabaseServer
        Database = $ModelstoreDatabase
    }

    if ($PSCmdlet.ParameterSetName -eq "ImportModelstore") {
        $params.File = $Path
        $params.SchemaName = $SchemaName

        if ($PSBoundParameters.ContainsKey("IdConflictMode")) {
            $params.IdConflict = $IdConflictMode
        }
    }
    elseif ($PSCmdlet.ParameterSetName -eq "ApplyModelstore") {
        $params.Apply = $SchemaName
    }

    if ($GenerateScript) {
        $arguments = Convert-HashToArgString -InputObject $params

        "Import-AxModelStore $($arguments -join ' ')"
    }
    else {
        Write-PSFMessage -Level Verbose -Message "Starting the export of the model store"
      
        $null = Import-Module $Script:AxPowerShellModule
        
        Import-AXModelStore @params

        Clear-Ax2012StandardPowershellModule
    }

    Invoke-TimeSignal -End
}


<#
    .SYNOPSIS
        Import AX 2012 model
         
    .DESCRIPTION
        Import AX 2012 model into the AX 2012 Model store
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER ModelstoreDatabase
        Name of the modelstore database
         
        Default value is: "MicrosoftDynamicsAx_model"
         
        Note: From AX 2012 R2 and upwards you need to provide the full name for the modelstore database. E.g. "AX2012R3_PROD_model"
         
    .PARAMETER Path
        Path to the folder containing the AX model file(s) that you want to import
         
        The cmdlet will traverse all sub folders for files and import them based on their names
         
    .PARAMETER ConflictMode
        Instructs the cmdlet to handle conflicts
         
        The list of options is:
        "Reject"
        "Push"
        "Overwrite"
         
    .PARAMETER CreateParents
        Switch to instruct the cmdlet to create missing parents on import
         
    .PARAMETER NoOptimize
        Switch to instruct the cmdlet to skip the optimization on import
         
        This makes sense if you are import more than 1-2 AX 2012 models at the same time
         
    .PARAMETER NoPrompt
        Switch to instruct the cmdlet not to prompt you with anything
         
    .PARAMETER GenerateScript
        Switch to instruct the cmdlet to output a script that you can execute manually later
         
        Using this will not import any AX 2012 models into the model store
         
    .EXAMPLE
        PS C:\> Import-AxModelV2 -Path "c:\temp\ax2012.tools\dev-models"
         
        The cmdlet will look for all the AX 2012 models located in "c:\temp\ax2012.tools\dev-models" or any of its sub folders.
        The ConflictMode is set to the default value of "OverWrite".
        The Database Server is set to the default value of "localhost".
        The Modelstore Database is set to the default value of "MicrosoftDynamicsAx_model".
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

Function Import-AxModelV2 {
    [CmdletBinding()]
    [OutputType([System.String], ParameterSetName="Generate")]
    Param(
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ValueFromPipeline = $true, Position = 1)]
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ValueFromPipeline = $true, Position = 2)]
        [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase,
        
        [Parameter(Mandatory = $false, Position = 3)]
        [string] $Path = $Script:DefaultTempPath,

        [Parameter(Mandatory = $false, Position = 4)]
        [ValidateSet("Reject", "Push", "Overwrite")]
        [string] $ConflictMode = "Overwrite",
        
        [switch] $CreateParents,
        
        [switch] $NoOptimize,

        [switch] $NoPrompt,

        [Parameter(ParameterSetName = "Generate")]
        [switch] $GenerateScript
    )

    BEGIN {
        $null = Import-Module $Script:AxPowerShellModule

        if (-not (Test-PathExists -Path $Path -Type Container)) { return }
    }

    PROCESS {
        if (Test-PSFFunctionInterrupt) { return }

        Invoke-TimeSignal -Start

        $AxModelsPath = (Get-ChildItem -Path $Path | Where-Object {$_.PSIsContainer} | Sort-Object CreationTime -Descending | Select-Object -First 1 | Select-Object Fullname).FullName
        Write-PSFMessage -Level Verbose -Message "The newest / latest folder is: $AxModelsPath" -Target $AxModelsPath

        $AxModelFiles = Get-ChildItem -Path $AxModelsPath -Recurse -File
        
        $params = @{ Server = $DatabaseServer; Conflict = $ConflictMode;
            Database = $ModelstoreDatabase
        }
        
        $paramsSwitch = Get-HashtableKey -InputObject $PSBoundParameters -Keys @("CreateParents", "NoOptimize", "NoPrompt")

        foreach ($item in $AxModelFiles) {
            Write-PSFMessage -Level Verbose -Message "Working on file: $($item.FullName)" -Target $item.FullName
            $clonedParams = Get-DeepClone $params
            $clonedParams += @{ File = $item.FullName }

            if ($GenerateScript) {
                $arguments = Convert-HashToArgString -InputObject $clonedParams
                $argumentsSwitch = Convert-HashToArgStringSwitch -InputObject $paramsSwitch
                "Install-AxModel $($arguments -join ' ') $($argumentsSwitch -join ' ')"
            }
            else {
                #Install-AXModel @clonedParams @paramsSwitch
            }
        }
        
        Invoke-TimeSignal -End
    }

    END {

        Clear-Ax2012StandardPowershellModule
    }
}


<#
    .SYNOPSIS
        Initialize an AX 2012 modelstore
         
    .DESCRIPTION
        Initialize an AX 2012 modelstore against a modelstore database
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER ModelstoreDatabase
        Name of the modelstore database
         
        Default value is: "MicrosoftDynamicsAx_model"
         
        Note: From AX 2012 R2 and upwards you need to provide the full name for the modelstore database. E.g. "AX2012R3_PROD_model"
         
    .PARAMETER SchemaName
        Name of the schema in the modelstore database that you want to work against
         
        Default value is: "TempSchema"
         
    .PARAMETER DropSchema
        Switch to instruct the cmdlet to drop the schema supplied with the -SchemaName parameter
         
    .PARAMETER CreateSchema
        Switch to instruct the cmdlet to create the schema supplied with the -SchemaName parameter
         
    .PARAMETER CreateDb
        Switch to instruct the cmdlet to create a new modelstore inside the supplied -ModelstoreDatabase parameter
         
    .PARAMETER GenerateScript
        Switch to instruct the cmdlet to only generate the needed command and not execute it
         
    .EXAMPLE
        PS C:\> Initialize-AXModelStoreV2 -SchemaName TempSchema -CreateSchema
         
        This will execute the cmdlet with some of the default values.
        This will work against the SQL server that is on localhost.
        The database is expected to be "MicrosoftDynamicsAx_model".
        The cmdlet will create the "TempSchema" schema inside the modelstore database.
         
    .EXAMPLE
        PS C:\> Initialize-AXModelStoreV2 -SchemaName TempSchema -DropSchema
         
        This will execute the cmdlet with some of the default values.
        This will work against the SQL server that is on localhost.
        The database is expected to be "MicrosoftDynamicsAx_model".
        The cmdlet will drop the "TempSchema" schema inside the modelstore database.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Initialize-AXModelStoreV2 {
    [CmdletBinding(DefaultParameterSetName = "CreateSchema")]
    [OutputType('System.String')]
    param (
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase,

        [Parameter(ParameterSetName = "Drop")]
        [Parameter(ParameterSetName = "CreateSchema")]
        [string] $SchemaName = "TempSchema",

        [Parameter(ParameterSetName = "Drop")]
        [switch] $DropSchema,

        [Parameter(ParameterSetName = "CreateSchema")]
        [switch] $CreateSchema,

        [Parameter(ParameterSetName = "CreateDB")]
        [switch] $CreateDb,
        
        [switch] $GenerateScript
    )

    Invoke-TimeSignal -Start
    
    if (-not (Test-PathExists -Path $Script:AxPowerShellModule -Type Leaf)) { return }

    $params = @{
        Server   = $DatabaseServer
        Database = $ModelstoreDatabase
    }

    if ($PSCmdlet.ParameterSetName -eq "CreateSchema") {
        $params.SchemaName = $SchemaName
    }
    elseif ($PSCmdlet.ParameterSetName -eq "Drop") {
        $params.Drop = $SchemaName
    }
    elseif ($PSCmdlet.ParameterSetName -eq "CreateDB") {
        $params.CreateDB = $true
    }

    if ($GenerateScript) {
        $arguments = Convert-HashToArgString -InputObject $params

        "Initialize-AXModelStore $($arguments -join ' ')"
    }
    else {
        Write-PSFMessage -Level Verbose -Message "Starting the initialization of the model store"
        
        $null = Import-Module $Script:AxPowerShellModule

        Initialize-AXModelStore @params

        Clear-Ax2012StandardPowershellModule

    }

    Invoke-TimeSignal -End
}


<#
    .SYNOPSIS
        Start the AxBuild.exe
         
    .DESCRIPTION
        Invoke the AxBuild.exe with the necessary parameters to make it compile your application
         
    .PARAMETER BinDirectory
        The full path to the bin directory where the AOS instance is physical installed
         
    .PARAMETER AlternativeBinPath
        The full path to the client bin directory where AX 2012 Client is physical installed
         
    .PARAMETER InstanceNumber
        The 2 digit ([0-9][0-9]) number that the AOS instance has on the server
         
    .PARAMETER DatabaseServer
        The name of the server running SQL Server
         
    .PARAMETER ModelstoreDatabase
        The name of the AX 2012 modelstore database
         
    .PARAMETER Workers
        Number of workers that you want to utilize while compiling
         
        The built-in logic from AxBuild.exe will choose a number equal to your visible cores
        Leaving it blank or with 0 (Zero) will use the built-in logic from AxBuild.exe
         
    .PARAMETER Log
        Path to the log file you want AxBuild.exe to output to
         
    .EXAMPLE
        PS C:\> Get-AxAosInstance | Invoke-AxBuild
         
        This will find all AOS instances using the Get-AxAosInstance on the machine and pipe them to Invoke-AxBuild.
        For each AOS instance found it will start the AxBuild.exe against their individual details.
        It will store the log file under the default ax2012.tools folder.
         
    .EXAMPLE
        PS C:\> Invoke-AxBuild
         
        This will start the AxBuild.exe against the ActiveAos configuration.
        It will store the log file under the default ax2012.tools folder.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
#>

function Invoke-AxBuild {
    [CmdletBinding()]
    param (

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 1)]
        [Alias('Path')]
        [string] $BinDirectory = $Script:ActiveAosBindirectory,

        [Alias('AltBin')]
        [string] $AlternativeBinPath = $Script:ClientBin,
        
        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 3)]
        [Alias('Aos')]
        [string] $InstanceNumber = $Script:ActiveAosInstanceNumber,

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 4)]
        [Alias('DBServer')]
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 5)]
        [Alias('Modelstore')]
        [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase,

        [int] $Workers,

        [string] $Log = $(Join-Path $Script:DefaultTempPath "AxBuildLog.txt")
    )

    begin {
        $executable = Join-Path $BinDirectory "AXBuild.exe"
        $compiler = Join-Path $BinDirectory "ax32serv.exe"

        if (-not (Test-PathExists -Path (Split-Path -Path $Log -Parent) -Type Container -Create)) { return }
        if (-not (Test-PathExists -Path $executable, $compiler -Type Leaf)) { return }
    }
    process {
        if (Test-PSFFunctionInterrupt) { return }

        $params = New-Object System.Collections.ArrayList

        [void]$params.Add("xppcompileall")
        [void]$params.Add("/altbin=`"$AlternativeBinPath`"")
        [void]$params.Add("/aos=$InstanceNumber")
        [void]$params.Add("/dbserver=`"$DatabaseServer`"")
        [void]$params.Add("/modelstore=`"$ModelstoreDatabase`"")
        [void]$params.Add("/log=`"$Log`"")
        [void]$params.Add("/compiler=`"$compiler`"")

        if ((-not ($null -eq $Workers)) -and ($Workers -gt 0)) {
            [void]$params.Add("/workers=$Workers")
        }

        Write-PSFMessage -Level Verbose -Message "Starting $executable with $($params -join " ")" -Target ($params -join " ")

        Start-Process -FilePath $executable -ArgumentList ($params -join " ") -NoNewWindow -Wait
    }
}


<#
    .SYNOPSIS
        Invoke a http request for a Logic App
         
    .DESCRIPTION
        Invoke a Logic App using a http request and pass a json object with details about the calling function
         
    .PARAMETER Url
        The URL for the http endpoint that you want to invoke
         
    .PARAMETER Email
        The email address of the receiver of the message that the cmdlet will send
         
    .PARAMETER Subject
        Subject string to apply to the email and to the IM message
         
    .PARAMETER IncludeAll
        Switch to instruct the cmdlet to include all cmdlets (names only) from the pipeline
         
    .PARAMETER AsJob
        Switch to instruct the cmdlet to run the invocation as a job (async)
         
    .EXAMPLE
        PS C:\> Invoke-AxLogicApp -Email administrator@contoso.com -Subject "Work is done" -Url https://prod-35.westeurope.logic.azure.com:443/
         
        This will simply invoke an Azure Logic App with the email, subject and URL parameters that are needed to invoke an Azure Logic App.
        A notification will be sent to "administrator@contoso.com".
         
    .EXAMPLE
        PS C:\> Start-AxEnvironment -Aos | Invoke-AxLogicApp
         
        This will execute the sync process and when it is done it will invoke an Azure Logic App with the default parameters that have been configured for the system.
         
    .EXAMPLE
        PS C:\> Start-AxEnvironment -Aos | Invoke-AxLogicApp -Email administrator@contoso.com -Subject "Work is done" -Url https://prod-35.westeurope.logic.azure.com:443/
         
        This will execute the sync process and when it is done it will invoke an Azure Logic App with the email, subject and URL parameters that are needed to invoke an Azure Logic App.
        A notification will be sent to "administrator@contoso.com".
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Invoke-AxLogicApp {
    param (
        [string] $Url = $Script:ActiveLogicappUrl,
        
        [string] $Email = $Script:ActiveLogicappEmail,

        [string] $Subject = $Script:ActiveLogicappSubject,

        [switch] $IncludeAll,
        
        [switch] $AsJob

    )

    begin {
    }
    
    process {
        $pipes = $MyInvocation.Line.Split("|")
         
        $arrList = New-Object -TypeName "System.Collections.ArrayList"
        foreach ($item in $pipes.Trim()) {
            $null = $arrList.Add( $item.Split(" ")[0])
        }

        $strMessage = "";

        if ($IncludeAll) {
            $strMessage = $arrList -Join ", "
        }
        else {
            $strMessage = $arrList[$MyInvocation.PipelinePosition - 2]
        }

        $strMessage = "The following list of cmdlets has executed: $strMessage"
        
        Invoke-PSNMessage -Url $URL -ReceiverEmail $Email -Subject $Subject -Message $strMessage -AsJob:$AsJob
    }
    
    end {
    }
}


<#
    .SYNOPSIS
        Fix table and field ID conflicts
         
    .DESCRIPTION
        Fixes both table and field IDs in the AX SqlDictionary (data db) to match the AX code (Model db)
         
        Useful for after a database has been restored and the table or field IDs do not match
        Run this command instead of letting the database synchronization process drop and recreate the table
         
        Before running:
        Stop the AOS
        Always take the appropriate SQL backups before running this script
         
        After running:
        Start the AOS
        Sync the database within AX
         
        Note:
        Objects that are new in AOT will get created in SQL dictionary when synchronization happens
         
    .PARAMETER DatabaseServer
        Server name of the database server
         
        Default value is: "localhost"
         
    .PARAMETER DatabaseName
        Name of the database
         
        Default value is: "MicrosoftDynamicsAx"
         
    .PARAMETER ModelstoreDatabase
        Name of the modelstore database
         
        Default value is: "MicrosoftDynamicsAx_model"
         
    .PARAMETER SqlUser
        User name of the SQL Server credential that you want to use when working against the database
         
    .PARAMETER SqlPwd
        Password of the SQL Server credential that you want to use when working against the database
         
    .PARAMETER GenerateScript
        When provided the SQL is returned and not executed
         
        Note: This is useful for troubleshooting or providing the script to a DBA with access to the server
         
    .EXAMPLE
        PS C:\> Resolve-AxTableFieldIDs
         
        This will execute the cmdlet with all the default values.
        This will work against the SQL server that is on localhost.
        The database is expected to be "MicrosoftDynamicsAx_model".
         
    .NOTES
        Author: Dag Calafell, III (@dodiggitydag)
        Reference: http://calafell.me/the-ultimate-ax-2012-table-and-field-id-fix-for-synchronization-errors/
#>

Function Resolve-AxTableFieldIDs {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    [OutputType('System.String')]
    Param(
        [Parameter(Mandatory = $false, Position = 1)]
        [string] $DatabaseServer = $Script:ActiveAosDatabaseserver,

        [Parameter(Mandatory = $false, Position = 2)]
        [string] $DatabaseName = $Script:ActiveAosDatabase,

        [Parameter(Mandatory = $false, Position = 3)]
        [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase,

        [Parameter(Mandatory = $false, Position = 4)]
        [string] $SqlUser,

        [Parameter(Mandatory = $false, Position = 5)]
        [string] $SqlPwd,

        [Switch] $GenerateScript
    )

    Invoke-TimeSignal -Start

    $baseParams = Get-DeepClone $PSBoundParameters
    $baseParams.Add("TrustedConnection", $true)

    $UseTrustedConnection = Test-TrustedConnection $baseParams

    $SqlParams = @{ DatabaseServer = $DatabaseServer; DatabaseName = $DatabaseName;
        SqlUser = $SqlUser; SqlPwd = $SqlPwd
    }

    $sqlCommand = Get-SqlCommand @SqlParams -TrustedConnection $UseTrustedConnection

    $commandText = (Get-Content "$script:ModuleRoot\internal\sql\resolve-sqldictionaryids.sql") -join [Environment]::NewLine
    
    $sqlCommand.CommandText = $commandText.Replace('@DatabaseName', $DatabaseName).Replace('@ModelDatabaseName', $ModelstoreDatabase)

    if ($GenerateScript) {
        (Get-SqlString $sqlCommand)
    }
    else {
        $handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] {param($sender, $event) Write-PSFMessage -Level Host -Message $($event.Message) -Target $($event.Message) }
        $sqlCommand.Connection.add_InfoMessage($handler)
        $sqlCommand.Connection.FireInfoMessageEventOnUserErrors = $true;

        try {
            Write-PSFMessage -Level InternalComment -Message "Executing a script against the database." -Target (Get-SqlString $sqlCommand)
            
            $sqlCommand.Connection.Open()
            $sqlCommand.ExecuteNonQuery()

            Write-PSFMessage -Level Host -Message "Complete"
        }
        catch {
            Write-PSFMessage -Level Host -Message "Something went wrong while working against the database" -Exception $PSItem.Exception
            Stop-PSFFunction -Message "Stopping because of errors"
            return
        }
        finally {
            if ($sqlCommand.Connection.State -ne [System.Data.ConnectionState]::Closed) {
                $sqlCommand.Connection.Close()
            }
    
            $sqlCommand.Dispose()
        }
    }

    Invoke-TimeSignal -End
}


<#
    .SYNOPSIS
        Set the active AX 2012 AOS configuration
         
    .DESCRIPTION
        Set the active AX 2012 AOS details and store it into the configuration storage
         
    .PARAMETER ComputerName
        The name of the computer / server that AOS resides on
         
    .PARAMETER BinDirectory
        The full path to the bin directory where the AOS instance is physical installed
         
    .PARAMETER InstanceNumber
        The 2 digit ([0-9][0-9]) number that the AOS instance has on the server
         
    .PARAMETER InstanceName
        The instance name the AOS server is registered with
         
    .PARAMETER DatabaseServer
        The name of the server running SQL Server
         
    .PARAMETER DatabaseName
        The name of the AX 2012 business data database
         
    .PARAMETER ModelstoreDatabase
        The name of the AX 2012 modelstore database
         
    .PARAMETER AosPort
        The TCP port that the AX 2012 AOS server is communicating with the AX clients on
         
    .PARAMETER WsdlPort
        The TCP port that the AX 2012 AOS server is communicating with all WSDL consuming applications on
         
    .PARAMETER NetTcpPort
        The TCP port that the AX 2012 AOS server is communicating with all NetTcp consuming applications on
         
    .PARAMETER ConfigStorageLocation
        Parameter used to instruct where to store the configuration objects
         
        The default value is "User" and this will store all configuration for the active user
         
        Valid options are:
        "User"
        "System"
         
        "System" will store the configuration so all users can access the configuration objects
         
    .PARAMETER Temporary
        Switch to instruct the cmdlet to only temporarily override the persisted settings in the configuration storage
         
    .PARAMETER Clear
        Switch to instruct the cmdlet to clear out all the stored configuration values
         
    .EXAMPLE
        PS C:\> Get-AxAosInstance | Select-Object -First 1 | Set-AxActiveAosConfiguration
         
        This will get all the AX 2012 AOS instances from the local machine and only select the first output.
        The output from the first AOS instance is saved into the configuration store.
         
    .EXAMPLE
        PS C:\> Set-AxActiveAosConfiguration -ComputerName AX2012PROD -DatabaseServer SQLSERVER -DatabaseName AX2012R3_PROD -ModelstoreDatabase AX2012R3_PROD_model -AosPort 2712
         
        This will update the active AOS configuration store settings.
        The computer name will be registered to: AX2012PROD
        The database server will be registered to: SQLSERVER
        The database name will be registered to: AX2012R3_PROD
        The model store database will be registered to: AX2012R3_PROD_model
        The AOS port will be registered to: 2712
         
    .EXAMPLE
        PS C:\> Set-AxActiveAosConfiguration -Clear
         
        This will clear out all the stored configuration values.
        It updates all the internal configuration variables, so all aos default values across the module will be empty.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Set-AxActiveAosConfiguration {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding()]
    param (

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 1)]
        [string[]] $ComputerName = @($env:computername),

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 2)]
        [string] $BinDirectory,

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 3)]
        [string] $InstanceNumber,
        
        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 4)]
        [string] $InstanceName,
        
        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 5)]
        [string] $DatabaseServer,
                
        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 6)]
        [string] $DatabaseName,
        
        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 7)]
        [string] $ModelstoreDatabase,

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 8)]
        [string] $AosPort,

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 9)]
        [string] $WsdlPort,

        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 10)]
        [string] $NetTcpPort,
        
        [ValidateSet('User', 'System')]
        [string] $ConfigStorageLocation = "User",

        [switch] $Temporary,

        [switch] $Clear
    )

    $configScope = Test-ConfigStorageLocation -ConfigStorageLocation $ConfigStorageLocation

    if (Test-PSFFunctionInterrupt) { return }
    
    if ($Clear) {

        Write-PSFMessage -Level Verbose -Message "Clearing all the ax2012.tools.active.aos configurations."

        foreach ($item in (Get-PSFConfig -FullName ax2012.tools.active.aos*)) {
            Set-PSFConfig -Fullname $item.FullName -Value ""
            if (-not $Temporary) { Register-PSFConfig -FullName $item.FullName -Scope $configScope }
        }
    }
    else {
        foreach ($key in $PSBoundParameters.Keys) {
            $value = $PSBoundParameters.Item($key)

            Write-PSFMessage -Level Verbose -Message "Working on $key with $value" -Target $value

            switch ($key) {
                "ComputerName" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.computername' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.computername' -Scope $configScope }
                }

                "BinDirectory" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.bindirectory' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.bindirectory' -Scope $configScope }
                }

                "InstanceNumber" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.instance.number' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.instance.number' -Scope $configScope }
                }

                "InstanceName" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.instancename' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.instancename' -Scope $configScope }
                }

                "DatabaseServer" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.databaseserver' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.databaseserver' -Scope $configScope }
                }
        
                "DatabaseName" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.database' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.database' -Scope $configScope }
                }

                "ModelstoreDatabase" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.modelstoredatabase' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.modelstoredatabase' -Scope $configScope }
                }

                "AosPort" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.aos.port' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.aos.port' -Scope $configScope }
                }

                "WsdlPort" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.wsdl.port' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.wsdl.port' -Scope $configScope }
                }

                "NetTcpPort" {
                    Set-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.nettcp.port' -Value $value
                    if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name 'active.aos.nettcp.port' -Scope $configScope }
                }

                Default {}
            }
       
        }
    }

    Update-ActiveVariables
}


<#
    .SYNOPSIS
        Set the details for the logic app invoke cmdlet
         
    .DESCRIPTION
        Store the needed details for the module to execute an Azure Logic App using a HTTP request
         
    .PARAMETER Url
        The URL for the http request endpoint of the desired logic app
         
    .PARAMETER Email
        The receiving email address that should be notified
         
    .PARAMETER Subject
        The subject of the email that you want to send
         
    .PARAMETER ConfigStorageLocation
        Parameter used to instruct where to store the configuration objects
         
        The default value is "User" and this will store all configuration for the active user
         
        Valid options are:
        "User"
        "System"
         
        "System" will store the configuration so all users can access the configuration objects
         
    .PARAMETER Temporary
        Switch to instruct the cmdlet to only temporarily override the persisted settings in the configuration storage
         
    .EXAMPLE
        PS C:\> Set-AxLogicAppConfig -Email administrator@contoso.com -Subject "Work is done" -Url https://prod-35.westeurope.logic.azure.com:443/
         
        This will set all the details about invoking the Logic App.
         
    .EXAMPLE
        PS C:\> Set-AxLogicAppConfig -Email administrator@contoso.com -Subject "Work is done" -Url https://prod-35.westeurope.logic.azure.com:443/ -ConfigStorageLocation "System"
         
        This will set all the details about invoking the Logic App.
        The data will be stored in the system wide configuration storage, which makes it accessible from all users.
         
    .EXAMPLE
        PS C:\> Set-AxLogicAppConfig -Email administrator@contoso.com -Subject "Work is done" -Url https://prod-35.westeurope.logic.azure.com:443/ -Temporary
         
        This will set all the details about invoking the Logic App.
        The update will only last for the rest of this PowerShell console session.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Set-AxLogicAppConfig {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true )]
        [string] $Url,

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

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

        [ValidateSet('User', 'System')]
        [string] $ConfigStorageLocation = "User",

        [switch] $Temporary
    )

    $configScope = Test-ConfigStorageLocation -ConfigStorageLocation $ConfigStorageLocation

    if (Test-PSFFunctionInterrupt) { return }
    
    foreach ($key in $PSBoundParameters.Keys) {
        $value = $PSBoundParameters.Item($key)
        $name = $null

        Write-PSFMessage -Level Verbose -Message "Working on $key with $value" -Target $value

        
        switch ($key) {
            "Email" {
                $name = 'active.logicapp.email'
                $Script:LogicAppEmail = $value
            }

            "Url" {
                $name = 'active.logicapp.url'
                $Script:LogicAppUrl = $value
            }

            "Subject" {
                $name = 'active.logicapp.subject'
                $Script:LogicAppSubject = $value
            }

            Default {}
        }

        Set-PSFConfig -Module 'ax2012.tools' -Name $name -Value $value
        if (-not $Temporary) { Register-PSFConfig -Module 'ax2012.tools' -Name $name -Scope $configScope }
    }
}


<#
    .SYNOPSIS
        Start an AX 2012 environment
         
    .DESCRIPTION
        Start AX 2012 services in your environment
         
    .PARAMETER Server
        Name of the computer(s) that you want to work against
         
        Default value is the name from the ComputerName from AxActiveAosConfiguration
         
    .PARAMETER DisplayName
        DisplayName of windows service that you want to work against
         
        Accepts wildcards for searching. E.g. -DisplayName "*ax*obj*"
         
    .PARAMETER Name
        Name of the Windows Service that you want to work against
         
        This parameter is used when piping in the details
         
        Designed to work together with the Get-AxEnvironment cmdlet
         
    .PARAMETER ShowOutput
        Switch to instruct the cmdlet to output the status for the service
         
    .EXAMPLE
        PS C:\> Start-AxEnvironment -Server TEST-AOS-01 -DisplayName *ax*obj*
         
        This will start the service(s) that match the search pattern "*ax*obj*" on the server named "TEST-AOS-01".
         
    .EXAMPLE
        PS C:\> Start-AxEnvironment -Server TEST-AOS-01 -DisplayName *ax*obj* -ShowOutput
         
        This will start the service(s) that match the search pattern "*ax*obj*" on the server named "TEST-AOS-01".
        It will show the status for the service(s) on the server afterwards.
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment -ComputerName TEST-AOS-01 -Aos | Start-AxEnvironment -ShowOutput
         
        This will scan the "TEST-AOS-01" server for all AOS instances and start them.
        It will show the status for the service(s) on the server afterwards.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Start-AxEnvironment {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(DefaultParameterSetName = "Default")]
    param (

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = "Pipeline")]
        [Parameter(ParameterSetName = "Default", Position = 1)]
        [Alias('ComputerName')]
        [string[]] $Server = $Script:ActiveAosComputername,

        [Parameter(Mandatory = $true, ParameterSetName = "Default", Position = 2)]
        [string] $DisplayName,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = "Pipeline")]
        [string[]] $Name,

        [switch] $ShowOutput
    )

    begin {
        $output = New-Object System.Collections.ArrayList
    }

    process {
        $baseParams = @{ComputerName = $Server; ErrorAction = "SilentlyContinue"}

        if ($PSBoundParameters.ContainsKey("Name")) {
            foreach ($item in $Name) {
                if (($item.Trim().Length -eq 0) -or ($Name -eq "*")) {
                    Write-PSFMessage -Level Host -Message "It seems that you didn't provide any Name. That would result in starting all services." -Exception $PSItem.Exception
                    Stop-PSFFunction -Message "Stopping because of missing filters."
                    return
                }
            }

            if (Test-PSFFunctionInterrupt) { return }

            $baseParams.Name = $Name
        }
        else {
            if (($DisplayName.Length -gt 0) -and (-not($DisplayName -eq "*"))) {
                if ($DisplayName -notmatch "\*" ) {
                    $DisplayName = "*$DisplayName*"
                }

                $baseParams.DisplayName = $DisplayName
            }
            else {
                Write-PSFMessage -Level Host -Message "It seems that you didn't provide any Display Name. That would result in starting all services." -Exception $PSItem.Exception
                Stop-PSFFunction -Message "Stopping because of missing filters."
                return
            }
        }

        Get-Service @baseParams | Start-Service -ErrorAction SilentlyContinue

        if ($ShowOutput) {
            $service = Get-Service @baseParams | Select-Object @{Name = "Server"; Expression = {$Server}}, Name, Status, DisplayName
            $null = $output.Add($service)
        }
    }

    end {
        if ($ShowOutput) {
            $output.ToArray() | Select-Object Server, DisplayName, Status, Name
        }
    }
}


<#
    .SYNOPSIS
        Stop an AX 2012 environment
         
    .DESCRIPTION
        Stop an AX 2012 services in your environment
         
    .PARAMETER Server
        Name of the computer(s) that you want to work against
         
        Default value is the name from the ComputerName from AxActiveAosConfiguration
         
    .PARAMETER DisplayName
        DisplayName of windows service that you want to work against
         
        Accepts wildcards for searching. E.g. -DisplayName "*ax*obj*"
         
    .PARAMETER Name
        Name of the Windows Service that you want to work against
         
        This parameter is used when piping in the details
         
        Designed to work together with the Get-AxEnvironment cmdlet
         
    .PARAMETER ShowOutput
        Switch to instruct the cmdlet to output the status for the service
         
    .PARAMETER Force
        Switch to instruct the cmdlet to force the stopping of the service
         
    .EXAMPLE
        PS C:\> Stop-AxEnvironment -Server TEST-AOS-01 -DisplayName *ax*obj*
         
        This will stop the service(s) that match the search pattern "*ax*obj*" on the server named "TEST-AOS-01".
         
    .EXAMPLE
        PS C:\> Stop-AxEnvironment -Server TEST-AOS-01 -DisplayName *ax*obj* -ShowOutput
         
        This will stop the service(s) that match the search pattern "*ax*obj*" on the server named "TEST-AOS-01".
        It will show the status for the service(s) on the server afterwards.
         
    .EXAMPLE
        PS C:\> Get-AxEnvironment -ComputerName TEST-AOS-01 -Aos | Stop-AxEnvironment -ShowOutput
         
        This will scan the "TEST-AOS-01" server for all AOS instances and stop them.
        It will show the status for the service(s) on the server afterwards.
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Stop-AxEnvironment {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(DefaultParameterSetName = "Default")]
    param (

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = "Pipeline")]
        [Parameter(ParameterSetName = "Default", Position = 1)]
        [Alias('ComputerName')]
        [string[]] $Server = $Script:ActiveAosComputername,

        [Parameter(Mandatory = $true, ParameterSetName = "Default", Position = 2)]
        [string] $DisplayName,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = "Pipeline")]
        [string[]] $Name,

        [switch] $ShowOutput,

        [switch] $Force

    )

    begin {
        $output = New-Object System.Collections.ArrayList
    }

    process {
        $baseParams = @{ComputerName = $Server; ErrorAction = "SilentlyContinue"}

        if ($PSBoundParameters.ContainsKey("Name")) {
            foreach ($item in $Name) {
                if (($item.Trim().Length -eq 0) -or ($Name -eq "*")) {
                    Write-PSFMessage -Level Host -Message "It seems that you didn't provide any Name. That would result in shutting down all services." -Exception $PSItem.Exception
                    Stop-PSFFunction -Message "Stopping because of missing filters."
                    return
                }
            }

            if (Test-PSFFunctionInterrupt) { return }

            $baseParams.Name = $Name
        }
        else {
            if (($DisplayName.Length -gt 0) -and (-not($DisplayName -eq "*"))) {
                if ($DisplayName -notmatch "\*" ) {
                    $DisplayName = "*$DisplayName*"
                }

                $baseParams.DisplayName = $DisplayName
            }
            else {
                Write-PSFMessage -Level Host -Message "It seems that you didn't provide any Display Name. That would result in shutting down all services." -Exception $PSItem.Exception
                Stop-PSFFunction -Message "Stopping because of missing filters."
                return
            }
        }

        Write-PSFMessage -Level Verbose -Message "Stopping the specified services." -Target ((Convert-HashToArgString $baseParams) -join ",")
        Get-Service @baseParams | Stop-Service -Force:$Force -ErrorAction SilentlyContinue
        #Write-PSFMessage -Level Host -Message "I would be stopping things now" -Target $Var ($Name -join ", ")

        if ($ShowOutput) {
            $service = Get-Service @baseParams | Select-Object @{Name = "Server"; Expression = {$Server}}, Name, Status, DisplayName
            $null = $output.Add($service)
        }
    }

    end {
        if ($ShowOutput) {
            $output.ToArray() | Select-Object Server, DisplayName, Status, Name
        }
    }
}