Skip to content

PowerShell wrapper Phase 1: cmdlet generation for the first 15 Graph modules - #7986

Closed
Joywambui-maina wants to merge 2 commits into
feature/PS-generatorfrom
joy/wrapper-generator
Closed

PowerShell wrapper Phase 1: cmdlet generation for the first 15 Graph modules#7986
Joywambui-maina wants to merge 2 commits into
feature/PS-generatorfrom
joy/wrapper-generator

Conversation

@Joywambui-maina

Copy link
Copy Markdown

Changes proposed in this pull request

  • Add PowerShellWrapper as a kiota generate option (-l PowerShellWrapper), routed to
    its own generation service instead of the CodeDOM/refiner/writer pipeline, the same
    shape as plugin generation.
  • Build cmdlet nouns from the URL path, singularized word by word, instead of from the
    operationId.
  • Reproduce the published SDK's hand-tuned names as checked-in data
    (NamingOverrides.cs, 3 entries, each citing the AutoRest directive it mirrors), and
    skip operations the published SDK deliberately does not ship.
  • Add 51 naming tests whose expected values are published Microsoft.Graph cmdlet names.
  • Document the naming algorithm rule by rule, with a shipping cmdlet as evidence for each
    rule, in src/Kiota.Builder/PowerShellWrapper/README.md, including known gaps.

Why

Generated cmdlet names previously leaked raw plural path segments: Get-MgUsersMessages
instead of Get-MgUserMessage, Get-MgSolutionsBookingBusinesses instead of
Get-MgBookingBusiness. The nouns were taken from operationIds, which carry whatever
plurality the spec author chose. The project requires generated cmdlets to keep the exact
names Microsoft.Graph customers already use.

Deriving the noun from the URL path makes naming deterministic (same OpenAPI input, same
cmdlet name) and gives naming and request routing one source of truth. The published
names are not fully algorithmic: a few come from hand-written AutoRest directives in the
SDK's module configs, so those cases live here as reviewable data with a cited source
each, and the generator itself stays generic.

Validation

dotnet test tests/Kiota.Builder.Tests/Kiota.Builder.Tests.csproj --filter "FullyQualifiedName~PowerShellWrapper"

51/51 tests passed. CI runs the same suite on this PR.

All 15 modules regenerated from their specs, then every generated cmdlet's HTTP method
and URL joined against MgCommandMetadata.json (the inventory behind
Find-MgGraphCommand): 66 of 66 cmdlets carry the exact published name, previously 30
of 66. Find-MgGraphCommand returns the same command names this generator now emits.

All 15 modules compiled, imported, and exercised against a live tenant: list reads,
item-by-id reads through the parameter-set dispatcher, -Filter query binding, write
cmdlets, and errors surfacing as error records from the correct cmdlet.

Cmdlet nouns are built from the URL path, singularized word by word, with a
small override table for the SDK's hand-tuned names. 66/66 name parity on the
15 pilot modules, 51 unit tests.
@Joywambui-maina
Joywambui-maina requested a review from a team as a code owner July 24, 2026 08:55
@github-code-quality

github-code-quality Bot commented Jul 24, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: C#

C# / code-coverage/dotnet

The overall coverage in commit c439641 in the joy/wrapper-generato... branch is 70%. Coverage data for the feature/PS-generator branch is not yet available.

Show a code coverage summary of the most covered files.
File feature/PS-generator joy/wrapper-generato... c439641 +/-
/home/runner/wo...guageRefiner.cs 98%
/home/runner/wo...criptRefiner.cs 98%
/home/runner/wo...MethodWriter.cs 97%
/home/runner/wo...MethodWriter.cs 96%
/home/runner/wo...MethodWriter.cs 96%
/home/runner/wo...MethodWriter.cs 95%
/home/runner/wo...rs/GoRefiner.cs 93%
/home/runner/wo...KiotaBuilder.cs 90%
/home/runner/wo...ationService.cs 89%
/home/runner/wo...xGenerator.g.cs 73%

Updated July 25, 2026 03:53 UTC

@Joywambui-maina

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree [company="{microsoft}"]

dotnet format --verify-no-changes was failing CI with CHARSET errors
since these files were saved without the BOM required by .editorconfig
(charset = utf-8-bom).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new PowerShellWrapper generation mode to Kiota that produces C#-implemented PowerShell cmdlets for Microsoft Graph, with cmdlet naming derived from URL paths (plus a small set of checked-in overrides) and backed by new golden naming tests.

Changes:

  • Introduces GenerationLanguage.PowerShellWrapper and routes kiota generate -l PowerShellWrapper to a dedicated generation service (GeneratePowerShellWrapperAsync).
  • Implements path-based noun construction with singularization, seam-collapse rules, and a small override/suppression table to match published Microsoft.Graph cmdlet names.
  • Adds PowerShellWrapper naming tests and documentation describing the naming algorithm and verification approach.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/Kiota.Builder.Tests/PowerShellWrapper/NamingTests.cs Adds golden tests for singularization and published cmdlet name resolution/suppression.
src/kiota/Handlers/KiotaGenerateCommandHandler.cs Routes PowerShellWrapper language to the new builder entry point.
src/Kiota.Builder/PowerShellWrapper/Singularizer.cs Implements word/segment singularization rules for cmdlet noun parity.
src/Kiota.Builder/PowerShellWrapper/SchemaProperties.cs Extracts shallow primitive body properties for write-cmdlet parameters.
src/Kiota.Builder/PowerShellWrapper/README.md Documents architecture, naming rules, overrides, and verification/gaps.
src/Kiota.Builder/PowerShellWrapper/PowerShellWrapperGenerationService.cs Orchestrates wrapper emission and GET pairing logic.
src/Kiota.Builder/PowerShellWrapper/OperationInfo.cs Defines operation metadata used for naming/emission decisions.
src/Kiota.Builder/PowerShellWrapper/NamingOverrides.cs Adds checked-in noun overrides and operation suppression data.
src/Kiota.Builder/PowerShellWrapper/EmitContext.cs Supplies module-specific context (namespaces) to emitter templates.
src/Kiota.Builder/PowerShellWrapper/CmdletNaming.cs Builds cmdlet verb/noun/class names and Kiota builder expressions.
src/Kiota.Builder/PowerShellWrapper/CmdletEmitter.cs Emits generated cmdlet class source (C#) for GET/POST/PATCH/DELETE shapes.
src/Kiota.Builder/KiotaBuilder.cs Adds GeneratePowerShellWrapperAsync and avoids unnecessary URI-space work for this language.
src/Kiota.Builder/GenerationLanguage.cs Adds the new PowerShellWrapper enum value and documentation.
Comments suppressed due to low confidence (6)

src/Kiota.Builder/PowerShellWrapper/CmdletEmitter.cs:86

  • HeaderBindingsFor interpolates OpenAPI-derived header names into a generated C# string literal (Headers.Add("...") ) without escaping. If RawName contains ", control characters, etc., the generated cmdlet source becomes uncompilable or injectable. Escape/sanitize RawName for a C# double-quoted literal at emission time.
                {{extraIndent}}if (this.IsParameterBound(nameof({{h.PsName}})))
                {{extraIndent}}    requestConfiguration.Headers.Add("{{h.RawName}}", {{h.PsName}}!);
        """));

src/Kiota.Builder/PowerShellWrapper/CmdletEmitter.cs:274

  • Same generated-source injection risk as above: naming.Noun is emitted into a C# string literal in an attribute without escaping. Sanitize before emitting.
    [Cmdlet(VerbsCommon.{{naming.VerbName}}, "{{naming.Noun}}")]

src/Kiota.Builder/PowerShellWrapper/CmdletEmitter.cs:379

  • listNaming.Noun comes from the OpenAPI path and is emitted into a generated C# string literal in the [Cmdlet] attribute. Escape it for a double-quoted literal context to avoid generated-source injection/uncompilable output from hostile specs.
    [Cmdlet(VerbsCommon.Get, "{{listNaming.Noun}}", DefaultParameterSetName = "List")]

src/Kiota.Builder/PowerShellWrapper/CmdletEmitter.cs:444

  • Cmdlet noun values are OpenAPI-derived and are written into generated C# string literals in attributes without escaping. Sanitize to prevent generated-source injection and invalid generated code when specs contain unexpected characters.
    [Cmdlet(VerbsCommon.{{naming.VerbName}}, "{{naming.Noun}}", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium)]

src/Kiota.Builder/PowerShellWrapper/CmdletEmitter.cs:501

  • Same issue as other [Cmdlet] attributes in this emitter: naming.Noun is interpolated into a generated C# string literal without escaping. Sanitize for double-quoted literal context at emission time.
    [Cmdlet(VerbsData.{{naming.VerbName}}, "{{naming.Noun}}", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium)]

src/Kiota.Builder/PowerShellWrapper/CmdletEmitter.cs:568

  • Same generated-source injection risk: naming.Noun is emitted into a generated C# string literal without escaping. Sanitize to keep generated cmdlets compilable and hardened against hostile OpenAPI inputs.
    [Cmdlet(VerbsCommon.{{naming.VerbName}}, "{{naming.Noun}}", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]

Comment on lines +141 to +145
{
if (!first)
expression.Append('.');
expression.Append(segment.ToFirstCharacterUpperCase());
}
Comment on lines +71 to +73
[Parameter(Mandatory = false{{setAttr}},
HelpMessage = "Sets the '{{h.RawName}}' request header (for example an ETag for optimistic concurrency; some Graph APIs require it even where the spec marks it optional).")]
public string? {{h.PsName}} { get; set; }

namespace {{ctx.CmdletNamespace}}
{
[Cmdlet(VerbsCommon.{{naming.VerbName}}, "{{naming.Noun}}")]
Comment on lines +80 to +83
var responseSchema = httpMethod == HttpMethod.Get
? operation.Responses?["2XX"].Content?["application/json"].Schema
: null;
var collectionValueSchema = responseSchema is not null ? FindProperty(responseSchema, "value") : null;
Comment on lines +240 to +244
private static string EmitNewFor(CmdletNaming naming, EmitContext ctx, OpenApiOperation operation)
{
var bodySchema = operation.RequestBody!.Content!["application/json"].Schema!;
return CmdletEmitter.EmitNew(naming, ctx, ResolveEntityTypeName(bodySchema, ctx.ModelsNamespace),
SchemaProperties.ExtractPrimitiveProperties(bodySchema), SchemaProperties.HasPasswordProfile(bodySchema));
Comment on lines +247 to +251
private static string EmitUpdateFor(CmdletNaming naming, EmitContext ctx, OpenApiOperation operation)
{
var bodySchema = operation.RequestBody!.Content!["application/json"].Schema!;
return CmdletEmitter.EmitUpdate(naming, ctx, ResolveEntityTypeName(bodySchema, ctx.ModelsNamespace),
SchemaProperties.ExtractPrimitiveProperties(bodySchema), SchemaProperties.HasPasswordProfile(bodySchema));
Comment on lines +71 to +77
private static string MapPsType(JsonSchemaType openApiType) => (openApiType & ~JsonSchemaType.Null) switch
{
JsonSchemaType.String => "string",
JsonSchemaType.Boolean => "bool",
JsonSchemaType.Integer or JsonSchemaType.Number => "int",
_ => "string",
};
{
// Repeated verbatim in every emitted cmdlet. EmitSharedAuth provides the two helpers this
// block depends on: IsParameterBound and StaticBearerTokenAuthenticationProvider.
private const string AuthBlock = """

@peombwa peombwa Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation emits C# via string-interpolation templates instead of CodeDOM + CSharpWriter. I think we can re-architecture the generator to live as a set of PowerShell scripts in the MS Graph PowerShell SDK repo instead of templates inside of Kiota, which goes against Kiota design.

// Emits PowerShell cmdlet classes that call a Kiota-generated C# client. Bypasses the
// CodeDOM/refiner/writer pipeline, same shape as plugin generation; see
// KiotaBuilder.GeneratePowerShellWrapperAsync.
PowerShellWrapper

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using PowerShell as a generation language here is interesting since the wrapper isn't producing an SDK. We should move the wrapper to the MS Graph SDK repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants