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 "," ) $mrProcessName = "MR2012ProcessService" $mrApplicationName = "MR2012ApplicationService" $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(@($mrProcessName, $mrApplicationName, $dixfName)) } else { if ($Aos) { for ($i = 1; $i -lt 100; $i++) { $null = $Services.Add("AOS60`$$($i.ToString("00"))") } } if ($ManagementReporter) { $null = $Services.Add($mrProcessName) $null = $Services.Add($mrApplicationName) } 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 All 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 -ShowOriginalProgress 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 -ShowOriginalProgress 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 { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidDefaultValueSwitchParameter", "")] [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Alias('Server')] [string[]] $ComputerName = $Script:ActiveAosComputername, [Parameter(ParameterSetName = 'Default')] [switch] $All = $true, [Alias('InstanceName')] [string] $AosInstanceName = $(if (-not ([System.String]::IsNullOrEmpty($Script:ActiveAosInstancename))) { "*$Script:ActiveAosInstancename" } else { "*" }), [Parameter(ParameterSetName = 'Specific')] [switch] $Aos, [Parameter(ParameterSetName = 'Specific')] [switch] $ManagementReporter, [Parameter(ParameterSetName = 'Specific')] [switch] $DIXF, [switch] $ScanAllAosServices ) if ($PSCmdlet.ParameterSetName -eq "Specific") { $All = $false } if (!$All -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) { if ($includeParamNames -notcontains $key ) {continue} Write-PSFMessage -Level Verbose -Message "Working on key: $key" -Target $key $null = $params.Add($key, $true) } if ($params.Count -eq 0) { if ($All) { $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 ($null -ne $searchServicesAos -and $searchServicesAos.count -gt 0) { Write-PSFMessage -Level Verbose -Message "`$searchServicesAos used for searching" $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 ($null -ne $Services -and $Services.count -gt 0) { Write-PSFMessage -Level Verbose -Message "`$Services used for searching" $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 Force Instruct the cmdlet to overwrite any existing bak (backup) tables from previous executions .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( [string] $DatabaseServer = $Script:ActiveAosDatabaseserver, [string] $DatabaseName = $Script:ActiveAosDatabase, [string] $ModelstoreDatabase = $Script:ActiveAosModelstoredatabase, [string] $SqlUser, [string] $SqlPwd, [Switch] $Force, [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 } $forceParameterValue = "0" if ($Force) { $forceParameterValue = "1" } $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).Replace("@ForceValue", $forceParameterValue) 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 Instruct the cmdlet to only temporarily override the persisted settings in the configuration storage .PARAMETER Clear 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 ShowOriginalProgress 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* -ShowOriginalProgress 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 -ShowOriginalProgress 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] $ShowOriginalProgress ) begin { $output = New-Object System.Collections.ArrayList $warningActionValue = "SilentlyContinue" if ($ShowOriginalProgress) { $warningActionValue = "Continue" } } 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 -WarningAction $warningActionValue $service = Get-Service @baseParams | Select-Object @{Name = "Server"; Expression = { $Server } }, Name, Status, DisplayName $null = $output.Add($service) } end { $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 ShowOriginalProgress 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* -ShowOriginalProgress 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 -ShowOriginalProgress 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] $ShowOriginalProgress, [switch] $Force ) begin { $output = New-Object System.Collections.ArrayList $warningActionValue = "SilentlyContinue" if ($ShowOriginalProgress) { $warningActionValue = "Continue" } } 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 -WarningAction $warningActionValue $service = Get-Service @baseParams | Select-Object @{Name = "Server"; Expression = { $Server } }, Name, Status, DisplayName $null = $output.Add($service) } end { $output.ToArray() | Select-Object Server, DisplayName, Status, Name } } |