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
    $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 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
        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:\> Invoke-AxExportModelstore
         
        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
}


@("AxUtilLib", "AxUtilLib.PowerShell", "Microsoft.Dynamics.Administration", "Microsoft.Dynamics.AX.Framework.Management", "Microsoft.Dynamics.ManagementUtilities")


<#
    .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 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 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:\> Invoke-AxImportModelstore -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
        Aaaaa aaa a aaa a a aa
         
    .DESCRIPTION
        Bbbb b b b b bbbbb bbbb
         
    .PARAMETER Path
        Cccc ccccc ccc cc cc
         
    .EXAMPLE
        PS C:\> Invoke-AxBuild
         
        This will work
         
    .NOTES
        Author: M�tz Jensen (@Splaxi)
         
#>

function Invoke-AxBuild {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
    [CmdletBinding()]
    param (
        [string] $Path = $Script:ActiveAosBindirectory

        <#
        ActiveAosAosPort
        ActiveAosBindirectory
        ActiveAosComputername
        ActiveAosDatabase
        ActiveAosDatabaseserver
        ActiveAosInstancename
        ActiveAosInstanceNumber
        ActiveAosModelstoredatabase
        ActiveAosNettcpPort
        ActiveAosWsdlPort
        #>

    )
    
    Get-Variable
}


<#
    .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
         
    .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
         
    .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
    )

    $configScope = Test-ConfigStorageLocation -ConfigStorageLocation $ConfigStorageLocation

    if (Test-PSFFunctionInterrupt) { return }
    
    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
                $Script:ActiveAosComputername = $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
                $Script:ActiveAosInstancename = $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
                $Script:ActiveAosDatabaseserver = $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
                $Script:ActiveAosModelstoredatabase = $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 {}
        }
       
    }
}


<#
    .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 ($PSCmdlet.ParameterSetName -eq "Pipeline") {
            $baseParams.Name = $Name
        }
        else {
            if ($DisplayName -notmatch "\*" ) {
                $DisplayName = "*$DisplayName*"
            }

            $baseParams.DisplayName = $DisplayName
        }

        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 ($PSCmdlet.ParameterSetName -eq "Pipeline") {
            $baseParams.Name = $Name
        }
        else {
            if ($DisplayName -notmatch "\*" ) {
                $DisplayName = "*$DisplayName*"
            }

            $baseParams.DisplayName = $DisplayName
        }

        Get-Service @baseParams | Stop-Service -Force:$Force -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
        }
    }
}