Skip to content

Commit 796c56a

Browse files
committed
Add stream support directives.
1 parent 93726fe commit 796c56a

File tree

4 files changed

+189
-2
lines changed

4 files changed

+189
-2
lines changed

config/ModulesMapping.jsonc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"Files": "^drives\\.|^shares\\.|^users.drive$|^groups.drive$",
1414
"Financials": "^financials\\.",
1515
"Groups": "^groups.group$|^groups.directoryObject$|^groups.conversation$|^groups.endpoint$|^groups.extension$|^groups.resourceSpecificPermissionGrant$|^groups.profilePhoto$|^groups.conversationThread$|^groupLifecyclePolicies\\.|^users.group$|^groups.directorySetting$|^groups.Actions$|^groups.Functions$",
16-
"Identity.DirectoryManagement": "^administrativeUnits\\.|^contacts\\.|^devices\\.|^domains\\.|^directoryRoles\\.|^directoryRoleTemplates\\.|^directorySettingTemplates\\.|^settings\\.|^subscribedSkus\\.|^contracts\\.|^directory\\.|^users.scopedRoleMembership$|^organization.organization$|^organization.organizationalBranding$|^organization.organizationSettings $|^organization.Actions$|^organization.extension$",
16+
"Identity.DirectoryManagement": "^administrativeUnits\\.|^contacts\\.|^devices\\.|^domains\\.|^directoryRoles\\.|^directoryRoleTemplates\\.|^directorySettingTemplates\\.|^settings\\.|^subscribedSkus\\.|^contracts\\.|^directory\\.|^users.scopedRoleMembership$|^organization.organization$|^organization.organizationalBranding$|^organization.organizationSettings$|^organization.Actions$|^organization.extension$",
1717
"Identity.Governance": "^accessReviews\\.|^businessFlowTemplates\\.|^programs\\.|^programControls\\.|^programControlTypes\\.|^privilegedRoles\\.|^privilegedRoleAssignments\\.|^privilegedRoleAssignmentRequests\\.|^privilegedApproval\\.|^privilegedOperationEvents\\.|^privilegedAccess\\.|^agreements\\.|^users.agreementAcceptance$|^identityGovernance.entitlementManagement$|^identityGovernance.Functions$|^identityGovernance.Actions$",
1818
"Identity.SignIns": "^organization.certificateBasedAuthConfiguration$|^invitations\\.|^identityProviders\\.|^oauth2PermissionGrants\\.|^riskDetections\\.|^riskyUsers\\.|^dataPolicyOperations\\.|^identity.identityUserFlow$|^trustFramework\\.|^informationProtection\\.|^policies\\.|^users.authentication$|^users.informationProtection$|^identity.conditionalAccessRoot$",
1919
"Mail": "^users.inferenceClassification$|^users.mailFolder$|^users.message$",
@@ -26,7 +26,7 @@
2626
"Search": "^search\\.|^external\\.",
2727
"Security": "^Security\\.",
2828
"Sites": "^sites.site$|^sites.itemAnalytics$|^sites.columnDefinition$|^sites.contentType$|^sites.drive$|^sites.list$|^sites.sitePage$|^users.site$|^groups.site$|^sites.Functions$|^sites.Actions$",
29-
"Teams": "^teams\\.|^chats\\.|^users.chat$|^appCatalogs$|^users.userTeamwork$|^teamwork\\.|^users.team$|^users.userTeamwork$|^groups.team$",
29+
"Teams": "^teams\\.|^chats\\.|^users.chat$|^appCatalogs.teamsApp$|^users.userTeamwork$|^teamwork\\.|^users.team$|^groups.team$",
3030
"Users": "^users.user$|^users.directoryObject$|^users.licenseDetails$|^users.notification$|^users.outlookUser$|^users.profilePhoto$|^users.userSettings$|^users.extension$|^users.oAuth2PermissionGrant$|^users.todo$",
3131
"Users.Actions": "^users.Actions$",
3232
"Users.Functions": "^users.Functions$"

src/readme.graph.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,48 @@ directive:
555555
return $;
556556
}
557557
558+
# Modify generated .cs file download cmdlets.
559+
- from: source-file-csharp
560+
where: $
561+
transform: >
562+
if (!$documentPath.match(/generated%2Fcmdlets%2FGet\w*\d*.cs/gm))
563+
{
564+
return $;
565+
} else {
566+
let outFileParameterRegex = /(^\s*)public\s*global::System\.String\s*OutFile\s*/gmi
567+
if($.match(outFileParameterRegex)) {
568+
let overrideOnOkCallRegex = /(^\s*)(overrideOnOk\(\s*responseMessage\s*,\s*response\s*,\s*ref\s*_returnNow\s*\);)/gmi
569+
// Handle file download.
570+
$ = $.replace(overrideOnOkCallRegex, '$1$2\n$1using(var stream = await response){ this.WriteToFile(stream, this.GetProviderPath(OutFile, false), _cancellationTokenSource.Token); _returnNow = global::System.Threading.Tasks.Task<bool>.FromResult(true);}\n$1');
571+
}
572+
return $;
573+
}
574+
575+
# Modify generated .cs file upload cmdlets.
576+
- from: source-file-csharp
577+
where: $
578+
transform: >
579+
if (!$documentPath.match(/generated%2Fcmdlets%2FSet\w*\d*.cs/gm))
580+
{
581+
return $;
582+
} else {
583+
let streamBodyParameterRegex = /(^\s*)public\s*global::System.IO.Stream\s*BodyParameter\s*/gmi
584+
if($.match(streamBodyParameterRegex)) {
585+
// Replace base class with FileUploadCmdlet.
586+
let psBaseClassImplementationRegex = /(\s*:\s*)(global::System.Management.Automation.PSCmdlet)/gmi
587+
$ = $.replace(psBaseClassImplementationRegex, '$1Microsoft.Graph.PowerShell.Cmdlets.Custom.FileUploadCmdlet');
588+
589+
// Set bodyParameter to required to false.
590+
let streamBodyParameterAnnotation = /(global::System\.IO\.Stream _bodyParameter;\s*\[global::System\.Management\.Automation\.Parameter\(Mandatory\s*=\s*)(true)/gmi
591+
$ = $.replace(streamBodyParameterAnnotation, '$1false');
592+
593+
// Handle file upload.
594+
let processRecordCallRegex = /(^\s*)(asyncCommandRuntime\.Wait\(\s*ProcessRecordAsync\s*\(\))/gmi
595+
$ = $.replace(processRecordCallRegex, 'if (!MyInvocation.BoundParameters.ContainsKey(nameof(BodyParameter))){BodyParameter = GetFileAsStream() ?? BodyParameter;}\n$1$2');
596+
}
597+
return $;
598+
}
599+
558600
# Modify generated runtime TypeConverterExtensions class.
559601
- from: source-file-csharp
560602
where: $

tools/Custom/FileUploadCmdlet.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
namespace Microsoft.Graph.PowerShell.Cmdlets.Custom
5+
{
6+
using System.IO;
7+
using System.Management.Automation;
8+
9+
public partial class FileUploadCmdlet : PSCmdlet
10+
{
11+
/// <summary>Backing field for <see cref="InFile" /> property.</summary>
12+
private string _inFile;
13+
14+
/// <summary>The path to the file to upload. This SHOULD include the file name and extension.</summary>
15+
[Parameter(Mandatory = true, HelpMessage = "The path to the file to upload. This should include a path and file name. If you omit the path, the current location will be used.")]
16+
[Runtime.Info(
17+
Required = true,
18+
ReadOnly = false,
19+
Description = @"The path to the file to upload. This should include a path and file name. If you omit the path, the current location will be used.",
20+
PossibleTypes = new[] { typeof(string) })]
21+
[ValidateNotNullOrEmpty()]
22+
[Category(ParameterCategory.Runtime)]
23+
public string InFile { get => this._inFile; set => this._inFile = value; }
24+
25+
/// <summary>
26+
/// Creates a file stream from the provided input file.
27+
/// </summary>
28+
/// <returns>A file stream.</returns>
29+
internal Stream GetFileAsStream()
30+
{
31+
if (MyInvocation.BoundParameters.ContainsKey(nameof(InFile)))
32+
{
33+
string resolvedFilePath = this.GetProviderPath(InFile, true);
34+
return new FileStream(resolvedFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
35+
}
36+
else
37+
{
38+
return null;
39+
}
40+
}
41+
}
42+
}

tools/Custom/PSCmdletExtensions.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
namespace Microsoft.Graph.PowerShell
5+
{
6+
using System;
7+
using System.Collections.ObjectModel;
8+
using System.IO;
9+
using System.Management.Automation;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
13+
internal static class PSCmdletExtensions
14+
{
15+
/// <summary>
16+
/// Gets a resolved or unresolved path from PSPath.
17+
/// </summary>
18+
/// <param name="cmdlet">The calling <see cref="PSCmdlet"/>.</param>
19+
/// <param name="filePath">The file path to get a provider path for.</param>
20+
/// <param name="isResolvedPath">Determines whether get a resolved or unresolved provider path.</param>
21+
/// <returns>The provider path from PSPath.</returns>
22+
internal static string GetProviderPath(this PSCmdlet cmdlet, string filePath, bool isResolvedPath)
23+
{
24+
string providerPath = null;
25+
ProviderInfo provider;
26+
try
27+
{
28+
var paths = new Collection<string>();
29+
if (isResolvedPath)
30+
{
31+
paths = cmdlet.SessionState.Path.GetResolvedProviderPathFromPSPath(filePath, out provider);
32+
}
33+
else
34+
{
35+
paths.Add(cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath(filePath, out provider, out _));
36+
}
37+
38+
if (provider.Name != "FileSystem" || paths.Count == 0)
39+
{
40+
cmdlet.ThrowTerminatingError(new ErrorRecord(new Exception("Invalid path."), string.Empty, ErrorCategory.InvalidArgument, filePath));
41+
}
42+
if (paths.Count > 1)
43+
{
44+
cmdlet.ThrowTerminatingError(new ErrorRecord(new Exception("Multiple paths not allowed."), string.Empty, ErrorCategory.InvalidArgument, filePath));
45+
}
46+
providerPath = paths[0];
47+
}
48+
catch (Exception)
49+
{
50+
providerPath = filePath;
51+
}
52+
53+
return providerPath;
54+
}
55+
56+
/// <summary>
57+
/// Saves a stream to a file on disk.
58+
/// </summary>
59+
/// <param name="cmdlet">The calling <see cref="PSCmdlet"/>.</param>
60+
/// <param name="inputStream">The stream to write to file.</param>
61+
/// <param name="filePath">The path to write the file to. This should include the file name and extension.</param>
62+
/// <param name="cancellationToken">A cancellation token that will be used to cancel the operation by the user.</param>
63+
internal static void WriteToFile(this PSCmdlet cmdlet, Stream inputStream, string filePath, CancellationToken cancellationToken)
64+
{
65+
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read))
66+
{
67+
cmdlet.WriteToStream(inputStream, fileStream, cancellationToken);
68+
}
69+
}
70+
71+
/// <summary>
72+
/// Write an input stream to an output stream.
73+
/// </summary>
74+
/// <param name="cmdlet">The calling <see cref="PSCmdlet"/>.</param>
75+
/// <param name="inputStream">The stream to write to an output stream.</param>
76+
/// <param name="outputStream">The stream to write the input stream to.</param>
77+
/// <param name="cancellationToken">A cancellation token that will be used to cancel the operation by the user.</param>
78+
private static void WriteToStream(this PSCmdlet cmdlet, Stream inputStream, Stream outputStream, CancellationToken cancellationToken)
79+
{
80+
var copyTask = inputStream.CopyToAsync(outputStream);
81+
var record = new ProgressRecord(000000000, "WriteRequestProgressActivity", "WriteRequestProgressStatus");
82+
try
83+
{
84+
do
85+
{
86+
record.StatusDescription = string.Format("Number of bytes processed {0}", outputStream.Position);
87+
cmdlet.WriteProgress(record);
88+
89+
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
90+
} while (!copyTask.IsCompleted && !cancellationToken.IsCancellationRequested);
91+
92+
if (copyTask.IsCompleted)
93+
{
94+
record.StatusDescription = string.Format("Number of bytes processed {0}", outputStream.Position);
95+
cmdlet.WriteProgress(record);
96+
}
97+
}
98+
catch (OperationCanceledException)
99+
{
100+
}
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)