PSAdx.psm1
[System.Reflection.Assembly]::LoadFrom( $(Join-Path -Path $PSScriptRoot -ChildPath "\bin\Kusto.Data.dll")) | Out-Null function PSLogger { param ( # The file that will recieve the log event [Parameter(Mandatory=$true)] $LogFile, # Strings that needs to be logged, allowing array for multi-line entries/batch [Parameter(Mandatory=$true)] [String[]] $LogText ) #Simple now but allowing for more later if needed $LogText | Out-File -FilePath $LogFile -NoClobber -Force } ############################## #.SYNOPSIS #Returns an object that represents an AnalysisPack # #.DESCRIPTION #The Invoke-PSAdxAnalysisPack cmdlet will return an object which contains the templates, queries, and connections of an analysis pack. # #.PARAMETER AnalysisPackPath #Full or relatice path to the Analysis Pack you wish to invoke. # #.PARAMETER ReferenceNumber #Optional. A reference number which you wish to associate the instantiation of the Analysis Pack. # #.EXAMPLE #$x = Import-PSAdxAnalysisPack -AnalysisPackPath C:\Users\scepperl\Documents\CssAdx\analysispacks\AzureSqlDw -ReferenceId 123456 -TargetConnection MyConnectionName -Template "default.xlsx" -MyCustomParam "custom_value"; # #.EXAMPLE #$x = Import-PSAdxAnalysisPack -AnalysisPackPath C:\Users\scepperl\Documents\CssAdx\analysispacks\AzureSqlDw -ReferenceId 123456 -TargetConnection MyConnectionName -Template "default.xlsx" -MyCustomParam "custom_value"; #$x.Analyze(); ############################## function Invoke-PSAdxAnalysisPack { [CmdletBinding(DefaultParametersetName="Command")] param ( [string]$AnalysisPackPath ,[string]$ReferenceId ) DynamicParam { $dyParams = Test-PSAdxAnalysisPack -Path $AnalysisPackPath $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary if($dyParams.Parameters.Count -gt 0) { foreach ($Item in $dyParams.Parameters) { $Attribute = New-Object System.Management.Automation.ParameterAttribute $Attribute.Mandatory = $true $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($Attribute) $ParamItem = New-Object System.Management.Automation.RuntimeDefinedParameter($Item, [string], $attributeCollection) $paramDictionary.Add($Item, $ParamItem) } } #Region Parameter $RegionAttribute = New-Object System.Management.Automation.ParameterAttribute $RegionAttribute.Mandatory = $true $regionAttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $regionAttributeCollection.Add($RegionAttribute) # Generate and set the ValidateSet $regionSet = New-Object -TypeName System.Collections.ArrayList foreach ($item in ([xml](Get-Content -Raw -Path (Join-Path -Path $AnalysisPackPath -Child "userconnections.xml"))).ArrayOfServerDescriptionBase.ServerDescriptionBase) {$regionSet.Add($item.Name) | Out-Null} $regionValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($regionSet.ToArray()) $regionAttributeCollection.Add($regionValidateSetAttribute) $regionParamItem = New-Object System.Management.Automation.RuntimeDefinedParameter('TargetConnection', [string], $regionAttributeCollection) $paramDictionary.Add('TargetConnection', $regionParamItem) #Template Parameter if ($dyParams.TemplateFile) { $TemplateAttribute = New-Object System.Management.Automation.ParameterAttribute $templateAttribute.Mandatory = $false $templateAttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $templateAttributeCollection.Add($TemplateAttribute) # Generate and set the ValidateSet $templateSet = Get-ChildItem (Join-Path -Path $AnalysisPackPath -Child "templates"); $templateValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($templateSet) $templateAttributeCollection.Add($templateValidateSetAttribute) $templateParamItem = New-Object System.Management.Automation.RuntimeDefinedParameter('Template', [string[]], $templateAttributeCollection) $paramDictionary.Add('Template', $templateParamItem) } return $paramDictionary } process { $cslParams = New-Object -TypeName PSObject; $BaseTemplates = ""; #Testing the pack try { $TestResult = Test-PSAdxAnalysisPack -Path $AnalysisPackPath -ErrorAction Stop; $cslParams = $TestResult.Parameters if ($TestResult.TemplateFile) { $BaseTemplates = Get-ChildItem -Path (Join-Path -Path $AnalysisPackPath -Child "templates"); } } catch { #Pack not valid } #Import analyze method from analysis pack . (Join-Path -Path $AnalysisPackPath -Child "analyze.ps1" -Resolve); # Ensure output folder exists $OutputPath = Join-Path -Path $AnalysisPackPath -ChildPath "Output"; if (-Not (Test-Path -Path $OutputPath -PathType Container)) {New-Item -Path $OutputPath -ItemType Directory -Force -ErrorAction SilentlyContinue}; $cleanOuputFolder = { Param ( [int]$DaysToRetain ) $dateToDelete = (Get-Date).AddDays($DaysToRetain * -1); Get-ChildItem (Join-Path -Path $AnalysisPackPath -Child "output") | Where-Object{$_.LastWriteTime -lt $dateToDelete} | Remove-Item -ErrorAction SilentlyContinue; } #Get all connections for population $AllConnections = ([xml](Get-Content -Raw -Path (Join-Path -Path $AnalysisPackPath -Child "userconnections.xml"))).ArrayOfServerDescriptionBase.ServerDescriptionBase; $queryTargetConnection = $PSBoundParameters.TargetConnection; $passingParam = @{}; foreach ($item in $cslParams) { if ($PSBoundParameters.ContainsKey($item)) {$passingParam.Add($item, $PSBoundParameters.Item($item))} } $ExecuteQuery = { Param ( $ConnectionName ) $FullConnectionString = ($this.Connections | Where-Object {$_.Name -eq $ConnectionName}).ConnectionString Invoke-PSAdxQuery -ConnectionString $FullConnectionString -DatabaseName ($FullConnectionString | Select-String -Pattern "Catalog\=(\w*)").Matches.Groups[1].Value -Query $this.QueryText -QueryParameters $this.Parameters; }; $props = @{ AnalysisPackPath = $AnalysisPackPath; ReferenceId = $ReferenceId; OutputPath = "$OutputPath"; Template = $(try {$PSBoundParameters.Template} catch {}); Templates = $BaseTemplates; Connections = $AllConnections; TargetConnection = $queryTargetConnection; Queries = Get-ChildItem (Join-Path -Path $AnalysisPackPath -Child "queries") | ForEach-Object { $props = @{ Name = $_.BaseName; QueryText = Get-Content -Raw -Path $_.FullName; Connections = $AllConnections; TargetConnection = $queryTargetConnection; Parameters = $passingParam }; $queryObject = New-Object -TypeName PSObject -Property $props; $queryObject | Add-Member -Name Execute -MemberType ScriptMethod -Value $ExecuteQuery; #Setting visiable properties. $visibleproperties = @('Name', 'QueryText'); $defaultDisplayPropertySet = New-Object -TypeName System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$visibleproperties); $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet); $queryObject | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers; return $queryObject; }; } $outObject = New-Object -TypeName PSObject -Property $props; $outObject | Add-Member -Name Parameters -MemberType NoteProperty -Value $passingParam; $outObject | Add-Member -Name Analyze -MemberType ScriptMethod -Value $analyze; $outObject | Add-Member -Name CleanOuputFolder -MemberType ScriptMethod -Value $cleanOuputFolder; #Remove old items from the output folder (doing this for the user every time) $outObject.CleanOuputFolder(90); #Run the analysis $outObject.Analyze() return $outObject; } } function Invoke-PSAdxQuery { [CmdletBinding(DefaultParametersetName="Command")] param ( [string]$ConnectionString ,[string]$DatabaseName ,[string]$Query ,$QueryParameters ) begin { try { $kcsb = New-Object Kusto.Data.KustoConnectionStringBuilder ($ConnectionString, $(($ConnectionString | Select-String -Pattern "Catalog\=(\w*)").Matches.Groups[1].Value)) $queryProvider = [Kusto.Data.Net.Client.KustoClientFactory]::CreateCslQueryProvider($kcsb); } catch { Write-Error -Message "Error connecting to cluster. Please try again." } } process { try { # Configure properties $crp = New-Object Kusto.Data.Common.ClientRequestProperties; $crp.ClientRequestId = "MyPowershellScript.ExecuteQuery." + [Guid]::NewGuid().ToString(); $crp.SetOption([Kusto.Data.Common.ClientRequestProperties]::OptionServerTimeout, [TimeSpan]::FromSeconds(300)); foreach($key in $QueryParameters.keys) { $crp.SetParameter($key, $QueryParameters[$key]) }; #Execute the query $reader = $queryProvider.ExecuteQuery($Query, $crp) $dataTable = [Kusto.Cloud.Platform.Data.ExtendedDataReader]::ToDataSet($reader).Tables return $dataTable } catch { Throw $_.Exception.Message } } end { } } #Function to get the parameters needed in an analysis pack CSL file function Get-PSAdxCSLParameter { param ( # Path to the CSL file [Parameter(Mandatory=$true)] [string] $Path ) begin { $foundParams = New-Object -TypeName System.Collections.ArrayList if (-Not (Test-Path -Path $Path -PathType Leaf)) { return $foundParams; } } process{ try { $regMatches = ((Get-Content -Path $Path | Select-String "^declare query_parameters.*").Matches[0].Value | Select-String -Pattern "(\w*):\w*" -AllMatches -ErrorAction SilentlyContinue) foreach ($item in $regMatches.Matches.Groups) { if ($item.Value -notmatch "(\(|\)|\,|\:|\^|\"")") { $foundParams.Add([string]($item.Value).Trim()) | Out-Null } } } catch {} return $foundParams } end{ } } #Function to validate an analysis pack function Test-PSAdxAnalysisPack { [CmdletBinding()] param ( # Path to the analysis pack [Parameter(Mandatory=$true)] [String]$Path ) begin { #Validate that the path exist and is a folder if (-Not (Test-Path -Path $Path -PathType Container)) { Write-Error -Message "Path to analysis pack invalid. The provided path must be a valid folder/directory." -ErrorAction Stop } $outputObject = New-Object -TypeName PSObject -Property @{}; } process { #region Critical Items #Validate the analyze.ps1 script exist in the root of the analysis pack. if (Test-Path -Path (Join-Path -Path $Path -ChildPath "analyze.ps1") -PathType Leaf) { $outputObject | Add-Member -NotePropertyName "AnalyzeScript" -NotePropertyValue $true; } else { $outputObject | Add-Member -NotePropertyName "AnalysisScript" -NotePropertyValue $false; } #Valdate that userconnections.xml exists if (Test-Path -Path (Join-Path -Path $Path -ChildPath "userconnections.xml") -PathType Leaf) { $outputObject | Add-Member -NotePropertyName "UserConnections" -NotePropertyValue $true; } else { $outputObject | Add-Member -NotePropertyName "UserConnections" -NotePropertyValue $false; } #Validate that the Queries folder exist in the pack. if (Test-Path -Path (Join-Path -Path $Path -ChildPath "queries") -PathType Container) { $outputObject | Add-Member -NotePropertyName "QueriesFolder" -NotePropertyValue $true; } else { $outputObject | Add-Member -NotePropertyName "QueriesFolder" -NotePropertyValue $false; } #Validate that at least one csl script exist in the Queries folder. try { if ((Get-ChildItem -Path (Join-Path -Path $Path -ChildPath "queries") -Name *.csl -ErrorAction Stop).Count -gt 0) { $outputObject | Add-Member -NotePropertyName "CSLFile" -NotePropertyValue $true; } else { $outputObject | Add-Member -NotePropertyName "CSLFile" -NotePropertyValue $false; } } catch {$outputObject | Add-Member -NotePropertyName "CSLFile" -NotePropertyValue $false;} #endregion #region Non-Critical Items #Validate Templates folder exists if (Test-Path -Path (Join-Path -Path $Path -ChildPath "templates") -PathType Container) { $outputObject | Add-Member -NotePropertyName "TemplatesFolder" -NotePropertyValue $true; } else { $outputObject | Add-Member -NotePropertyName "TemplatesFolder" -NotePropertyValue $false; } #Validate that there is at least one template (any file) try { if ((Get-ChildItem -Path (Join-Path -Path $Path -ChildPath "templates") -Name *.* -ErrorAction Stop).Count -gt 0) { $outputObject | Add-Member -NotePropertyName "TemplateFile" -NotePropertyValue $true; } else { $outputObject | Add-Member -NotePropertyName "TemplateFile" -NotePropertyValue $false; } } catch {$outputObject | Add-Member -NotePropertyName "TemplateFile" -NotePropertyValue $false;} #endregion #region Additional Items #List Required Parameters in CSL queries if ($outputObject.CSLFile) { $foundParams = New-Object -TypeName System.Collections.ArrayList foreach ($item in Get-ChildItem -Path (Join-Path -Path $Path -ChildPath "queries") -Name *.csl -ErrorAction Stop) { ## Regex to get parameters from file foreach ($paramater in (Get-PSAdxCSLParameter -Path $item.PSPath)) { $foundParams.Add($paramater) | Out-Null; } } $outputObject | Add-Member -NotePropertyName "Parameters" -NotePropertyValue ($foundParams | Select-Object -Unique); } else { $outputObject | Add-Member -NotePropertyName "Parameters" -NotePropertyValue "None"; } #endregion } end { if ($outputObject.AnalyzeScript -eq $false -OR $outputObject.UserConnections -eq $false -OR $outputObject.QueriesFolder -eq $false -OR $outputObject.CSLFile -eq $false) { $outputObject | Add-Member -NotePropertyName "Result" -NotePropertyValue $false; Write-Error -Message "The provided analysis pack at $($PSBoundParameters.Path.ToString()) is not valid." } else {$outputObject | Add-Member -NotePropertyName "Result" -NotePropertyValue $true;} return $outputObject; } } |