Git Product home page Git Product logo

requirements's Introduction

Breaking changes

v2.3

  • Requirement nesting is no longer supported. Requirements are supposed to be atomic components of a system, and by definition, atomic components cannot contain other atomic components and still be atomic. Recommendation is to migrate to our supported patterns.
  • Format-Callstack, the formatter for nested Requirements, is no longer supported. Recommendation is to migrate to Format-Verbose.
  • Name is no longer a supported property on Requirements. Recommendation is to migrate to namespaces.

Overview

Requirements is a PowerShell Gallery module for declaratively describing a system as a set of "requirements", then idempotently setting each requirement to its desired state.

The background motivation and implementation design are discussed in detail in Declarative Idempotency.

Trevor Sullivan provides a good overview and (slightly outdated) tutorial video about Requirements.

What is a Requirement

A "Requirement" is a single atomic component of a system configuration. For a system to be in its desired state, all Requirements in the system must be in a desired state.

A Requirement is an object defined by three properties:

  • Describe - A string that describes the desired state of the Requirement.
  • Test - A scriptblock that returns whether the Requirement is in its desired state.
  • Set - A scriptblock that can be run to put the Requirement into its desired state if it is not in a desired state.

Why Requirements

Code against the Desired State

In DevOps, you may be managing a fleet of servers, containers, cloud resources, files on disk, or many other kinds of components in a heterogeneous system. Lets say you have n components in your system and every component is either in a GOOD or BAD state. You have two options:

  • You can try and account for every possible configuration of your system and transition between those states, but then you will have 2**n possible states to manage.
  • You can only account for the desired state of each individual component, so you will only have n states to account for. Much simpler!

Generically define Requirements

If you only manage cloud resources, then try to use Terraform, ARM, or CloudFormation. If you only manage kubernetes resources, then try and use Helm. These are domain-specific frameworks for managing the desired state of resources and are best suited for their task.

However, you will often find you have complex configurations, such as configurations that can only be described in PowerShell. You may even have macroconfigurations that consist of one or more Terraform templates. In this case you will probably want something more generic to glue your configurations together without sacrificing the declarative desired state paradigm. This is where Requirements comes in.

Comparison to DSC

Desired State Configurations allow you to declaratively describe a configuration, then let the local configuration manager handle with setting the configuration to its desired state. This pattern from the outside may seem similar to Requirements, but there are crucial differences.

DSC is optimized for handling many configurations asynchronously. For example, applying a configuration in parallel to multiple nodes. In contrast, Requirements applies a single configuration synchronously. This enables usage in different scenarios, including:

  • CI/CD scripts
  • CLIs
  • Dockerfiles
  • Linux

While Requirements supports DSC resources, it does not have a hard dependency on DSC's configuration manager, so if your Requirements do not include DSC resources they will work on any platform that PowerShell Core supports.

Quickstart

Defining Requirements

The easiest way to declare a requirement is to define it as a hashtable and let PowerShell's implicit casting handle the rest.

$requirements = @(
    @{
        Describe = "Resource 1 is present in the system"
        Test     = { $mySystem -contains 1 }
        Set      = {
            $mySystem.Add(1) | Out-Null
            Start-Sleep 1
        }
    },
    @{
        Describe = "Resource 2 is present in the system"
        Test     = { $mySystem -contains 2 }
        Set      = {
            $mySystem.Add(2) | Out-Null
            Start-Sleep 1
        }
    },
    @{
        Describe = "Resource 3 is present in the system"
        Test     = { $mySystem -contains 3 }
        Set      = {
            $mySystem.Add(3) | Out-Null
            Start-Sleep 1
        }
    }
)

Note: Describe the desired state, not the transition

Our Describe describes the desired state of the Requirement (ex: "Resource 1 is present in the system") and not the Set block's transitioning action (ex: "Adding Resource 1 to the system"). This is because the Set block is not called if the Requirement is already in its desired state, so if we used the latter Describe and Resource 1 was already present in the system, we would be inaccurately logging that the Requirement is modifying the system when it is actually taking no action.

The sooner you embrace the Desired State mindset, the less friction you will have writing Requirements and managing your complex system configurations.

Enforcing the Configuration

Once you have an array of Requirements, you can simply pipe the Requirements into Invoke-Requirement to put each Requirement into its desired state.

$requirements | Invoke-Requirement

The status of each Requirement will be logged to the output stream. By default they are shown with Format-List, but you can pipe the results to Format-Table, or use one of the packaged formatters for Requirements-specific event formatting and filtering.

Advanced Configurations

Types of Requirements

As you learned previously, Requirements consist of Describe, Test, and Set properties. There are 4 types of Requirements--one for every permutation of including or excluding Test and Set. Note that Describe must always be present.

Standard Requirement

This is the kind you are already familiar with. It includes both a Test and Set.

Validation Requirements

If you wish to assert that a precondition is met before continuing, you can leave out the Set block. This is useful for Defensive programming, or when a Requirement requires manual steps.

@{
    Describe = "Azure CLI is authenticated"
    Test     = { az account }
}

Idempotent Set Requirements

Sometimes, your Set block is already idempotent and an associated Test block cannot be defined. In this case, you can leave out the Test block.

@{
    Describe = "Initial state of system is backed up"
    Set      = { Get-StateOfSystem | Out-File "$BackupContainer/$(Get-Date -Format 'yyyyMMddhhmmss').log" }
}

Patterns

Some people have trouble managing large configurations with Requirements because they try and explicitly define a single array literal of Requirements; however, this is unnecessary and Requirements can be handled like any other PowerShell object. Here are some examples of patterns for managing Requirements.

Avoiding state with selectors

Requirements should strongly avoid maintaining internal state. Requirements is for enforcing declarative programming, whereas maintaining state is an imperative loophole that breaks the declarative paradigm.

Instead, use selectors to easily derive up-to-date properties of the system using unit-testable functions.

<#
.SYNOPSIS
  Gets the random storage account name from the environment in the cloud
#>
function Select-StorageAccountName([string]$EnvName) { ... }

<#
.SYNOPSIS
  Returns a Requirement that ensures a storage ccount exists in Azure
#>
function New-StorageAccountRequirement {
    @{
        Describe = "Storage Account exists"
        Test     = { Test-StorageAccountExists (Select-StorageAccountName $env:EnvName) }
        Set      = { New-StorageAccount (Select-StorageAccountName $env:EnvName) }
    }
}

Reusable requirements

You can wrap Requirements in a parameterized function or script to avoid redifining Requirements--

function New-ResourceGroupRequirement {
    Param(
        [string]$Name,
        [string]$Location
    )

    New-RequirementGroup "rg" -ScriptBlock {
        @{
            Describe = "Logged in to Azure"
            Test     = { Get-AzAccount }
            Set      = { Connect-AzAccount }
        }
        @{
            Describe = "Resource Group '$Name' exists"
            Test     = { Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue }
            Set      = { New-AzResourceGroup -Name $Name -Location $Location }
        }
    }
}

Then call your function to generate the Requirement--

$Name = "my-rg"
$Location = "West US 2"

& {
    New-ResourceGroupRequirement -Name $Name -Location $Location
    @{
        Describe = "Do something with the resource group"
        Test     = { ... }
        Set      = { ... }
    }
} `
| Invoke-Requirement `
| Format-Table

Defining Requirements with control flow

Using control flow statements, like if and foreach, can dramatically simplify your Requirement definitions. Let's see if we can simiplify our quickstart example.

foreach ($resourceId in 1..3) {
    @{
        Describe = "Resource $resourceId is present in the system"
        Test     = { $mySystem -contains $resourceId }.GetNewClosure()
        Set      = { $mySystem.Add($resourceId) | Out-Null; Start-Sleep 1 }.GetNewClosure()
    }
}

Notice that we had to call .GetNewClosure() to capture the current value of $resourceId in the scriptblock--otherwise the value would be 3 or $null depending on where we invoked it.

When you define Requirements with control flow in this manner, the Requirements are written to output. As such, this logic should be wrapped in a script, function, or scriptblock.

Managing large configurations with Namespaces

You can group Requirements into namespaces for clearer logging. To add a namespace to Requirements, use the New-RequirementGroup function. You can nest namespaces as well.

New-RequirementGroup "local" {
    New-RequirementGroup "clis" {
        @{
            Describe = "az is installed"
            Test     = { ... }
            Set      = { ... }
        }
        @{
            Describe = "kubectl is installed"
            Test     = { ... }
            Set      = { ... }
        }
    }
    New-RequirementGroup "configs" {
        @{
            Describe = "cluster config is built"
            Test     = { ... }
            Set      = { ... }
        }
    }
}
New-RequirementGroup "cloud" {
    @{
        Describe = "Terraform is deployed"
        Test     = { ... }
        Set      = { ... }
    }
}

The above example would result in the Requirements below.

Namespace     Describe
---------     --------
local:clis    az is installed
local:clis    kubectl is installed
local:configs cluster config is built
cloud         Terraform is deployed

Isomorphic Enforcement

Isomorphic execution means that our Requirements are enforced the same regardless of what context they are enforced in. You will want your Requirements to run in a CICD pipeline for safe deployment practices and run manually from your local machine for development purposes, but in both contexts the Requirements should run exactly the same.

We will accomplish this by implementing Separation of Concerns, separating our Requirement definitions from our execution logic:

  • myrequirements.ps1, which will return an array of Requirements.

  • Invoke-Verbose.ps1, which will be called in a CICD pipeline and write verbose status information to the output stream.

    ./myrequirements.ps1 | Invoke-Requirement | Format-Verbose
  • Invoke-Checklist.ps1, which will be called in a console and interactively write to the host.

    ./myrequirements.ps1 | Invoke-Requirement | Format-Checklist

Defining DSC Resources

If you're using Windows and PowerShell 5, you can use DSC resources with Requirements.

$requirement = @{
    Describe     = "My Dsc Requirement"
    ResourceName = "File"
    ModuleName   = "PSDesiredStateConfiguration"
    Property     = @{
        Contents        = "Hello World"
        DestinationPath = "C:\myFile.txt"
        Force           = $true
    }
}
New-Requirement @requirement | Invoke-Requirement | Format-Checklist

Formatting the logs

Invoke-Requirement will output logging events for each step in a Requirement's execution lifecycle. You can capture these logs with Format-Table or Format-List, or

$requirements | Invoke-Requirement | Format-Table

Format-Table

These logs were using -Autosize parameter, which better formats the columns, but does not support outputting as a stream.

  Method Lifecycle Name       Date
  ------ --------- ----       ----
    Test     Start Resource 1 6/12/2019 12:00:25 PM
    Test      Stop Resource 1 6/12/2019 12:00:25 PM
     Set     Start Resource 1 6/12/2019 12:00:25 PM
     Set      Stop Resource 1 6/12/2019 12:00:26 PM
Validate     Start Resource 1 6/12/2019 12:00:26 PM
Validate      Stop Resource 1 6/12/2019 12:00:26 PM
    Test     Start Resource 2 6/12/2019 12:00:26 PM
    Test      Stop Resource 2 6/12/2019 12:00:26 PM
     Set     Start Resource 2 6/12/2019 12:00:26 PM
...

Format-Checklist

Format-Checklist will present a live-updating checklist to the user.

Format-Checklist output

Format-Verbose

Unlike Format-Checklist, Format-Verbose prints all log events and includes metadata. For complex use cases, you can define nested Requirements (Requirements that contain more Requirements in their Set block). Format-Verbose will print the stack of Requirement names of each Requirement as its processed.

Format-Verbose output

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

requirements's People

Contributors

chriskuech avatar microsoft-github-policy-service[bot] avatar microsoftopensource avatar msftgits avatar omiossec avatar willwh avatar zachchilders avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

requirements's Issues

Is Remote execution viable use case?

Is executing requirements on remote machine a viable use case for the tool?
For example extending the requirement schema with ComputerName and winrm connectivity details, so that the tool can orchestrate the execution on remote machines.

If so, I can contribute.

Request for new Report formatter

I would like to have a summary format that is like Format-Checklist the final state of each Requirement is listed with a bool.

eg.

Requirement                         Result
-----------                         ------
SecretManagement is installed         True
A New-Password function exists        True
SQL SA Password secret is created     True
Docker Network is created             True
SQL Server image is cached            True
SQL Server container exists           True
SQL Server container is running       True
Create ProGet db                      True
Proget image is cached                True
Proget container exists               True
Local proget-packages folder exists   True
Proget container is running           True

Format-Checklist corner case

Format-Checklist has reached an unexpected state 'TestSet Set Stop True'.
If you are piping the output of Invoke-Requirement directly to this
cmdlet, then this is probably a bug in Format-Checklist.
At C:\Users\chkuech\Documents\PowerShell\Modules\Requirements\2.2.4\src\formatters.ps1:128 char:13
+             throw @"
+             ~~~~~~~~
+ CategoryInfo          : OperationStopped: (Format-Checklist ha\u2026n Format-Checklist.:String) [], RuntimeException
+ FullyQualifiedErrorId : Format-Checklist has reached an unexpected state 'TestSet Set Stop True'.
If you are piping the output of Invoke-Requirement directly to this
cmdlet, then this is probably a bug in Format-Checklist.

Cannot validate argument on parameter when using Remote Variables

Dear @chriskuech,

I use this requirement block :

@{ Describe = "Set Secret" Test = { Invoke-CommandAs -ComputerName $VmName -ScriptBlock { Get-SecretInfo -Name $Using:UserSecret } -AsSystem } Set = { Invoke-CommandAs -ComputerName $VmName -ScriptBlock { Set-Secret -Name $Using:UserSecret -Secret $Using:PasswordSecret -Vault CredManStore } -AsSystem } }

But I get the error:

Cannot validate argument on parameter 'Name'. The argument is null or empty. Provide an argument that is not null or empty, and
then try the command again.
+ CategoryInfo : InvalidData : (:) [Set-Secret], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.SecretManagement.SetSecretCommand
+ PSComputerName : MYVMNAME

I already tried the same cmdlet without Requirements module and it works.

It seems the Remote Variables are not usable with Requirements, or is it a mistake from my side?

Thank you for your support!

Invoke-requirement fail if set action is not defined

Hi,
following your video i found this:

$requirements = @(
@{

    Describe = 'Folder tempi present in the system'
    Test     = { test-path -path 'c:/temp' }
    #Set      = { new-item -ItemType Directory -Path 'c:/temp'}
}

)

c:/temp still is not created, but the command fail:
pplyRequirement : Failed to apply Requirement 'Folder tempi present in the system'
At C:\Program Files\WindowsPowerShell\Modules\Requirements\2.3.6\src\core.ps1:49 char:23

  • $Requirements | % { applyRequirement $_ }
  •                   ~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    • FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,applyRequirement

Unable to find type [RequirementEvent]

It looks like the PS classes are not being properly exposed. Invoke-Requirement does not see the class RequirementEvent.

Unable to find type [RequirementEvent].
At C:\Users\Brandon\Documents\PowerShell\Modules\Requirements\2.0\src\interface.ps1:87 char:17
+     [OutputType([RequirementEvent])]
+                 ~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (RequirementEvent:TypeName) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TypeNotFound

Here is a repo script:

Import-Module Requirements

$requirements = @(
    @{
        Name     = "Resource 1"
        Describe = "Resource 1 is present in the system"
        Test     = { $mySystem -contains 1 }
        Set      = { $mySystem.Add(1) | Out-Null; Start-Sleep 1 }
    },
    @{
        Name     = "Resource 2"
        Describe = "Resource 2 is present in the system"
        Test     = { $mySystem -contains 2 }
        Set      = { $mySystem.Add(2) | Out-Null; Start-Sleep 1 }
    }
)

# demo using Format-Table
$mySystem = [System.Collections.ArrayList]::new()
$requirements | Invoke-Requirement | Format-Table

Improve documentation around Parameter values not accessible in Test and Set

Sample code: https://gist.github.com/cdhunt/80984dea7c6e45f42badf6c7f7f7bd8f

I've tried .GetNewClosure() on the Test and Set block as described in the Readme and $Name is always $null.

However, adding .GetNewClosure() on the New-RequirementGroup scriptblock did create a correctly scoped closure.

Name                           Value
----                           -----
PSVersion                      7.0.3
PSEdition                      Core
GitCommitId                    7.0.3
OS                             Linux 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Negative tests

Missed some cases where Requirements failed to error correctly. We should enumerate and test these cases:

Methods Scenario
Test Test outputs a falsey value
Set Set throws an error
Test Set Test fails both times it is called

Calling only tests (without applying)

Hi guys,

For my use case, I would need to call the test blocks only of the requirements. I would still need to set them, but I would like to keep control of the action of 'setting' and use the test functions to simply have an overview of the state of a machine, without necessarly applying the setting.

Is that possible with this module? I looked and tried a bit, but I couldn't find it in the invoke-requirement parameter set.
Perhaps, that could be a nice feature, having a -TestsOnly parameter or so .:)

Poor user experience mixing DSC requirements

Examples of using normal requirements mixed with DSC requirements would be helpful. The suggested method of using implicit and defining an array does not work if DSC requirements are inserted. Error below is given:

The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

A current workaround is to pipe the requirements array to New-Requirement while splatting the hashtable. Because we need to splat we have to use a foreach-object instead of a normal pipe:

$requirements | % { $r = $_; New-Requirement @r } | Invoke-Requirement | Format-Callstack

Better unit tests for formatters

  • Check the full string content of a requirement evaluation log output
  • Check that TestSet, Test, and Set all do not throw exceptions

CI/CD

I have few remarks regarding how the module is tested and build

I did not see any build or CD information in the module. There is no badge, no build process.
If you look at the final product, the one you get from the PowerShell Gallery, include the same folder as the GitHub repos. This include the source, the .git, the .vscode and the images folders, readme.md and the test files.

My suggestions:

  • Create a build process (Azure DevOps ? but perhaps it exists elsewhere)
    Include a module creation with one psm1 file and a class folder for type.ps1
  • Test the newly created module (test the module and the scripts)
  • Include a badge on the readme page
  • Automate module publishing

Status of this project?

Hi team,

Interesting project, thank you for putting effort and open-sourcing this.
Can be very useful PS_based replacement of ServerSpec, InSpec and other non-PS based tools.

That said, can you please mention the status of this project?
Eg., who and how works on this, what is the roadmap and future, will it be supported in a long run or just abandoned at some point?

All this is useful to make informed decision on Requirements usage.

Integration tests

We currently only have unit tests, where we dot-source the files and extensively test each function. We currently do not have integration tests where we Import-Module Requirements and test the functions. We have manually-validated integration testing using /examples.ps1. We need true integration tests, perhaps copied/forked from our interface.tests.ps1 tests.

We have had a few (now solved) issues already due to lack of true integration testing.

`Invoke-Requirement` continues after failed `Test` Requirement

Repro

@(
  @{
    Describe = 1
    Test     = { $true }
  },
  @{
    Describe = 2
    Test     = { $false }
  },
  @{
    Describe = 3
    Test     = { $true }
  }
) | Invoke-Requirement

Expected

Invoke-Requirement throws a validation error after the second Requirement

Observed

Invoke-Requirement processes all Requirements

Make "Name" useful

Goals

  • Consistent behavior across formatters
  • Requirement composition with namespacing

Tasks

  • Implement Push-Requirement [-Namespace] <string> [[-Requirements] <Requirements[]>] [[-ScriptBlock] <ScriptBlock>] for composing arrays of requirements, automatically prefixing the Name property of the Requirements with Namespace.
  • Consistent use of Name vs Description in formatters
  • Deprecate nested Requirements
  • Update README with modified patterns

Better Docs

Topics that need to be covered

  • What is a Requirement?
    • What does a single Requirement look like?
    • What are all the properties on a Requirement?
    • Which properties are mandatory/optional?
  • What are the major patterns for building Requirements?
    • Dynamically generating Requirements using control flow (if, foreach, etc)
    • "Getter/setter" pattern for getting properties of your system
    • "Generator function" pattern for returning Requirements from a function based on parameters

Parallel execution of Requirements

Invoke-Requirement should have a switch -Parallel that allows requirements to be executed in parallel according to the Name and DependsOn properties of each Requirement.

This opt-in feature would provide a major improvement over DSC’s synchronous execution.

DSC resources broken in latest version

Latest version still uses the deprecated "Name" property when creating DSC based requirement:

  switch ($PSCmdlet.ParameterSetName) {
    "Script" {
      [Requirement]@{
        Namespace = $Namespace
        Describe  = $Describe
        Test      = $Test
        Set       = $Set
        DependsOn = $DependsOn
      }
    }
    "Dsc" {
      $dscParams = @{
        Name       = $ResourceName
        ModuleName = $ModuleName
        Property   = $Property
      }
      [Requirement]@{
        Name      = $Namespace
        Describe  = $Describe
        Test      = { Invoke-DscResource -Method "Test" @dscParams }.GetNewClosure()
        Set       = { Invoke-DscResource -Method "Set" @dscParams }.GetNewClosure()
        DependsOn = $DependsOn
      }
    }
  }

The DSC portion needs to be updated to

      [Requirement]@{
        Namespace      = $Namespace
        Describe  = $Describe
        Test      = { Invoke-DscResource -Method "Test" @dscParams }.GetNewClosure()
        Set       = { Invoke-DscResource -Method "Set" @dscParams }.GetNewClosure()
        DependsOn = $DependsOn
      }

Would submit PR but don't have CLA...

Format-Checklist and Format-Callstack not included as exported functions in Requirements.psd1

PS C:\users\zer0trust\Desktop> get-command -Module Requirements

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Invoke-Requirement                                 2.1        requirements
Function        New-Requirement                                    2.1        requirements
Function        Set-Requirement                                    2.1        requirements
Function        Test-Requirement                                   2.1        requirements

From Requirements.psd1:

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
  FunctionsToExport = @(
    'Invoke-Requirement',
    'New-Requirement',
    'Set-Requirement',
    'Test-Requirement'
  )

Format-Checklist exits PowerShell session on failing test

Getting a strange problem using format-checklist, if the test fails then the PowerShell session exits. This is my first attempt at using this module but I'm sure nothing I'm doing should cause this?

Example code:

$requirements = @(
    @{
        Name     = "format-checklist-test"
        Describe = "Failing test exits session"
        Test     = { Test-Path ./sagsgsg }
    }
)

$requirements | invoke-requirement | format-checklist

07:46:15 [ X ] Failing test exits session

If the test is successful then it works fine, but if it fails then the PowerShell window closes after printing the result (I only managed to see the output by opening a second PowerShell session within the first one.)

I do not get the same problem when using format-callstack or no formatting at all.

Tested on 2 machines with same results


> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.17134.858
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17134.858
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

> get-module requirements

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Script     2.2.6      requirements                        {Format-CallStack, Format-Checklist, Invoke-Requirement, New-Requirement...}

Namespace issue in New-RequirementGroup

I downloaded and have been trying out the module from the gallery version 2.3.4.

Whilst it's very likely that I'm doing something wrong, when I take the sample namespace code from the readme, the output on my systems shows :: instead of the actual namespace from the requriementgroup.

I found that i had to update line 162 of interface.ps1 to use $name instead ($r.Namespace = $Name, $r.Namespace -join $NamespaceDelimiter) because some for some reason the alias doesn't work on my system.

Sorry if this isn't logged in the exactly correct method (it's my first day)

Namespace does not end up in output Requirements

Using the following snippet from "Managing large configurations with Namespaces" produces Requirements without a Namespace (Namespace = "::" or ":"):

New-RequirementGroup "local" {
    New-RequirementGroup "clis" {
        @{
            Describe = "az is installed"
            Test     = { ... }
            Set      = { ... }
        }
        @{
            Describe = "kubectl is installed"
            Test     = { ... }
            Set      = { ... }
        }
    }
    New-RequirementGroup "configs" {
        @{
            Describe = "cluster config is built"
            Test     = { ... }
            Set      = { ... }
        }
    }
}
New-RequirementGroup "cloud" {
    @{
        Describe = "Terraform is deployed"
        Test     = { ... }
        Set      = { ... }
    }
}

This is the output:

DependsOn : {}
Namespace : ::
Describe  : az is installed
Test      :  ... 
Set       :  ... 

DependsOn : {}
Namespace : ::
Describe  : kubectl is installed
Test      :  ... 
Set       :  ... 

DependsOn : {}
Namespace : ::
Describe  : cluster config is built
Test      :  ... 
Set       :  ... 

DependsOn : {}
Namespace : :
Describe  : Terraform is deployed
Test      :  ... 
Set       :  ... 

Requirements version 2.3.5
Same results in Powershell 5.1 and 7.0 (preview)

Name                           Value
----                           -----
PSVersion                      5.1.17763.1007
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17763.1007
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
Name                           Value
----                           -----
PSVersion                      7.0.0-rc.3
PSEdition                      Core
GitCommitId                    7.0.0-rc.3
OS                             Microsoft Windows 10.0.17763
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Push-Namespace not listed in FunctionsToExport in the PSD

The current version of the PSD lists the following:

FunctionsToExport = @(
    'Format-Checklist',
    'Format-Verbose',
    'Invoke-Requirement',
    'New-Requirement',
    'Set-Requirement',
    'Test-Requirement'
)

Note, Push-Namespace is not listed and thus not available when module is imported to a PS session.

Unless this is currently intentional, recommend updating the PSD to the following so Push-Namespace is provided:

FunctionsToExport = @(
    'Format-Checklist',
    'Format-Verbose',
    'Invoke-Requirement',
    'New-Requirement',
    'Set-Requirement',
    'Test-Requirement',
    'Push-Namespace'
)

Better error logging

Current error reporting:
image

v1 behavior:

  • Error is captured and printed after the Requirements output
  • Exception callstack is printed with internal Requirements calls popped off.

Passing variable through Requirement blocks

Hello @chriskuech ,

Could we passthrough variables between block ?

Because I use "invoke-command" cmdlet in block of Requirements and I would like to use a PSSession instead of creating a new session each time I invoke a remote command. Initiate a New-PSSession inside a Requirement is mandatory because in my code I test the WinRM connection before invoking any remote command, like:

@{ Describe = "Test WinRM connection '$VmName'" Test = { $online = $false ; while ($online -ne $true) { try { Test-WSMan -ComputerName $VmName -ErrorAction Stop ; $online = $true } catch { Start-Sleep 5 } } } },

Thank you !

Format-Checklist cosmetic error in the test output

While working on some examples with Requirements I have noticed that output for the test state in on the same line

11:46:46 [   ] Create a folder for the app11:46:46 [   ] Create the json config File
$WebConfigRequirements = @(
    @{
        name = "AppFolder"
        describe = "Create a folder for the app" 
        test = {
            test-path -Path $AppPath
        }
        set = {
            new-item -path $AppPath -ItemType Directory
        }
    },
    @{
        name = "configfile"
        describe = "Create the json config File"
        test = {
            test-path -Path "$($AppPath)config.json"
        }
        set = {
            new-item -Path "$($AppPath)config.json"
        }
    }
)
$WebConfigRequirements | Invoke-Requirement | Format-Checklist

I will make some investigation on it

Data type error

[PS C:\users\zer0trust\Desktop> $requirements | Invoke-Requirement | Format-Checklist
Format-Checklist : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:38
+ $requirements | Invoke-Requirement | Format-Checklist
+                                      ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (RequirementEvent:RequirementEvent) [Format-Checklist], ParameterBindingException
    + FullyQualifiedErrorId : InputObjectNotBound,Format-Checklist]
PS C:\users\zer0trust\Desktop> $results = $requirements | Invoke-Requirement
PS C:\users\zer0trust\Desktop> $results.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

The Format-Checklist function appears to be looking for a "RequirementEvent" data type from the output of Invoke-Requirement, but the output of the commands show that it has the System.Array type instead.

Dot Sourced variables only exist within context of current Requirement

I've only just started using this module in the past few days, so I'll apologize in advance if this is something that's already covered in the documentation that I just haven't seen.

I've created an external file named ResourceVariables.ps1, so that anybody using my script can easily plug in their own variables without having to make any changes to the actual requirements script file (CreateResources.ps1)

Within CreateResources.ps1, I'm dot sourcing ResourceVariables.ps1 to import it's variables. However, the variables only exist within the current Resource block, ie:

@(

	@{
		Describe = "First Requirement"
		Set = { . .\ResourceVariables.ps1;
				Write-Host "Import Variables block value of var1 is $var1"
				}
	}
	
	@{
		Describe = "Second Requirement"
		Set = { Write-Host "Use Variables block value of var1 is $var1" }
	
	}
) | Invoke-Requirement | Format-Verbose

In the above example, only "First Requirement" has a value for $var1, but "Second Requirement" does not.

If I declare $var1 within CreateResource.ps1 instead, then both requirements have access to the $var1. But similarly, if I set the value of $var1 within "First Requirement", then "Second Requirement" does not see that value.

I presume this has something to do with requirement nesting, which is no longer supported. But I don't see anything in the Patterns section that would help resolve this issue.

Can anybody give me some guidance on where I'm going wrong ?

Format-Checklist incorrectly parses "Date" field from pipeline

PS C:\users\zer0trust\Desktop> $requirements | Invoke-Requirement | Format-Checklist
03:27:04 [   ] Add test.txt fileFormat-Checklist : Cannot bind parameter 'Date' to the target. Exception setting "Date": "Cannot convert null to type "System.DateTime"."
At line:1 char:38
+ $requirements | Invoke-Requirement | Format-Checklist
+                                      ~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (:) [Format-Checklist], ParameterBindingException
    + FullyQualifiedErrorId : ParameterBindingFailed,Format-Checklist

The "Date" field output from Invoke-Requirement has spaces in it.

Date        : 7/1/2019 3:26:43 PM
Method      : Validate
State       : Stop
Result      : True

Modifying line 28 of formatters.ps1 as follows:

$timestamp = Get-Date -Date "$_.Date" -Format 'hh:mm:ss'

fixed the issue on my client.

Latest 2.3.1 version of Requirements module throws an error about pipeline input for Invoke-Requirement

Reproduction

  • MacOS Catalina
  • PowerShell 6.2.3
  • Requirements module 2.3.1
$Requirements = @(
    @{
        Name = 'vscode-powershell'
        Describe = 'VSCode PowerShell Extension is installed'
        Test = { (code --list-extensions) -match 'ms\-vscode\.powershell' }
        Set = {
            $VSCodePowerShell = 'https://github.com/PowerShell/vscode-powershell/releases/download/v2019.9.0/PowerShell-2019.9.0.vsix'
            Invoke-WebRequest -OutFile ~/powershell.vsix -Uri $VSCodePowerShell
            code --install-extension ~/powershell.vsix
        }
    }
)

$RequirementsResult = $Requirements | Invoke-Requirement

Expected Result

Invoke-Requirement runs successfully.

Actual Result

Invoke-Requirement : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:39
+ $RequirementsResult = $Requirements | Invoke-Requirement
+                                       ~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (System.Collections.Hashtable:Hashtable) [Invoke-Requirement], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Invoke-Requirement

Workaround

  • Downgrade to 2.2.7
  • I haven't tested other interim versions

Format-Checklist does not accept parameter input

Format-Checklist fails when passing a RequirementEvent[] object as a parameter. It should format the objects as it does when they are passed in from the pipeline.

Error:

Format-Checklist : Cannot bind parameter 'Date' to the target. Exception setting "Date": "Cannot convert null to type
"System.DateTime"."
At line:25 char:1
+ Format-Checklist -RequirementEvent $output   # fails
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (:) [Format-Checklist], ParameterBindingException
    + FullyQualifiedErrorId : ParameterBindingFailed,Format-Checklist

Code to reproduce:

$mySystem = [collections.generic.list[int]]::new()

$requirements = @(
    @{
        Name     = "Resource 1"
        Describe = "Resource 1 is present in the system"
        Test     = { $mySystem -contains 1 }
        Set      = { $mySystem.Add(1) | Out-Null; Start-Sleep 1 }
    },
    @{
        Name     = "Resource 2"
        Describe = "Resource 2 is present in the system"
        Test     = { $mySystem -contains 2 }
        Set      = { $mySystem.Add(2) | Out-Null; Start-Sleep 1 }
    }
)

$output = $requirements | Invoke-Requirement

$output | Format-Table | Out-Host            # works
Format-Table -InputObject $output | Out-Host # works

$output | Format-Checklist | Out-Host        # works
Format-Checklist -RequirementEvent $output   # fails

Format-Callstack not printing dates

The Format-Callstack example in example.ps1 does not print the date. I think $_.Date needs to be reassigned outside of the switch expression, just like the other variables. The unit tests for Format-Callstack should've caught this issue.

Type in Defining DSC Resources in README.md

There is a small error in the README.md file regarding DSC in Requirement

    Property     = @{
        Contents        = "Hello World"
        DestinationFile = "C:\myFile.txt"
        Force           = $true
    }

Should be

Property     = @{
        Contents        = "Hello World"
        DestinationPath = "C:\myFile.txt"
        Force           = $true
    }

I will make a PR

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.