Public/New-CyaConfig.ps1

function New-CyaConfig {
  <#
  .SYNOPSIS
  Creates CyaConfigs.

  .DESCRIPTION
  A CyaConfig is an encrypted collection of small files or environment variables.
  Items in the CyaConfig can be protected (encrypted to a new file and source file
  deleted or removed from the shell) or unprotected (decrypted and set on the
  shell or written to the file path defined).

  .PARAMETER Name
  [String] The name of the CyaConfig

  .PARAMETER Type
  [String] File or EnvVar (Environment Variable)

  .PARAMETER EnvVarName
  [String] Name of the environment variable

  .PARAMETER EnvVarValue
  [String] Value of the environment variable

  .PARAMETER EnvVarCollection
  [Object] A hashtable or any object with Name and Value

  .PARAMETER File
  [Object] A file or list of files

  .PARAMETER ProtectOnExit
  [Int] Set to 1 ot $True if you want to delete the unprotected files on exit

  .PARAMETER CyaPassword
  [String] The CyaPassword to encrypt and decrypt the CyaConfig, defaults to "Default"

  .PARAMETER Password
  [SecureString] Your password to decrypt the CyaPassword

  .OUTPUTS
  [Object] CyaConfig item status

  .NOTES
    Author: Nick Vissari

  .EXAMPLE
  New-CyaConfig

  cmdlet New-CyaConfig at command pipeline position 1
  Supply values for the following parameters:
  Name: sample
  WARNING: CyaPassword "Default" not found, creating now with New-CyaPassword.
  Enter new password: ********
  Confirm new password: ********

  Config type
  [E] EnvVar [F] File [?] Help (default is "E"):
  Variable 1 name (Enter when done): MYVAR
  MYVAR value: *****
  Variable 2 name (Enter when done): MYOTHERVAR
  MYOTHERVAR value: *****
  Variable 3 name (Enter when done):

  Name : sample
  Type : EnvVar
  CyaPassword : Default
  ProtectOnExit : True
  Item : MYVAR
  Status : Protected

  Name : sample
  Type : EnvVar
  CyaPassword : Default
  ProtectOnExit : True
  Item : MYOTHERVAR
  Status : Protected


  Description
  -----------
  With no parameters specified, the user is prompted for every option. This
  includes setting the default password, which results in a new CyaPassword
  as well as CyaConfig.

  .EXAMPLE
  New-CyaConfig -Name sample -EnvVarName MYVAR -EnvVarValue 12345 -CyaPassword Default
  Enter password for CyaPassword "Default": ********

  Name : sample
  Type : EnvVar
  CyaPassword : Default
  ProtectOnExit : True
  Item : MYVAR
  Status : Protected


  Description
  -----------
  A single environment variable name and value can be specified. Keep in my the
  value will be in your command history.

  .EXAMPLE
  Get-ChildItem | New-CyaConfig -Name sample -ProtectOnExit $true
  Enter password for CyaPassword "Default": ********

  Name : sample
  Type : File
  CyaPassword : Default
  ProtectOnExit : True
  Item : C:\Users\nickadam\sample\file1.conf
  Status : Unprotected

  Name : sample
  Type : File
  CyaPassword : Default
  ProtectOnExit : True
  Item : C:\Users\nickadam\sample\file2.json
  Status : Unprotected


  Description
  -----------
  A CyaConfig can be created from files in the pipeline. ProtectOnExit, if
  enabled, will attempt to delete unprotected files from the system when the
  shell exists cleanly. File hashes are checked to ensure the file hasn't been
  modified since it was protected.

  .EXAMPLE
  New-CyaConfig -Name sample -File file1.conf -ProtectOnExit $true
  Enter password for CyaPassword "Default": ********

  Name : sample
  Type : File
  CyaPassword : Default
  ProtectOnExit : True
  Item : C:\Users\nickadam\sample\file1.conf
  Status : Unprotected


  Description
  -----------
  A file or list of files can be provided.

  .EXAMPLE
  "MYVAR", "MYOTHERVAR" | New-CyaConfig -Name sample
  Enter password for CyaPassword "Default": ********

  Name : sample
  Type : EnvVar
  CyaPassword : Default
  ProtectOnExit : True
  Item : MYVAR
  Status : Unprotected

  Name : sample
  Type : EnvVar
  CyaPassword : Default
  ProtectOnExit : True
  Item : MYOTHERVAR
  Status : Unprotected


  Description
  -----------
  A CyaConfig can be created from set environment variables.

  .EXAMPLE
  New-CyaConfig -EnvVarCollection (Get-ChildItem Env: | where{$_.Name -like "MY*"}) -Name sample
  Enter password for CyaPassword "Default": ********

  Name : sample
  Type : EnvVar
  CyaPassword : Default
  ProtectOnExit : True
  Item : MYOTHERVAR
  Status : Unprotected

  Name : sample
  Type : EnvVar
  CyaPassword : Default
  ProtectOnExit : True
  Item : MYVAR
  Status : Unprotected


  Description
  -----------
  A collection of environment variables can be specified as a hashtable or any
  iterable list of objects with Name and Value.

  .LINK
  Get-CyaConfig

  .LINK
  Protect-CyaConfig

  .LINK
  Unprotect-CyaConfig

  .LINK
  Rename-CyaConfig

  .LINK
  Remove-CyaConfig

  .LINK
  https://github.com/nickadam/powershell-cya

  #>


  [CmdletBinding(SupportsShouldProcess,
  DefaultParameterSetName = "SomethingFromPipeline")]
  param(
    [Parameter(Position=0, Mandatory)]
    [String]$Name,

    [Parameter(Mandatory,
    ParameterSetName="EnvVar")]
    [String]$EnvVarName,

    [Parameter(Mandatory,
    ParameterSetName="EnvVar")]
    [Object]$EnvVarValue,

    [Parameter(Mandatory,
    ParameterSetName="EnvVarCollection")]
    [Object]$EnvVarCollection,

    [Parameter(Mandatory,
    ParameterSetName="FileOrFiles")]
    [Object]$File,

    [Parameter(ParameterSetName="SomethingFromPipeline")]
    [Parameter(ParameterSetName="FileOrFiles")]
    [ValidateSet(0, 1)]
    [Int]$ProtectOnExit = -1,

    [alias("CyaPassword")]
    [String]$CyaPwName="Default",
    [SecureString]$Password,

    [Parameter(ValueFromPipeline,
    DontShow,
    ParameterSetName="SomethingFromPipeline")]
    [Object]$InputObject,

    [ValidateSet("EnvVar", "File")]
    [String]$Type
  )

  begin {
    $InputObjects = @()
  }

  process {
    if($InputObject){
      $InputObjects += $InputObject
    }
  }

  end {
    $CyaPassword = $CyaPwName
    # InputObjects could be a list of files or environment variables
    if($InputObjects){
      $File = $False
      $EnvVarCollection = @()
      ForEach($Item in $InputObjects){
        if($Type -eq "File" -or (Get-Item $Item -ErrorAction SilentlyContinue)){
          if($File){
            Continue
          }
          # must be a list of files
          $File = $InputObjects
        }else{
          # must be a list of environment variables
          $ItemEnvVarName = $Item
          $ItemEnvVarValue = Get-EnvVarValueByName -Name $ItemEnvVarName
          if(-not $ItemEnvVarValue){
            $Message = "The piped item `"$ItemEnvVarName`" is not a file or a " +
            "set environment variable and can't be added to a collection."
            Throw $Message
          }
          $EnvVarCollection += [PSCustomObject]@{
            "Name" = $ItemEnvVarName
            "Value" = $ItemEnvVarValue
          }
        }
      }
    }

    # create new CyaPassword
    if(-not (Get-CyaPassword -Name $CyaPassword -EA SilentlyContinue)){
      Write-Warning "CyaPassword `"$CyaPassword`" not found, creating now with New-CyaPassword."
      if(!$Password){
        $Password = Get-NewPassword
      }
      New-CyaPassword -Name $CyaPassword -Password $Password
    }

    # Attempt to set $Type
    if($EnvVarName -or $EnvVarCollection){
      $Type = "EnvVar"
    }
    if($File){
      $Type = "File"
    }

    $CyaConfigPath = Get-CyaConfigPath
    $ConfigPath = Join-Path -Path $CyaConfigPath -ChildPath $Name

    # Check if config already exists
    if(Test-Path $ConfigPath){
      Throw "Config `"$Name`" already exists"
    }

    # Show option for type
    if(-not $Type){
      $Caption = "Config type"
      $Choices = "EnvVar", "File"
      $Type = Invoke-ChoicePrompt -Caption $Caption -Choices $Choices
    }

    # Create Environment Variable config
    if($Type -eq "EnvVar"){
      if($EnvVarName -and $EnvVarValue){
        $EnvVarCollection = [PSCustomObject]@{
          "Name" = $EnvVarName
          "Value" = $EnvVarValue
        }
      }

      # no EnvVar specified, prompt user
      if(-not $EnvVarCollection){
        $Collecting = $True
        $EnvVarCollection = @()
        $n = 0
        while($Collecting){
          $n++
          $EnvVarName = Read-Host -Prompt "Variable $n name (Enter when done)"

          # done collecting
          if(-not $EnvVarName){
            $Collecting = $False
            Continue
          }

          $Message = "$EnvVarName value"

          # check if environment variable is currently set
          $SetValue = Get-EnvVarValueByName -Name $EnvVarName
          if($SetValue){
            $Message = "$EnvVarName value [$SetValue]"
          }
          $EnvVarSecureString = Read-Host -AsSecureString -Prompt $Message
          $EnvVarValue = Get-SecureStringText -SecureString $EnvVarSecureString

          # use current environment variable set
          if($SetValue -and (-not $EnvVarValue)){
            $EnvVarValue = $SetValue
          }

          $EnvVar = [PSCustomObject]@{
            "Name" = $EnvVarName
            "Value" = $EnvVarValue
          }
          $EnvVarCollection += $EnvVar
        }
      }

      # nothing to do
      if(-not $EnvVarCollection){
        Write-Warning "Nothing to do"
        return
      }

      if(-not $Password){
        $Password = Read-Host -Prompt "Enter password for CyaPassword `"$CyaPassword`"" -AsSecureString
      }
      $Key = Get-Key -CyaPassword $CyaPassword -Password $Password

      # convert hashtable to list of objects
      $EnvVarCollectionList = @()
      if($EnvVarCollection.GetType().Name -eq "Hashtable"){
        $EnvVarCollection.Keys | ForEach-Object {
          $EnvVarName = $_
          $EnvVarValue = $EnvVarCollection.$EnvVarName
          $EnvVarCollectionList += [PSCustomObject]@{
            "Name" = $EnvVarName
            "Value" = $EnvVarValue
          }
        }
        $EnvVarCollection = $EnvVarCollectionList
      }

      $Cipherbundle = $EnvVarCollection | ConvertTo-Cipherbundle -Key $Key -Name $Name

      if($Cipherbundle.length -eq 1){
        $CyaConfig = [PSCustomObject]@{
          "Type" = "EnvVar"
          "CyaPassword" = $CyaPassword
          "Variables" = @($Cipherbundle)
        }
      }else{
        $CyaConfig = [PSCustomObject]@{
          "Type" = "EnvVar"
          "CyaPassword" = $CyaPassword
          "Variables" = $Cipherbundle
        }
      }
    }

    if($Type -eq "File"){
      # set protect on exit
      if($ProtectOnExit -eq -1){
        $Caption = "Protect on exit"
        $Message = "Would you like to automatically run Protect-CyaConfig (deletes unencrypted config files) on this config when unloading the Cya module or exiting powershell?"
        $Choices = "Yes", "No"
        $Default = 1
        $Response = Invoke-ChoicePrompt -Caption $Caption -Message $Message -Choices $Choices -Default $Default
        $ProtectOnExit = 0
        if($Response -eq "Yes"){
          $ProtectOnExit = 1
        }
      }

      # no files specified, prompt user
      if(-not $File){
        $File = @()
        $Collecting = $True
        $n = 0
        while($Collecting){
          $n++
          $FilePath = Read-Host -Prompt "File $n path (Enter when done)"
          if($FilePath){
            if(-not (Test-Path $FilePath -PathType Leaf)){
              Throw "File $FilePath not found"
            }
            $File += $FilePath
          }else{
            $Collecting = $False
          }
        }
      }

      # nothing to do
      if(-not $File){
        Write-Warning "Nothing to do"
        return
      }

      # if files were explicitly specified as strings or something, check each one
      $File | ForEach-Object {
        $FilePath = $_
        if(-not (Test-Path $FilePath -PathType Leaf)){
          Throw "File $FilePath not found"
        }
        if((Get-Item $FilePath).Length -eq 0){
          Throw "File $FilePath is empty"
        }
      }

      # get the key
      if(-not $Password){
        $Password = Read-Host -Prompt "Enter password for CyaPassword `"$CyaPassword`"" -AsSecureString
      }
      $Key = Get-Key -CyaPassword $CyaPassword -Password $Password

      # encrypt all the files
      $FileCollection = $File | Get-Item | ConvertTo-Cipherbundle -Key $Key -Name $Name

      if($FileCollection.length -eq 1){
        $CyaConfig = [PSCustomObject]@{
          "Type" = "File"
          "CyaPassword" = $CyaPassword
          "ProtectOnExit" = [Bool]$ProtectOnExit
          "Files" = @($FileCollection)
        }
      }else{
        $CyaConfig = [PSCustomObject]@{
          "Type" = "File"
          "CyaPassword" = $CyaPassword
          "ProtectOnExit" = [Bool]$ProtectOnExit
          "Files" = $FileCollection
        }
      }
    }

    # nothing to do
    if(-not $CyaConfig){
      Write-Warning "Nothing to do"
      return
    }

    # Create missing directory
    if(-not (Test-Path $CyaConfigPath)){
      mkdir -p $CyaConfigPath | Out-Null
    }

    # write config file
    $CyaConfig | ConvertTo-Json | Out-File -Encoding Default $ConfigPath
    if($PSCmdlet.ShouldProcess("Get-CyaConfig $Name", "", "")){
      Get-CyaConfig -Name $Name -Status
    }
  }
}