GithubHelp home page GithubHelp logo

powershell / crescendo Goto Github PK

View Code? Open in Web Editor NEW
380.0 380.0 37.0 1.04 MB

a module for wrapping native applications in a PowerShell function and module

License: MIT License

PowerShell 99.92% C# 0.08%

crescendo's Introduction

logo PowerShell

Welcome to the PowerShell GitHub Community! PowerShell is a cross-platform (Windows, Linux, and macOS) automation and configuration tool/framework that works well with your existing tools and is optimized for dealing with structured data (e.g. JSON, CSV, XML, etc.), REST APIs, and object models. It includes a command-line shell, an associated scripting language, and a framework for processing cmdlets.

Windows PowerShell vs. PowerShell Core

Although this repository started as a fork of the Windows PowerShell codebase, changes made in this repository are not automatically ported back to Windows PowerShell 5.1. This also means that issues tracked here are only for PowerShell Core 6 and higher. Windows PowerShell specific issues should be reported with the Feedback Hub app, by choosing "Apps > PowerShell" in the category.

New to PowerShell?

If you are new to PowerShell and want to learn more, we recommend reviewing the getting started documentation.

Get PowerShell

You can download and install a PowerShell package for any of the following platforms.

Supported Platform Download (LTS) Downloads (stable) Downloads (preview) How to Install
Windows (x64) .msi .msi .msi Instructions
Windows (x86) .msi .msi .msi Instructions
Ubuntu 22.04 .deb .deb .deb Instructions
Ubuntu 20.04 .deb .deb .deb Instructions
Ubuntu 18.04 .deb .deb .deb Instructions
Ubuntu 16.04 .deb N/A N/A Instructions
Debian 10 .deb .deb .deb Instructions
Debian 11 .deb .deb .deb
CentOS 7 .rpm .rpm .rpm Instructions
CentOS 8 .rpm .rpm .rpm
Red Hat Enterprise Linux 7 .rpm .rpm .rpm Instructions
openSUSE 42.3 .rpm .rpm .rpm Instructions
Fedora 35 .rpm .rpm .rpm Instructions
macOS 10.13+ (x64) .pkg .pkg .pkg Instructions
macOS 11+ (arm64) .pkg .pkg .pkg Instructions
Docker Instructions

You can download and install a PowerShell package for any of the following platforms, which are supported by the community.

Platform Downloads (stable) Downloads (preview) How to Install
Arch Linux Instructions
Kali Linux .deb .deb Instructions
Many Linux distributions Snapcraft Snapcraft

You can also download the PowerShell binary archives for Windows, macOS, and Linux.

Platform Downloads (stable) Downloads (preview) How to Install
Windows 32-bit/64-bit 32-bit/64-bit Instructions
macOS (x64) 64-bit 64-bit Instructions
macOS (arm64) 64-bit 64-bit Instructions
Linux 64-bit 64-bit Instructions
Windows (ARM) 64-bit (preview) 64-bit Instructions
Raspbian (ARM) 32-bit/64-bit 32-bit/64-bit Instructions

To install a specific version, visit releases.

Upgrading PowerShell

For best results when upgrading, you should use the same install method you used when you first installed PowerShell. The update method will be different for each platform and install method. For more information, see Installing PowerShell.

Community Dashboard

Dashboard with visualizations for community contributions and project status using PowerShell, Azure, and PowerBI.

For more information on how and why we built this dashboard, check out this blog post.

Discussions

GitHub Discussions is a feature to enable free and open discussions within the community for topics that are not related to code, unlike issues.

This is an experiment we are trying in our repositories, to see if it helps move discussions out of issues so that issues remain actionable by the team or members of the community. There should be no expectation that PowerShell team members are regular participants in these discussions. Individual PowerShell team members may choose to participate in discussions, but the expectation is that community members help drive discussions so that team members can focus on issues.

Create or join a discussion.

Chat

Want to chat with other members of the PowerShell community?

There are dozens of topic-specific channels on our community-driven PowerShell Virtual User Group, which you can join on:

Add-ons and libraries

Awesome PowerShell has a great curated list of add-ons and resources.

Building the Repository

Linux Windows macOS
Instructions Instructions Instructions

If you have any problems building, consult the developer FAQ.

Build status of nightly builds

Azure CI (Windows) Azure CI (Linux) Azure CI (macOS) CodeFactor Grade
windows-nightly-image linux-nightly-image macOS-nightly-image cf-image

Downloading the Source Code

You can clone the repository:

git clone https://github.com/PowerShell/PowerShell.git

For more information, see working with the PowerShell repository.

Developing and Contributing

Please look into the Contribution Guide to know how to develop and contribute. If you are developing .NET Core C# applications targeting PowerShell Core, check out our FAQ to learn more about the PowerShell SDK NuGet package.

Also, make sure to check out our PowerShell-RFC repository for request-for-comments (RFC) documents to submit and give comments on proposed and future designs.

Support

For support, see the Support Section.

Legal and Licensing

PowerShell is licensed under the MIT license.

Windows Docker Files and Images

License: By requesting and using the Container OS Image for Windows containers, you acknowledge, understand, and consent to the Supplemental License Terms available on Docker Hub:

Telemetry

Please visit our about_Telemetry topic to read details about telemetry gathered by PowerShell.

Governance

The governance policy for the PowerShell project is described here.

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.

crescendo's People

Contributors

anmenaga avatar fh-inway avatar jameswtruher avatar jaykul avatar meir017 avatar michaeltlombardi avatar s-t-s avatar sdwheeler avatar stevel-msft avatar thejasonhelmick 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

crescendo's Issues

Export-CrescendoModule should create configuration file if does not exist

Export-CrescendoModule should create configuration file if file does not exist and not throw an error:

PS > Export-CrescendoModule -ModuleName something something.json -Force
Resolve-Path: C:\Program Files\WindowsPowerShell\Modules\Microsoft.PowerShell.Crescendo\0.5.1\Microsoft.PowerShell.Crescendo.psm1:688
Line |
 688 |  …  $resolvedConfigurationPaths = (Resolve-Path $ConfigurationFile).Path
     |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot find path 'C:\source\demo\something.json' because it does not exist.

Question on Parameter definiton "DefaultValue"

Hi all !

The current schema (preview-4) says this about setting a default value on a parameter:

"DefaultValue": {
            "type": "string",
            "description": "The default value for the parameter."
          }

Now if i have a [switch] parameter like "-nobannner" in my schema as below

{
   "ParameterType": "Switch",
   "Name": "NoBanner",
   "OriginalName": "-nobanner",
   "DefaultValue" : **"????"**
   "ParameterSetName": ["Default"],        
}

, how do i use the [string] value for the default value of -nobanner ?

i tried:

  • true
  • $true
  • -nobanner

already
Thx / R.

Error Handling for Commands

What is the expectation around error handling for commands? I often wrap native command so I can get good error handling for automation situations. This generally means checking the $LASTEXITCODE to validate the command completed successfully. I did a test wrapping git and if the command fails you get no error. Looking at the generated command it has no error checking around the execution. Is there a way to enable that in the .json file or a plan for adding built-in or opt-in error handling?

Add an option to check if requirements are met to run a command

Some commands need an elevated prompt or need to be run from a specific account, or need specific conditions to be met.

It would be handy to have an option that would add something like

#Requires -RunAsAdministrator

to the code in the generated module.

Or perhaps an option to add a user-provided code block where one can test if specific conditions are met.

Generated commands cannot handle output written to the error stream

Today I was attempting to write wrappers around some binaries used at my company. These binaries write to stderr when an error is encountered. Unfortunately, the generated functions use the call operator to invoke the native binaries and stderr output is not captured.

This means that if the native command encounters an error, I cannot write an OutputHandler to parse those errors or throw exceptions.

I'd like to suggest using a System.Diagnostics.Process object to execute the inner commands instead of using the call operator. This would enable a developer to read anything written to the error stream via ErrorHandlers (which could be written the same way as OutputHandlers) and more effectively throw exceptions.

Why JSON instead of psd1?

While I applaud the PowerShell team's efforts to make it easier to bring native commands into the object pipeline it seems an odd choice to use JSON instead of the PowerShell-native psd1 format.

An issue I can see right away is that authoring the handlers is a lot less awkward in psd1, because you get syntax highlighting and IntelliSense. And you won't have to fight with quoting.
image

Handling native positional parameters

It isn't clear to me if there is a way to handle positional parameters in native commands. For example, the ARP command has a positional parameter value for in Internet address, among others. How would I reference that? I've tried looking through documentation but can't find an easy to understand explanation. Or has this not been addressed yet?

Sysinternals as example/community repo

Hi Crescendo Team !

Every admin in the windows world loves sysinternals. If we would have Powershell Crescendo commandlets around sysinternal command, this may give Crescendo a massive push in adaption.

Would be great if the powershell team could host a repo with a few examples and the community may contribute to a PSsysinternals module based on Crescendo.

To be discussed/R.

Question: What does "OriginalText" do ?

Crescendo json schema do have a OriginalText outside of Usage but it doesn't have any description showing where its used, neither consumed in the crescendo module generation code.

Allow for module manifest adaptations

When using the command Export-CrescendoModule to generate an updated module, the module manifest (.psd1) file is also rewritten from scratch. This file often contains customizations and adaptations to the module that are outside of the scope for Crescendo: e.g. custom formats, default command prefixes. All such adaptations must then be re-inserted from scratch.

I can understand that Crescendo maintains "full ownership" of the .psm1 part of the module, but the manifest needs to be customizable at time of export, e.g. through parameters. Either that, or one should be given the option to keep it unchanged.

Export-CrescendoModule has odd support for -WhatIf

With most PowerShell commands, if you call a command inside a function that supports should process, the preference is passed to the underlying command.

function foo-test {
 [cmdletbinding(SupportsShouldProcess)]
param($name)
Stop-Service $name
}

foo-test bits -WhatIf
What if: Performing the operation "Stop-Service" on target "Background Intelligent Transfer Service (bits)".

But when Export-CrescendoModule is used, it doesn't appear to pickup SupportsShouldProcerss. Yet, the command will recognize it when used directly:

Export-CrescendoModule -ConfigurationFile .\CmdKey-crescendo.json -modulename foo -WhatIf

What if: Performing the operation "Output to File" on target "foo.psm1".
What if: Performing the operation "Output to File" on target "foo.psm1".
What if: Performing the operation "Output to File" on target "foo.psm1".

Although oddly, it appears to be writing the file 3 times.

Credential Parameters

There should be a way to have a Credential parameter that can automatically be applied to UserName/Password, possibly Domain/UserName/Password.

For example something like Invoke-NetUse -Share '\server\share' -DriveLetter 'D:' -Credential (Get-Credential DOMAIN\UserName) should execute the native net.exe like this:

net.exe use D: \\server\share /username:DOMAIN\UserName<ENTER>
Enter the password for 'METHODE-GLOBAL\admin.sg503689' to connect to 'cncn01-fs01.methode.global': <password><ENTER>

The idea being that we limit the amount of time the plaintext password is in memory and prevent a plain text password from being entered into the console.

OutputHandlers has support for external scripts

Doing anything beyond a one-liner in the Handler property is extremely error-prone. Even simple typos aren't picked up because it's just a string value. It would be useful if there was a HandlerPath property that would pull in code from a script file. That would make any reasonably complex hander logic easier to maintain.

Root command's options

As of the current version 0.4.0, it seems that we can not make a cmdlet that has options between root command and sub command.

For example, docker command's --log-level option whose accepted values are debug, info, warn, error, and fatal (see: docker cli reference).

If we make Build-DockerImage cmdlet by docker image build, we will write the follwoing json for now:

"OriginalName": "docker",
"OriginalCommandElements": [
    "image",
    "build"
], ...

But if Debug switch corresponding to --log-level debug is needed, we need the following features:

  • To specify the --log-level debug as the original name and value of Debug switch, for example, OriginalNameAndValue in the below json
  • To insert the --log-level debug between docker and image like docker --log-level debug image build
"Parameters": [
    {
        "Name" : "Debug",
        "ParameterType" : "switch",
        "OriginalNameAndValue" : "--log-level debug" // <-- inserted between `docker` and `image`
    }
], ...

Crescendo should export .psd1 with .psm1

PowerShell Crescendo exports a module .psm1 containing the crescendo proxy functions that enable easier administration and automation of native commands. Export-CrescendoModule should include and export a module manifest .psd1 for the following reasons:

  • Best practice to include a manifest with the Module
  • Authors will be able to version the module, specify files in the module (such as nested modules), run scripts to customize the user's environment, load type and formatting files, define system requirements, and limit the members that the module exports

In addition, add PowerShell Gallery tag to module manifest private data

  • Add a PowerShell Gallery tag to the private data section of the module manifest to allow customers to quickly filter and recognize Crescendo generated modules on PSGallery.
  • Tag: Crescendo

Invoke-WindowsNativeAppWithElevation is it actual elevation?

I was looking over the release notes for preview 2 and it mentions it has support for elevating a command on Windows. Looking at the code it seems like this is done through Invoke-WindowsNativeAppWithElevation. Essentially it calls Start-Process -FilePath cmd.exe -ArgumentList @('/c, '/d', $pwd, 'command to run') -Credential $cred. If you have UAC enabled on your host or you are targeting a user that is not the builtin Administrator account the new process won't actually be elevated. This is because Start-Process ... -Credential relies on `CreateProcessWithLogonW which essentially creates an interactive token for the specified user and starts the process with that new token.

The interactive token retrieved in this call is only elevated if

While you could argue it "elevates" the token from a network to interactive logon which the former is a bit tricky with some APIs this isn't really the case because Start-Process ... -Credential fails on a network logon like WinRM due to the station not being set up properly beforehand.

Ultimately the only true way to elevate a process is by using -Verb RunAs and accept the UAC prompt. From an automation perspective this is not possible because you can't automatically accept this prompt so the usability is limited on Windows at best.

It's also questionable why you call cmd.exe /c ... in the Start-Process call. Why not just invoke the executable directly, by bringing cmd into the picture you now add another layer of complication and quote escaping rules people need to consider.

PowerShell Crescendo should have a versioned schema

PowerShell Crescendo should have a versioned schema to support future updates without breaking previous versions. Currently, this is not the case. We are discussing the format and will add notes here, however it should follow a standard schema versioning format including the date of release.

Reference parameters in the OutputHandler

In my OutputHandler I'm trying to include some basic error handling. I want to be able to include a command in the handler like

Write-Warning  "Failed to find a matching entry for $list"

In my Parameters section, the Name is set to "List" but this doesn't seem to work. Is there some other way to reference parameters or does this functionality not exist yet?

Print generated command for debugging

I'm trying to wrap the twilio cli to experiment with Crescendo, but I'm not getting the expected output, nor an error to help me figure out what's wrong.
Is there a way to print out the fully built command?
For example, I built Get-PhoneNumber cmdlet using Crescendo, but the output is

Unleash the power of Twilio from your command prompt. Visit https://twil.io/cli for documentation.

VERSION
  twilio-cli/3.1.0 darwin-x64 node-v16.14.0

USAGE
  $ twilio [COMMAND]
...

Which is the default output if you run twilio without arguments.

It would be helpful to see the output'd command for Get-PhoneNumber which should be twilio phone-numbers -o=json but I don't know what it is.

Improve error when OriginalName file is not found

When the OriginalName executable is not found during module import, the error thrown can be improved.

Current behavior

&: C:\temp\ipconfig.psm1:68
Line |
  68 |              $result = & "C:/SysinternalsSuite/handle.exe" $__commandA …
     |                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The term 'C:/SysinternalsSuite/handle.exe' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the
     | spelling of the name, or if a path was included, verify that the path is correct and try again.

Expected behavior

&: C:\temp\ipconfig.psm1:68
Line |
  68 |              $result = & "C:/SysinternalsSuite/handle.exe" $__commandA …
     |                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The file 'C:/SysinternalsSuite/handle.exe' is not available in the system which is required for this module to execute.
     | Make sure the executable is present at the designated location.

PowerShell Crescendo json files should support multiple native command definitions

PowerShell Crescendo currently supports generating multiple json files into a single PowerShell module.

Export-CrescendoModule -ConfigurationFile .\get-ipconfig.proxy.json,.\Remove-ipconfig.proxy.json,.\New-ipconfig.proxy.json -ModuleName ./ipconfig.psm1
  • It would be easier to develop and maintain multiple native commands in a single json file.
  • Crescendo should support both linux and windows native commands in the same json file.

Originally posted by @theJasonHelmick in #45

Command suggestions for native command testing

The Native Command Proxy framework is designed to reduce the maintenance cost and burden of wrapping native commands for a PowerShell-like cmdlet experience.

From customer feedback, we’ve identified a series of high value linux commands related to the installation of packages. In an effort to test the native command framework, we should consider testing against one or several of these commands.

Commands:

  • dpkg
  • dpkg-query
  • rpm
  • apt
  • dnf
  • zypper
  • snap

@JamesWTruher , while we cannot work with all of these in our initial testing, what do you think of focusing on apt, specifically apt install?

Quoting empty string parameter values

Proxy functions generated by Crescendo currently do not add quotes around empty strings, if such value is provided for a string parameter. This leads to broken command lines, causing either syntax errors or parameter mismatches.

Context

Consider the "install" command of the Chocolatey command line executable (choco.exe).
The command, like other choco commands, accepts both named and unnamed parameters. Unnamed parameters are package names to install and at least one must be provided. Named parameters can be switches (which do not accept a value, such as --force) or regular parameters, with the value separated from the parameter name either by whitespace (e.g. --timeout 3600) or by the equals sign (e.g. --timeout=3600). For some of the latter parameters, an empty string is a valid value, for example --package-parameters "" (typed in cmd.exe) would mean that no special parameters should be passed to the package install script, same as if the --package-parameters parameter was omitted.

Reproduction

Given this simple Crescendo proxy definition:

$c = New-CrescendoCommand -Verb Test -Noun PassingParametersToChoco
$c.OriginalName = 'choco'
$c.OriginalCommandElements = @('install', '--verbose', '--debug')
$c.Parameters = @(@{ Name = 'Name'; ParameterType = 'string'; OriginalPosition = 1 }, @{ Name = 'PackageParameters'; ParameterType = 'string'; OriginalName = '--package-parameters'; OriginalPosition = 2 }, @{ Name = 'Yes'; ParameterType = 'switch'; OriginalName = '--yes'; OriginalPosition = 3 })
@{ Commands = @($c) } | ConvertTo-Json -Depth 100 | Out-File .\Test-PassingParametersToChoco.def.json
Export-CrescendoModule -ConfigurationFile .\*.def.json -ModuleName .\TestPassingParameters.psm1 -Force

(I set OriginalPosition, even though choco.exe puts no restrictions on parameter order, so that the tests are deterministic.)

I can now invoke Test-PassingParametersToChoco with various parameter combinations and observe the actual command line passed to choco.exe (it is printed by choco.exe thanks to the --debug switch and it can also be seen in Sysinternals Process Monitor). (To avoid making changes to my system, I use a non-elevated PowerShell console; choco.exe will detect the lack of admin rights and ask interactively for permission to continue, which is why it is required to type "y " after invoking Test-PassingParametersToChoco in order to see the output from choco.exe. Alternatively, the command line can be inspected in Process Monitor and the invocation terminated with Ctrl+C.)

  1. Passing only package name

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
  1. Passing empty package parameters

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters ''

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters
(...)
Install Command

Installs a package or a list of packages (sometimes specified as a
(rest of help text for the install command)
(...)
System.ApplicationException: Package name is required. Please pass at least one package name to install.

The argument parser in choco.exe signals an error, because there is no value specified for the "--package-parameters" parameter. Due to unfortunate ordering of code inside choco.exe, a misleading error message about missing package name is printed. It can be noted that because parameter validation failed there was no need to respond to the "continue without admin rights?" prompt.

  1. Passing empty package parameters and an additional switch

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters '' -Yes

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters --yes

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters --yes
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
NotSilent='False'|PackageParameters='--yes'|

In this case, because the --package-parameters parameter with empty value was followed by a switch (--yes), the command line passed to choco.exe resulted in "--yes" being interpreted as the value of the --package-parameters parameter.

Additional experiments

  1. Passing a package parameter (without spaces)

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters 'foo'

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters foo

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters foo
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
NotSilent='False'|PackageParameters='foo'|

The --package-parameters value was passed properly.

  1. Passing some package parameters with a space

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters 'foo bar'

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters "foo bar"

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters "foo bar"
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
NotSilent='False'|PackageParameters='foo bar'|

The --package-parameters value was automatically and correctly quoted.

  1. Passing package parameters consisting of a space only

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters ' '

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters " "

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters " "
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
(no debug line with PackageParameters value)

The --package-parameters value was automatically and correctly quoted. In this case choco.exe recognized the whitespace-only value and treated it as if the --package-parameters parameter was not passed.

  1. Without Crescendo

These direct invocations of choco.exe behave incorrectly, in a similar manner (the empty string just disappears), if run from PowerShell:
choco install --verbose --debug cpu-z --package-parameters ''
choco install --verbose --debug cpu-z --package-parameters ([string]::Empty)
$chocoargs = @('install', '--verbose', '--debug', 'cpu-z', '--package-parameters', ''); choco @chocoargs
choco install --verbose --debug cpu-z --package-parameters ""
choco install --verbose --debug cpu-z --package-parameters "" --yes

which means that it is PowerShell itself which fails to quote those empty strings.

On the other hand, the last two lines do work correctly from cmd.exe. Example:

cmd.exe statement:
choco install --verbose --debug cpu-z --package-parameters ""

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters ""

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters ""
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
(no debug line with PackageParameters value)

Conclusion

I believe the proxies generated by Crescendo should recognize empty strings provided as string parameter values and ensure those empty strings are passed as "" to the invoked executable. This would be consistent with how parameters with spaces are handled and would fix the currently broken behavior when the caller provides an empty string to the proxy.

Schema location

For testing purposes, I downloaded the schema and use a relative path in my son.

 "$schema": "./Microsoft.PowerShell.Crescendo.Schema.json",

But should this eventually be an online reference? And if so, has it been set? Or do we reference it from this repo?

Help files should have some examples

Things I build for myself that I never plan to share with anyone are better documented than this.

I guess the 'New' commands are supposed to help create the json, and we're supposed to nest accordingly and output to a file? I don't understand how to create a valid json using the commands provided. Am I supposed to create the json scaffolding by hand?

Handling parameters that are Mandatory in some parameter sets, but not others

If you look at the [Parameter] attribute, you duplicate it for all parameter sets. So a parameter could be position=0 in one set and position=3 in another. Plus, the Mandatory value could change. You need to use that to tell PowerShell which set to use. Unfortunately, Crescendo doesn't seem to support that. The way I read it, the parameter set is an array, but those share position and mandatory values. So how does PowerShell know which parameter set is being used at runtime?

No syntax check for Outputhander

If code inside handler has syntax errors, its not captured during the command creation, but Import-Module fails for the generated module with syntax errors.

Provide option to specify platform dependencies

Not all commands are the same, or use the same parameters, across platforms.
A feature to allow specifying platform dependencies for parameters and handlers could be handy.

That would allow generating a module that is portable across platforms.

OriginalName is [string] should be [string[]]

I can see that the OriginalName is string but it could have several names, such as a short and a long version (or several).
for instance --noop, --dry-run, --no-act,--simulate.

It would be good to persist those in the object model so that we can, optionally, create aliases from these originalNames.

Conversion of input parameter values

The binary I'm working with takes a comma-delimited list of strings. I would like to take in a string[] and simply -join '' at runtime.

Args list should look like this

list
items
--categories
passwords,logins

Currently looks like this.

list
items
--categories
passwords
logins

Definition

        {
            "Name": "Categories",
            "OriginalName": "--categories",
            "ParameterType": "string[]",
            "Description": "Only list items in these categories",
            "Position": 0
        },

Sudo/Runas support for PowerShell Crescendo

In many cases when working with native commands, a change to the elevation level is required to achieve the expected results on either Linux or Windows systems. As an example, on a linux system the sudo command changes elevation and the results of the native command:

> whoami
> JasonHelmick
> sudo whoami
> Root

We are thinking of how best to support the elevation needs of native commands with PowerShell Crescendo. Below is an example of how this might be written into the json file.

{
"$schema" : "./Microsoft.PowerShell.Crescendo.Schema.json",
"Verb": "Invoke",
"Noun": "whoami",
"OriginalName":"/usr/bin/whoami",
"ElevationCommand": {
"Command": "sudo"
}
}

The json could also would support arguments passed to the elevation command, similar to this:

"ElevationCommand": "command",
"Arguments": [
"arg1",
"arg2"
]

First, would appreciate feedback on handling elevation and the above example.
Also, based on the discussion #45 (PowerShell Crescendo json files should support multiple native commands)

  • should we support both linux and Windows elevation in the same json
  • if so, what scenarios do you see this addressing?

Originally posted by @theJasonHelmick in #47

Create object wrappers for core GNU/*nix utilities

This got brought up during the sync with azure-cli. It would be awesome to have some kind of wrappers for core GNU/*nix management tools (e.g. ifconfig, iptables, etc.) so that you can manipulate the output of these tools as if they were PS cmdlets.

So, for instance, where ifconfig might return:

eth0 Link encap:Ethernet HWaddr 00:0C:29:40:93:9C
     inet addr:192.168.154.102 Bcast:192.168.154.255 Mask:255.255.255.0
     UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
     RX packets:1771 errors:0 dropped:0 overruns:0 frame:0
     TX packets:359 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:1000
     RX bytes:138184 (134.9 KiB) TX bytes:49108 (47.9 KiB)
     Interrupt:67 Base address:0x2000

Get-Ifconfig might return an object that includes properties like inetaddr, Bcast, and Mask. Even if these are all strings, I imagine it could actually entice a beginner to intermediate *nix admin to use PowerShell for scripting against tools that may have better core networking/storage/etc support than .NET-based cmdlets.

Gap character parameter

Instead of the NoGap bool, it would be more useful to have a gap character property. By default, it would be a space but could be an empty string or a colon or whatever separator is needed.

Can we talk about PowerShell script ... style?

Hopefully without offending anyone. Let's just call this a code review. I believe that any tool which generates code should generate the best possible code, because there are always going to be new users that start their experience using your code generator, and then learn their style from that code.

Here, for example, is the current output from the sample apt from the blog post (after I fixed the json quotes):

# Module created by Microsoft.PowerShell.Crescendo
Function Get-InstalledPackage
{
[CmdletBinding()]

param(    )

BEGIN {
    $__PARAMETERMAP = @{}
    $__outputHandlers = @{
        Default = @{ StreamOutput = $False; Handler = { $args[0]| select-object -skip 1 | %{$n,$v,$p,$s = "$_" -split ' '; [pscustomobject]@{ Name = $n -replace '/now'; Version = $v; Architecture = $p; State = $s.Trim('[]') -split ',' } } } }
    }
}
PROCESS {
    $__commandArgs = @(
        "-q"
        "list"
        "--installed"
    )
    $__boundparms = $PSBoundParameters
    $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $PSBoundParameters[$_.Name]}).ForEach({$PSBoundParameters[$_.Name] = [switch]::new($false)})
    if ($PSBoundParameters["Debug"]){wait-debugger}
    foreach ($paramName in $PSBoundParameters.Keys|Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) {
        $value = $PSBoundParameters[$paramName]
        $param = $__PARAMETERMAP[$paramName]
        if ($param) {
            if ( $value -is [switch] ) { $__commandArgs += $value.IsPresent ? $param.OriginalName : $param.DefaultMissingValue }
            elseif ( $param.NoGap ) { $__commandArgs += "{0}""{1}""" -f $param.OriginalName, $value }
            else { $__commandArgs += $param.OriginalName; $__commandArgs += $value |Foreach-Object {$_}}
        }
    }
    $__commandArgs = $__commandArgs|Where-Object {$_}
    if ($PSBoundParameters["Debug"]){wait-debugger}
    if ( $PSBoundParameters["Verbose"]) {
         Write-Verbose -Verbose -Message apt
         $__commandArgs | Write-Verbose -Verbose
    }
    $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName]
    if (! $__handlerInfo ) {
        $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present
    }
    $__handler = $__handlerInfo.Handler
    if ( $PSCmdlet.ShouldProcess("apt")) {
        if ( $__handlerInfo.StreamOutput ) {
            & "apt" $__commandArgs | & $__handler
        }
        else {
            $result = & "apt" $__commandArgs
            & $__handler $result
        }
    }
  } # end PROCESS

<#
.SYNOPSIS
Could not find apt to generate help.

.DESCRIPTION See help for apt

#>
}

Export-ModuleMember -Function Get-InstalledPackage

So first things first: can we get consistent about capitalization?

  • Most of us don't capitalize any keywords in PowerShell, and that includes Function and BEGIN ...
  • Usually we choose a single capitalization style for variable names. I feel like we have a mix of ALL CAPS and camelCase here.

Secondly: can we get consistent about white space and line wrapping?

  • Personally I prefer OTBS, although I know the Microsoft team, being mostly C# developers, have probably been conditioned to Alman, but this code does a crazy mix. It's mostly K&R, but the first opening brace is on a new line, and then there's this weird if/elseif/else where each segment is on a single line (which doesn't match any of the rest of the code).
  • There's a lot of squeezing things onto one line in this, from the if/elseif/else block on lines 27..29 to the various wait-debugger lines (please tell me you don't intend to leave those in, in the final product?).
  • There is even one line like this, where not only is the else on the same line with the code, but the code is actually two lines stuck together with a ; for no real reason.
else { $__commandArgs += $param.OriginalName; $__commandArgs += $value |Foreach-Object {$_}}
  • There are a lot of extra spaces inside parenthesis (not consistently enough to look intentional).
  • Each line with a pipe | has a different number of spaces around it...
  • Most of this can be fixed if I run the whole thing through PSScriptAnalyzer's formatter/fixers (especially if I turn on the options to fix aliases and capitalization). Generated code needs to pass ScriptAnalyzer with flying colors!

Third: can we get consistent about variable names?

  • I assume there's a python programmer on the team, because of the $__ ...
  • I can see the value in prefixing the generated variables, to avoid the possibility of conflicting with the variable names used in the output handlers. However, if you're going to do that, you need to do it religiously, and you've missed $value and $param .. and $paramname and $result
  • Personally, given that $_ is the most common PowerShell variable, I don't like the double-underbar prefix, and I'd rather see you name the generated variables in a uniquely PowerShell way: ${Parameter Map}, ${Output Handlers}, ${Command Arguments}, ${Bound Parameters}, ${Parameter Value} and ${Parameter Options}. This is equally unlikely to collide with user-chosen variables, and can't be confused with properties of $_

There are some other miscellaneous problems with the code currently:

  • The variable '__boundparms' is assigned but never used (on line 20)
  • The blog post said this would work in 5.1 but you're using ternary on line 27...
  • The (ab)use of -Debug to turn on Wait-Debugger is a little strange, to say the least. I don't think it's worth having in "production" code -- it'll just slow us down.
  • Are we sure we want the "StreamOutput" to do | & $handler and not | % $handler? That's going to be faster, but will require a, uhm, certain level of sophistication from authors ... which is complicated by the fact that the else case passes the parameters differently. Couldn't the non-streaming still pipe the input in, but after collecting it into a single $result, like: , $result | % $handler

Positional parameter cannot be found error for OriginalName with spaces when using -Verbose

While doing the tutorial with azcmagent from the documentation, I ran into the issue that when one of the generated cmdlets was executed with the -Verbose switch, I got an error:

Write-Verbose: A positional parameter cannot be found that accepts argument 'Files\AzureConnectedMachineAgent\azcmagent.exe'.

This is because in the Crescendo configuration file, OriginalName is specified as
OriginalName = "c:/program files/AzureConnectedMachineAgent/azcmagent.exe"
Note the space between program and files.

The Export-CrescendoModule function (or more specifically the GetInvocationCommand method of the Command class) handles this by wrapping OriginalName in double quotes - except for this line:

$sb.AppendLine(' Write-Verbose -Verbose -Message ' + $this.OriginalName)

ReadMe mixed up words

*Submitting as issue as it is indicated that PRs are not accepted currently.

While reading the readme I can across the following:

The Microsoft.PowerShell.Crescendo module provides a way to more easily for native commands to participate in the PowerShell pipeline by facilitating parameter handling...

I believe this should be:

The Microsoft.PowerShell.Crescendo module provides native commands a way to more easily participate in the PowerShell pipeline by facilitating parameter handling...

Discussion: DSL: json => PS with custom attributes

Hi maintainers,

I love that we have Crescendo and I have many use cases. But it's a hard sell to my team, because the creation of JSON files is different enough to the normal workflow to present a barrier to adoption.

I propose a DSL based on custom attributes that lets these be written as powershell script instead.

As a discussion starter:

[Crescendo.Command(OriginalName = 'bcu.exe', OriginalCommandElements = @('diag'), OutputHandlers = 'Parse-BcuDiagOutput')]
function Get-BcuDiag
{
    param
    (
        [Parameter()]
        [Crescendo.Parameter(OriginalName = "--tempshow")]
        [switch]$ShowTemperature
    )

    # no function body - that's Crescendo's job
}

New-CrescendoCommand Should Include an OriginalName Parameter

To me, New-CrescendoCommand should include an OriginalName parameter. While the property can be added, as is seen in the below example, I strongly believe that this property should be easier to include, and therefore a parameter should be included for it. It's likely that a user may not know how to programmatically add a value to this property, or care to manually manipulate the PowerShell object and/or JSON. Thank you!

# Invoke New-CrescedoCommand function.
[PS7.1.3][C:\] $Command = New-CrescendoCommand -Verb Connect -Noun RemoteComputer

# Add OriginalName property.
[PS7.1.3][C:\] $Command.OriginalName = "/Windows/System32/mstsc.exe"

# Convert $Command to JSON.
[PS7.1.3][C:\] $Command = $Command | ConvertTo-Json
[PS7.1.3][C:\] $Command

{
  "Verb": "Connect",
  "Noun": "RemoteComputer",
  "OriginalName": "/Windows/System32/mstsc.exe",
  "OriginalCommandElements": null,
  "Aliases": null,
  "DefaultParameterSetName": null,
  "SupportsShouldProcess": false,
  "SupportsTransactions": false,
  "NoInvocation": false,
  "Description": null,
  "Usage": null,
  "Parameters": [],
  "Examples": [],
  "OriginalText": null,
  "HelpLinks": null,
  "OutputHandlers": null
}

Wrap setfacl/getfacl in set/get-acl

setfacl/getfacl is standard on RHEL and optional on other Linus distros. It's only for file access, but may be useful to abstract it in cmdlets

Crescendo should support the help parsing of modern native commands

After discussing with @JamesWTruher , it may be possible for crescendo to enumerate the help of some modern native commands to autogenerate the configuration file with parameters and help. Modern native commands that emit their help as structured data (i.e. xml, json) such as Docker or Kubectl, could use this information to generate the configuration file.

This would decrease the development time for more complex commands, and increase the command parameter coverage - allowing the module author to focus on cmdlet design and output handling.

This could be added to Export-CrescendoCommand:
-OriginalCommand - path and executable of the native command
-Verb - Desired verb of the new cmdlet
-Noun - Desired noun of the new cmdlet

Default parameter values are not used

ModuleType Version    PreRelease Name                              
---------- -------    ---------- ---- 
Script     0.6.1                 Microsoft.PowerShell.Crescendo   

PS> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.1.4
PSEdition                      Core
GitCommitId                    7.1.4
OS                             Microsoft Windows 10.0.22000
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0


The resulting PowerShell code does not use/honor the default values of parameters.

Input:

{
    "$schema": "./Microsoft.PowerShell.Crescendo.Schema.json",
    "Commands": [
         {
             "Verb": "Search",
             "Noun": "WinGet",
             "OriginalName":"winget.exe",
             "OriginalCommandElements": ["search"],
             "Parameters": [
                {
                    "ParameterSetName": ["query"],
                    "Name":"Filter",
                    "OriginalName": "-q",
                    "ParameterType": "string",
                    "Description": "This switch provides compartment configuration details"
                },
                {
                    "ParameterSetName": ["query"],
                    "Name":"Store",
                    "OriginalName": "-s",
                    "ParameterType": "string",
                    "DefaultValue": "WinGet",
                    "Description": "This switch provides compartment configuration details"
                }
            ]
         }
    ]
}

But even with the default value for store to WinGet we get this behavior

PS > Search-WinGet -Filter Citrix -Verbose

VERBOSE: winget.exe
VERBOSE: search
VERBOSE: -q
VERBOSE: Citrix
VERBOSE: Performing the operation "Search-WinGet" on target "winget.exe search -q Citrix".

but if I add the Store parameter it works

PS >  Search-WinGet -Filter Citrix -store Winget  -Verbose       
VERBOSE: winget.exe
VERBOSE: search
VERBOSE: -s
VERBOSE: Winget
VERBOSE: -q
VERBOSE: Citrix
VERBOSE: Performing the operation "Search-WinGet" on target "winget.exe search -s Winget -q Citrix".

The resulting function has the correct param block for Store that should default to WinGet

param(
[Parameter(ParameterSetName='query')]
[string]$Filter,
[Parameter(ParameterSetName='query')]
[string]$Store = "WinGet"
    )

But later on in the generated code there is this line

foreach ($paramName in $PSBoundParameters.Keys|Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) {

And that seems to filters away handling of parameters with default values as $PSBoundParameters only holds parameters that has been defined. See example here:

function Foo {
    [CmdletBinding()]
    param (
        [Parameter(ParameterSetName = 'query')]
        [string]$Filter,
        [Parameter(ParameterSetName = 'query')]
        [string]$Store = "WinGet"
    )
    $PSBoundParameters.Keys
}

Foo -Filter citrix  # Would only return filter

Ipconfig.psm1 syntax error?

My question has actually been resolved.
Under Powershell, the "Ternary" syntax is not known and is therefore misinterpreted by ISE in Powershell 5.1. It should be: $oName = ($LineToCheck -match 'Configuration') ? 'IpConfiguration' : 'EthernetAdapter' ?

... but it is exciting

Hello,
I am currently trying the IPCONFIG example. Thereby Export-CrescendoModule generates a psdm1 module which is displayed in the Powershell ISE with syntax error (red underline at the error location). This errored module cannot be imported of course.
The syntax (?) error is in this area:
if ( $LineToCheck ) {
if ( $post ) { [pscustomobject]$ht |add-member -pass -typename $oName }
$oName = ($LineToCheck -match 'Configuration') { 'IpConfiguration' } else {'EthernetAdapter'}
$ht = @{};
$post = $true
}
Maybe just a curly bracket is missing I can't figure it out though.
Now I am not the outstanding Powershell professional programmer. But maybe I am doing something wrong.
Thanks a lot

reredok

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.