diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..100c540 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Tests/Constants.Local.ps1 diff --git a/Build/build.requirements.psd1 b/Build/build.requirements.psd1 index 2b501a6..8c7fe10 100644 --- a/Build/build.requirements.psd1 +++ b/Build/build.requirements.psd1 @@ -4,14 +4,16 @@ Target = '$ENV:USERPROFILE\Documents\WindowsPowerShell\Modules' AddToPath = $True Parameters = @{ - Force = $True + #Force = $True } } + 'PSScriptAnalyzer' = '1.17.1' 'psake' = '4.7.0' 'PSDeploy' = '0.2.5' 'BuildHelpers' = '1.1.1' 'Pester' = '4.6.0' 'PSFramework' = '0.10.31.176' 'PSCodeCovIo' = '1.0.1' + 'Az' = '1.0.0' } \ No newline at end of file diff --git a/Build/deploy.psdeploy.ps1 b/Build/deploy.psdeploy.ps1 index 4469664..4d84a1d 100644 --- a/Build/deploy.psdeploy.ps1 +++ b/Build/deploy.psdeploy.ps1 @@ -37,11 +37,10 @@ if( } else { - "Skipping deployment: To deploy, ensure that...`n" + - "`t* You are in a known build system (Current: $ENV:BHBuildSystem)`n" + - "`t* You are committing to the master branch (Current: $ENV:BHBranchName) `n" + - "`t* Your commit message includes !deploy (Current: $ENV:BHCommitMessage)" | - Write-Host + Write-PSFMessage -Level 'Output' -Message ("Skipping deployment: To deploy, ensure that...`n" + + "`t* You are in a known build system (Current: $ENV:BHBuildSystem)`n" + + "`t* You are committing to the master branch (Current: $ENV:BHBranchName) `n" + + "`t* Your commit message includes !deploy (Current: $ENV:BHCommitMessage)") } # Publish to AppVeyor if we're in AppVeyor diff --git a/Build/psake.ps1 b/Build/psake.ps1 index 4249002..d924365 100644 --- a/Build/psake.ps1 +++ b/Build/psake.ps1 @@ -31,6 +31,47 @@ Task Init { } Task Test -Depends Init { + $lines + "`n`tSTATUS: Starting DPA VM" + + if (-not $ENV:PSDPA_AZ) { + Stop-PSFFunction -Message "Environment variable PSDPA_AZ is not set" -EnableException $true + } + + if (-not $ENV:PSDPA_AZ_APP) { + Stop-PSFFunction -Message "Environment variable PSDPA_AZ_APP is not set" -EnableException $true + } + + try { + $azPassword = ConvertTo-SecureString -String $ENV:PSDPA_AZ -AsPlainText -Force + $azCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @($ENV:PSDPA_AZ_APP, $azPassword) + $null = Connect-AzAccount -Tenant $ENV:PSDPA_AZ_TENANT -Credential $azCredential -ServicePrincipal + + $dpaVm = Get-AzVM -ResourceGroupName 'PSDPA' -Name 'dpa' -Status + $dpaVmStatus = $dpaVm.Statuses | Where-Object { $_.Code -like 'PowerState/*' } + + if ($dpaVmStatus.DisplayStatus -ne 'VM running') { + "Starting DPA VM" + $azStart = $dpaVm | Start-AzVM + + "Waiting up to 10 minutes for DPA to start" + $maxCycles = 60 + $cycles = 0 + do { + Start-Sleep -Seconds 10 + $cycles++ + } until ((Test-NetConnection '13.67.213.239' -Port 8123 -ErrorAction 'SilentlyContinue' -WarningAction 'SilentlyContinue' | Where-Object { $_.TcpTestSucceeded }) -or $cycles++ -ge $maxCycles ) + + Start-Sleep -Seconds 180 + } else { + "DPA VM is already running" + } + } catch { + Stop-PSFFunction -Message "Could not start Azure DPA VM" -ErrorRecord $_ -EnableException $true + } + $lines + "`n" + $lines "`n`tSTATUS: Testing with PowerShell $PSVersion" @@ -59,6 +100,13 @@ Task Test -Depends Init { } Remove-Item "$ProjectRoot\$TestFile" -Force -ErrorAction SilentlyContinue + + try { + $dpaVm | Stop-AzVM -Confirm:$false -Force + } catch { + Stop-PSFFunction -Message "Could not stop Azure DPA VM" -ErrorRecord $_ -EnableException $true + } + # Failed tests? # Need to tell psake or it will proceed to the deployment. Danger! if($TestResults.FailedCount -gt 0) @@ -66,6 +114,9 @@ Task Test -Depends Init { Write-Error "Failed '$($TestResults.FailedCount)' tests, build failed" } "`n" + + $lines + "`n`tSTATUS: Stopping DPA VM" } Task Build -Depends Test { @@ -75,16 +126,13 @@ Task Build -Depends Test { Set-ModuleFunctions # Bump the module version if we didn't already - Try - { - $GalleryVersion = Get-NextPSGalleryVersion -Name $env:BHProjectName -ErrorAction Stop - $GithubVersion = Get-MetaData -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -ErrorAction Stop + try { + [version]$GalleryVersion = Get-NextNugetPackageVersion -Name $env:BHProjectName -ErrorAction Stop + [version]$GithubVersion = Get-MetaData -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -ErrorAction Stop if($GalleryVersion -ge $GithubVersion) { Update-Metadata -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -Value $GalleryVersion -ErrorAction stop } - } - Catch - { + } catch { "Failed to update version for '$env:BHProjectName': $_.`nContinuing with existing version" } } diff --git a/Invoke-DpaFormatter.ps1 b/Invoke-DpaFormatter.ps1 new file mode 100644 index 0000000..6214aba --- /dev/null +++ b/Invoke-DpaFormatter.ps1 @@ -0,0 +1,88 @@ +function Invoke-DpaFormatter { + <# + .SYNOPSIS + Helps formatting function files to dbatools' standards + + .DESCRIPTION + Uses PSSA's Invoke-Formatter to format the target files and saves it without the BOM. + + .PARAMETER Path + The path to the ps1 file that needs to be formatted + + .PARAMETER EnableException + By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. + This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. + Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. + + .NOTES + Tags: Formatting + Author: Simone Bizzotto + + Website: https://dbatools.io + Copyright: (c) 2018 by dbatools, licensed under MIT + License: MIT https://opensource.org/licenses/MIT + + .LINK + https://dbatools.io/Invoke-DbatoolsFormatter + + .EXAMPLE + PS C:\> Invoke-DbatoolsFormatter -Path C:\dbatools\functions\Get-DbaDatabase.ps1 + + Reformats C:\dbatools\functions\Get-DbaDatabase.ps1 to dbatools' standards + + #> + [CmdletBinding()] + param ( + [parameter(Mandatory, ValueFromPipeline)] + [object[]]$Path, + [switch]$EnableException + ) + begin { + $HasInvokeFormatter = $null -ne (Get-Command Invoke-Formatter -ErrorAction SilentlyContinue).Version + if (!($HasInvokeFormatter)) { + Stop-Function -Message "You need a recent version of PSScriptAnalyzer installed" + } + $CBHRex = [regex]'(?smi)\s+<#[^#]*#>' + $CBHStartRex = [regex]'(?[ ]+)<#' + $CBHEndRex = [regex]'(?[ ]*)#>' + } + process { + foreach ($p in $Path) { + try { + $realPath = (Resolve-Path -Path $p -ErrorAction Stop).Path + } catch { + Stop-Function -Message "Cannot find or resolve $p" -Continue + } + + $content = Get-Content -Path $realPath -Raw -Encoding UTF8 + #strip ending empty lines + $content = $content -replace "(?s)`r`n\s*$" + try { + $content = Invoke-Formatter -ScriptDefinition $content -Settings CodeFormattingOTBS -ErrorAction Stop + } catch { + Write-Message -Level Warning "Unable to format $p" + } + #match the ending indentation of CBH with the starting one, see #4373 + $CBH = $CBHRex.Match($content).Value + if ($CBH) { + #get starting spaces + $startSpaces = $CBHStartRex.Match($CBH).Groups['spaces'] + if ($startSpaces) { + #get end + $newCBH = $CBHEndRex.Replace($CBH, "$startSpaces#>") + if ($newCBH) { + #replace the CBH + $content = $content.Replace($CBH, $newCBH) + } + } + } + $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False + $realContent = @() + #trim whitespace lines + foreach ($line in $content.Split("`n")) { + $realContent += $line.TrimEnd() + } + [System.IO.File]::WriteAllText($realPath, ($realContent -Join "`r`n"), $Utf8NoBomEncoding) + } + } +} \ No newline at end of file diff --git a/PSDPA/Classes/AccessToken.ps1 b/PSDPA/Classes/AccessToken.ps1 index 366c7d1..1795251 100644 --- a/PSDPA/Classes/AccessToken.ps1 +++ b/PSDPA/Classes/AccessToken.ps1 @@ -23,8 +23,8 @@ class AccessToken { [bool] IsValid() { return ` - (Get-Date) -lt $this.Expiration -and - $null -ne $this.AccessToken -and - $null -ne $this.TokenType + (Get-Date) -lt $this.Expiration -and + $null -ne $this.AccessToken -and + $null -ne $this.TokenType } -} +} \ No newline at end of file diff --git a/PSDPA/Classes/Annotation.ps1 b/PSDPA/Classes/Annotation.ps1 index aa7e1ab..8541347 100644 --- a/PSDPA/Classes/Annotation.ps1 +++ b/PSDPA/Classes/Annotation.ps1 @@ -30,4 +30,4 @@ class Annotation { $this.Description = $Json.description $this.Time = $Json.time } -} +} \ No newline at end of file diff --git a/PSDPA/Classes/Monitor.ps1 b/PSDPA/Classes/Monitor.ps1 index 18803be..a661bfc 100644 --- a/PSDPA/Classes/Monitor.ps1 +++ b/PSDPA/Classes/Monitor.ps1 @@ -70,15 +70,19 @@ class SqlServerMonitor : Monitor { } } +class AzureSQLDatabaseMonitor : Monitor { + AzureSQLDatabaseMonitor ([PSCustomObject] $Json) : base ($Json) {} +} + class MonitorFactory { static [Monitor[]] $Monitors static [Object] getByType ([Object] $O) { - return [MonitorFactory]::Monitors.Where({$_ -is $O}) + return [MonitorFactory]::Monitors.Where( {$_ -is $O}) } static [Object] getByName ([String] $Name) { - return [MonitorFactory]::Monitors.Where({$_.Name -eq $Name}) + return [MonitorFactory]::Monitors.Where( {$_.Name -eq $Name}) } [Monitor] New ([PSCustomObject] $Json) { @@ -86,4 +90,4 @@ class MonitorFactory { return (New-Object -TypeName "$type" -ArgumentList $Json) } -} +} \ No newline at end of file diff --git a/PSDPA/Private/Get-DpaAccessToken.ps1 b/PSDPA/Private/Get-DpaAccessToken.ps1 index e890bdc..24f9190 100644 --- a/PSDPA/Private/Get-DpaAccessToken.ps1 +++ b/PSDPA/Private/Get-DpaAccessToken.ps1 @@ -7,19 +7,21 @@ function Get-DpaAccessToken { $refreshToken = (Get-DpaConfig -Name 'refreshtoken').Value $request = @{ - grant_type = 'refresh_token' + grant_type = 'refresh_token' refresh_token = $refreshToken } try { + Write-PSFMessage -Level 'Verbose' -Message 'Getting an access token' + $response = Invoke-RestMethod -Uri $authTokenUri -Method 'POST' -Body $request $accessToken = New-Object -TypeName 'AccessToken' -ArgumentList $response Set-PSFConfig -Module 'psdpa' -Name 'accesstoken' -Value $accessToken $PSDefaultParameterValues['Invoke-DpaRequest:AccessToken'] = $accessToken + return $accessToken - } - catch { + } catch { Stop-PSFFunction -Message "Could not obtain access token" -ErrorRecord $_ -EnableException $EnableException } } \ No newline at end of file diff --git a/PSDPA/Private/Invoke-DpaRequest.ps1 b/PSDPA/Private/Invoke-DpaRequest.ps1 index 57e6d04..8ceed11 100644 --- a/PSDPA/Private/Invoke-DpaRequest.ps1 +++ b/PSDPA/Private/Invoke-DpaRequest.ps1 @@ -17,18 +17,13 @@ function Invoke-DpaRequest { $Parameters ) - if (-not $PSBoundParameters.ContainsKey('AccessToken')) { + if (-not $PSBoundParameters.ContainsKey('AccessToken') -or -not $AccessToken.IsValid()) { $AccessToken = Get-DpaAccessToken } - if (-not $AccessToken.IsValid()) { - Stop-PSFFunction -Message "You do not have a valid access token" - return - } - $headers = @{ - 'Accept' = 'application/json' - 'Content-Type' = 'application/json;charset=UTF-8' + 'Accept' = 'application/json' + 'Content-Type' = 'application/json;charset=UTF-8' 'Authorization' = $AccessToken.ToAuthorizationHeader() } @@ -48,12 +43,13 @@ function Invoke-DpaRequest { } $params = @{ - 'Uri' = $uri + 'Uri' = $uri 'Headers' = $headers - 'Method' = $Method + 'Method' = $Method } if ($PSBoundParameters.ContainsKey('Request')) { $params['Body'] = $Request | ConvertTo-Json + Write-Verbose $params['Body'] } Invoke-RestMethod @params diff --git a/PSDPA/Public/Add-DpaAnnotation.ps1 b/PSDPA/Public/Add-DpaAnnotation.ps1 index ffe050c..53705a2 100644 --- a/PSDPA/Public/Add-DpaAnnotation.ps1 +++ b/PSDPA/Public/Add-DpaAnnotation.ps1 @@ -92,25 +92,30 @@ function Add-DpaAnnotation { begin { if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Monitor = Get-DpaMonitor -MonitorName $MonitorName - } - elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { + } elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { $Monitor = Get-DpaMonitor -DatabaseId $DatabaseId } } process { foreach ($monitorObject in $Monitor) { + Write-PSFMessage -Level Verbose -Message "Adding annotation to Database ID $($monitorObject.DatabaseId)" $endpoint = "/databases/$($monitorObject.DatabaseId)/annotations" $request = @{ - 'title' = $Title + 'title' = $Title 'description' = $Description - 'createdBy' = $CreatedBy - 'time' = $Time.ToString("yyyy-MM-ddTHH\:mm\:sszzz") + 'createdBy' = $CreatedBy + 'time' = $Time.ToString("yyyy-MM-ddTHH\:mm\:sszzz") } $response = Invoke-DpaRequest -Endpoint $endpoint -Method 'Post' -Request $request - New-Object -TypeName 'Annotation' -ArgumentList $response.data + + try { + New-Object -TypeName 'Annotation' -ArgumentList $monitorObject, $response.data + } catch { + Stop-PSFFunction -Level Critical -Message 'Could not create annotation from API response' -ErrorRecord $_ -EnableException $EnableException + } } } -} +} \ No newline at end of file diff --git a/PSDPA/Public/Get-DpaAnnotation.ps1 b/PSDPA/Public/Get-DpaAnnotation.ps1 index 4888e62..ee65ad5 100644 --- a/PSDPA/Public/Get-DpaAnnotation.ps1 +++ b/PSDPA/Public/Get-DpaAnnotation.ps1 @@ -80,8 +80,7 @@ function Get-DpaAnnotation { begin { if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Monitor = Get-DpaMonitor -MonitorName $MonitorName - } - elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { + } elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { $Monitor = Get-DpaMonitor -DatabaseId $DatabaseId } } @@ -92,7 +91,7 @@ function Get-DpaAnnotation { $parameters = @{ 'startTime' = $StartTime.ToString("yyyy-MM-ddTHH\:mm\:ss.fffzzz") - 'endTime' = $EndTime.ToString("yyyy-MM-ddTHH\:mm\:ss.fffzzz") + 'endTime' = $EndTime.ToString("yyyy-MM-ddTHH\:mm\:ss.fffzzz") } $response = Invoke-DpaRequest -Endpoint $endpoint -Method 'Get' -Parameters $parameters diff --git a/PSDPA/Public/Get-DpaConfig.ps1 b/PSDPA/Public/Get-DpaConfig.ps1 index 3b33f17..79fc4bb 100644 --- a/PSDPA/Public/Get-DpaConfig.ps1 +++ b/PSDPA/Public/Get-DpaConfig.ps1 @@ -36,10 +36,10 @@ function Get-DpaConfig { $Name = $Name.ToLower() $results = [PSFramework.Configuration.ConfigurationHost]::Configurations.Values | Where-Object { - ($_.Name -like $Name) -and - ($_.Module -like $Module) -and - ((-not $_.Hidden) -or ($Force)) - } | Sort-Object Module, Name + ($_.Name -like $Name) -and + ($_.Module -like $Module) -and + ((-not $_.Hidden) -or ($Force)) + } | Sort-Object Module, Name $results | Select-Object Name, Value, Description } \ No newline at end of file diff --git a/PSDPA/Public/Get-DpaLicense.ps1 b/PSDPA/Public/Get-DpaLicense.ps1 index ffee5fb..f5ed995 100644 --- a/PSDPA/Public/Get-DpaLicense.ps1 +++ b/PSDPA/Public/Get-DpaLicense.ps1 @@ -60,11 +60,11 @@ function Get-DpaLicense { foreach ($license in $response) { [PSCustomObject] @{ - Product = $license.licenseProduct - Category = $license.licenseCategory + Product = $license.licenseProduct + Category = $license.licenseCategory Available = [int] $license.licensesAvailable - Consumed = [int] $license.licensesConsumed - Total = [int] $license.licensesAvailable + [int] $license.licensesConsumed + Consumed = [int] $license.licensesConsumed + Total = [int] $license.licensesAvailable + [int] $license.licensesConsumed } } } \ No newline at end of file diff --git a/PSDPA/Public/Get-DpaMonitor.ps1 b/PSDPA/Public/Get-DpaMonitor.ps1 index 2b7bc47..b6c94a3 100644 --- a/PSDPA/Public/Get-DpaMonitor.ps1 +++ b/PSDPA/Public/Get-DpaMonitor.ps1 @@ -52,8 +52,7 @@ function Get-DpaMonitor { if ($PSBoundParameters.ContainsKey('DatabaseId') -and $DatabaseId.Count -eq 1) { Write-PSFMessage -Level Verbose -Message 'Getting a single monitor' $endpoint = "/databases/$DatabaseId/monitor-information" - } - else { + } else { Write-PSFMessage -Level Verbose -Message 'Getting all monitors' $endpoint = '/databases/monitor-information' } @@ -61,8 +60,7 @@ function Get-DpaMonitor { try { $response = Invoke-DpaRequest -Endpoint $endpoint -Method 'Get' $monitors = $response.data - } - catch { + } catch { if ($_.Exception.Response.StatusCode.value__ -eq 422) { return $null } @@ -72,8 +70,7 @@ function Get-DpaMonitor { if ($PSBoundParameters.ContainsKey('DatabaseId') -and $DatabaseId -is [array]) { $monitors = $monitors | Where-Object { $_.dbid -in $DatabaseId } - } - elseif ($PSCmdlet.ParameterSetName -eq 'ByName') { + } elseif ($PSCmdlet.ParameterSetName -eq 'ByName') { $monitors = $monitors | Where-Object { $_.name -in $MonitorName } } @@ -81,4 +78,4 @@ function Get-DpaMonitor { foreach ($monitor in $monitors) { $monitorFactory.New($monitor) } -} +} \ No newline at end of file diff --git a/PSDPA/Public/Get-DpaMonitorLicense.ps1 b/PSDPA/Public/Get-DpaMonitorLicense.ps1 index 778f01b..ff20460 100644 --- a/PSDPA/Public/Get-DpaMonitorLicense.ps1 +++ b/PSDPA/Public/Get-DpaMonitorLicense.ps1 @@ -60,8 +60,7 @@ function Get-DpaMonitorLicense { begin { if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Monitor = Get-DpaMonitor -MonitorName $MonitorName - } - elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { + } elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { $Monitor = Get-DpaMonitor -DatabaseId $DatabaseId } } @@ -72,9 +71,9 @@ function Get-DpaMonitorLicense { $response = Invoke-DpaRequest -Endpoint $endpoint -Method 'Get' [PSCustomObject] @{ - ServerName = $response.data.serverName - OverLicensed = $response.data.overLicensed - VmLicenseProduct = $response.data.vmLicenseProduct + ServerName = $response.data.serverName + OverLicensed = $response.data.overLicensed + VmLicenseProduct = $response.data.vmLicenseProduct PerformanceLicenseProduct = $response.data.performanceLicenseProduct } } diff --git a/PSDPA/Public/New-DpaMonitor.ps1 b/PSDPA/Public/New-DpaMonitor.ps1 index 51f5d95..193bd98 100644 --- a/PSDPA/Public/New-DpaMonitor.ps1 +++ b/PSDPA/Public/New-DpaMonitor.ps1 @@ -1,10 +1,64 @@ -function Add-DpaMonitor { +<# + +.SYNOPSIS +Adds a monitor to DPA. + +.DESCRIPTION +Adds a database server to DPA to be monitored. + +.PARAMETER ServerName +Server (Name or IP). + +.PARAMETER Port +Port number. + +.PARAMETER DatabaseType +Type of database server. + +.PARAMETER Database +Name of the database to connect to (Azure SQL DB or Db2 only). + +.PARAMETER DisplayName +Name to display in DPA. + +.PARAMETER AmazonRDS +Indicates whether or not the server is an Amazon RDS server. + +.PARAMETER Credential +Credential to use to connect to the server to setup DPA objects. + +.PARAMETER CreateMonitoringUser +Indicates whether or not to create the monitoring user, or if it has already been created. + +.PARAMETER MonitoringCredential +Credential DPA will use to monitor the server with. + +.PARAMETER EnableException +Replaces user friendly yellow warnings with bloody red exceptions of doom! Use +this if you want the function to throw terminating errors you want to catch. + +.EXAMPLE +$sysadminCredential = Get-Credential +$monitoringCredential = Get-Credential + +New-DpaMonitor -ServerName 'mytestserver.database.windows.net' -Port 1433 -DatabaseType 'AzureSQLDB' -DisplayName 'mytestserver' -Database 'mytestdatabase' -Credential $sysadminCredential -MonitoringCredential $monitoringCredential -CreateMonitoringUser + +Registers an Azure SQL DB for monitoring and creates the monitoring credential. + +.NOTES +Author: Andrew Wickham ( @awickham ) + +Copyright: (C) Andrew Wickham, andrew@awickham.com +License: MIT https://opensource.org/licenses/MIT + +#> +function New-DpaMonitor { [CmdletBinding()] param ( [Parameter(Mandatory)] $ServerName, - [Parameter()] + [Parameter(Mandatory)] $Port, [Parameter(Mandatory)] @@ -12,26 +66,24 @@ function Add-DpaMonitor { $DatabaseType, [Parameter()] - $DisplayName, + $Database, [Parameter()] - [switch] $AmazonRDS, - - [Parameter()] - [string] $RepositoryTableSpace, - - [Parameter()] - [string] $JdbcUrlProperties, + $DisplayName, [Parameter()] - [string] $ConnectionProperties, + [switch] $AmazonRDS, [Parameter(Mandatory)] [PSCredential] $Credential, + [Parameter()] + [switch] $CreateMonitoringUser, + [Parameter()] [PSCredential] $MonitoringCredential, + [Parameter()] [switch] $EnableException ) @@ -58,7 +110,7 @@ function Add-DpaMonitor { } $request = @{ - serverName = $ServerName + serverName = $ServerName databaseType = $DatabaseType.ToUpper() } @@ -70,18 +122,6 @@ function Add-DpaMonitor { $request['amazonRds'] = $AmazonRDS } - if ($PSBoundParameters.ContainsKey('RepositoryTableSpace')) { - $request['repositoryTableSpace'] = $RepositoryTableSpace - } - - if ($PSBoundParameters.ContainsKey('JdbcUrlProperties')) { - $request['jdbcUrlProperties'] = $JdbcUrlProperties - } - - if ($PSBoundParameters.ContainsKey('ConnectionProperties')) { - $request['connectionProperties'] = $ConnectionProperties - } - if ($DatabaseType -eq 'SQLServer') { $isDomainUser = $Credential.GetNetworkCredential().Domain -ne '' if ($isDomainUser) { @@ -93,12 +133,21 @@ function Add-DpaMonitor { $request['sysAdminUser'] = $Credential.UserName $request['sysAdminPassword'] = $Credential.GetNetworkCredential().Password + if ($CreateMonitoringUser) { + $request['monitoringUserIsNew'] = $true + } else { + $request['monitoringUserIsNew'] = $false + } + + if ($PSBoundParameters.ContainsKey('Database') -and $DatabaseType -in @('AzureSQLDB', 'Db2')) { + $request['database'] = $Database + } + if ($DatabaseType -ne 'Db2') { if ($PSBoundParameters.ContainsKey('MonitoringCredential') -and $DatabaseType -ne 'Db2') { $request['monitoringUser'] = $MonitoringCredential.UserName $request['monitoringUserPassword'] = $MonitoringCredential.GetNetworkCredential().Password - } - else { + } else { $request['monitoringUser'] = $Credential.UserName $request['monitoringUserPassword'] = $Credential.GetNetworkCredential().Password } @@ -106,9 +155,13 @@ function Add-DpaMonitor { try { $response = Invoke-DpaRequest -Endpoint '/databases/register-monitor' -Request $request -Method 'POST' - } - catch { - Stop-PSFFunction -Message "Could not register monitor" -ErrorRecord $_ + } catch { + $responseStream = $_.Exception.Response.GetResponseStream() + $streamReader = New-Object System.IO.StreamReader $responseStream + + $response = $streamReader.ReadToEnd() | ConvertFrom-Json + + Stop-PSFFunction -Message $response.messages[0].reason -ErrorRecord $_ return } diff --git a/PSDPA/Public/Remove-DpaAnnotation.ps1 b/PSDPA/Public/Remove-DpaAnnotation.ps1 index 588abca..809bd3b 100644 --- a/PSDPA/Public/Remove-DpaAnnotation.ps1 +++ b/PSDPA/Public/Remove-DpaAnnotation.ps1 @@ -27,7 +27,7 @@ License: MIT https://opensource.org/licenses/MIT #> function Remove-DpaAnnotation { - [CmdletBinding(DefaultParameterSetName = 'ById')] + [CmdletBinding(DefaultParameterSetName = 'ById', SupportsShouldProcess)] param ( [Parameter(ParameterSetName = 'ByObject', ValueFromPipeline)] [Annotation[]] $Annotation, @@ -37,12 +37,13 @@ function Remove-DpaAnnotation { ) foreach ($annotationObject in $Annotation) { - $endpoint = "databases/$($annotationObject.DatabaseId)/annotations/$($annotationObject.AnnotationId)" - try { - $response = Invoke-DpaRequest -Endpoint $endpoint -Method 'Delete' - } - catch { - Stop-PSFFunction -Message "Could not remove annotation" -EnableException:$EnableException -ErrorRecord $_ + if ($PSCmdlet.ShouldProcess($annotationObject.Title, 'Remove Annotation')) { + $endpoint = "databases/$($annotationObject.DatabaseId)/annotations/$($annotationObject.AnnotationId)" + try { + $response = Invoke-DpaRequest -Endpoint $endpoint -Method 'Delete' + } catch { + Stop-PSFFunction -Message "Could not remove annotation" -EnableException:$EnableException -ErrorRecord $_ + } } } } \ No newline at end of file diff --git a/PSDPA/Public/Set-DpaConfig.ps1 b/PSDPA/Public/Set-DpaConfig.ps1 index 6dabab8..aee4496 100644 --- a/PSDPA/Public/Set-DpaConfig.ps1 +++ b/PSDPA/Public/Set-DpaConfig.ps1 @@ -1,5 +1,28 @@ +<# + +.SYNOPSIS +Sets configuration options for PSDPA. + +.PARAMETER BaseUri +The base URI for the DPA API. + +.PARAMETER RefreshToken +The refresh token for the DPA API. + +.EXAMPLE +Set-DpaConfig -BaseUri 'http://yourserver:8123/iwc/api' -RefreshToken 'yourrefreshtoken' + +Sets the Base URI and Refresh Token for the DPA API. + +.NOTES +Author: Andrew Wickham ( @awickham ) + +Copyright: (C) Andrew Wickham, andrew@awickham.com +License: MIT https://opensource.org/licenses/MIT + +#> function Set-DpaConfig { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter()] $BaseUri, @@ -9,17 +32,19 @@ function Set-DpaConfig { ) process { - foreach ($parameter in $PSBoundParameters.GetEnumerator()) { - $name = $parameter.Key.ToLower() + if ($PSCmdlet.ShouldProcess('Updated Config')) { + foreach ($parameter in $PSBoundParameters.GetEnumerator()) { + $name = $parameter.Key.ToLower() - Set-PSFConfig -Module psdpa -Name $name -Value $parameter.Value - Register-PSFConfig -FullName psdpa.$name -EnableException -WarningAction SilentlyContinue + Set-PSFConfig -Module psdpa -Name $name -Value $parameter.Value + Register-PSFConfig -FullName psdpa.$name -EnableException -WarningAction SilentlyContinue - if ($name -eq 'refreshtoken') { - Set-Variable -Scope 1 -Name PSDefaultParameterValues -Value @{ 'PSDPA:AccessToken' = $value } + if ($name -eq 'refreshtoken') { + Set-Variable -Scope 1 -Name PSDefaultParameterValues -Value @{ 'PSDPA:AccessToken' = $value } + } } - } - Get-DpaConfig -Name $name + Get-DpaConfig -Name $name + } } -} +} \ No newline at end of file diff --git a/PSDPA/Public/Start-DpaMonitor.ps1 b/PSDPA/Public/Start-DpaMonitor.ps1 index b58dc65..1b86c55 100644 --- a/PSDPA/Public/Start-DpaMonitor.ps1 +++ b/PSDPA/Public/Start-DpaMonitor.ps1 @@ -37,7 +37,7 @@ License: MIT https://opensource.org/licenses/MIT #> function Start-DpaMonitor { - [CmdletBinding(DefaultParameterSetName = 'ByName')] + [CmdletBinding(DefaultParameterSetName = 'ByName', SupportsShouldProcess)] param ( [Parameter(ParameterSetName = 'ByDatabaseId', Mandatory)] [int[]] $DatabaseId, @@ -56,8 +56,7 @@ function Start-DpaMonitor { begin { if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Monitor = Get-DpaMonitor -MonitorName $MonitorName - } - elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { + } elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { $Monitor = Get-DpaMonitor -DatabaseId $DatabaseId } @@ -68,11 +67,12 @@ function Start-DpaMonitor { process { foreach ($monitorObject in $Monitor) { - try { - $response = Invoke-DpaRequest -Endpoint "/databases/$($monitorObject.Dbid)/monitor-status" -Method 'PUT' -Request $request - } - catch { - Stop-PSFFunction -Message "Could not start the monitor" -EnableException:$EnableException -ErrorRecord $_ -Target $monitorObject.Name + if ($PSCmdlet.ShouldProcess($monitor.Name, 'Start Monitor')) { + try { + $response = Invoke-DpaRequest -Endpoint "/databases/$($monitorObject.Dbid)/monitor-status" -Method 'PUT' -Request $request + } catch { + Stop-PSFFunction -Message "Could not start the monitor" -EnableException:$EnableException -ErrorRecord $_ -Target $monitorObject.Name + } } } } diff --git a/PSDPA/Public/Stop-DpaMonitor.ps1 b/PSDPA/Public/Stop-DpaMonitor.ps1 index 63e4c7f..b22a5e0 100644 --- a/PSDPA/Public/Stop-DpaMonitor.ps1 +++ b/PSDPA/Public/Stop-DpaMonitor.ps1 @@ -37,7 +37,7 @@ License: MIT https://opensource.org/licenses/MIT #> function Stop-DpaMonitor { - [CmdletBinding(DefaultParameterSetName = 'ByName')] + [CmdletBinding(DefaultParameterSetName = 'ByName', SupportsShouldProcess)] param ( [Parameter(ParameterSetName = 'ByDatabaseId', Mandatory)] [int[]] $DatabaseId, @@ -55,8 +55,7 @@ function Stop-DpaMonitor { begin { if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Monitor = Get-DpaMonitor -MonitorName $MonitorName - } - elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { + } elseif ($PSCmdlet.ParameterSetName -eq 'ByDatabaseId') { $Monitor = Get-DpaMonitor -DatabaseId $DatabaseId } @@ -70,8 +69,7 @@ function Stop-DpaMonitor { if ($PSCmdlet.ShouldProcess($monitor.Name, 'Stop Monitor')) { try { $response = Invoke-DpaRequest -Endpoint "/databases/$($monitorObject.Dbid)/monitor-status" -Method 'PUT' -Request $request - } - catch { + } catch { Stop-PSFFunction -Message "Could not stop the monitor" -ErrorRecord $_ -Target $monitorObject.Name } } diff --git a/README.md b/README.md index d1ef446..d4af1eb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # PSDPA Solarwinds DPA PowerShell Module (Unofficial) + PSDPA is an open source PowerShell module for the Solarwinds DPA REST API. The goal of the module is to provide complete PowerShell coverage for the API. -| Branch | Status | -|:--- |:--- | -| Master | [![Master Build Status](https://ci.appveyor.com/api/projects/status/i165eqibj5cvger3/branch/master?svg=true)](https://ci.appveyor.com/project/awickham10/psdpa/branch/master) [![Master Code Coverage](https://codecov.io/gh/awickham10/psdpa/branch/master/graph/badge.svg)](https://codecov.io/gh/awickham10/psdpa) | -| Development |[![Development Build Status](https://ci.appveyor.com/api/projects/status/i165eqibj5cvger3/branch/development?svg=true)](https://ci.appveyor.com/project/awickham10/psdpa/branch/development) [![Development Code Coverage](https://codecov.io/gh/awickham10/psdpa/branch/development/graph/badge.svg)](https://codecov.io/gh/awickham10/psdpa) | +| Branch | Status | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Master | [![Master Build Status](https://ci.appveyor.com/api/projects/status/i165eqibj5cvger3/branch/master?svg=true)](https://ci.appveyor.com/project/awickham10/psdpa/branch/master) [![Master Code Coverage](https://codecov.io/gh/awickham10/psdpa/branch/master/graph/badge.svg)](https://codecov.io/gh/awickham10/psdpa) [![PSGallery Version](https://img.shields.io/powershellgallery/v/PSDPA.svg?style=flat&label=PSGallery)](https://www.powershellgallery.com/packages/PSDPA) | +| Development | [![Development Build Status](https://ci.appveyor.com/api/projects/status/i165eqibj5cvger3/branch/development?svg=true)](https://ci.appveyor.com/project/awickham10/psdpa/branch/development) [![Development Code Coverage](https://codecov.io/gh/awickham10/psdpa/branch/development/graph/badge.svg)](https://codecov.io/gh/awickham10/psdpa) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c303d5eae85a4840908206a4a1bcf92d)](https://www.codacy.com/app/awickham10/psdpa?utm_source=github.com&utm_medium=referral&utm_content=awickham10/psdpa&utm_campaign=Badge_Grade) | ## Instructions -``` powershell + +```powershell # Install the PSDPA module from the gallery Install-Module PSDPA @@ -23,26 +25,34 @@ Set-DpaConfig -BaseUri 'https://yourdpaserver:8124/iwc/api' -RefreshToken 'yourp # Get listing of monitors Get-DpaMonitor + +# Add an annotation to all monitors +Get-DpaMonitor | Add-DpaAnnotation -Title 'Patching' -Description 'Quarterly patching' ``` ## Functions + ### Configuration -* Set-DpaConfig - Complete -* Get-DpaConfig - Complete + +- Set-DpaConfig - Complete +- Get-DpaConfig - Complete ### Monitor -* Get-DpaMonitor - Complete -* Start-DpaMonitor - Complete -* Stop-DpaMonitor - Complete -* Set-DpaMonitorPassword - Not Implemented -* Add-DpaMonitor - Not Implemented + +- Get-DpaMonitor - Complete +- Start-DpaMonitor - Complete +- Stop-DpaMonitor - Complete +- Set-DpaMonitorPassword - Not Implemented +- New-DpaMonitor - Test Coverage Needed ### Licensing -* Get-DpaLicense - Complete -* Get-DpaMonitorLicense - Complete -* Set-DpaLicense - Not Implemented + +- Get-DpaLicense - Complete +- Get-DpaMonitorLicense - Complete +- Set-DpaLicense - Not Implemented ### Annotations -* Get-DpaAnnotation - Complete -* Add-DpaAnnotation - Test Coverage Needed -* Remove-DpaAnnotation - Complete + +- Get-DpaAnnotation - Complete +- Add-DpaAnnotation - Complete +- Remove-DpaAnnotation - Complete diff --git a/Tests/Add-DpaAnnotation.Tests.ps1 b/Tests/Add-DpaAnnotation.Tests.ps1 index 02416b8..679bd7c 100644 --- a/Tests/Add-DpaAnnotation.Tests.ps1 +++ b/Tests/Add-DpaAnnotation.Tests.ps1 @@ -15,17 +15,111 @@ Describe "$CommandName Unit Tests" -Tag 'Unit' { param ( $Name , $Mandatory = $true ) $command | Should -HaveParameter $Name -Mandatory:$Mandatory } + } +} - It 'should default Time to the current time' { +Describe "$CommandName Integration Tests" -Tag 'Integration' { + InModuleScope -ModuleName 'PSDPA' { + Context 'Azure SQL DB' { + BeforeAll { + # set the start time and remove ticks since DPA doesn't support them + $contextStartTime = Get-Date + $contextStartTime = $contextStartTime.AddTicks(-($contextStartTime.Ticks % [TimeSpan]::TicksPerSecond)); - } + # get our test monitor + $monitor = Get-DpaMonitor -MonitorName 'PSDPATESTDB01@PSDPATEST01' -EnableException - It 'should default CreatedBy to the current user' { + # default annotation parameters to use for tests + $annotationParams = @{ + Monitor = $monitor + Title = 'Testing API' + Description = 'This is a test of Add-DpaAnnotation' + CreatedBy = 'Test User' + Time = $contextStartTime + } + } - } - } -} + BeforeEach { + $script:annotation = $null + } -Describe "$CommandName Integration Tests" -Tag 'Integration' { + It 'should add an annotation when using -Monitor' { + { $script:annotation = Add-DpaAnnotation @annotationParams -EnableException } | Should -Not -Throw + + foreach ($annotationParam in $annotationParams.Keys) { + $annotation.$annotationParam | Should -BeExactly $annotationParams[$annotationParam] + } + + $annotation.Type | Should -Be 'API' + } + + It 'should add an annotation when using -DatabaseId' { + $thisAnnotationParams = $annotationParams.Clone() + $thisAnnotationParams.Remove('Monitor') + $thisAnnotationParams['DatabaseId'] = $monitor.DatabaseId + + { $script:annotation = Add-DpaAnnotation @thisAnnotationParams -EnableException } | Should -Not -Throw + + foreach ($annotationParam in $thisAnnotationParams.Keys) { + $annotation.$annotationParam | Should -BeExactly $thisAnnotationParams[$annotationParam] + } + + $annotation.Type | Should -Be 'API' + } + + It 'should add an annotation when using -MonitorName' { + $thisAnnotationParams = $annotationParams.Clone() + $thisAnnotationParams.Remove('Monitor') + $thisAnnotationParams['MonitorName'] = $monitor.Name + { $script:annotation = Add-DpaAnnotation @thisAnnotationParams -EnableException } | Should -Not -Throw + + $annotation.DatabaseId | Should -BeExactly $monitor.DatabaseId + $annotation.Type | Should -Be 'API' + } + + It 'should add an annotation when piping a monitor' { + $thisAnnotationParams = $annotationParams.Clone() + $thisAnnotationParams.Remove('Monitor') + + { $script:annotation = $monitor | Add-DpaAnnotation @thisAnnotationParams -EnableException } | Should -Not -Throw + + foreach ($annotationParam in $thisAnnotationParams.Keys) { + $annotation.$annotationParam | Should -BeExactly $thisAnnotationParams[$annotationParam] + } + + $annotation.DatabaseId | Should -BeExactly $monitor.DatabaseId + $annotation.Type | Should -Be 'API' + } + + It 'should default CreatedBy to the current user' { + $thisAnnotationParams = $annotationParams.Clone() + $thisAnnotationParams.Remove('CreatedBy') + + { $script:annotation = Add-DpaAnnotation @thisAnnotationParams -EnableException } | Should -Not -Throw + + foreach ($annotationParam in $thisAnnotationParams.Keys) { + $annotation.$annotationParam | Should -BeExactly $thisAnnotationParams[$annotationParam] + } + + $annotation.CreatedBy | Should -Be $env:USERNAME + } + + It 'should default Time to the current time' { + $thisAnnotationParams = $annotationParams.Clone() + $thisAnnotationParams.Remove('Time') + + { $script:annotation = Add-DpaAnnotation @thisAnnotationParams -EnableException } | Should -Not -Throw + + foreach ($annotationParam in $thisAnnotationParams.Keys) { + $annotation.$annotationParam | Should -BeExactly $thisAnnotationParams[$annotationParam] + } + + # give ourselves a 10 minute swing on time just in case server times vary + $currentTime = Get-Date + $annotation.Time | Should -BeGreaterThan $currentTime.AddMinutes(-5) + $annotation.Time | Should -BeLessThan $currentTime.AddMinutes(5) + } + } + } } \ No newline at end of file diff --git a/Tests/Constants.ps1 b/Tests/Constants.ps1 index 98ba528..beaf345 100644 --- a/Tests/Constants.ps1 +++ b/Tests/Constants.ps1 @@ -1,8 +1,18 @@ -$localConstants = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Constants.Local.ps1' +Write-PSFMessage -Level Verbose -Message 'Loading constants from Constants.ps1' + +$localConstants = Join-Path -Path (Split-Path -Path $ConstantsFile -Parent) -ChildPath 'Constants.Local.ps1' if (Test-Path -Path $localConstants) { - .\$localConstants + . $localConstants return } -Set-PSFConfig -Module 'PSDPA' -Name 'baseuri' -Value 'https://myfakedpaserver:8124/iwc/api' -Set-PSFConfig -Module 'PSDPA' -Name 'refreshtoken' -Value 'thisismyrefreshtoken' \ No newline at end of file +if (-not $ENV:PSDPA_BASEURI) { + Stop-PSFFunction -Message "Environment variable PSDPA_BASEURI is not set" -EnableException $true +} + +if (-not $ENV:PSDPA_TOKEN) { + Stop-PSFFunction -Message "Environment variable PSDPA_TOKEN is not set" -EnableException $true +} + +Set-PSFConfig -Module 'PSDPA' -Name 'baseuri' -Value $ENV:PSDPA_BASEURI +Set-PSFConfig -Module 'PSDPA' -Name 'refreshtoken' -Value $ENV:PSDPA_TOKEN \ No newline at end of file diff --git a/Tests/Get-DpaConfig.Tests.ps1 b/Tests/Get-DpaConfig.Tests.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Get-DpaMonitor.Tests.ps1 b/Tests/Get-DpaMonitor.Tests.ps1 index a5f35e8..809a17d 100644 --- a/Tests/Get-DpaMonitor.Tests.ps1 +++ b/Tests/Get-DpaMonitor.Tests.ps1 @@ -73,13 +73,13 @@ Describe "$CommandName Integration Tests" -Tag 'Integration' { } It 'should return an empty resultset when a monitor is not found' { - Get-DpaMonitor -DatabaseId 0 | Should -HaveCount 0 + Get-DpaMonitor -DatabaseId 0 -WarningAction 'SilentlyContinue' | Should -HaveCount 0 Assert-MockCalled -CommandName 'Invoke-RestMethod' -Times 1 } It 'should not throw an exception when monitor is not found and -EnableException is not used' { - { Get-DpaMonitor -DatabaseId 0 } | Should -Not -Throw 'Not Found' + { Get-DpaMonitor -DatabaseId 0 -WarningAction 'SilentlyContinue' } | Should -Not -Throw 'Not Found' Assert-MockCalled -CommandName 'Invoke-RestMethod' -Times 1 } diff --git a/Tests/New-DpaMonitor.Tests.ps1 b/Tests/New-DpaMonitor.Tests.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/Tests/PSDPA.Tests.ps1 b/Tests/PSDPA.Tests.ps1 index 0ecdc25..13370d7 100644 --- a/Tests/PSDPA.Tests.ps1 +++ b/Tests/PSDPA.Tests.ps1 @@ -1,8 +1,135 @@ . $PSScriptRoot\Shared.ps1 +$Path = Split-Path -Parent $MyInvocation.MyCommand.Path +if ($ENV:BHProjectPath) { + $ModulePath = Join-Path $ENV:BHProjectPath $ModuleName +} else { + $ModulePath = (Get-Item $Path).Parent.FullName + '\' + $ModuleName +} -Describe "Module Manifest Tests PS$PSVersion" { - It 'Passes Test-ModuleManifest' { - Test-ModuleManifest -Path $ENV:BHPSModuleManifest - $? | Should Be $true +function Split-ArrayInParts($array, [int]$parts) { + #splits an array in "equal" parts + $size = $array.Length / $parts + $counter = [pscustomobject] @{ Value = 0 } + $groups = $array | Group-Object -Property { [math]::Floor($counter.Value++ / $size) } + $rtn = @() + foreach ($g in $groups) { + $rtn += , @($g.Group) + } + $rtn +} + +Describe "$ModuleName style" -Tag 'Compliance' { + <# + Ensures common formatting standards are applied: + - OTSB style, courtesy of PSSA's Invoke-Formatter, is what dbatools uses + - UTF8 without BOM is what is going to be used in PS Core, so we adopt this standard for dbatools + #> + $AllFiles = Get-ChildItem -Path $ModulePath -File -Recurse -Filter '*.ps*1' + $AllFunctionFiles = Get-ChildItem -Path "$ModulePath\Public", "$ModulePath\Private", "$ModulePath\Classes" -Filter '*.ps*1' + Context "formatting" { + $maxConcurrentJobs = $env:NUMBER_OF_PROCESSORS + $whatever = Split-ArrayInParts -array $AllFunctionFiles -parts $maxConcurrentJobs + $jobs = @() + foreach ($piece in $whatever) { + $jobs += Start-Job -ScriptBlock { + foreach ($p in $Args) { + $content = Get-Content -Path $p.FullName -Raw -Encoding UTF8 + $result = Invoke-Formatter -ScriptDefinition $content -Settings CodeFormattingOTBS + if ($result -ne $content) { + $p + } + } + } -ArgumentList $piece + } + $null = $jobs | Wait-Job #-Timeout 120 + $results = $jobs | Receive-Job + + foreach ($f in $results) { + It "$f is adopting OTSB formatting style. Please run Invoke-DbatoolsFormatter against the failing file and commit the changes." { + 1 | Should -Be 0 + } + } + } + + Context "BOM" { + foreach ($f in $AllFiles) { + [byte[]]$byteContent = Get-Content -Path $f.FullName -Encoding Byte -ReadCount 4 -TotalCount 4 + if ( $byteContent[0] -eq 0xef -and $byteContent[1] -eq 0xbb -and $byteContent[2] -eq 0xbf ) { + It "$f has no BOM in it" { + "utf8bom" | Should -Be "utf8" + } + } + } + } + + Context "indentation" { + foreach ($f in $AllFiles) { + $LeadingTabs = Select-String -Path $f -Pattern '^[\t]+' + if ($LeadingTabs.Count -gt 0) { + It "$f is not indented with tabs (line(s) $($LeadingTabs.LineNumber -join ','))" { + $LeadingTabs.Count | Should -Be 0 + } + } + $TrailingSpaces = Select-String -Path $f -Pattern '([^ \t\r\n])[ \t]+$' + if ($TrailingSpaces.Count -gt 0) { + It "$f has no trailing spaces (line(s) $($TrailingSpaces.LineNumber -join ','))" { + $TrailingSpaces.Count | Should -Be 0 + } + } + } + } +} + +Describe "$ModuleName style" -Tag 'Compliance' { + <# + Ensures avoiding already discovered pitfalls + #> + $AllPublicFunctions = Get-ChildItem -Path "$ModulePath\Public" -Filter '*.ps*1' + + Context "NoCompatibleTLS" { + # .NET defaults clash with recent TLS hardening (e.g. no TLS 1.2 by default) + foreach ($f in $AllPublicFunctions) { + $NotAllowed = Select-String -Path $f -Pattern 'Invoke-WebRequest | New-Object System.Net.WebClient|\.DownloadFile' + if ($NotAllowed.Count -gt 0) { + It "$f should instead use Invoke-TlsWebRequest, see #4250" { + $NotAllowed.Count | Should -Be 0 + } + } + } + } + +} + + +Describe "$ModuleName ScriptAnalyzerErrors" -Tag 'Compliance' { + $ScriptAnalyzerErrors = @() + $ScriptAnalyzerErrors += Invoke-ScriptAnalyzer -Path "$ModulePath\Public" -Severity Error + $ScriptAnalyzerErrors += Invoke-ScriptAnalyzer -Path "$ModulePath\Private" -Severity Error + Context "Errors" { + if ($ScriptAnalyzerErrors.Count -gt 0) { + foreach ($err in $ScriptAnalyzerErrors) { + It "$($err.scriptName) has Error(s) : $($err.RuleName)" { + $err.Message | Should -Be $null + } + } + } + } +} + +Describe "$ModuleName Tests missing" -Tag 'Tests' { + $functions = Get-ChildItem (Join-Path -Path $ModulePath -ChildPath 'Public') -Recurse -Include *.ps1 + Context "Every function should have tests" { + foreach ($f in $functions) { + It "$($f.basename) has a tests.ps1 file" { + Test-Path "Tests\$($f.basename).Tests.ps1" | Should Be $true + } + <# + If (Test-Path "Tests\$($f.basename).Tests.ps1") { + It "$($f.basename) has validate parameters unit test" { + "Tests\$($f.basename).Tests.ps1" | should FileContentMatch 'Context "Validate parameters"' + } + } + #> + } } } \ No newline at end of file diff --git a/Tests/Set-DpaConfig.Tests.ps1 b/Tests/Set-DpaConfig.Tests.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Shared.ps1 b/Tests/Shared.ps1 index db1c19d..cf3c0a9 100644 --- a/Tests/Shared.ps1 +++ b/Tests/Shared.ps1 @@ -6,7 +6,8 @@ if(-not $ENV:BHProjectPath) $PSVersion = $PSVersionTable.PSVersion.Major $ModuleName = $ENV:BHProjectName -. (Join-Path -Path $PSScriptRoot -ChildPath 'Constants.ps1') +$ConstantsFile = Join-Path -Path $PSScriptRoot -ChildPath 'Constants.ps1' +. $ConstantsFile Remove-Module $ENV:BHProjectName -ErrorAction SilentlyContinue Import-Module (Join-Path $ENV:BHProjectPath $ModuleName) -Force \ No newline at end of file diff --git a/Tools/Get-AppVeyorBuildIPs.ps1 b/Tools/Get-AppVeyorBuildIPs.ps1 new file mode 100644 index 0000000..872f386 --- /dev/null +++ b/Tools/Get-AppVeyorBuildIPs.ps1 @@ -0,0 +1,18 @@ +# modified from https://community.spiceworks.com/scripts/show/2327-what-is-my-ip-get-whatismyip +param ( + [Parameter()] + [switch] $EnableException +) + +try { + $webRequest = Invoke-WebRequest -Uri 'https://www.appveyor.com/docs/build-environment/#ip-addresses' -ErrorAction Stop -UseBasicParsing + + $matches = $webRequest.Content | Select-String -Pattern "\b(?:\d{1,3}\.){3}\d{1,3}\b" -AllMatches + if ($matches) { + $matches.Matches.Value + } else { + Stop-PSFFunction -Message 'Unable to locate any IPs' -EnableException:$EnableException + } +} catch { + Stop-PSFFunction -Message 'Unable to process AppVeyor docs' -ErrorRecord $_ -EnableException:$EnableException +} \ No newline at end of file diff --git a/appveyor.debug.yml b/appveyor.debug.yml new file mode 100644 index 0000000..6396464 --- /dev/null +++ b/appveyor.debug.yml @@ -0,0 +1,33 @@ +image: Visual Studio 2017 + +#Publish to PowerShell Gallery with this key +environment: + NugetApiKey: + secure: 76Y+lNAkDCWwiO9jWmihZBlOKPD2rtU3b+dd5Fpkz34WtXFqTKDQFp30IA60Cj3e + CODECOV_TOKEN: + secure: xn88TAvqQYjOWy5VnVNLLDNIeYi75NcrDMZdcEoAFPpW91KcdPSwk4NnvUxLI9hG + PSDPA_BASEURI: + secure: /Ah4/9vQOkXmMZdB8SrlHqueDF5uMC/ME3pc8jgl7qqiR1rpG+W3mjyeR+FbxOPh + PSDPA_TOKEN: + secure: kTA548nZiwq71GIiCau5wvSPTVNn9dVfaHC//NuCKo2fljzkpL+niUwjfOVvVIbDPf8zOxznxspn1GrEkm27Dg8XGHyd9XfD8leyiKTp0gavhNLVkrVzG1/Zqj6eyFO7IKVGYB4hs7nJJasuAFxVtBjcR9UZdFEbw8d/Z06YqQo= + +cache: + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules\PSScriptAnalyzer -> Build\build.requirements.psd1 + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules\Pester -> Build\build.requirements.psd1 + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules\PSCodeCovIo -> Build\build.requirements.psd1 + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules\PSFramework -> Build\build.requirements.psd1 + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules\BuildHelpers -> Build\build.requirements.psd1 + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules\PSDeploy -> Build\build.requirements.psd1 + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules\psake -> Build\build.requirements.psd1 + +# Skip on updates to the readme. +# We can force this by adding [skip ci] or [ci skip] anywhere in commit message +skip_commits: + message: /updated readme.*|update readme.*s/ + +build: false + +#Kick off the CI/CD pipeline +test_script: + - ps: $VerbosePreference = 'Continue' + - ps: . .\build\Start-Build.ps1 -Task Deploy \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index c54f8a3..0853103 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -# See http://www.appveyor.com/docs/appveyor-yml for many more options +image: Visual Studio 2017 #Publish to PowerShell Gallery with this key environment: @@ -6,8 +6,17 @@ environment: secure: 76Y+lNAkDCWwiO9jWmihZBlOKPD2rtU3b+dd5Fpkz34WtXFqTKDQFp30IA60Cj3e CODECOV_TOKEN: secure: xn88TAvqQYjOWy5VnVNLLDNIeYi75NcrDMZdcEoAFPpW91KcdPSwk4NnvUxLI9hG + PSDPA_BASEURI: + secure: /Ah4/9vQOkXmMZdB8SrlHqueDF5uMC/ME3pc8jgl7qqiR1rpG+W3mjyeR+FbxOPh + PSDPA_TOKEN: + secure: kTA548nZiwq71GIiCau5wvSPTVNn9dVfaHC//NuCKo2fljzkpL+niUwjfOVvVIbDPf8zOxznxspn1GrEkm27Dg8XGHyd9XfD8leyiKTp0gavhNLVkrVzG1/Zqj6eyFO7IKVGYB4hs7nJJasuAFxVtBjcR9UZdFEbw8d/Z06YqQo= + PSDPA_AZ: + secure: 3vZUMa+iGf9aqcTolqBjG+tkPnbe3HIzVMBVJkW4dVTqi8t36MmPeXuMcKefJsXe + PSDPA_AZ_TENANT: 1e220ef3-d984-4f20-9bc7-a54afa8ec2af + PSDPA_AZ_APP: 66fb92de-ce0b-48cd-8839-d70991013dd8 -os: WMF 5 +cache: + - C:\Users\appveyor\Documents\WindowsPowerShell\Modules -> Build\build.requirements.psd1 # Skip on updates to the readme. # We can force this by adding [skip ci] or [ci skip] anywhere in commit message