Adding owner to Pester test results - nunit

Is there a way to add an owner per test to the NUnit results exported by Pester?
What I’m hoping to do is notify the owner via teams or email through an Azure devops pipeline (I don’t have this fully sorted yet). As a start I think I’ll need the option to add an owner per test.

There's no functionality explicitly in Pester for identifying a test owner. You can add Tags to tests, but it doesn't look like Tags are exported anywhere in the NUnit results. I think the only option for now would be to add an owner into the title of the test, and then parse that out from the NUnit results:
Describe 'My Tests' {
It 'Should be true [owner: Mark Wragg]' {
$true | Should -Be $true
}
It 'Should be false [owner: Mark Wragg]' {
$true | Should -Be $true
}
It 'Should be true [owner: Bob Bobson]' {
$false | Should -Be $true
}
}
Output in NUnit file:
<results>
<test-case description="Should be true [owner: Mark Wragg]" name="My Tests.Should be true [owner: Mark Wragg]" time="0.0134" asserts="0" success="True" result="Success" executed="True" />
<test-case description="Should be false [owner: Mark Wragg]" name="My Tests.Should be false [owner: Mark Wragg]" time="0.0133" asserts="0" success="True" result="Success" executed="True" />
<test-case description="Should be true [owner: Bob Bobson]" name="My Tests.Should be true [owner: Bob Bobson]" time="0.1436" asserts="0" success="False" result="Failure" executed="True">

Related

Using variables in DevOps YAML Pipelines

I'm trying to add some conditional logic to my Azure DevOps pipeline to perform actions based on if there are pending changes in the Git repository. I've created a PowerShell script to check for changes and set a variable, which is working:
$gitStatus = (git status --porcelain) | Out-String
if ($gitStatus) {
Write-Host "##vso[task.setvariable variable=changes;]true"
Write-Host "##[debug]Changes found"
} else {
Write-Host "##vso[task.setvariable variable=changes;]false"
Write-Host "##[debug]No changes found"
}
I can then output the resulting value of "changes" in my pipeline as follows:
- script: echo Output - $(changes)
This returns "Output - true" as expected
If I then add the following to my YAML...
- ${{ if eq(variables.changes, true) }}:
- script: echo Changes = True
- ${{ else }}:
- script: echo Changes = False
I always receive "Changes = False"
Any help would be gratefully received.
Thanks to input from 4c74356b41 I've come up with the following solution:
- script: echo Changes = True
condition: eq(variables.changes, true)
- script: echo Changes = False
condition: ne(variables.changes, true)
The final pipeline will call templates with the required actions to be performed based on the result of the check, but the above works well enough to prove the concept.

Check if XML Node is true, and if so, execute a SQL statement

I'm working on a script that reads from an XML list of environments and reiterates through each entry, restores a database, runs schema changes on the database, and then backs up said database. I have all of these pieces working, but I'd like to add logic to the script to include a step to set the DB Owner before running the schema changes if the XML node exists;
Currently, if I run this, it writes 'No DBOwner Specified' no matter what.
$dbOwnerExists = $($Environment.Backup.DatabaseBackup.DBOwner)
if ($dbOwnerExists -eq $true) {
Invoke-Sqlcmd -ServerInstance "$($DatabaseBackup.Instance)" -Query "EXEC sp_changedbowner $($Environment.Backup.DatabaseBackup.DBOwner)"
} else {
Write-Output 'No DBOwner Specified'
}
Current XML Looks like this
<Environments>
<Environment Database="exampleDatabase">
<Backup Restore="TRUE">
<DatabaseBackups Instance="SQLInstance" SrcLocation="\\Hostname\c$\temp\example.bak" DstLocation="\\Hostname\c$\temp\example.bak" DBOwner=""sa""/>
<DatabaseBackups Instance="SQLInstance" SrcLocation="\\Hostname\c$\temp\example2.bak" DstLocation="\\Hostname\c$\temp\example2.bak"/>
</Backup>
</Environment>
</Environments>
It seems that powershell doesn't like my logic to check if DBOwner exists in the XML, maybe $true is not the best way to check this?
I haven't seen your full code so this is the best I can do.
Let's assume that you have the XML stored in variable $XML and $XML was defined like this:
[xml]$xml = #"
<Environments>
<Environment Database="exampleDatabase">
<Backup Restore="TRUE">
<DatabaseBackups Instance="SQLInstance" SrcLocation="\\Hostname\c$\temp\example.bak" DstLocation="\\Hostname\c$\temp\example.bak" DBOwner=""sa""/>
<DatabaseBackups Instance="SQLInstance" SrcLocation="\\Hostname\c$\temp\example2.bak" DstLocation="\\Hostname\c$\temp\example2.bak"/>
</Backup>
</Environment>
</Environments>
"#
You can check whether DBOwner exists or not just by checking the value with
$XML.Environments.Environment.Backup.DatabaseBackups.DBOwner
Which with the XML would output:
"sa"
But to get a true/false value on whether it exists or not, you can use the [bool] datatype like you did in your answer:
[bool]$XML.Environments.Environment.Backup.DatabaseBackups.DBOwner
But I found a glitch with this in which it would still output true if the node didn't exist like so:
[bool]$XML.Environments.Environment.Backup.DatabaseBackups.fasdasd
True
so I changed it to this.
[bool][string]$($XML.Environments.Environment.Backup.DatabaseBackups.DBOwner).trim
Which in this case would output:
True
And to put this in an if statement since it is already a boolean you could put it in the in statement like so:
if([bool][string]$($XML.Environments.Environment.Backup.DatabaseBackups.DBOwner).trim){
Invoke-Sqlcmd -ServerInstance "$($DatabaseBackup.Instance)" -Query "EXEC sp_changedbowner $($Environment.Backup.DatabaseBackup.DBOwner)"
} else {
Write-Output 'No DBOwner Specified'
}
To test if this works we can also try doing this on something that doesn't exist like $XML.Environments.Environment.Backup.DatabaseBackups.SomethingThatDoesntExist which should return false and does like so:
[bool][string]($XML.Environments.Environment.Backup.DatabaseBackups.SomethingThatDoesntExist).trim()
False
To test the if statement we can do
if([bool][string]$($XML.Environments.Environment.Backup.DatabaseBackups.DBOwner).trim){
Write-Output "Exists"
} else {
Write-Warning 'No DBOwner Specified'
}
Which would output
Exists
and we could also test if it doesn't exist with
if([bool][string]$($XML.Environments.Environment.Backup.DatabaseBackups.fasdasd).trim){
Write-Output "Exists"
} else {
Write-Warning 'No DBOwner Specified'
}
The output would be:
WARNING: No DBOwner Specified
NOTE: The [bool]s in the if statements are unnecessary since if already converts things to booleans but I just put it in for easier understanding this would work fine too:
if([string]$($XML.Environments.Environment.Backup.DatabaseBackups.fasdasd).trim){
Write-Output "Exists"
} else {
Write-Warning 'No DBOwner Specified'
}
I was able to figure this out with the following, which works well enough for me!
$dbOwnerExists=$null
$dbOwnerExists = $($DatabaseBackup.DBOwner)
if ($dbOwnerExists) {
Invoke-Sqlcmd -ServerInstance "$($DatabaseBackup.Instance)" -Query "USE $($Environment.Database) EXEC sp_changedbowner '$($DatabaseBackup.DBOwner)'"
} else {
Write-Output 'No DBOwner Specified'
}

How do I perform a logical If..then in powershell against JSON data?

I'm trying to loop through a JSON array of desired registry values, and then inspect the registry value for the correct setting.
The issue I have is that I'm not correctly defining the 'if..()' logic test in my loop. The problem code is located in the line: if($protocols[$i][$tempdisabledKVString] -eq "true")
I have the following object:
$protocolsJSON = #"
[
{
"Name": "TLS 1.2",
"Server-Enabled": True,
"Client-Enabled": True
}
]
"#
$protocols = $protocolsJSON | ConvertFrom-Json
Which fails the nested if statement below (undesired behavior)
elseif ($isDefaultDisabled -eq 0) # Protocol is manually enabled in registry (part 1.A)
{
if($protocols[$i][$tempdisabledKVString] -eq "True") # Protocol should be manually enabled in registry (part 1.B)
{
# For TLS 1.2 to be enabled and negotiated on servers that run Windows Server 2008 R2,
# you MUST create the DisabledByDefault entry in the appropriate subkey (Client, Server)
# and set it to "0". The entry will not be seen in the registry and it is set to "1" by default.
$errorString = "Warning: Protocol is only partially enabled."
$TLSProtocolResult.Errors = $errorString
}
else
{
write-host "DEBUG " $protocols[$i][$tempdisabledKVString]
write-host "DEBUG " $protocols[$i]
write-host "DEBUG " [$tempdisabledKVString]
$errorString = "Error: Protocol should be disabled."
$TLSProtocolResult.Errors = $errorString
}
}
Which produces the following output
DEBUG
DEBUG #{Name=TLS 1.2; Server-Enabled=True; Client-Enabled=True}
DEBUG [Server-Disabled]
DEBUG
DEBUG #{Name=TLS 1.2; Server-Enabled=True; Client-Enabled=True}
DEBUG [Client-Disabled]
How do I edit the IF statement so that I can test the true/false status of $protocols[$i][$tempdisabledKVString]?
The problem is you're trying to access a property as if it were a nested array.
Try this:
$protocolsJSON = #"
[
{
"Name": "TLS 1.2",
"Server-Enabled": true,
"Client-Enabled": true
}
]
"#
$protocols = $protocolsJSON | ConvertFrom-Json
$property = "Server-Enabled"
Write-Host "RESULT: $($protocols[0].$property)"
Your issue is most likely the JSON not being parsed. Try including quotes around your values. i.e. replace: "Server-Enabled": True, with "Server-Enabled": "True",.
Also, when if $tempdisabledKVString is the name of a property, you need to access it as a property rather than an index. i.e. replace $protocols[$i][$tempdisabledKVString] with $protocols[$i]."$tempdisabledKVString".
Clear-Host
$protocolsJSON = #"
[
{
"Name": "TLS 1.2",
"Server-Enabled": "True",
"Client-Enabled": "True"
}
]
"#
$protocols = $protocolsJSON | ConvertFrom-Json
$i = 0
$tempdisabledKVString = 'Server-Enabled'
if ($protocols[$i]."$tempdisabledKVString" -eq 'True') {
":)"
} else {
":("
}
In theory these issues should have caused exceptions to be thrown. For some reason you're not seeing those, or that would have prompted you to find the cause. Please check the value of $ErrorActionPreference; by default it should be set to Continue; but it looks like it may have been updated to SilentlyContinue for your session. It's OK to have this setting in some scenarios; but generally better to have errors be thrown when they occur so that you can see what's going wrong.

IIS Administration from TFS Release automation - PowerShell script fails Get-ChildItem -Path IIS:\Sites

I'm having difficulty in using the Powershell WebAdministration Module with Powershell 5.1.
This is on a Server 2008R2 machine, running IIS 7.5
There seems to be an issue with this module in that occasionally the module requires a few ms to complete initialization after loading. The recommendation is to do a simple 'write-output' after loading to allow the server to complete the init tasks. I dont see it on all the servers I'm managing, but this particular server is consistent in its need.
There's also an issue that I found people had with Get-Sites failing that could be dealt with by wrapping in a try/catch.
However, the problem I'm seeing is that even with the identified workarounds, I'm not getting consistent results between an interactive run, and a run that is performed from TFS Automated release.
Import-Module WebAdministration
$sites="none"
Write-Output "suggested as a work around for the task dying for no apparent reason"
try {
$sites = Get-ChildItem -Path IIS:\Sites
Write-Output "part of try"
} catch {
$sites = Get-ChildItem -Path IIS:\Sites
Write-Output "part of catch"
} finally {
Write-Output $sites
Write-Output $sites.GetType()
}
When run via TFS Release automation (agent version 2.117.2, PowerShell on the target machine version 1.0.47):
2018-01-25T13:18:29.5474995Z Importing alias 'End-WebCommitDelay'.
2018-01-25T13:18:29.5474995Z
2018-01-25T13:18:29.5474995Z suggested as a work around for the task dying for no apparent reason
2018-01-25T13:18:29.5474995Z part of catch
2018-01-25T13:18:29.5474995Z
2018-01-25T13:18:29.5474995Z
2018-01-25T13:18:29.5631000Z Deployment status for machine 'DESTSERV:5985' : 'Passed'
(no sitelist is returned)
When run as an interactive process (with same user)
PS C:\Users\Install> C:\Installers\Modules\test-iis.ps1
suggested as a work around for the task dying for no apparent reason
part of try
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
AppTest 2 Started E:\WebApps\AppTest http *:80:
https *:443:
Test 1 Stopped C:\inetpub\wwwroot\Test http *:80:
https
Module : CommonLanguageRuntimeLibrary
Assembly : mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
TypeHandle : System.RuntimeTypeHandle
DeclaringMethod :
BaseType : System.Array
UnderlyingSystemType : System.Object[]
FullName : System.Object[]
AssemblyQualifiedName : System.Object[], mscorlib, Version=4.0.0.0,
Culture=neutral,
PublicKeyToken=b77a5c561934e089
Namespace : System
GUID : 00000000-0000-0000-0000-000000000000
IsEnum : False
GenericParameterAttributes :
IsSecurityCritical : False
IsSecuritySafeCritical : False
IsSecurityTransparent : True
IsGenericTypeDefinition : False
IsGenericParameter : False
GenericParameterPosition :
IsGenericType : False
IsConstructedGenericType : False
ContainsGenericParameters : False
StructLayoutAttribute :
Name : Object[]
MemberType : TypeInfo
DeclaringType :
ReflectedType :
MetadataToken : 33554432
GenericTypeParameters : {}
DeclaredConstructors : {Void .ctor(Int32)}
DeclaredEvents : {}
DeclaredFields : {}
DeclaredMembers : {Void Set(Int32, System.Object), System.Object&
Address(Int32), System.Object Get(Int32),
Void .ctor(Int32)}
DeclaredMethods : {Void Set(Int32, System.Object), System.Object&
Address(Int32), System.Object Get(Int32)}
DeclaredNestedTypes : {}
DeclaredProperties : {}
ImplementedInterfaces : {System.ICloneable, System.Collections.IList,
System.Collections.ICollection,
System.Collections.IEnumerable...}
TypeInitializer :
IsNested : False
Attributes : AutoLayout, AnsiClass, Class, Public, Sealed,
Serializable
IsVisible : True
IsNotPublic : False
IsPublic : True
IsNestedPublic : False
IsNestedPrivate : False
IsNestedFamily : False
IsNestedAssembly : False
IsNestedFamANDAssem : False
IsNestedFamORAssem : False
IsAutoLayout : True
IsLayoutSequential : False
IsExplicitLayout : False
IsClass : True
IsInterface : False
IsValueType : False
IsAbstract : False
IsSealed : True
IsSpecialName : False
IsImport : False
IsSerializable : True
IsAnsiClass : True
IsUnicodeClass : False
IsAutoClass : False
IsArray : True
IsByRef : False
IsPointer : False
IsPrimitive : False
IsCOMObject : False
HasElementType : True
IsContextful : False
IsMarshalByRef : False
GenericTypeArguments : {}
CustomAttributes : {[System.SerializableAttribute()]}
PS C:\Users\Install>
When WebAdministration doesn't import properly it seems there is more going wrong than just a slow initialization. The IIS: provider is not functional.. it throws no error. It does return something though - the value set at the top of the script is being overwritten... I just dont get the sites list.
As can be seen by the interactive run, there are sites present, so an empty return value does not make any sense.
Updated:
All the suggested workarounds have been applied yet the Get-ChildItem in the catch fails to produce the sites list. How do I get a consistent result where I can obtain the full list of sites in IIS both interactively and through TFS Release's PowerShell on the target machine task??
I can run the script via TFS Release automation without any error and get the consistent result with the interactive process. (Agent version 2.117.1, PowerShell on the target machine version 5.1.14393.1944).
You need to set the TFS build agent service account as an local administrator account in the target machine (Just add the build agent service account to the local administrators group).
So, please have a try with that. If that still not work, just try to upgrade the PowerShell version on your target machine (may be caused by the PS version). Refer to Install PowerShell 5 in Windows Server 2008 R2 for details.
UPDATE:
Generally, the output of the scripts will not back to build process when run the PS scripts with task PowerShell on the target machines.
You need to update the script to get back the outputs (use Write-Verbose instead of Write-Output ), just try below scripts, it works for me:
Import-Module WebAdministration
$sites="none"
Write-Verbose "suggested as a work around for the task dying for no apparent reason"
try {
$sites = Get-ChildItem -Path IIS:\Sites | Out-String
Write-Verbose "part of try"
} catch {
$sites = Get-ChildItem -Path IIS:\Sites | Out-String
Write-Verbose "part of catch"
} finally {
Write-Verbose $sites -verbose
Write-Verbose $sites.GetType() -verbose
}

Assertion over each item in collection in Pester

I am doing some infrastructure testing in Pester and there is repeating scenario that I don't know how to approach.
Let's say, I want to check whether all required web roles are enabled on IIS. I have a collection of required web roles and for each of them I want to assert it is enabled.
My current code looks like this:
$requiredRoles = #(
"Web-Default-Doc",
"Web-Dir-Browsing",
"Web-Http-Errors",
"Web-Static-Content",
"Web-Http-Redirect"
)
Context "WebRoles" {
It "Has installed proper web roles" {
$requiredRoles | % {
$feature = Get-WindowsOptionalFeature -FeatureName $_ -online
$feature.State | Should Be "Enabled"
}
}
}
It works in the sense that the test will fail if any of the roles are not enabled/installed. But that is hardly useful if the output of such Pester test looks like this:
Context WebRoles
[-] Has installed proper web roles 2.69s
Expected: {Enabled}
But was: {Disabled}
283: $feature.State | Should Be "Enabled"
This result doesn't give any clue about which feature is the Disabled one.
Is there any recommended practice in these scenarios? I was thinking about some string manipulation...
Context "WebRoles" {
It "Has installed proper web roles" {
$requiredRoles | % {
$feature = Get-WindowsOptionalFeature -FeatureName $_ -online
$toCompare = "{0}_{1}" -f $feature.FeatureName,$feature.State
$toCompare | Should Be ("{0}_{1}" -f $_,"Enabled")
}
}
}
which would output:
Context WebRoles
[-] Has installed proper web roles 2.39s
Expected string length 27 but was 28. Strings differ at index 20.
Expected: {IIS-DefaultDocument_Enabled}
But was: {IIS-DefaultDocument_Disabled}
-------------------------------^
284: $toCompare | Should Be ("{0}_{1}" -f $_,"Enabled")
...which is better, but it doesn't feel very good...
Also, there is second problem with the fact that the test will stop on first fail and I would need to re-run the test after I fix each feature...
Any ideas?
Put your It inside the loop like so:
Context "WebRoles" {
$requiredRole | ForEach-Object {
It "Has installed web role $_" {
(Get-WindowsOptionalFeature -FeatureName $_ -online).State | Should Be "Enabled"
}
}
}