From 940e2ca5264a91767f7db445ba5cfe85db26a2e0 Mon Sep 17 00:00:00 2001
From: Chris Hunt <chunt@stackoverflow.com>
Date: Fri, 16 Sep 2022 10:48:22 -0400
Subject: [PATCH 1/2] Get, List, Create

Password is being set to "System.Security.SecureString"
---
 op.crescendo.config.json             |  96 ++++++
 src/class/CommandBuilder.ps1         | 492 ---------------------------
 src/class/OpCommand.ps1              |   0
 src/class/en-US/message.psd1         |   6 -
 src/op-powershell.psd1               | 139 ++++++++
 src/op-powershell.psm1               | 352 ++++++++++++++++++-
 tests/class_CommandBuilder.Tests.ps1 | 251 --------------
 tests/class_OpCommand.Tests.ps1      | 267 ---------------
 8 files changed, 578 insertions(+), 1025 deletions(-)
 create mode 100644 op.crescendo.config.json
 delete mode 100644 src/class/CommandBuilder.ps1
 delete mode 100644 src/class/OpCommand.ps1
 delete mode 100644 src/class/en-US/message.psd1
 create mode 100644 src/op-powershell.psd1
 delete mode 100644 tests/class_CommandBuilder.Tests.ps1
 delete mode 100644 tests/class_OpCommand.Tests.ps1

diff --git a/op.crescendo.config.json b/op.crescendo.config.json
new file mode 100644
index 0000000..cb81451
--- /dev/null
+++ b/op.crescendo.config.json
@@ -0,0 +1,96 @@
+{
+    "$schema": "https://raw.githubusercontent.com/PowerShell/Crescendo/master/Microsoft.PowerShell.Crescendo/schemas/2022-06",
+    "Commands": [
+        {
+            "Verb": "Get",
+            "Noun": "OpSecretList",
+            "OriginalName": "op",
+            "OriginalCommandElements": [
+                "item",
+                "list",
+                "--format",
+                "json"
+            ],
+            "Description": "List secrets",
+            "OutputHandlers": [
+                {
+                    "ParameterSetName": "Default",
+                    "Handler": "$input | ConvertFrom-Json",
+                    "StreamOutput": true
+                }
+            ]
+        },
+        {
+            "Verb": "Get",
+            "Noun": "OpSecret",
+            "OriginalName": "op",
+            "OriginalCommandElements": [
+                "item",
+                "get",
+                "--format",
+                "json"
+            ],
+            "Description": "Get a secret",
+            "Parameters": [
+                {
+                    "Name": "Name",
+                    "OriginalName": "",
+                    "Position": 0,
+                    "Description": "An item's name or its unique identifier",
+                    "ParameterType": "string"
+                }
+            ],
+            "OutputHandlers": [
+                {
+                    "ParameterSetName": "Default",
+                    "Handler": "$input | ConvertFrom-Json",
+                    "StreamOutput": true
+                }
+            ]
+        },
+        {
+            "Verb": "New",
+            "Noun": "OpSecret",
+            "OriginalName": "op",
+            "OriginalCommandElements": [
+                "item",
+                "create"
+            ],
+            "Description": "Create a new secret",
+            "Parameters": [
+                {
+                    "Name": "Category",
+                    "OriginalName": "--category=",
+                    "ParameterType": "string",
+                    "Description": "Specify which template to use",
+                    "DefaultValue": "login",
+                    "AdditionalParameterAttributes": [
+                        "[ValidateSet('login','password')]"
+                    ],
+                    "NoGap": true
+                },
+                {
+                    "Name": "Name",
+                    "OriginalName": "--title=",
+                    "ParameterType": "string",
+                    "NoGap": true
+                },
+                {
+                    "Name": "Username",
+                    "OriginalName": "--username=",
+                    "ParameterType": "string",
+                    "NoGap": true
+                },
+                {
+                    "Name": "Password",
+                    "OriginalName": "password=",
+                    "ParameterType": "SecureString",
+                    "Description": "The password",
+                    "ArgumentTransform": "param([SecureString]$p) ConvertFrom-SecureString -SecureString $p -AsPlainText",
+                    "Mandatory": true,
+                    "NoGap": true
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/class/CommandBuilder.ps1 b/src/class/CommandBuilder.ps1
deleted file mode 100644
index 8f39f98..0000000
--- a/src/class/CommandBuilder.ps1
+++ /dev/null
@@ -1,492 +0,0 @@
-class CommandArgument {
-    [string] $Name
-    [bool] $HasValue = $false
-    [bool] $IsSensitive = $false
-    [object[]] $Value
-    hidden  [string] $Seperator = ''
-    hidden [scriptblock] $ValueReduce = { $args -join ',' }
-    hidden [bool] $HasQuote = $false
-
-    CommandArgument([string]$name) {
-        $this.Name = $name
-    }
-
-    CommandArgument([string]$name, [object[]]$value) {
-        $this.Name = $name
-        $this.Value = $value
-        $this.Seperator = ' '
-        $this.HasValue = $true
-    }
-
-    CommandArgument([string]$name, [object[]]$value, [string]$seperator) {
-        $this.Name = $name
-        $this.Value = $value
-        $this.HasValue = $true
-        $this.Seperator = $seperator
-    }
-
-    CommandArgument([string]$name, [object[]]$value, [string]$seperator, [scriptblock]$reduce) {
-        $this.Name = $name
-        $this.Value = $value
-        $this.HasValue = $true
-        $this.Seperator = $seperator
-        $this.ValueReduce = $reduce
-    }
-
-    [CommandArgument] AddValue($v) {
-        $this.Value += $v
-        $this.HasValue = $true
-
-        if ([string]::IsNullOrEmpty($this.Seperator)) {
-            $this.Seperator = ' '
-        }
-
-        return $this
-    }
-
-    [CommandArgument] SetSeparator([string]$string) {
-        $this.Seperator = $string
-
-        return $this
-    }
-
-    [CommandArgument] SetQuotedValue() {
-        $this.HasQuote = $true
-
-        return $this
-    }
-
-    [CommandArgument] SetValueReduce([scriptblock]$script) {
-        if ($script.ToString() -notlike '*$args *') {
-            throw 'ScriptBlock must contain "$args" variable.'
-        }
-
-        $this.ValueReduce = $script
-
-        return $this
-    }
-
-    [string] ToString() {
-        return $this.ToString($false)
-    }
-
-    [string] ToString([bool]$sanitize) {
-        $_argument = $this.Name
-        $_value = [string]::Empty
-        $_combined = [string]::Empty
-
-        if ($this.HasValue) {
-            $_value = if ($this.Value.Count -gt 1) { $this.ValueReduce.Invoke($this.Value) } else { $this.Value }
-
-            if ($this.IsSensitive -and $sanitize) {
-                $_value = '*****'
-            }
-
-            if ($this.HasQuote) {
-                $_value = '"{0}"' -f $_value
-            }
-        }
-
-        $_combined = $_argument, $_value -join $this.Seperator
-        return $_combined
-    }
-
-    [System.Collections.Generic.List[CommandArgument]] AsMultipleArguments() {
-        $_argArray = [System.Collections.Generic.List[CommandArgument]]::new()
-        if ($this.HasValue) {
-            foreach ($_value in $this.Value) {
-                $_newArg = [CommandArgument]::new($this.Name, $_value)
-                $_newArg.Seperator = $this.Seperator
-                $_newArg.ValueReduce = $this.ValueReduce
-                $_newArg.IsSensitive = $this.IsSensitive
-                $_argArray.Add($_newArg)
-            }
-        }
-
-        return $_argArray
-    }
-}
-
-class CommandRunResult {
-    [bool] $Success
-    [string] $Output
-    hidden [string] $StdOut
-    hidden [string] $StdErr
-
-    CommandRunResult() {
-        $this.Success = $false
-    }
-}
-
-class CommandBuilder {
-    [string] $Name
-    hidden [System.Collections.Generic.List[CommandArgument]] $ArgumentList = [System.Collections.Generic.List[CommandArgument]]::new()
-
-    CommandBuilder($name) {
-        $this.Name = $name
-
-    }
-
-    [CommandBuilder] AddArgument([CommandArgument]$arg) {
-        $this.ArgumentList.Add($arg)
-
-        return $this
-    }
-
-    [CommandBuilder] AddArgument([string]$name) {
-        $this.ArgumentList.Add([CommandArgument]::new($name))
-
-        return $this
-    }
-
-    [CommandBuilder] AddArgument([string]$name, [object]$value) {
-        $this.ArgumentList.Add([CommandArgument]::new($name, $value))
-
-        return $this
-    }
-
-    [CommandBuilder] AddArgument([string]$name, [object]$value, [string]$seperator) {
-        $this.ArgumentList.Add([CommandArgument]::new($name, $value, $seperator))
-
-        return $this
-    }
-
-    [string] ToString() {
-        return $this.ToString($false)
-    }
-
-    [string] ToString([bool]$sanitize) {
-        $_command = $this.Name
-
-        if ($this.ArgumentList.Count -ge 1) {
-            $_argument = $this.ArgumentList | ForEach-Object {
-                $_.ToString($sanitize)
-            }
-            return '{0} {1}' -f $_command, ($_argument -join ' ')
-        }
-        else {
-            return '{0}' -f $_command
-        }
-    }
-
-    [Diagnostics.ProcessStartInfo] GetProcessStartInfo() {
-        $_processInfo = [Diagnostics.ProcessStartInfo]::new()
-        $_processInfo.FileName = $this.Name
-        $_processInfo.RedirectStandardError = $true
-        $_processInfo.RedirectStandardOutput = $true
-        $_processInfo.RedirectStandardInput = $false
-        $_processInfo.UseShellExecute = $false
-
-        $this.ArgumentList | ForEach-Object {
-            if ($_.HasValue) {
-                $_value = $_.ValueReduce.Invoke($_.Value)
-                $_processInfo.ArgumentList.Add($_.Name)
-                $_processInfo.ArgumentList.Add($_value)
-            }
-            else {
-                $_processInfo.ArgumentList.Add($_.Name)
-            }
-        }
-
-        return $_processInfo
-    }
-
-    [string] ParseStdErr([string]$message) {
-        return $message
-    }
-
-    [CommandRunResult] Run() {
-        Write-Verbose ('(Run) Command="{0}"' -f $this.ToString($true))
-
-        $_process = [Diagnostics.Process]::new()
-        $_process.StartInfo = $this.GetProcessStartInfo()
-        $_cleanExit = $false
-        $_message = [string]::Empty
-        $_result = [CommandRunResult]::new()
-
-        try {
-            $_process.Start() | Out-Null
-        }
-        catch [ObjectDisposedException] {
-            Write-Error 'No file name was specified.'
-        }
-        catch [InvalidOperationException] {
-            Write-Error 'The process object has already been disposed.'
-        }
-        catch [PlatformNotSupportedException] {
-            Write-Error 'This member is not supported on this platform.'
-        }
-        catch {
-            Write-Error 'An error occurred when opening the associated file.'
-        }
-
-        try {
-            $_process.WaitForExit(10000)
-            $_cleanExit = $true
-        }
-        catch [SystemException] {
-            Write-Error 'No process Id has been set, and a Handle from which the Id property can be determined does not exist or there is no process associated with this Process object.'
-        }
-        catch {
-            Write-Error 'The wait setting could not be accessed.'
-        }
-
-        if ($_cleanExit) {
-
-            $_stdOut = $_process.StandardOutput.ReadToEnd()
-            $_message = $_stdOut
-            $_stdErr = $_process.StandardError.ReadToEnd()
-
-            if ([string]::IsNullOrEmpty($_stdErr) ) {
-                $_result.Success = $true
-            }
-            else {
-                $_message = $this.ParseStdErr($_stdErr)
-            }
-
-            $_result.Output = $_message
-            $_result.StdOut = $_stdOut
-            $_result.StdErr = $_stdErr
-        }
-
-        return $_result
-    }
-}
-
-class OpCommandRunResult : CommandRunResult {
-    [bool] $SigninRequired
-
-    OpCommandRunResult() : base() {
-        $this.SigninRequire = $false
-    }
-}
-
-class OpCommand : CommandBuilder {
-    hidden $LocalizedMessage
-
-    OpCommand() : base('op') {
-        $this.LocalizedMessage = Import-LocalizedData -FileName message.psd1 -BaseDirectory $PSScriptRoot -ErrorAction Stop
-    }
-
-    [string] ParseStdErr([string]$message) {
-        $_patternSignIn = [regex]'\[ERROR\] (?<date>\d{4}\W\d{1,2}\W\d{1,2}) (?<time>\d{2}:\d{2}:\d{2}).+(?<message>sign(ed){0,1} in)'
-
-        if ($_patternSignIn.Match($message).Success) {
-            return 'errorSignin'
-        }
-
-        return [string]::Empty
-    }
-
-    [OpCommandRunResult] Run() {
-        Write-Verbose ('(Run) Command="{0}"' -f $this.ToString($true))
-
-        $_process = [Diagnostics.Process]::new()
-        $_process.StartInfo = $this.GetProcessStartInfo()
-        $_cleanExit = $false
-        $_message = [string]::Empty
-        $_result = [OpCommandRunResult]::new()
-
-        try {
-            $_process.Start() | Out-Null
-        }
-        catch [ObjectDisposedException] {
-            Write-Error 'No file name was specified.'
-        }
-        catch [InvalidOperationException] {
-            Write-Error 'The process object has already been disposed.'
-        }
-        catch [PlatformNotSupportedException] {
-            Write-Error 'This member is not supported on this platform.'
-        }
-        catch {
-            Write-Error 'An error occurred when opening the associated file.'
-        }
-
-        try {
-            $_process.WaitForExit(10000)
-            $_cleanExit = $true
-        }
-        catch [SystemException] {
-            Write-Error 'No process Id has been set, and a Handle from which the Id property can be determined does not exist or there is no process associated with this Process object.'
-        }
-        catch {
-            Write-Error 'The wait setting could not be accessed.'
-        }
-
-        if ($_cleanExit) {
-
-            $_stdOut = $_process.StandardOutput.ReadToEnd()
-            $_message = $_stdOut
-            $_stdErr = $_process.StandardError.ReadToEnd()
-
-            if ([string]::IsNullOrEmpty($_stdErr) ) {
-                $_result.Success = $true
-            }
-            else {
-                $_parsedErrorMessage = $this.ParseStdErr($_stdErr)
-
-                if ($_parsedErrorMessage -eq 'errorSignin') {
-                    $_result.SigninRequired = $true
-                    $_message = $this.LocalizedMessage[$_parsedErrorMessage]
-                }
-            }
-
-            $_result.Output = $_message
-            $_result.StdOut = $_stdOut
-            $_result.StdErr = $_stdErr
-        }
-
-        return $_result
-    }
-}
-
-class OpCommandList : OpCommand {
-
-    OpCommandList() : base() {
-        $this.AddArgument('list')
-    }
-}
-
-class OpCommandGet : OpCommand {
-
-    OpCommandGet() : base() {
-        $this.AddArgument('get')
-    }
-}
-
-class OpCommandListItem : OpCommandList {
-
-    OpCommandListItem() : base() {
-        $this.AddArgument('items')
-    }
-
-    [OpCommandListItem] WithVault([string]$name) {
-        $this.AddArgument('--vault', $name)
-        return $this
-    }
-
-    [OpCommandListItem] WithCategory([string[]]$category) {
-        $this.AddArgument('--categories', $category)
-        return $this
-    }
-
-    [OpCommandListItem] WithCategory([string]$c1, [string]$c2) {
-        $this.AddArgument('--categories', @($c1, $c2))
-        return $this
-    }
-
-    [OpCommandListItem] WithCategory([string]$c1, [string]$c2, [string]$c3) {
-        $this.AddArgument('--categories', @($c1, $c2, $c3))
-        return $this
-    }
-
-    [OpCommandListItem] WithTag([string[]]$tag) {
-        $this.AddArgument('--tags', $tag)
-        return $this
-    }
-
-    [OpCommandListItem] WithTag([string]$t1, [string]$t2) {
-        $this.AddArgument('--tags', @($t1, $t2))
-        return $this
-    }
-
-    [OpCommandListItem] WithTag([string]$t1, [string]$t2, [string]$t3) {
-        $this.AddArgument('--tags', @($t1, $t2, $t3))
-        return $this
-    }
-}
-
-class OpCommandGetItem : OpCommandGet {
-    hidden [bool] $hasField = $false
-    hidden [bool] $hasFormat = $false
-
-    OpCommandGetItem([string]$item) : base() {
-        $this.AddArgument('item', $item)
-    }
-
-    [OpCommandGetItem] WithVault([string]$name) {
-        $this.AddArgument('--vault', $name)
-        return $this
-    }
-
-    [OpCommandGetItem] WithField([string[]]$field) {
-        $this.AddArgument('--fields', $field)
-        $this.hasField = $true
-        return $this
-    }
-
-    [OpCommandGetItem] WithField([string]$f1, [string]$f2) {
-        $this.AddArgument('--fields', @($f1, $f2))
-        $this.hasField = $true
-        return $this
-    }
-
-    [OpCommandGetItem] WithField([string]$f1, [string]$f2, [string]$f3) {
-        $this.AddArgument('--fields', @($f1, $f2, $f3))
-        $this.hasField = $true
-        return $this
-    }
-
-    hidden [OpCommandGetItem] WithFormat([string]$format) {
-        if ($this.hasFormat) {
-            Write-Error -Message 'Format has already been set'
-            return $this
-        }
-
-        if (-not $this.hasField) {
-            Write-Error -Message 'Format can only be used with Fields'
-            return $this
-        }
-
-        $this.AddArgument('--format', $format)
-        $this.hasFormat = $true
-
-        return $this
-    }
-
-    [OpCommandGetItem] WithJsonFormat() {
-        return $this.WithFormat('JSON')
-    }
-
-    [OpCommandGetItem] WithCsvFormat() {
-        return $this.WithFormat('Csv')
-    }
-
-    [OpCommandGetItem] IncludeTrash() {
-        $this.AddArgument('--include-trash')
-        return $this
-    }
-}
-
-class OpCommandSignin : OpCommand {
-    hidden [securestring]$Password
-
-    OpCommandSignin([string]$account, [securestring]$password) : base() {
-        $this.AddArgument('signin')
-        $this.AddArgument($account)
-        $this.AddArgument('--raw')
-        $this.Password = $password
-    }
-
-    [OpCommandRunResult] Run() {
-        Write-Verbose ('(Run) Command="{0}"' -f $this.ToString($true))
-
-        $_result = [OpCommandRunResult]::new()
-
-        $_expression = $this.ToString()
-        $_output = $this.LocalizedMessage['errorNoOutput']
-
-        try {
-            $_output = Invoke-Expression -Command $_expression -ErrorAction Stop 2>&1
-        }
-        catch {
-            Write-Error -Message $_.Message
-        }
-
-        $_result.Output = $_output
-        $_result.Success = $true
-
-        return $_result
-    }
-}
\ No newline at end of file
diff --git a/src/class/OpCommand.ps1 b/src/class/OpCommand.ps1
deleted file mode 100644
index e69de29..0000000
diff --git a/src/class/en-US/message.psd1 b/src/class/en-US/message.psd1
deleted file mode 100644
index cd0a72b..0000000
--- a/src/class/en-US/message.psd1
+++ /dev/null
@@ -1,6 +0,0 @@
-
-ConvertFrom-StringData -StringData @'
-errorSignin = You are not currently signed in.
-errorNoOutput = The command returned no output.
-'@
-
diff --git a/src/op-powershell.psd1 b/src/op-powershell.psd1
new file mode 100644
index 0000000..19a4aa3
--- /dev/null
+++ b/src/op-powershell.psd1
@@ -0,0 +1,139 @@
+#
+# Module manifest for module 'op-powershell'
+#
+# Generated by: chunt
+#
+# Generated on: 9/16/2022
+#
+
+@{
+
+# Script module or binary module file associated with this manifest.
+RootModule = 'op-powershell.psm1'
+
+# Version number of this module.
+ModuleVersion = '0.0.1'
+
+# Supported PSEditions
+# CompatiblePSEditions = @()
+
+# ID used to uniquely identify this module
+GUID = '6151db2d-c545-4c9a-8f90-65822264bddc'
+
+# Author of this module
+Author = 'chunt'
+
+# Company or vendor of this module
+CompanyName = 'Unknown'
+
+# Copyright statement for this module
+Copyright = '(c) chunt. All rights reserved.'
+
+# Description of the functionality provided by this module
+# Description = ''
+
+# Minimum version of the PowerShell engine required by this module
+PowerShellVersion = '5.1.0'
+
+# Name of the PowerShell host required by this module
+# PowerShellHostName = ''
+
+# Minimum version of the PowerShell host required by this module
+# PowerShellHostVersion = ''
+
+# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
+# DotNetFrameworkVersion = ''
+
+# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
+# ClrVersion = ''
+
+# Processor architecture (None, X86, Amd64) required by this module
+# ProcessorArchitecture = ''
+
+# Modules that must be imported into the global environment prior to importing this module
+# RequiredModules = @()
+
+# Assemblies that must be loaded prior to importing this module
+# RequiredAssemblies = @()
+
+# Script files (.ps1) that are run in the caller's environment prior to importing this module.
+# ScriptsToProcess = @()
+
+# Type files (.ps1xml) to be loaded when importing this module
+# TypesToProcess = @()
+
+# Format files (.ps1xml) to be loaded when importing this module
+# FormatsToProcess = @()
+
+# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
+# NestedModules = @()
+
+# 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 = 'Get-OpSecretList', 'Get-OpSecret', 'New-OpSecret'
+
+# Cmdlets 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 cmdlets to export.
+CmdletsToExport = @()
+
+# Variables to export from this module
+# VariablesToExport = @()
+
+# Aliases 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 aliases to export.
+AliasesToExport = @()
+
+# DSC resources to export from this module
+# DscResourcesToExport = @()
+
+# List of all modules packaged with this module
+# ModuleList = @()
+
+# List of all files packaged with this module
+# FileList = @()
+
+# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
+PrivateData = @{
+
+    PSData = @{
+
+        # Tags applied to this module. These help with module discovery in online galleries.
+        Tags = 'CrescendoBuilt'
+
+        # A URL to the license for this module.
+        # LicenseUri = ''
+
+        # A URL to the main website for this project.
+        # ProjectUri = ''
+
+        # A URL to an icon representing this module.
+        # IconUri = ''
+
+        # ReleaseNotes of this module
+        # ReleaseNotes = ''
+
+        # Prerelease string of this module
+        # Prerelease = ''
+
+        # Flag to indicate whether the module requires explicit user acceptance for install/update/save
+        # RequireLicenseAcceptance = $false
+
+        # External dependent modules of this module
+        # ExternalModuleDependencies = @()
+
+    } # End of PSData hashtable
+
+
+    # CrescendoVersion
+    CrescendoVersion = '1.0.0'
+
+    # CrescendoGenerated
+    CrescendoGenerated = '09/16/2022 10:46:23'
+
+} # End of PrivateData hashtable
+
+# HelpInfo URI of this module
+# HelpInfoURI = ''
+
+# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
+# DefaultCommandPrefix = ''
+
+}
+
diff --git a/src/op-powershell.psm1 b/src/op-powershell.psm1
index 3fb9eb6..bfc8523 100644
--- a/src/op-powershell.psm1
+++ b/src/op-powershell.psm1
@@ -1,11 +1,345 @@
-$classFolder = Join-Path -Path $PSScriptRoot -ChildPath class
-$publicFolder = Join-Path -Path $PSScriptRoot -ChildPath public
-$CommandBuilderFile = Join-Path -Path $classFolder -ChildPath CommandBuilder.ps1
-$OpCommandFile = Join-Path -Path $classFolder -ChildPath OpCommand.ps1
+# Module created by Microsoft.PowerShell.Crescendo
+class PowerShellCustomFunctionAttribute : System.Attribute { 
+    [bool]$RequiresElevation
+    [string]$Source
+    PowerShellCustomFunctionAttribute() { $this.RequiresElevation = $false; $this.Source = "Microsoft.PowerShell.Crescendo" }
+    PowerShellCustomFunctionAttribute([bool]$rElevation) {
+        $this.RequiresElevation = $rElevation
+        $this.Source = "Microsoft.PowerShell.Crescendo"
+    }
+}
+
+
+
+function Get-OpSecretList
+{
+[PowerShellCustomFunctionAttribute(RequiresElevation=$False)]
+[CmdletBinding()]
+
+param(    )
+
+BEGIN {
+    $__PARAMETERMAP = @{}
+    $__outputHandlers = @{
+        Default = @{ StreamOutput = $True; Handler = { $input | ConvertFrom-Json } }
+    }
+}
+
+PROCESS {
+    $__boundParameters = $PSBoundParameters
+    $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name
+    $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_})
+    $__commandArgs = @()
+    $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)})
+    if ($__boundParameters["Debug"]){wait-debugger}
+    $__commandArgs += 'item'
+    $__commandArgs += 'list'
+    $__commandArgs += '--format'
+    $__commandArgs += 'json'
+    foreach ($paramName in $__boundParameters.Keys|
+            Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}|
+            Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) {
+        $value = $__boundParameters[$paramName]
+        $param = $__PARAMETERMAP[$paramName]
+        if ($param) {
+            if ($value -is [switch]) {
+                 if ($value.IsPresent) {
+                     if ($param.OriginalName) { $__commandArgs += $param.OriginalName }
+                 }
+                 elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue }
+            }
+            elseif ( $param.NoGap ) {
+                $pFmt = "{0}{1}"
+                if($value -match "\s") { $pFmt = "{0}""{1}""" }
+                $__commandArgs += $pFmt -f $param.OriginalName, $value
+            }
+            else {
+                if($param.OriginalName) { $__commandArgs += $param.OriginalName }
+                $__commandArgs += $value | Foreach-Object {$_}
+            }
+        }
+    }
+    $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null}
+    if ($__boundParameters["Debug"]){wait-debugger}
+    if ( $__boundParameters["Verbose"]) {
+         Write-Verbose -Verbose -Message op
+         $__commandArgs | Write-Verbose -Verbose
+    }
+    $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName]
+    if (! $__handlerInfo ) {
+        $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present
+    }
+    $__handler = $__handlerInfo.Handler
+    if ( $PSCmdlet.ShouldProcess("op $__commandArgs")) {
+    # check for the application and throw if it cannot be found
+        if ( -not (Get-Command -ErrorAction Ignore "op")) {
+          throw "Cannot find executable 'op'"
+        }
+        if ( $__handlerInfo.StreamOutput ) {
+            & "op" $__commandArgs | & $__handler
+        }
+        else {
+            $result = & "op" $__commandArgs
+            & $__handler $result
+        }
+    }
+  } # end PROCESS
+
+<#
+
+
+.DESCRIPTION
+List secrets
+
+#>
+}
+
+
+
+
+function Get-OpSecret
+{
+[PowerShellCustomFunctionAttribute(RequiresElevation=$False)]
+[CmdletBinding()]
+
+param(
+[Parameter(Position=0)]
+[string]$Name
+    )
+
+BEGIN {
+    $__PARAMETERMAP = @{
+         Name = @{
+               OriginalName = ''
+               OriginalPosition = '0'
+               Position = '0'
+               ParameterType = 'string'
+               ApplyToExecutable = $False
+               NoGap = $False
+               }
+    }
+
+    $__outputHandlers = @{
+        Default = @{ StreamOutput = $True; Handler = { $input | ConvertFrom-Json } }
+    }
+}
+
+PROCESS {
+    $__boundParameters = $PSBoundParameters
+    $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name
+    $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_})
+    $__commandArgs = @()
+    $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)})
+    if ($__boundParameters["Debug"]){wait-debugger}
+    $__commandArgs += 'item'
+    $__commandArgs += 'get'
+    $__commandArgs += '--format'
+    $__commandArgs += 'json'
+    foreach ($paramName in $__boundParameters.Keys|
+            Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}|
+            Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) {
+        $value = $__boundParameters[$paramName]
+        $param = $__PARAMETERMAP[$paramName]
+        if ($param) {
+            if ($value -is [switch]) {
+                 if ($value.IsPresent) {
+                     if ($param.OriginalName) { $__commandArgs += $param.OriginalName }
+                 }
+                 elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue }
+            }
+            elseif ( $param.NoGap ) {
+                $pFmt = "{0}{1}"
+                if($value -match "\s") { $pFmt = "{0}""{1}""" }
+                $__commandArgs += $pFmt -f $param.OriginalName, $value
+            }
+            else {
+                if($param.OriginalName) { $__commandArgs += $param.OriginalName }
+                $__commandArgs += $value | Foreach-Object {$_}
+            }
+        }
+    }
+    $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null}
+    if ($__boundParameters["Debug"]){wait-debugger}
+    if ( $__boundParameters["Verbose"]) {
+         Write-Verbose -Verbose -Message op
+         $__commandArgs | Write-Verbose -Verbose
+    }
+    $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName]
+    if (! $__handlerInfo ) {
+        $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present
+    }
+    $__handler = $__handlerInfo.Handler
+    if ( $PSCmdlet.ShouldProcess("op $__commandArgs")) {
+    # check for the application and throw if it cannot be found
+        if ( -not (Get-Command -ErrorAction Ignore "op")) {
+          throw "Cannot find executable 'op'"
+        }
+        if ( $__handlerInfo.StreamOutput ) {
+            & "op" $__commandArgs | & $__handler
+        }
+        else {
+            $result = & "op" $__commandArgs
+            & $__handler $result
+        }
+    }
+  } # end PROCESS
+
+<#
+
+
+.DESCRIPTION
+Get a secret
+
+.PARAMETER Name
+An item's name or its unique identifier
+
+
+
+#>
+}
+
+
+
+
+function New-OpSecret
+{
+[PowerShellCustomFunctionAttribute(RequiresElevation=$False)]
+[CmdletBinding()]
+
+param(
+[ValidateSet('login','password')]
+[Parameter()]
+[PSDefaultValue(Value="login")]
+[string]$Category = "login",
+[Parameter()]
+[string]$Name,
+[Parameter()]
+[string]$Username,
+[Parameter(Mandatory=$true)]
+[SecureString]$Password
+    )
+
+BEGIN {
+    $__PARAMETERMAP = @{
+         Category = @{
+               OriginalName = '--category='
+               OriginalPosition = '0'
+               Position = '2147483647'
+               ParameterType = 'string'
+               ApplyToExecutable = $False
+               NoGap = $True
+               }
+         Name = @{
+               OriginalName = '--title='
+               OriginalPosition = '0'
+               Position = '2147483647'
+               ParameterType = 'string'
+               ApplyToExecutable = $False
+               NoGap = $True
+               }
+         Username = @{
+               OriginalName = '--username='
+               OriginalPosition = '0'
+               Position = '2147483647'
+               ParameterType = 'string'
+               ApplyToExecutable = $False
+               NoGap = $True
+               }
+         Password = @{
+               OriginalName = 'password='
+               OriginalPosition = '0'
+               Position = '2147483647'
+               ParameterType = 'SecureString'
+               ApplyToExecutable = $False
+               NoGap = $True
+               }
+    }
+
+    $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } }
+}
+
+PROCESS {
+    $__boundParameters = $PSBoundParameters
+    $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name
+    $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_})
+    $__commandArgs = @()
+    $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)})
+    if ($__boundParameters["Debug"]){wait-debugger}
+    $__commandArgs += 'item'
+    $__commandArgs += 'create'
+    foreach ($paramName in $__boundParameters.Keys|
+            Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}|
+            Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) {
+        $value = $__boundParameters[$paramName]
+        $param = $__PARAMETERMAP[$paramName]
+        if ($param) {
+            if ($value -is [switch]) {
+                 if ($value.IsPresent) {
+                     if ($param.OriginalName) { $__commandArgs += $param.OriginalName }
+                 }
+                 elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue }
+            }
+            elseif ( $param.NoGap ) {
+                $pFmt = "{0}{1}"
+                if($value -match "\s") { $pFmt = "{0}""{1}""" }
+                $__commandArgs += $pFmt -f $param.OriginalName, $value
+            }
+            else {
+                if($param.OriginalName) { $__commandArgs += $param.OriginalName }
+                $__commandArgs += $value | Foreach-Object {$_}
+            }
+        }
+    }
+    $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null}
+    if ($__boundParameters["Debug"]){wait-debugger}
+    if ( $__boundParameters["Verbose"]) {
+         Write-Verbose -Verbose -Message op
+         $__commandArgs | Write-Verbose -Verbose
+    }
+    $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName]
+    if (! $__handlerInfo ) {
+        $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present
+    }
+    $__handler = $__handlerInfo.Handler
+    if ( $PSCmdlet.ShouldProcess("op $__commandArgs")) {
+    # check for the application and throw if it cannot be found
+        if ( -not (Get-Command -ErrorAction Ignore "op")) {
+          throw "Cannot find executable 'op'"
+        }
+        if ( $__handlerInfo.StreamOutput ) {
+            & "op" $__commandArgs | & $__handler
+        }
+        else {
+            $result = & "op" $__commandArgs
+            & $__handler $result
+        }
+    }
+  } # end PROCESS
+
+<#
+
+
+.DESCRIPTION
+Create a new secret
+
+.PARAMETER Category
+Specify which template to use
+
+
+.PARAMETER Name
+
+
+
+.PARAMETER Username
+
+
+
+.PARAMETER Password
+The password
+
+
+
+#>
+}
 
-. $CommandBuilderFile
-. $OpCommandFile
 
-Get-ChildItem -Path $publicFolder | ForEach-Object {
-    . $_.FullName
-}
\ No newline at end of file
diff --git a/tests/class_CommandBuilder.Tests.ps1 b/tests/class_CommandBuilder.Tests.ps1
deleted file mode 100644
index 1bb877b..0000000
--- a/tests/class_CommandBuilder.Tests.ps1
+++ /dev/null
@@ -1,251 +0,0 @@
-Describe 'CommandBuilder' {
-    BeforeAll {
-        $class = Join-Path -Path $PSScriptRoot -ChildPath '..' -AdditionalChildPath 'src', 'class', 'CommandBuilder.ps1'
-        . $class
-    }
-    Context "CommandArgument" {
-        Context "Defaults" {
-            BeforeAll {
-                $sut = [CommandArgument]::New('test')
-            }
-            It 'Should have a name property' {
-                $sut.Name | Should -Be 'test'
-            }
-            It 'Should not have a value' {
-                $sut.HasValue | Should -BeFalse
-                $sut.Value | Should -BeNullOrEmpty
-            }
-            It 'Should have no seperator' {
-                $sut.Seperator | Should -BeNullOrEmpty
-            }
-            It 'ToString return should be Name' {
-                $result = $sut.ToString()
-                $result | Should -Be 'test'
-            }
-        }
-        Context "Defaults with single Value" {
-            BeforeAll {
-                $sut = [CommandArgument]::New('test', 'value')
-            }
-
-            It 'Should have a name property' {
-                $sut.Name | Should -Be 'test'
-            }
-            It 'Should have a value' {
-                $sut.HasValue | Should -BeTrue
-                $sut.Value | Should -Be 'value'
-            }
-            It 'Should have a space seperator' {
-                $sut.Seperator | Should -Be ' '
-            }
-            It 'ToString return should be Name Value' {
-                $result = $sut.ToString()
-                $result | Should -Be 'test value'
-            }
-        }
-        Context "Defaults with multi Value" {
-            BeforeAll {
-                $sut = [CommandArgument]::New('test', @('value1', 'value2'))
-            }
-
-            It 'Should have a name property' {
-                $sut.Name | Should -Be 'test'
-            }
-            It 'Should have a value array' {
-                $sut.HasValue | Should -BeTrue
-                $sut.Value.Count | Should -Be 2
-            }
-            It 'Should have a space seperator' {
-                $sut.Seperator | Should -Be ' '
-            }
-            It 'ToString return should be Name Value' {
-                $result = $sut.ToString()
-                $result | Should -Be 'test value1,value2'
-            }
-        }
-        Context "Seperator" {
-            BeforeAll {
-                $sut = [CommandArgument]::New('test', 'value')
-                $sut = $sut.SetSeparator(':')
-            }
-
-            It 'Should have a space seperator' {
-                $sut.Seperator | Should -Be ':'
-            }
-            It 'ToString return should be Name Value' {
-                $result = $sut.ToString()
-                $result | Should -Be 'test:value'
-            }
-        }
-        Context "Reduce with one value" {
-            BeforeAll {
-                $sut = [CommandArgument]::New('test', 'value')
-                $sut = $sut.SetValueReduce( { $args -join ' ' })
-            }
-
-            It 'Should have a space seperator' {
-                $sut.ValueReduce.ToString().Trim() | Should -Be '$args -join '' '''
-            }
-            It 'ToString return should be Name Value' {
-                $result = $sut.ToString()
-                $result | Should -Be 'test value'
-            }
-            It 'AsMultipleArguments should also be Name Value' {
-                $result = $sut.AsMultipleArguments()
-
-                $result.Count | Should -Be 1
-                $result[0].ToString() | Should -Be 'test value'
-            }
-        }
-        Context "Reduce with multiple values" {
-            BeforeAll {
-                $sut = [CommandArgument]::New('test', @('value1', 'value2'))
-                $sut = $sut.SetValueReduce( { $args -join ' ' } )
-            }
-
-            It 'ToString return should be Name Value Value' {
-                $result = $sut.ToString()
-
-                $sut.Value.Count | Should -Be 2
-                $sut.HasValue | Should -BeTrue
-                $result | Should -Be 'test value1 value2'
-            }
-            It 'Should throw if Reduce doesn''t reference $args' {
-                { $sut.SetValueReduce( { $_ -join ' ' } ) } | Should -Throw 'ScriptBlock must contain "$args" variable.'
-            }
-            It 'AsMultipleArguments should return a List of Argument' {
-                $result = $sut.AsMultipleArguments()
-
-                $result.Count | Should -Be 2
-                $result[0].ToString() | Should -Be 'test value1'
-                $result[1].ToString() | Should -Be 'test value2'
-            }
-        }
-        Context 'Add Value' {
-            BeforeAll {
-                $sut = [CommandArgument]::New('test')
-
-            }
-            It 'Should not have a value' {
-                $sut.HasValue | Should -BeFalse
-                $sut.Seperator | Should -BeNullOrEmpty
-                $sut.Value | Should -BeNullOrEmpty
-            }
-            It 'AddValue should add to Value array' {
-                $new = $sut.AddValue('value')
-
-                $new.Value.Count | Should -Be 1
-                $new.HasValue | Should -BeTrue
-                $result = $new.ToString()
-                $result | Should -Be 'test value'
-            }
-        }
-    }
-    Context "ThingToRun" {
-        Context "No arguments" {
-            BeforeAll {
-                $sut = [CommandBuilder]::New('test')
-            }
-            It 'Should have a Name' {
-                $sut.Name | Should -Be 'test'
-            }
-            It 'Should have no arguments' {
-                $sut.ArgumentList.count | Should -Be 0
-            }
-        }
-        Context "With one argument" {
-            BeforeAll {
-                $sut = [CommandBuilder]::New('test')
-                $arg = [CommandArgument]::New('-arg', 'value')
-                $sut = $sut.AddArgument($arg)
-            }
-            It 'Should have a Name' {
-                $sut.Name | Should -Be 'test'
-
-            }
-            It 'Should an argument' {
-                $sut.ArgumentList.count | Should -Be 1
-                $sut.ToString() | Should -Be 'test -arg value'
-            }
-        }
-        Context "With multiple argument" {
-            BeforeAll {
-                $sut = [CommandBuilder]::New('test')
-                $arg1 = [CommandArgument]::New('-arg', 'value')
-                $arg2 = [CommandArgument]::New('-flag')
-                $sut = $sut.AddArgument($arg1).AddArgument($arg2)
-
-            }
-            It 'Should have a Name' {
-                $sut.Name | Should -Be 'test'
-
-            }
-            It 'Should an argument' {
-                $sut.ArgumentList.count | Should -Be 2
-                $sut.ToString() | Should -Be 'test -arg value -flag'
-            }
-        }
-        Context "With one sensitive value" {
-            BeforeEach {
-                $sut = [CommandBuilder]::New('test')
-                $arg = [CommandArgument]::New('-arg', 'value')
-                $arg.IsSensitive = $true
-                $sut = $sut.AddArgument($arg)
-            }
-            It 'Should have a Name' {
-                $sut.Name | Should -Be 'test'
-
-            }
-            It 'Should mask an argument' {
-                $sut.ArgumentList.count | Should -Be 1
-                $sut.ToString($true) | Should -Be 'test -arg *****'
-            }
-            It 'Should not mask by default' {
-                $sut.ArgumentList.count | Should -Be 1
-                $sut.ToString() | Should -Be 'test -arg value'
-            }
-        }
-        Context "With multiple values, one sensitive" {
-            BeforeEach {
-                $sut = [CommandBuilder]::New('test')
-                $arg1 = [CommandArgument]::New('-arg', 'value')
-                $arg1.IsSensitive = $true
-                $arg2 = [CommandArgument]::New('-arg2', 'value2')
-                $sut = $sut.AddArgument($arg1).AddArgument($arg2)
-            }
-            It 'Should only the sensetive argument' {
-                $sut.ArgumentList.count | Should -Be 2
-                $sut.ToString($true) | Should -Be 'test -arg ***** -arg2 value2'
-            }
-        }
-        Context 'Helper methods' {
-            It '.AddArgument($name)' {
-                $sut = [CommandBuilder]::New('test').AddArgument('-flag')
-                $sut.ArgumentList.count | Should -Be 1
-                $sut.ToString() | Should -Be 'test -flag'
-            }
-            It '.AddArgument($name, $value)' {
-                $sut = [CommandBuilder]::New('test').AddArgument('-flag', 'value')
-                $sut.ArgumentList.count | Should -Be 1
-                $sut.ToString() | Should -Be 'test -flag value'
-            }
-            It '.AddArgument($name, $value, $seperator)' {
-                $sut = [CommandBuilder]::New('test').AddArgument('-flag', 'value', ':')
-                $sut.ArgumentList.count | Should -Be 1
-                $sut.ToString() | Should -Be 'test -flag:value'
-            }
-        }
-        Context 'Get ProcessStartInfo' {
-            BeforeAll {
-                $sut = [CommandBuilder]::New('test').AddArgument('-flag')
-            }
-            It 'Should return a ProcessStartInfo object' {
-                $result = $sut.GetProcessStartInfo()
-
-                $result.FileName | Should -Be 'test'
-                $result.ArgumentList | Should -Be '-flag'
-            }
-
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/class_OpCommand.Tests.ps1 b/tests/class_OpCommand.Tests.ps1
deleted file mode 100644
index 294e417..0000000
--- a/tests/class_OpCommand.Tests.ps1
+++ /dev/null
@@ -1,267 +0,0 @@
-Describe 'OpCommand' {
-    BeforeAll {
-        $class = Join-Path -Path $PSScriptRoot -ChildPath '..' -AdditionalChildPath 'src', 'class', 'CommandBuilder.ps1'
-        . $class
-    }
-    Context 'Constructor' {
-        BeforeAll {
-            $sut = [OpCommand]::New()
-        }
-        It 'Should be a valid CommandBuilder child object' {
-            $sut.Name | Should -Be 'op'
-        }
-        It 'Should not have a value' {
-            $sut.HasValue | Should -BeFalse
-            $sut.Value | Should -BeNullOrEmpty
-        }
-        It 'Should have no seperator' {
-            $sut.Seperator | Should -BeNullOrEmpty
-        }
-        It 'ToString return should be Name' {
-            $result = $sut.ToString()
-            $result | Should -Be 'op'
-        }
-        It 'Has message data' {
-            $sut.LocalizedMessage | Should -Not -BeNullOrEmpty
-            $sut.LocalizedMessage.Count | Should -Be 2
-        }
-    }
-    Context 'List' {
-        BeforeAll {
-            $sut = [OpCommandList]::New()
-        }
-        It 'Should be a valid CommandBuilder child object' {
-            $sut.Name | Should -Be 'op'
-        }
-        It 'Should not have a value' {
-            $sut.HasValue | Should -BeFalse
-            $sut.Value | Should -BeNullOrEmpty
-        }
-        It 'Should have no seperator' {
-            $sut.Seperator | Should -BeNullOrEmpty
-        }
-        It 'ToString return should be Name' {
-            $result = $sut.ToString()
-            $result | Should -Be 'op list'
-        }
-    }
-    Context 'List Items' {
-        BeforeAll {
-            $sut = [OpCommandListItem]::New()
-        }
-        It 'Should be a valid CommandBuilder child object' {
-            $sut.Name | Should -Be 'op'
-        }
-        It 'ToString return should be Name' {
-            $result = $sut.ToString()
-            $result | Should -Be 'op list items'
-        }
-    }
-    Context 'List Items with Arguments' {
-        BeforeEach {
-            $sut = [OpCommandListItem]::New()
-        }
-
-        It 'With Vault' {
-            $result = $sut.WithVault('test_vault').ToString()
-            $result | Should -Be 'op list items --vault test_vault'
-        }
-        It 'With one Category' {
-            $result = $sut.WithCategory('Login').ToString()
-            $result | Should -Be 'op list items --categories Login'
-        }
-        It 'With more than one Category' {
-            $result = $sut.WithCategory(@('Login', 'Password')).ToString()
-            $result | Should -Be 'op list items --categories Login,Password'
-        }
-        It 'With more than one Category using first overload' {
-            $result = $sut.WithCategory('Login', 'Password').ToString()
-            $result | Should -Be 'op list items --categories Login,Password'
-        }
-        It 'With more than one Category using second overload' {
-            $result = $sut.WithCategory('Login', 'Password', 'Server').ToString()
-            $result | Should -Be 'op list items --categories Login,Password,Server'
-        }
-        It 'With one Tag' {
-            $result = $sut.WithTag('tag1').ToString()
-            $result | Should -Be 'op list items --tags tag1'
-        }
-        It 'With more than one Tag' {
-            $result = $sut.WithTag(@('tag1', 'tag2')).ToString()
-            $result | Should -Be 'op list items --tags tag1,tag2'
-        }
-        It 'With more than one Tag using first overload' {
-            $result = $sut.WithTag('tag1', 'tag2').ToString()
-            $result | Should -Be 'op list items --tags tag1,tag2'
-        }
-        It 'With more than one Tag using second overload' {
-            $result = $sut.WithTag('tag1', 'tag2', 'tag3').ToString()
-            $result | Should -Be 'op list items --tags tag1,tag2,tag3'
-        }
-        It 'With Category and Tag' {
-            $result = $sut.WithCategory('Login', 'Password').WithTag('tag1', 'tag2').ToString()
-            $result | Should -Be 'op list items --categories Login,Password --tags tag1,tag2'
-        }
-        It 'With Category, Tag and Vault' {
-            $result = $sut.WithCategory('Login', 'Password').WithTag('tag1', 'tag2').WithVault('test_vault').ToString()
-            $result | Should -Be 'op list items --categories Login,Password --tags tag1,tag2 --vault test_vault'
-        }
-    }
-    Context 'List Item as ProcessStartInfo' {
-        BeforeEach {
-            $sut = [OpCommandListItem]::New().WithCategory('Login', 'Password').WithTag('tag1', 'tag2').WithVault('test_vault').GetProcessStartInfo()
-        }
-        It 'Is as [Diagnostics.ProcessStartInfo]' {
-            $sut | Should -BeOfType Diagnostics.ProcessStartInfo
-        }
-        It 'Should have valid properties' {
-            $sut.FileName = 'op'
-            $sut.RedirectStandardError = $true
-            $sut.RedirectStandardOutput = $true
-        }
-        It 'Should have all arguments' {
-            $sut.ArgumentList.Count | Should -Be 8
-            $sut.ArgumentList[0] | Should -Be 'list'
-            $sut.ArgumentList[1] | Should -Be 'items'
-            $sut.ArgumentList[2] | Should -Be '--categories'
-            $sut.ArgumentList[3] | Should -Be 'Login,Password'
-            $sut.ArgumentList[4] | Should -Be '--tags'
-            $sut.ArgumentList[5] | Should -Be 'tag1,tag2'
-            $sut.ArgumentList[6] | Should -Be '--vault'
-            $sut.ArgumentList[7] | Should -Be 'test_vault'
-        }
-    }
-    Context 'Get Item with Arguments' {
-        BeforeEach {
-            $sut = [OpCommandGetItem]::New('test_secret')
-        }
-        It 'Constructor' {
-            $result = $sut.ToString()
-            $result | Should -Be 'op get item test_secret'
-        }
-        It 'With Vault' {
-            $result = $sut.WithVault('test_vault').ToString()
-            $result | Should -Be 'op get item test_secret --vault test_vault'
-        }
-        It 'With one Field' {
-            $result = $sut.WithField('website').ToString()
-            $result | Should -Be 'op get item test_secret --fields website'
-        }
-        It 'With more than one Field' {
-            $result = $sut.WithField(@('website', 'username')).ToString()
-            $result | Should -Be 'op get item test_secret --fields website,username'
-        }
-        It 'With more than one Field using first overload' {
-            $result = $sut.WithField('website', 'username').ToString()
-            $result | Should -Be 'op get item test_secret --fields website,username'
-        }
-        It 'With more than one Field using second overload' {
-            $result = $sut.WithField('website', 'username', 'Server').ToString()
-            $result | Should -Be 'op get item test_secret --fields website,username,Server'
-        }
-
-        It 'With Field and Format Json' {
-            $result = $sut.WithField('website', 'username').WithJsonFormat().ToString()
-            $result | Should -Be 'op get item test_secret --fields website,username --format JSON'
-        }
-        It 'With Field and Format CSV' {
-            $result = $sut.WithField('website', 'username').WithCsvFormat().ToString()
-            $result | Should -Be 'op get item test_secret --fields website,username --format CSV'
-        }
-        It 'Format without Field errors' {
-            $ErrorActionPreference = 'Stop'
-            { $sut.WithCsvFormat().ToString() } | Should -Throw 'Format can only be used with Fields'
-
-        }
-        It 'Can''t add Format without Field' {
-            $ErrorActionPreference = 'SilentlyContinue'
-            $result = $sut.WithCsvFormat().ToString()
-            $result | Should -Be 'op get item test_secret'
-        }
-        It 'Change Format errors' {
-            $ErrorActionPreference = 'Stop'
-            { $sut.WithField('website', 'username').WithJsonFormat().WithCsvFormat() } | Should -Throw 'Format has already been set'
-        }
-        It 'Can''t change Format' {
-            $ErrorActionPreference = 'SilentlyContinue'
-            $result = $sut.WithField('website', 'username').WithJsonFormat().WithCsvFormat().ToString()
-            $result | Should -Be 'op get item test_secret --fields website,username --format JSON'
-        }
-    }
-    Context 'Get Item as ProcessStartInfo' {
-        BeforeEach {
-            $sut = [OpCommandGetItem]::New('test_secret').WithField('website', 'username').WithJsonFormat().GetProcessStartInfo()
-        }
-        It 'Is as [Diagnostics.ProcessStartInfo]' {
-            $sut | Should -BeOfType Diagnostics.ProcessStartInfo
-        }
-        It 'Should have valid properties' {
-            $sut.FileName = 'op'
-            $sut.RedirectStandardError = $true
-            $sut.RedirectStandardOutput = $true
-        }
-        It 'Should have all arguments' {
-            $sut.ArgumentList.Count | Should -Be 7
-            $sut.ArgumentList[0] | Should -Be 'get'
-            $sut.ArgumentList[1] | Should -Be 'item'
-            $sut.ArgumentList[2] | Should -Be 'test_secret'
-            $sut.ArgumentList[3] | Should -Be '--fields'
-            $sut.ArgumentList[4] | Should -Be 'website,username'
-            $sut.ArgumentList[5] | Should -Be '--format'
-            $sut.ArgumentList[6] | Should -Be 'JSON'
-        }
-    }
-    Context 'ParseError' {
-        BeforeEach {
-            $commandHelpMessage = @'
-To list objects and events, use one of the `list` subcommands.
-
-Usage:
-    op list [command]
-
-Available Commands:
-    documents   Get a list of documents
-    events      Get a list of events from the Activity Log
-    groups      Get a list of groups
-    items       Get a list of items
-    templates   Get a list of templates
-    users       Get the list of users
-    vaults      Get a list of vaults
-
-Flags:
-    -h, --help   get help with list
-
-Global Flags:
-        --account shorthand   use the account with this shorthand
-        --cache               store and use cached information
-        --config directory    use this configuration directory
-        --session token       authenticate with this session token
-
-Use "op list [command] --help" for more information about a command.
-'@
-            $signinError = '[ERROR] 2020/12/11 13:48:31 session expired, sign in to create a new session'
-            $signinError2 = '[ERROR] 2020/12/11 15:01:17 You are not currently signed in. Please run `op signin --help` for instructions'
-            $ErrorActionPreference = 'Stop'
-        }
-        It 'Base passes the message through to Error' {
-            $sut = [CommandBuilder]::new('command')
-            $sut.ParseStdErr($signinError)  | Should -Be '[ERROR] 2020/12/11 13:48:31 session expired, sign in to create a new session'
-        }
-        It 'Parses Command Help should have no Error message' {
-            $sut = [OpCommand]::new()
-            $sut.ParseStdErr($commandHelpMessage) | Should -BeNullOrEmpty
-        }
-        It 'Parses Signin Error should an Error message' {
-            $sut = [OpCommand]::new()
-            $sut.ParseStdErr($signinError)  | Should -Be 'errorSignin'
-        }
-        It 'Parses Signin Error 2 should an Error message' {
-            $sut = [OpCommand]::new()
-            $sut.ParseStdErr($signinError2)  | Should -Be 'errorSignin'
-        }
-        It 'Child class should parse Error message' {
-            $sut = [OpCommandListItem]::new()
-            $sut.ParseStdErr($signinError)  | Should -Be 'errorSignin'
-        }
-    }
-}

From 1cf7e04651ae2decc016f4fad21049c552cfd648 Mon Sep 17 00:00:00 2001
From: Chris Hunt <chunt@stackoverflow.com>
Date: Fri, 16 Sep 2022 11:11:14 -0400
Subject: [PATCH 2/2] Handle --vault and structure output

---
 op.crescendo.config.json | 35 +++++++++++++++++++++--
 src/op-powershell.psd1   |  4 +--
 src/op-powershell.psm1   | 62 ++++++++++++++++++++++++++++++++++++----
 3 files changed, 90 insertions(+), 11 deletions(-)

diff --git a/op.crescendo.config.json b/op.crescendo.config.json
index cb81451..f73c420 100644
--- a/op.crescendo.config.json
+++ b/op.crescendo.config.json
@@ -12,10 +12,18 @@
                 "json"
             ],
             "Description": "List secrets",
+            "Parameters": [
+                {
+                    "Name": "Vault",
+                    "OriginalName": "--vault=",
+                    "ParameterType": "string",
+                    "NoGap": true
+                }
+            ],
             "OutputHandlers": [
                 {
                     "ParameterSetName": "Default",
-                    "Handler": "$input | ConvertFrom-Json",
+                    "Handler": "$input | ConvertFrom-Json | Foreach-Object {[PSCustomObject]@{PSTypeName='OpLoginSummary'; Id=$_.id; Name=$_.title; Vault=$_.vault.name; Created=$_.created_at; Updated=$_.updated_at} }",
                     "StreamOutput": true
                 }
             ]
@@ -38,12 +46,18 @@
                     "Position": 0,
                     "Description": "An item's name or its unique identifier",
                     "ParameterType": "string"
+                },
+                {
+                    "Name": "Vault",
+                    "OriginalName": "--vault=",
+                    "ParameterType": "string",
+                    "NoGap": true
                 }
             ],
             "OutputHandlers": [
                 {
                     "ParameterSetName": "Default",
-                    "Handler": "$input | ConvertFrom-Json",
+                    "Handler": "$input | ConvertFrom-Json | Foreach-Object {[PSCustomObject]@{PSTypeName='OpLogin'; Id=$_.id; Name=$_.title; UserName=$_.fields.Where({$_.id -eq 'username'}).value; Vault=$_.vault.name; Created=$_.created_at; Updated=$_.updated_at}}",
                     "StreamOutput": true
                 }
             ]
@@ -54,7 +68,9 @@
             "OriginalName": "op",
             "OriginalCommandElements": [
                 "item",
-                "create"
+                "create",
+                "--format",
+                "json"
             ],
             "Description": "Create a new secret",
             "Parameters": [
@@ -75,6 +91,12 @@
                     "ParameterType": "string",
                     "NoGap": true
                 },
+                {
+                    "Name": "Vault",
+                    "OriginalName": "--vault=",
+                    "ParameterType": "string",
+                    "NoGap": true
+                },
                 {
                     "Name": "Username",
                     "OriginalName": "--username=",
@@ -90,6 +112,13 @@
                     "Mandatory": true,
                     "NoGap": true
                 }
+            ],
+            "OutputHandlers": [
+                {
+                    "ParameterSetName": "Default",
+                    "Handler": "$input | ConvertFrom-Json",
+                    "StreamOutput": true
+                }
             ]
         }
     ]
diff --git a/src/op-powershell.psd1 b/src/op-powershell.psd1
index 19a4aa3..06cfa74 100644
--- a/src/op-powershell.psd1
+++ b/src/op-powershell.psd1
@@ -18,7 +18,7 @@ ModuleVersion = '0.0.1'
 # CompatiblePSEditions = @()
 
 # ID used to uniquely identify this module
-GUID = '6151db2d-c545-4c9a-8f90-65822264bddc'
+GUID = '6c51033a-7546-44c9-a8e1-201d53b411e2'
 
 # Author of this module
 Author = 'chunt'
@@ -125,7 +125,7 @@ PrivateData = @{
     CrescendoVersion = '1.0.0'
 
     # CrescendoGenerated
-    CrescendoGenerated = '09/16/2022 10:46:23'
+    CrescendoGenerated = '09/16/2022 11:05:56'
 
 } # End of PrivateData hashtable
 
diff --git a/src/op-powershell.psm1 b/src/op-powershell.psm1
index bfc8523..55df3e6 100644
--- a/src/op-powershell.psm1
+++ b/src/op-powershell.psm1
@@ -16,12 +16,25 @@ function Get-OpSecretList
 [PowerShellCustomFunctionAttribute(RequiresElevation=$False)]
 [CmdletBinding()]
 
-param(    )
+param(
+[Parameter()]
+[string]$Vault
+    )
 
 BEGIN {
-    $__PARAMETERMAP = @{}
+    $__PARAMETERMAP = @{
+         Vault = @{
+               OriginalName = '--vault='
+               OriginalPosition = '0'
+               Position = '2147483647'
+               ParameterType = 'string'
+               ApplyToExecutable = $False
+               NoGap = $True
+               }
+    }
+
     $__outputHandlers = @{
-        Default = @{ StreamOutput = $True; Handler = { $input | ConvertFrom-Json } }
+        Default = @{ StreamOutput = $True; Handler = { $input | ConvertFrom-Json | Foreach-Object {[PSCustomObject]@{Id=$_.id; Name=$_.title; Vault=$_.vault.name; Created=$_.created_at; Updated=$_.updated_at} } } }
     }
 }
 
@@ -91,6 +104,11 @@ PROCESS {
 .DESCRIPTION
 List secrets
 
+.PARAMETER Vault
+
+
+
+
 #>
 }
 
@@ -104,7 +122,9 @@ function Get-OpSecret
 
 param(
 [Parameter(Position=0)]
-[string]$Name
+[string]$Name,
+[Parameter()]
+[string]$Vault
     )
 
 BEGIN {
@@ -117,10 +137,18 @@ BEGIN {
                ApplyToExecutable = $False
                NoGap = $False
                }
+         Vault = @{
+               OriginalName = '--vault='
+               OriginalPosition = '0'
+               Position = '2147483647'
+               ParameterType = 'string'
+               ApplyToExecutable = $False
+               NoGap = $True
+               }
     }
 
     $__outputHandlers = @{
-        Default = @{ StreamOutput = $True; Handler = { $input | ConvertFrom-Json } }
+        Default = @{ StreamOutput = $True; Handler = { $input | ConvertFrom-Json | Foreach-Object {[PSCustomObject]@{Id=$_.id; Name=$_.title; UserName=$_.fields.Where({$_.id -eq 'username'}).value; Vault=$_.vault.name; Created=$_.created_at; Updated=$_.updated_at}} } }
     }
 }
 
@@ -194,6 +222,10 @@ Get a secret
 An item's name or its unique identifier
 
 
+.PARAMETER Vault
+
+
+
 
 #>
 }
@@ -214,6 +246,8 @@ param(
 [Parameter()]
 [string]$Name,
 [Parameter()]
+[string]$Vault,
+[Parameter()]
 [string]$Username,
 [Parameter(Mandatory=$true)]
 [SecureString]$Password
@@ -237,6 +271,14 @@ BEGIN {
                ApplyToExecutable = $False
                NoGap = $True
                }
+         Vault = @{
+               OriginalName = '--vault='
+               OriginalPosition = '0'
+               Position = '2147483647'
+               ParameterType = 'string'
+               ApplyToExecutable = $False
+               NoGap = $True
+               }
          Username = @{
                OriginalName = '--username='
                OriginalPosition = '0'
@@ -255,7 +297,9 @@ BEGIN {
                }
     }
 
-    $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } }
+    $__outputHandlers = @{
+        Default = @{ StreamOutput = $True; Handler = { $input | ConvertFrom-Json } }
+    }
 }
 
 PROCESS {
@@ -267,6 +311,8 @@ PROCESS {
     if ($__boundParameters["Debug"]){wait-debugger}
     $__commandArgs += 'item'
     $__commandArgs += 'create'
+    $__commandArgs += '--format'
+    $__commandArgs += 'json'
     foreach ($paramName in $__boundParameters.Keys|
             Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}|
             Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) {
@@ -330,6 +376,10 @@ Specify which template to use
 
 
 
+.PARAMETER Vault
+
+
+
 .PARAMETER Username