diff --git a/src/Authentication/Authentication/Cmdlets/InvokeGraphRequest.cs b/src/Authentication/Authentication/Cmdlets/InvokeGraphRequest.cs
new file mode 100644
index 00000000000..fd98edb37e1
--- /dev/null
+++ b/src/Authentication/Authentication/Cmdlets/InvokeGraphRequest.cs
@@ -0,0 +1,1074 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security;
+using System.Text;
+using System.Threading;
+
+using Microsoft.Graph.PowerShell.Authentication.Helpers;
+using Microsoft.Graph.PowerShell.Authentication.Models;
+using Microsoft.Graph.PowerShell.Authentication.Properties;
+using Microsoft.PowerShell.Commands;
+
+using DriveNotFoundException = System.Management.Automation.DriveNotFoundException;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
+{
+ [Cmdlet(VerbsLifecycle.Invoke, "GraphRequest", DefaultParameterSetName = Constants.UserParameterSet)]
+ public class InvokeGraphRequest : PSCmdlet
+ {
+ private readonly CancellationTokenSource _cancellationTokenSource;
+ private readonly InvokeGraphRequestUserAgent _graphRequestUserAgent;
+ private string _originalFilePath;
+
+ public InvokeGraphRequest()
+ {
+ _cancellationTokenSource = new CancellationTokenSource();
+ _graphRequestUserAgent = new InvokeGraphRequestUserAgent(this);
+ Authentication = GraphRequestAuthenticationType.Default;
+ }
+
+ ///
+ /// Http Method
+ ///
+ [Parameter(ParameterSetName = Constants.UserParameterSet,
+ Position = 1,
+ HelpMessage = "Http Method")]
+ public GraphRequestMethod Method { get; set; } = GraphRequestMethod.GET;
+
+ ///
+ /// Uri to call using the Graph HttpClient can be segments such as /beta/me
+ /// or fully qualified url such as https://graph.microsoft.com/beta/me
+ ///
+ [Parameter(ParameterSetName = Constants.UserParameterSet,
+ Position = 2,
+ Mandatory = true,
+ HelpMessage =
+ "Uri to call can be segments such as /beta/me or fully qualified https://graph.microsoft.com/beta/me")]
+ public Uri Uri { get; set; }
+
+ ///
+ /// Optional Http Body
+ ///
+ [Parameter(ParameterSetName = Constants.UserParameterSet,
+ Position = 3,
+ HelpMessage = "Request Body. Required when Method is Post or Patch",
+ ValueFromPipeline = true)]
+ public object Body { get; set; }
+
+ ///
+ /// Optional Custom Headers
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 4,
+ HelpMessage = "Optional Custom Headers")]
+ public IDictionary Headers { get; set; }
+
+ ///
+ /// Relative or absolute path where the response body will be saved.
+ /// Not allowed when InferOutputFileName is specified.
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 5,
+ HelpMessage = "Output file where the response body will be saved")]
+ public string OutputFilePath { get; set; }
+
+ ///
+ /// Infer Download FileName from ContentDisposition Header.
+ /// Not allowed when OutputFilePath is specified.
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 6,
+ HelpMessage = "Infer output filename")]
+ public SwitchParameter InferOutputFileName { get; set; }
+
+ ///
+ /// Gets or sets the InputFilePath property to send in the request
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 7,
+ HelpMessage = "Input file to send in the request")]
+ public virtual string InputFilePath { get; set; }
+
+ ///
+ /// Indicates that the cmdlet returns the results, in addition to writing them to a file.
+ /// only valid when the OutFile parameter is also used.
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 8,
+ HelpMessage =
+ "Indicates that the cmdlet returns the results, in addition to writing them to a file. Only valid when the OutFile parameter is also used. ")]
+ public SwitchParameter PassThru { get; set; }
+
+ ///
+ /// OAuth or Bearer Token to use instead of already acquired token
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 9,
+ HelpMessage = "OAuth or Bearer Token to use instead of already acquired token")]
+ public SecureString Token { get; set; }
+
+ ///
+ /// Add headers to Request Header collection without validation
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 10,
+ HelpMessage = "Add headers to Request Header collection without validation")]
+ public SwitchParameter SkipHeaderValidation { get; set; }
+
+ ///
+ /// Custom Content Type
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 11,
+ HelpMessage = "Custom Content Type")]
+ public virtual string ContentType { get; set; }
+
+ ///
+ /// Graph Authentication Type
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 12,
+ HelpMessage = "Graph Authentication Type")]
+ public GraphRequestAuthenticationType Authentication { get; set; }
+
+ ///
+ /// Specifies a web request session. Enter the variable name, including the dollar sign ($).
+ /// You can't use the SessionVariable and WebSession parameters in the same command.
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 13,
+ HelpMessage = "Specifies a web request session. Enter the variable name, including the dollar sign ($)." +
+ "You can't use the SessionVariable and GraphRequestSession parameters in the same command.")]
+ [Alias("SV")]
+ public string SessionVariable { get; set; }
+
+ ///
+ /// Response Headers Variable
+ ///
+ [Parameter(Mandatory = false,
+ ParameterSetName = Constants.UserParameterSet,
+ Position = 14,
+ HelpMessage = "Response Headers Variable")]
+ [Alias("RHV")]
+ public string ResponseHeadersVariable { get; set; }
+
+ ///
+ /// Response Status Code Variable
+ ///
+ [Parameter(Position = 15, ParameterSetName = Constants.UserParameterSet,
+ Mandatory = false,
+ HelpMessage = "Response Status Code Variable")]
+ public string StatusCodeVariable { get; set; }
+
+ ///
+ /// Gets or sets whether to skip checking HTTP status for error codes.
+ ///
+ [Parameter(Position = 16, ParameterSetName = Constants.UserParameterSet, Mandatory = false,
+ HelpMessage = "Skip Checking Http Errors")]
+ public virtual SwitchParameter SkipHttpErrorCheck { get; set; }
+
+ ///
+ /// Gets or sets the Session property.
+ ///
+ [Parameter(Mandatory = false,
+ Position = 17,
+ ParameterSetName = Constants.UserParameterSet,
+ HelpMessage = "Custom Graph Request Session")]
+ public GraphRequestSession GraphRequestSession { get; set; }
+
+ ///
+ /// Custom User Specified User Agent
+ ///
+ [Parameter(Mandatory = false,
+ Position = 18,
+ ParameterSetName = Constants.UserParameterSet,
+ HelpMessage = "Custom User Specified User Agent")]
+ public string UserAgent { get; set; }
+
+ ///
+ /// Wait for .NET debugger to attach
+ ///
+ [Parameter(Mandatory = false,
+ DontShow = true,
+ HelpMessage = "Wait for .NET debugger to attach")]
+ public SwitchParameter Break { get; set; }
+
+ internal string QualifiedOutFile => QualifyFilePath(OutputFilePath);
+
+ internal bool ShouldSaveToOutFile => !string.IsNullOrEmpty(OutputFilePath);
+ ///
+ /// Only write to pipeline if outfile is not specified, inference is not specified but PassThru is set.
+ ///
+ internal bool ShouldWriteToPipeline => (!ShouldSaveToOutFile && !InferOutputFileName) || PassThru;
+
+ internal bool ShouldCheckHttpStatus => !SkipHttpErrorCheck;
+
+ private static ErrorRecord GenerateHttpErrorRecord(HttpMessageFormatter httpResponseMessageFormatter,
+ HttpRequestMessage httpRequestMessage)
+ {
+ // Load into buffer to avoid stream already consumed issues.
+ httpResponseMessageFormatter.LoadIntoBufferAsync()
+ .GetAwaiter()
+ .GetResult();
+ var currentResponse = httpResponseMessageFormatter.HttpResponseMessage;
+ var errorMessage =
+ Resources.ResponseStatusCodeFailure.FormatCurrentCulture(currentResponse.StatusCode,
+ currentResponse.ReasonPhrase);
+ var httpException = new HttpResponseException(errorMessage, currentResponse);
+ var errorRecord = new ErrorRecord(httpException, Errors.InvokeGraphHttpResponseException,
+ ErrorCategory.InvalidOperation, httpRequestMessage);
+ var detailMsg = httpResponseMessageFormatter.ReadAsStringAsync()
+ .GetAwaiter()
+ .GetResult();
+ if (!string.IsNullOrEmpty(detailMsg))
+ {
+ errorRecord.ErrorDetails = new ErrorDetails(detailMsg);
+ }
+
+ return errorRecord;
+ }
+
+ ///
+ /// When -Verbose is specified, print out response status
+ ///
+ ///
+ private void ReportRequestStatus(HttpRequestMessage requestMessage)
+ {
+ var requestContentLength = requestMessage.Content?.Headers.ContentLength.Value ?? 0;
+
+ var reqVerboseMsg = Resources.InvokeGraphRequestVerboseMessage.FormatCurrentCulture(requestMessage.Method,
+ requestMessage.RequestUri,
+ requestContentLength);
+ WriteVerbose(reqVerboseMsg);
+ }
+
+ ///
+ /// When -Verbose is specified, print out response status
+ ///
+ ///
+ private void ReportResponseStatus(HttpResponseMessage responseMessage)
+ {
+ var contentType = responseMessage.GetContentType();
+ var respVerboseMsg = Resources.InvokeGraphResponseVerboseMessage.FormatCurrentCulture(
+ responseMessage.Content.Headers.ContentLength,
+ contentType);
+ WriteVerbose(respVerboseMsg);
+ }
+
+ ///
+ /// Compose a request, setting Uri and Headers.
+ ///
+ ///
+ ///
+ private HttpRequestMessage GetRequest(Uri uri)
+ {
+ var requestUri = PrepareUri(uri);
+ var httpMethod = GetHttpMethod(Method);
+ // create the base WebRequest object
+ var request = new HttpRequestMessage(httpMethod, requestUri);
+
+ // pull in session data
+ if (GraphRequestSession.Headers.Count > 0)
+ {
+ GraphRequestSession.ContentHeaders.Clear();
+ foreach (var entry in GraphRequestSession.Headers)
+ {
+ if (HttpKnownHeaderNames.ContentHeaders.Contains(entry.Key))
+ {
+ GraphRequestSession.ContentHeaders.Add(entry.Key, entry.Value);
+ }
+ else
+ {
+ if (SkipHeaderValidation)
+ {
+ request.Headers.TryAddWithoutValidation(entry.Key, entry.Value);
+ }
+ else
+ {
+ request.Headers.Add(entry.Key, entry.Value);
+ }
+ }
+ }
+ }
+
+ // Set 'Transfer-Encoding: chunked' if 'Transfer-Encoding' is specified
+ if (GraphRequestSession.Headers.ContainsKey(HttpKnownHeaderNames.TransferEncoding))
+ {
+ request.Headers.TransferEncodingChunked = true;
+ }
+
+ // Set 'User-Agent' if WebSession.Headers doesn't already contain it
+ if (GraphRequestSession.Headers.TryGetValue(HttpKnownHeaderNames.UserAgent, out var userAgent))
+ {
+ GraphRequestSession.UserAgent = userAgent;
+ }
+ else
+ {
+ if (SkipHeaderValidation)
+ {
+ request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.UserAgent,
+ GraphRequestSession.UserAgent);
+ }
+ else
+ {
+ request.Headers.Add(HttpKnownHeaderNames.UserAgent, GraphRequestSession.UserAgent);
+ }
+ }
+
+ return request;
+ }
+
+ ///
+ /// Compose Request Uri
+ ///
+ ///
+ ///
+ private Uri PrepareUri(Uri uri)
+ {
+ // before creating the web request,
+ // preprocess Body if content is a dictionary and method is GET (set as query)
+ if (Method == GraphRequestMethod.GET && LanguagePrimitives.TryConvertTo(Body, out IDictionary bodyAsDictionary))
+ {
+ var uriBuilder = new UriBuilder(uri);
+ if (uriBuilder.Query != null && uriBuilder.Query.Length > 1)
+ {
+ uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + bodyAsDictionary.FormatDictionary();
+ }
+ else if (bodyAsDictionary != null)
+ {
+ uriBuilder.Query = bodyAsDictionary.FormatDictionary();
+ }
+
+ uri = uriBuilder.Uri;
+ // set body to null to prevent later FillRequestStream
+ Body = null;
+ }
+
+ return uri;
+ }
+
+ ///
+ /// Process Http Response
+ ///
+ ///
+ internal void ProcessResponse(HttpResponseMessage response)
+ {
+ if (response == null) throw new ArgumentNullException(nameof(response));
+
+ var baseResponseStream = response.GetResponseStream();
+
+ if (ShouldWriteToPipeline)
+ {
+ using (var responseStream = new BufferingStreamReader(baseResponseStream))
+ {
+ // determine the response type
+ var returnType = response.CheckReturnType();
+ // Try to get the response encoding from the ContentType header.
+ Encoding encoding = null;
+ var charSet = response.Content.Headers.ContentType?.CharSet;
+ if (!string.IsNullOrEmpty(charSet))
+ {
+ charSet.TryGetEncoding(out encoding);
+ }
+
+ if (string.IsNullOrEmpty(charSet) && returnType == RestReturnType.Json)
+ {
+ encoding = Encoding.UTF8;
+ }
+
+ Exception ex = null;
+
+ var str = responseStream.DecodeStream(ref encoding);
+
+ string encodingVerboseName;
+ try
+ {
+ encodingVerboseName = string.IsNullOrEmpty(encoding.HeaderName)
+ ? encoding.EncodingName
+ : encoding.HeaderName;
+ }
+ catch (NotSupportedException)
+ {
+ encodingVerboseName = encoding.EncodingName;
+ }
+
+ // NOTE: Tests use this verbose output to verify the encoding.
+ WriteVerbose(Resources.ContentEncodingVerboseMessage.FormatCurrentCulture(encodingVerboseName));
+ var convertSuccess = str.TryConvert(out var obj, ref ex);
+ if (!convertSuccess)
+ {
+ // fallback to string
+ obj = str;
+ }
+
+ WriteObject(obj);
+ }
+ }
+
+ if (ShouldSaveToOutFile)
+ {
+ baseResponseStream.SaveStreamToFile(QualifiedOutFile, this, _cancellationTokenSource.Token);
+ }
+
+ if (InferOutputFileName.IsPresent)
+ {
+ if (response.Content.Headers.ContentDisposition != null)
+ {
+ if (!string.IsNullOrWhiteSpace(response.Content.Headers.ContentDisposition.FileName))
+ {
+ var fileName = response.Content.Headers.ContentDisposition.FileNameStar;
+ var fullFileName = QualifyFilePath(fileName);
+ WriteVerbose(
+ Resources.InferredFileNameVerboseMessage.FormatCurrentCulture(fileName, fullFileName));
+ baseResponseStream.SaveStreamToFile(fullFileName, this, _cancellationTokenSource.Token);
+ }
+ }
+ else
+ {
+ WriteVerbose(Resources.InferredFileNameErrorMessage);
+ }
+ }
+
+ if (!string.IsNullOrEmpty(StatusCodeVariable))
+ {
+ var vi = SessionState.PSVariable;
+ vi.Set(StatusCodeVariable, (int)response.StatusCode);
+ }
+
+ if (!string.IsNullOrEmpty(ResponseHeadersVariable))
+ {
+ var vi = SessionState.PSVariable;
+ vi.Set(ResponseHeadersVariable, response.GetHttpResponseHeaders());
+ }
+ }
+
+
+ ///
+ /// Gets a Custom AuthProvider or configured default provided depending on Auth Scheme specified.
+ ///
+ ///
+ private IAuthenticationProvider GetAuthProvider()
+ {
+ if (Authentication == GraphRequestAuthenticationType.UserProvidedToken)
+ {
+ return new InvokeGraphRequestAuthProvider(GraphRequestSession);
+ }
+
+ return AuthenticationHelpers.GetAuthProvider(GraphSession.Instance.AuthContext);
+ }
+
+ ///
+ /// Gets a Graph HttpClient with a custom or default auth provider.
+ ///
+ ///
+ private HttpClient GetHttpClient()
+ {
+ var provider = GetAuthProvider();
+ var client = HttpHelpers.GetGraphHttpClient(provider);
+ return client;
+ }
+
+ ///
+ /// Executes the HTTP Request and returns a response
+ ///
+ ///
+ ///
+ ///
+ private HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ var cancellationToken = _cancellationTokenSource.Token;
+ var response = client.SendAsync(request, cancellationToken)
+ .GetAwaiter()
+ .GetResult();
+
+ return response;
+ }
+
+ ///
+ /// Set the request content
+ ///
+ ///
+ ///
+ ///
+ private long SetRequestContent(HttpRequestMessage request, IDictionary content)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+
+ var body = content.FormatDictionary();
+ return SetRequestContent(request, body);
+ }
+
+ ///
+ /// Set the request content
+ ///
+ ///
+ ///
+ ///
+ private long SetRequestContent(HttpRequestMessage request, string content)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ if (content == null)
+ {
+ return 0;
+ }
+
+ Encoding encoding = null;
+ // When contentType is set, coerce to correct encoding.
+ if (ContentType != null)
+ {
+ // If Content-Type contains the encoding format (as CharSet), use this encoding format
+ // to encode the Body of the WebRequest sent to the server. Default Encoding format
+ // would be used if Charset is not supplied in the Content-Type property.
+ try
+ {
+ var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(ContentType);
+ if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet))
+ {
+ encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet);
+ }
+ }
+ catch (FormatException ex)
+ {
+ if (!SkipHeaderValidation)
+ {
+ var outerEx = new ValidationMetadataException(Resources.ContentTypeExceptionErrorMessage, ex);
+ var er = new ErrorRecord(outerEx, Errors.InvokeGraphContentTypeException,
+ ErrorCategory.InvalidArgument, ContentType);
+ ThrowTerminatingError(er);
+ }
+ }
+ catch (ArgumentException ex)
+ {
+ if (!SkipHeaderValidation)
+ {
+ var outerEx = new ValidationMetadataException(Resources.ContentTypeExceptionErrorMessage, ex);
+ var er = new ErrorRecord(outerEx, Errors.InvokeGraphContentTypeException,
+ ErrorCategory.InvalidArgument, ContentType);
+ ThrowTerminatingError(er);
+ }
+ }
+ }
+
+ var bytes = content.EncodeToBytes(encoding);
+ var byteArrayContent = new ByteArrayContent(bytes);
+ request.Content = byteArrayContent;
+
+ return byteArrayContent.Headers.ContentLength.Value;
+ }
+
+ ///
+ /// Hydrate the request with the requisite data.
+ /// for Body handle Dictionaries, Streams and Byte Arrays, coerce
+ /// into a string if none of the above types.
+ ///
+ ///
+ private void FillRequestStream(HttpRequestMessage request)
+ {
+ if (request == null) throw new ArgumentNullException(nameof(request));
+ if (ContentType != null)
+ {
+ GraphRequestSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = ContentType;
+ }
+ else if (Method == GraphRequestMethod.POST)
+ {
+ GraphRequestSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out var contentType);
+ if (string.IsNullOrWhiteSpace(contentType))
+ {
+ // Assume application/json if not set by user
+ GraphRequestSession.ContentHeaders[HttpKnownHeaderNames.ContentType] =
+ CoreConstants.MimeTypeNames.Application.Json;
+ }
+ }
+
+ // coerce body into a usable form
+ if (Body != null)
+ {
+ var content = Body;
+ // make sure we're using the base object of the body, not the PSObject wrapper
+ if (Body is PSObject psBody)
+ {
+ content = psBody.BaseObject;
+ }
+ if (content is IDictionary dictionary && request.Method != HttpMethod.Get)
+ {
+ SetRequestContent(request, dictionary);
+ }
+ else if (content is Stream stream)
+ {
+ SetRequestContent(request, stream);
+ }
+ else if (content is byte[] bytes)
+ {
+ SetRequestContent(request, bytes);
+ }
+ else
+ {
+ // Assume its a string
+ SetRequestContent(request,
+ (string)LanguagePrimitives.ConvertTo(content, typeof(string), CultureInfo.InvariantCulture));
+ }
+ }
+ else if (InputFilePath != null) // copy InputFilePath data
+ {
+ try
+ {
+ // open the input file
+ SetRequestContent(request,
+ new FileStream(InputFilePath, FileMode.Open, FileAccess.Read, FileShare.Read));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ var msg = Resources.AccessDenied.FormatCurrentCulture(_originalFilePath);
+ throw new UnauthorizedAccessException(msg);
+ }
+ }
+
+ // Add the content headers
+ if (request.Content == null)
+ {
+ request.Content = new StringContent(string.Empty);
+ request.Content.Headers.Clear();
+ }
+
+ foreach (var entry in GraphRequestSession.ContentHeaders.Where(header =>
+ !string.IsNullOrWhiteSpace(header.Value)))
+ {
+ if (SkipHeaderValidation)
+ {
+ request.Content.Headers.TryAddWithoutValidation(entry.Key, entry.Value);
+ }
+ else
+ {
+ try
+ {
+ request.Content.Headers.Add(entry.Key, entry.Value);
+ }
+ catch (FormatException ex)
+ {
+ var outerEx = new ValidationMetadataException(Resources.ContentTypeExceptionErrorMessage, ex);
+ var er = new ErrorRecord(outerEx, Errors.InvokeGraphContentTypeException,
+ ErrorCategory.InvalidArgument, ContentType);
+ ThrowTerminatingError(er);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Sets the body of the to be a byte array
+ ///
+ ///
+ ///
+ ///
+ private static long SetRequestContent(HttpRequestMessage request, byte[] content)
+ {
+ if (request == null)
+ throw new ArgumentNullException(nameof(request));
+ if (content == null)
+ return 0;
+
+ var byteArrayContent = new ByteArrayContent(content);
+ request.Content = byteArrayContent;
+
+ return byteArrayContent.Headers.ContentLength.Value;
+ }
+
+ ///
+ /// Sets the body of the request to be a Stream
+ ///
+ ///
+ ///
+ ///
+ private static long SetRequestContent(HttpRequestMessage request, Stream contentStream)
+ {
+ if (request == null)
+ throw new ArgumentNullException(nameof(request));
+ if (contentStream == null)
+ throw new ArgumentNullException(nameof(contentStream));
+
+ var streamContent = new StreamContent(contentStream);
+ request.Content = streamContent;
+
+ return streamContent.Headers.ContentLength.Value;
+ }
+
+ ///
+ /// Maps from HttpVerb to System.Net.Http.HttpMethod
+ ///
+ /// System.Net.Http.HttpMethod
+ private static HttpMethod GetHttpMethod(GraphRequestMethod graphRequestMethod)
+ {
+ return new HttpMethod(graphRequestMethod.ToString().ToUpperInvariant());
+ }
+
+ ///
+ /// Prepare GraphRequestSession to be used downstream.
+ ///
+ internal virtual void PrepareSession()
+ {
+ // Create a new GraphRequestSession object to work with if one is not supplied
+ GraphRequestSession = GraphRequestSession ?? new GraphRequestSession();
+ if (SessionVariable != null)
+ {
+ // save the session back to the PS environment if requested
+ var vi = SessionState.PSVariable;
+ vi.Set(SessionVariable, GraphRequestSession);
+ }
+
+ if (Authentication == GraphRequestAuthenticationType.UserProvidedToken && Token != null)
+ {
+ GraphRequestSession.Token = Token;
+ GraphRequestSession.AuthenticationType = Authentication;
+ }
+
+ //
+ // Handle Custom User Agents
+ //
+ GraphRequestSession.UserAgent = UserAgent ?? _graphRequestUserAgent.UserAgent;
+
+ // Store the other supplied headers
+ if (Headers != null)
+ {
+ foreach (var key in Headers.Keys)
+ {
+ var value = Headers[key];
+
+ // null is not valid value for header.
+ // We silently ignore header if value is null.
+ if (!(value is null))
+ {
+ // add the header value (or overwrite it if already present)
+ GraphRequestSession.Headers[key] = value;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Validate the Request Uri must have the same Host as GraphHttpClient BaseAddress.
+ ///
+ ///
+ private void ValidateRequestUri(HttpClient httpClient)
+ {
+ if (Uri == null)
+ {
+ var error = GetValidationError(
+ Resources.InvokeGraphRequestMissingUriErrorMessage.FormatCurrentCulture(nameof(Uri)),
+ Errors.InvokeGraphRequestInvalidHost,
+ nameof(Uri));
+ ThrowTerminatingError(error);
+ }
+
+ if (string.IsNullOrWhiteSpace(Uri.ToString()))
+ {
+ var error = GetValidationError(
+ Resources.InvokeGraphRequestInvalidUriErrorMessage.FormatCurrentCulture(nameof(Uri)),
+ Errors.InvokeGraphRequestInvalidHost,
+ nameof(Uri));
+ ThrowTerminatingError(error);
+ }
+ // Ensure that the Passed in Uri has the same Host as the HttpClient.
+ if (Uri.IsAbsoluteUri && httpClient.BaseAddress.Host != Uri.Host)
+ {
+ var error = GetValidationError(
+ Resources.InvokeGraphRequestInvalidHostErrorMessage.FormatCurrentCulture(Uri.Host),
+ Errors.InvokeGraphRequestInvalidHost,
+ nameof(Uri));
+ ThrowTerminatingError(error);
+ }
+ }
+
+ ///
+ /// Validate Passed In Parameters
+ ///
+ private void ValidateParameters()
+ {
+ if (GraphRequestSession != null && SessionVariable != null)
+ {
+ var error = GetValidationError(
+ Resources.GraphRequestSessionConflict,
+ Errors.InvokeGraphRequestSessionConflictException);
+ ThrowTerminatingError(error);
+ }
+
+ // When PATCH or POST is specified, ensure a body is present
+ if ((Method == GraphRequestMethod.PATCH || Method == GraphRequestMethod.POST) && (Body == null && string.IsNullOrWhiteSpace(InputFilePath)))
+ {
+ var error = GetValidationError(
+ Resources.BodyMissingWhenMethodIsSpecified.FormatCurrentCulture(nameof(Body), Method),
+ Errors.InvokeGraphRequestBodyMissingWhenMethodIsSpecified,
+ nameof(Body));
+ ThrowTerminatingError(error);
+ }
+
+ if (PassThru && OutputFilePath == null)
+ {
+ var error = GetValidationError(
+ Resources.PassThruWithOutputFilePathMissing.FormatCurrentCulture(nameof(PassThru),
+ nameof(OutputFilePath)),
+ Errors.InvokeGraphRequestOutFileMissingException,
+ nameof(PassThru));
+ ThrowTerminatingError(error);
+ }
+
+ if (Authentication == GraphRequestAuthenticationType.Default && Token != null)
+ {
+ var error = GetValidationError(
+ Resources.AuthenticationTokenConflict.FormatCurrentCulture(Authentication, nameof(Token)),
+ Errors.InvokeGraphRequestAuthenticationTokenConflictException);
+ ThrowTerminatingError(error);
+ }
+
+ // Token shouldn't be null when UserProvidedToken is specified
+ if (Authentication == GraphRequestAuthenticationType.UserProvidedToken && Token == null)
+ {
+ var error = GetValidationError(
+ Resources.AuthenticationCredentialNotSupplied.FormatCurrentCulture(Authentication, nameof(Token)),
+ Errors.InvokeGraphRequestAuthenticationTokenConflictException);
+ ThrowTerminatingError(error);
+ }
+
+ // Only Body or InputFilePath can be specified at a time
+ if (Body != null && !string.IsNullOrWhiteSpace(InputFilePath))
+ {
+ var error = GetValidationError(
+ Resources.BodyConflict.FormatCurrentCulture(nameof(Body), nameof(InputFilePath)),
+ Errors.InvokeGraphRequestBodyConflictException);
+ ThrowTerminatingError(error);
+ }
+
+ if (InferOutputFileName.IsPresent && !string.IsNullOrWhiteSpace(OutputFilePath))
+ {
+ var error = GetValidationError(
+ Resources.InferFileNameOutFilePathConflict.FormatCurrentCulture(nameof(InferOutputFileName), nameof(OutputFilePath)),
+ Errors.InvokeGraphRequestBodyConflictException);
+ ThrowTerminatingError(error);
+ }
+ // Ensure InputFilePath is an Existing Item
+ if (InputFilePath != null)
+ {
+ ErrorRecord errorRecord = null;
+
+ try
+ {
+ var providerPaths = GetResolvedProviderPathFromPSPath(InputFilePath, out var provider);
+
+ if (!provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase))
+ {
+ errorRecord = GetValidationError(
+ Resources.NotFileSystemPath.FormatCurrentCulture(InputFilePath),
+ Errors.InvokeGraphRequestFileNotFilesystemPathException, InputFilePath);
+ }
+ else
+ {
+ if (providerPaths.Count > 1)
+ {
+ errorRecord = GetValidationError(
+ Resources.MultiplePathsResolved.FormatCurrentCulture(InputFilePath),
+ Errors.InvokeGraphRequestInputFileMultiplePathsResolvedException, InputFilePath);
+ }
+ else if (providerPaths.Count == 0)
+ {
+ errorRecord = GetValidationError(
+ Resources.NoPathResolved.FormatCurrentCulture(InputFilePath),
+ Errors.InvokeGraphRequestInputFileNoPathResolvedException, InputFilePath);
+ }
+ else
+ {
+ if (Directory.Exists(providerPaths[0]))
+ {
+ errorRecord = GetValidationError(
+ Resources.DirectoryPathSpecified.FormatCurrentCulture(providerPaths[0]),
+ Errors.InvokeGraphRequestInputFileNotFilePathException, InputFilePath);
+ }
+
+ _originalFilePath = InputFilePath;
+ InputFilePath = providerPaths[0];
+ }
+ }
+ }
+ catch (ItemNotFoundException pathNotFound)
+ {
+ errorRecord = new ErrorRecord(pathNotFound.ErrorRecord, pathNotFound);
+ }
+ catch (ProviderNotFoundException providerNotFound)
+ {
+ errorRecord = new ErrorRecord(providerNotFound.ErrorRecord, providerNotFound);
+ }
+ catch (DriveNotFoundException driveNotFound)
+ {
+ errorRecord = new ErrorRecord(driveNotFound.ErrorRecord, driveNotFound);
+ }
+
+ if (errorRecord != null)
+ ThrowTerminatingError(errorRecord);
+ }
+ }
+
+ ///
+ /// Composes a validation error
+ ///
+ ///
+ ///
+ ///
+ private ErrorRecord GetValidationError(string msg, string errorId)
+ {
+ var ex = new ValidationMetadataException(msg);
+ var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this);
+ return error;
+ }
+
+ ///
+ /// Composes a validation error
+ ///
+ ///
+ ///
+ ///
+ ///
+ private ErrorRecord GetValidationError(string msg, string errorId, params object[] args)
+ {
+ msg = msg.FormatCurrentCulture(args);
+ var ex = new ValidationMetadataException(msg);
+ var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this);
+ return error;
+ }
+
+ ///
+ /// Generate a fully qualified file path
+ ///
+ ///
+ ///
+ private string QualifyFilePath(string path)
+ {
+ var resolvedFilePath = PathUtils.ResolveFilePath(path, this, true);
+ return resolvedFilePath;
+ }
+
+ #region CmdLet LifeCycle
+
+ protected override void BeginProcessing()
+ {
+ if (Break)
+ {
+ this.Break();
+ }
+
+ ValidateParameters();
+ base.BeginProcessing();
+ }
+
+ protected override void ProcessRecord()
+ {
+ base.ProcessRecord();
+ try
+ {
+ PrepareSession();
+ using (var client = GetHttpClient())
+ {
+ ValidateRequestUri(client);
+ using (var httpRequestMessage = GetRequest(Uri))
+ {
+ using (var httpRequestMessageFormatter = new HttpMessageFormatter(httpRequestMessage))
+ {
+ FillRequestStream(httpRequestMessage);
+ try
+ {
+ ReportRequestStatus(httpRequestMessageFormatter.HttpRequestMessage);
+ var httpResponseMessage = GetResponse(client, httpRequestMessage);
+ using (var httpResponseMessageFormatter = new HttpMessageFormatter(httpResponseMessage))
+ {
+ ReportResponseStatus(httpResponseMessageFormatter.HttpResponseMessage);
+ var isSuccess = httpResponseMessage.IsSuccessStatusCode;
+ if (ShouldCheckHttpStatus && !isSuccess)
+ {
+ var httpErrorRecord =
+ GenerateHttpErrorRecord(httpResponseMessageFormatter, httpRequestMessage);
+ ThrowTerminatingError(httpErrorRecord);
+ }
+
+ ProcessResponse(httpResponseMessage);
+ }
+ }
+ catch (HttpRequestException ex)
+ {
+ var er = new ErrorRecord(ex, Errors.InvokeGraphHttpResponseException,
+ ErrorCategory.InvalidOperation,
+ httpRequestMessage);
+ if (ex.InnerException != null)
+ {
+ er.ErrorDetails = new ErrorDetails(ex.InnerException.Message);
+ }
+
+ ThrowTerminatingError(er);
+ }
+ }
+ }
+ }
+ }
+ catch (HttpRequestException httpRequestException)
+ {
+ WriteError(new ErrorRecord(httpRequestException, ErrorCategory.ConnectionError.ToString(),
+ ErrorCategory.InvalidResult, null));
+ throw;
+ }
+ catch (Exception exception)
+ {
+ WriteError(new ErrorRecord(exception, ErrorCategory.ConnectionError.ToString(),
+ ErrorCategory.InvalidOperation, null));
+ throw;
+ }
+ }
+
+ protected override void EndProcessing()
+ {
+ base.EndProcessing();
+ }
+
+ protected override void StopProcessing()
+ {
+ _cancellationTokenSource.Cancel();
+ base.StopProcessing();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/AttachDebugger.cs b/src/Authentication/Authentication/Helpers/AttachDebugger.cs
new file mode 100644
index 00000000000..a8d3bbb36a8
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/AttachDebugger.cs
@@ -0,0 +1,37 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Management.Automation;
+using System.Threading;
+using Debugger = System.Diagnostics.Debugger;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal static class AttachDebugger
+ {
+ internal static void Break(this PSCmdlet invokedCmdLet)
+ {
+ while (!Debugger.IsAttached)
+ {
+ Console.Error.WriteLine($"Waiting for debugger to attach to process {Process.GetCurrentProcess().Id}");
+ for (var i = 0; i < 50; i++)
+ {
+ if (Debugger.IsAttached)
+ {
+ break;
+ }
+
+ Thread.Sleep(100);
+ Console.Error.Write(".");
+ }
+
+ Console.Error.WriteLine();
+ }
+
+ Debugger.Break();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/BufferingStreamReader.cs b/src/Authentication/Authentication/Helpers/BufferingStreamReader.cs
new file mode 100644
index 00000000000..887c69f057a
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/BufferingStreamReader.cs
@@ -0,0 +1,105 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.IO;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal class BufferingStreamReader : Stream
+ {
+ internal BufferingStreamReader(Stream baseStream)
+ {
+ _baseStream = baseStream;
+ _streamBuffer = new MemoryStream();
+ _length = long.MaxValue;
+ _copyBuffer = new byte[4096];
+ }
+
+ private readonly Stream _baseStream;
+ private readonly MemoryStream _streamBuffer;
+ private readonly byte[] _copyBuffer;
+
+ public override bool CanRead => true;
+
+ public override bool CanSeek => true;
+
+ public override bool CanWrite => false;
+
+ public override void Flush()
+ {
+ _streamBuffer.SetLength(0);
+ }
+
+ public override long Length => _length;
+
+ private long _length;
+
+ public override long Position
+ {
+ get => _streamBuffer.Position;
+
+ set => _streamBuffer.Position = value;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ var previousPosition = Position;
+ var consumedStream = false;
+ var totalCount = count;
+ while ((!consumedStream) &&
+ ((Position + totalCount) > _streamBuffer.Length))
+ {
+ // If we don't have enough data to fill this from memory, cache more.
+ // We try to read 4096 bytes from base stream every time, so at most we
+ // may cache 4095 bytes more than what is required by the Read operation.
+ var bytesRead = _baseStream.Read(_copyBuffer, 0, _copyBuffer.Length);
+
+ if (_streamBuffer.Position < _streamBuffer.Length)
+ {
+ // Win8: 651902 no need to -1 here as Position refers to the place
+ // where we can start writing from.
+ _streamBuffer.Position = _streamBuffer.Length;
+ }
+
+ _streamBuffer.Write(_copyBuffer, 0, bytesRead);
+
+ totalCount -= bytesRead;
+ if (bytesRead < _copyBuffer.Length)
+ {
+ consumedStream = true;
+ }
+ }
+
+ // Reset our backing store to its official position, as reading
+ // for the CopyTo updates the position.
+ _streamBuffer.Seek(previousPosition, SeekOrigin.Begin);
+
+ // Read from the backing store into the requested buffer.
+ var read = _streamBuffer.Read(buffer, offset, count);
+
+ if (read < count)
+ {
+ SetLength(Position);
+ }
+
+ return read;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ return _streamBuffer.Seek(offset, origin);
+ }
+
+ public override void SetLength(long value)
+ {
+ _length = value;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/ContentHelper.cs b/src/Authentication/Authentication/Helpers/ContentHelper.cs
new file mode 100644
index 00000000000..8526d0cca9a
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/ContentHelper.cs
@@ -0,0 +1,188 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Globalization;
+using System.Management.Automation;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+
+using Microsoft.Graph.PowerShell.Authentication.Models;
+using Microsoft.Win32;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal static class ContentHelper
+ {
+ #region Constants
+
+ // default codepage encoding for web content. See RFC 2616.
+ private const string DefaultCodePage = "ISO-8859-1";
+
+ #endregion Constants
+
+ #region Fields
+ internal static RestReturnType CheckReturnType(this HttpResponseMessage response)
+ {
+ if (response == null) throw new ArgumentNullException(nameof(response));
+
+ var rt = RestReturnType.Detect;
+ var contentType = response.GetContentType();
+ if (string.IsNullOrEmpty(contentType))
+ rt = RestReturnType.Detect;
+ else if (ContentHelper.IsJson(contentType))
+ rt = RestReturnType.Json;
+ else if (ContentHelper.IsXml(contentType))
+ rt = RestReturnType.Xml;
+
+ return rt;
+ }
+ // used to split contentType arguments
+ private static readonly char[] ContentTypeParamSeparator = { ';' };
+
+ #endregion Fields
+
+ #region Internal Methods
+
+ internal static string GetContentType(this HttpResponseMessage response)
+ {
+ if (response == null)
+ {
+ throw new ArgumentNullException(nameof(response));
+ }
+ // ContentType may not exist in response header. Return null if not.
+ return response.Content.Headers.ContentType?.MediaType;
+ }
+
+ internal static Encoding GetDefaultEncoding()
+ {
+ return GetEncodingOrDefault(null);
+ }
+
+ internal static Encoding GetEncodingOrDefault(string characterSet)
+ {
+ // get the name of the codepage to use for response content
+ var codepage = string.IsNullOrEmpty(characterSet) ? DefaultCodePage : characterSet;
+ Encoding encoding;
+ try
+ {
+ encoding = Encoding.GetEncoding(codepage);
+ }
+ catch (ArgumentException)
+ {
+ // 0, default code page
+ encoding = Encoding.GetEncoding(0);
+ }
+
+ return encoding;
+ }
+
+ internal static bool IsJson(string contentType)
+ {
+ contentType = GetContentTypeSignature(contentType);
+ return CheckIsJson(contentType);
+ }
+
+ internal static bool IsText(string contentType)
+ {
+ contentType = GetContentTypeSignature(contentType);
+ return CheckIsText(contentType);
+ }
+
+ internal static bool IsXml(string contentType)
+ {
+ contentType = GetContentTypeSignature(contentType);
+ return CheckIsXml(contentType);
+ }
+
+ #endregion Internal Methods
+
+ #region Private Helper Methods
+
+ private static bool CheckIsJson(string contentType)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ return false;
+
+ // the correct type for JSON content, as specified in RFC 4627
+ var isJson = contentType.Equals("application/json", StringComparison.OrdinalIgnoreCase);
+
+ // add in these other "javascript" related types that
+ // sometimes get sent down as the mime type for JSON content
+ switch (contentType.ToLower(CultureInfo.InvariantCulture))
+ {
+ case "text/json":
+ case "application/x-javascript":
+ case "text/x-javascript":
+ case "application/javascript":
+ case "text/javascript":
+ isJson = true;
+ break;
+ }
+ return isJson;
+ }
+
+ private static bool CheckIsText(string contentType)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ return false;
+
+ // any text, xml or json types are text
+ var isText = contentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase)
+ || CheckIsXml(contentType)
+ || CheckIsJson(contentType);
+
+ // Further content type analysis is available on Windows
+ if (Platform.IsWindows && !isText)
+ {
+ // Media types registered with Windows as having a perceived type of text, are text
+ using (var contentTypeKey = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + contentType))
+ {
+ if (contentTypeKey?.GetValue("Extension") is string extension)
+ {
+ using (var extensionKey = Registry.ClassesRoot.OpenSubKey(extension))
+ {
+ if (extensionKey != null)
+ {
+ var perceivedType = extensionKey.GetValue("PerceivedType") as string;
+ isText = perceivedType == "text";
+ }
+ }
+ }
+ }
+ }
+ return isText;
+ }
+
+ private static bool CheckIsXml(string contentType)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ return false;
+
+ // RFC 3023: Media types with the suffix "+xml" are XML
+ switch (contentType.ToLower(CultureInfo.InvariantCulture))
+ {
+ case "application/xml":
+ case "application/xml-external-parsed-entity":
+ case "application/xml-dtd":
+ case var x when x.EndsWith("+xml"):
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static string GetContentTypeSignature(string contentType)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ return null;
+
+ var sig = contentType.Split(ContentTypeParamSeparator, 2)[0].ToUpperInvariant();
+ return sig;
+ }
+
+ #endregion Private Helper Methods
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/Errors.cs b/src/Authentication/Authentication/Helpers/Errors.cs
new file mode 100644
index 00000000000..36bac6a647e
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/Errors.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ public static class Errors
+ {
+ public const string InvokeGraphHttpResponseException = nameof(InvokeGraphHttpResponseException);
+ public const string InvokeGraphContentTypeException = nameof(InvokeGraphContentTypeException);
+ public const string InvokeGraphRequestInvalidHost = nameof(InvokeGraphRequestInvalidHost);
+ public const string InvokeGraphRequestSessionConflictException = nameof(InvokeGraphRequestSessionConflictException);
+ public const string InvokeGraphRequestBodyMissingWhenMethodIsSpecified = nameof(InvokeGraphRequestBodyMissingWhenMethodIsSpecified);
+ public const string InvokeGraphRequestOutFileMissingException = nameof(InvokeGraphRequestOutFileMissingException);
+ public const string InvokeGraphRequestAuthenticationTokenConflictException = nameof(InvokeGraphRequestAuthenticationTokenConflictException);
+ public const string InvokeGraphRequestAuthenticationCredentialNotSuppliedException = nameof(InvokeGraphRequestAuthenticationCredentialNotSuppliedException);
+ public const string InvokeGraphRequestBodyConflictException = nameof(InvokeGraphRequestBodyConflictException);
+ public const string InvokeGraphRequestFileNotFilesystemPathException = nameof(InvokeGraphRequestFileNotFilesystemPathException);
+ public const string InvokeGraphRequestInputFileMultiplePathsResolvedException = nameof(InvokeGraphRequestInputFileMultiplePathsResolvedException);
+ public const string InvokeGraphRequestInputFileNoPathResolvedException = nameof(InvokeGraphRequestInputFileNoPathResolvedException);
+ public const string InvokeGraphRequestInputFileNotFilePathException = nameof(InvokeGraphRequestInputFileNotFilePathException);
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/GraphRequestSession.cs b/src/Authentication/Authentication/Helpers/GraphRequestSession.cs
new file mode 100644
index 00000000000..d142b9798cf
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/GraphRequestSession.cs
@@ -0,0 +1,50 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Security;
+using Microsoft.Graph.PowerShell.Authentication.Cmdlets;
+using Microsoft.Graph.PowerShell.Authentication.Models;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ public class GraphRequestSession
+ {
+ ///
+ /// Gets or sets the Header property.
+ ///
+ public Dictionary Headers { get; set; }
+
+ ///
+ /// Gets or sets the content Headers when using HttpClient.
+ ///
+ public Dictionary ContentHeaders { get; set; }
+
+ ///
+ /// Gets or Sets the User Agent when using HttpClient
+ ///
+ public string UserAgent { get; set; }
+
+ ///
+ /// Gets or Sets a User Specified JWT Token
+ ///
+ public SecureString Token { get; set; }
+
+ ///
+ /// Gets or Sets the AuthenticationType to be used for the current Session
+ ///
+ public GraphRequestAuthenticationType AuthenticationType { get; set; }
+
+ ///
+ /// Construct a new instance of a WebRequestSession object.
+ ///
+ public GraphRequestSession()
+ {
+ // build the headers collection
+ Headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ ContentHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/HttpHelpers.cs b/src/Authentication/Authentication/Helpers/HttpHelpers.cs
index 55f490fdb5d..8fe189e320e 100644
--- a/src/Authentication/Authentication/Helpers/HttpHelpers.cs
+++ b/src/Authentication/Authentication/Helpers/HttpHelpers.cs
@@ -3,6 +3,7 @@
// ------------------------------------------------------------------------------
namespace Microsoft.Graph.PowerShell.Authentication.Helpers
{
+ using System;
using Microsoft.Graph.PowerShell.Authentication.Cmdlets;
using System.Collections.Generic;
using System.Linq;
@@ -46,10 +47,41 @@ public static HttpClient GetGraphHttpClient(IAuthContext authConfig = null)
HttpClient httpClient = GraphClientFactory.Create(defaultHandlers);
// Prepend new SDKVersionHeaders
- IEnumerable previousSDKHeaders = httpClient.DefaultRequestHeaders.GetValues(CoreConstants.Headers.SdkVersionHeaderName);
+ PrependNewSDKVersionHeaders(httpClient);
+
+ return httpClient;
+ }
+
+ ///
+ /// Prepend new SDKVersionHeaders
+ ///
+ ///
+ private static void PrependNewSDKVersionHeaders(HttpClient httpClient)
+ {
+ IEnumerable previousSDKHeaders =
+ httpClient.DefaultRequestHeaders.GetValues(CoreConstants.Headers.SdkVersionHeaderName);
httpClient.DefaultRequestHeaders.Remove(CoreConstants.Headers.SdkVersionHeaderName);
- httpClient.DefaultRequestHeaders.Add(CoreConstants.Headers.SdkVersionHeaderName, previousSDKHeaders.Prepend(AuthModuleVersionHeaderValue));
+ httpClient.DefaultRequestHeaders.Add(CoreConstants.Headers.SdkVersionHeaderName,
+ previousSDKHeaders.Prepend(AuthModuleVersionHeaderValue));
+ }
+
+ ///
+ /// Creates a pre-configured Microsoft Graph .
+ /// with a custom authenticationProvider
+ ///
+ /// Custom AuthProvider
+ ///
+ public static HttpClient GetGraphHttpClient(IAuthenticationProvider customAuthProvider)
+ {
+ IList defaultHandlers = GraphClientFactory.CreateDefaultHandlers(customAuthProvider);
+
+ // Register ODataQueryOptionsHandler after AuthHandler.
+ defaultHandlers.Insert(1, (new ODataQueryOptionsHandler()));
+ HttpClient httpClient = GraphClientFactory.Create(defaultHandlers);
+
+ // Prepend new SDKVersionHeaders
+ PrependNewSDKVersionHeaders(httpClient);
return httpClient;
}
}
diff --git a/src/Authentication/Authentication/Helpers/HttpKnownHeaderNames.cs b/src/Authentication/Authentication/Helpers/HttpKnownHeaderNames.cs
new file mode 100644
index 00000000000..f9d589c2272
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/HttpKnownHeaderNames.cs
@@ -0,0 +1,106 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal static class HttpKnownHeaderNames
+ {
+ #region Known_HTTP_Header_Names
+
+ // Known HTTP Header Names.
+ // List comes from corefx/System/Net/HttpKnownHeaderNames.cs
+ public const string Accept = "Accept";
+ public const string AcceptCharset = "Accept-Charset";
+ public const string AcceptEncoding = "Accept-Encoding";
+ public const string AcceptLanguage = "Accept-Language";
+ public const string AcceptRanges = "Accept-Ranges";
+ public const string Age = "Age";
+ public const string Allow = "Allow";
+ public const string Authorization = "Authorization";
+ public const string CacheControl = "Cache-Control";
+ public const string Connection = "Connection";
+ public const string ContentDisposition = "Content-Disposition";
+ public const string ContentEncoding = "Content-Encoding";
+ public const string ContentLanguage = "Content-Language";
+ public const string ContentLength = "Content-Length";
+ public const string ContentLocation = "Content-Location";
+ public const string ContentMD5 = "Content-MD5";
+ public const string ContentRange = "Content-Range";
+ public const string ContentType = "Content-Type";
+ public const string Cookie = "Cookie";
+ public const string Cookie2 = "Cookie2";
+ public const string Date = "Date";
+ public const string ETag = "ETag";
+ public const string Expect = "Expect";
+ public const string Expires = "Expires";
+ public const string From = "From";
+ public const string Host = "Host";
+ public const string IfMatch = "If-Match";
+ public const string IfModifiedSince = "If-Modified-Since";
+ public const string IfNoneMatch = "If-None-Match";
+ public const string IfRange = "If-Range";
+ public const string IfUnmodifiedSince = "If-Unmodified-Since";
+ public const string KeepAlive = "Keep-Alive";
+ public const string LastModified = "Last-Modified";
+ public const string Location = "Location";
+ public const string MaxForwards = "Max-Forwards";
+ public const string Origin = "Origin";
+ public const string P3P = "P3P";
+ public const string Pragma = "Pragma";
+ public const string ProxyAuthenticate = "Proxy-Authenticate";
+ public const string ProxyAuthorization = "Proxy-Authorization";
+ public const string ProxyConnection = "Proxy-Connection";
+ public const string Range = "Range";
+ public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched.
+ public const string RetryAfter = "Retry-After";
+ public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
+ public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
+ public const string SecWebSocketKey = "Sec-WebSocket-Key";
+ public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
+ public const string SecWebSocketVersion = "Sec-WebSocket-Version";
+ public const string Server = "Server";
+ public const string SetCookie = "Set-Cookie";
+ public const string SetCookie2 = "Set-Cookie2";
+ public const string TE = "TE";
+ public const string Trailer = "Trailer";
+ public const string TransferEncoding = "Transfer-Encoding";
+ public const string Upgrade = "Upgrade";
+ public const string UserAgent = "User-Agent";
+ public const string Vary = "Vary";
+ public const string Via = "Via";
+ public const string WWWAuthenticate = "WWW-Authenticate";
+ public const string Warning = "Warning";
+ public const string XAspNetVersion = "X-AspNet-Version";
+ public const string XPoweredBy = "X-Powered-By";
+ public const string StrictTransportSecurity = "Strict-Transport-Security";
+ public const string Duration = "Duration";
+ public const string FeatureFlag = "FeatureFlag";
+ public const string SdkVersion = "SdkVersion";
+ #endregion Known_HTTP_Header_Names
+
+ private static HashSet _contentHeaderSet = null;
+
+ internal static HashSet ContentHeaders =>
+ _contentHeaderSet ?? (_contentHeaderSet = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ HttpKnownHeaderNames.Allow,
+ HttpKnownHeaderNames.ContentDisposition,
+ HttpKnownHeaderNames.ContentEncoding,
+ HttpKnownHeaderNames.ContentLanguage,
+ HttpKnownHeaderNames.ContentLength,
+ HttpKnownHeaderNames.ContentLocation,
+ HttpKnownHeaderNames.ContentMD5,
+ HttpKnownHeaderNames.ContentRange,
+ HttpKnownHeaderNames.ContentType,
+ HttpKnownHeaderNames.Expires,
+ HttpKnownHeaderNames.LastModified
+ });
+
+
+ }
+}
diff --git a/src/Authentication/Authentication/Helpers/HttpMessageFormatter.cs b/src/Authentication/Authentication/Helpers/HttpMessageFormatter.cs
new file mode 100644
index 00000000000..229e247d512
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/HttpMessageFormatter.cs
@@ -0,0 +1,334 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Graph.PowerShell.Authentication.Properties;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ ///
+ /// Derived class which can encapsulate an
+ /// or an as an entity with media type "application/http".
+ ///
+ internal class HttpMessageFormatter : HttpContent
+ {
+ private const string SP = " ";
+ private const string ColonSP = ": ";
+ private const string CRLF = "\r\n";
+ private const string CommaSeparator = ", ";
+
+ private const int DefaultHeaderAllocation = 2 * 1024;
+
+ private const string DefaultMediaType = "application/http";
+
+ private const string MsgTypeParameter = "msgtype";
+ private const string DefaultRequestMsgType = "request";
+ private const string DefaultResponseMsgType = "response";
+
+ // Set of header fields that only support single values such as Set-Cookie.
+ private static readonly HashSet SingleValueHeaderFields =
+ new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ HttpKnownHeaderNames.Cookie,
+ HttpKnownHeaderNames.SetCookie,
+ HttpKnownHeaderNames.XPoweredBy
+ };
+
+ // Set of header fields that should get serialized as space-separated values such as User-Agent.
+ private static readonly HashSet SpaceSeparatedValueHeaderFields =
+ new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ HttpKnownHeaderNames.UserAgent
+ };
+
+ // Set of header fields that should not get serialized
+ private static readonly HashSet NeverSerializedHeaderFields =
+ new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ private bool _contentConsumed;
+ private Lazy> _streamTask;
+
+ ///
+ /// Initializes a new instance of the class encapsulating an
+ /// .
+ ///
+ /// The instance to encapsulate.
+ public HttpMessageFormatter(HttpRequestMessage httpRequest)
+ {
+ HttpRequestMessage = httpRequest ?? throw new ArgumentNullException(nameof(httpRequest));
+ Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType);
+ Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultRequestMsgType));
+
+ InitializeStreamTask();
+ }
+
+ ///
+ /// Initializes a new instance of the class encapsulating an
+ /// .
+ ///
+ /// The instance to encapsulate.
+ public HttpMessageFormatter(HttpResponseMessage httpResponse)
+ {
+ HttpResponseMessage = httpResponse ?? throw new ArgumentNullException(nameof(httpResponse));
+ Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType);
+ Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultResponseMsgType));
+
+ InitializeStreamTask();
+ }
+
+ private HttpContent Content =>
+ HttpRequestMessage != null ? HttpRequestMessage.Content : HttpResponseMessage.Content;
+
+ ///
+ /// Gets the HTTP request message.
+ ///
+ public HttpRequestMessage HttpRequestMessage { get; }
+
+ ///
+ /// Gets the HTTP response message.
+ ///
+ public HttpResponseMessage HttpResponseMessage { get; }
+
+ private void InitializeStreamTask()
+ {
+ _streamTask = new Lazy>(() => Content?.ReadAsStreamAsync());
+ }
+
+ ///
+ /// Validates whether the content contains an HTTP Request or an HTTP Response.
+ ///
+ /// The content to validate.
+ /// if set to true if the content is either an HTTP Request or an HTTP Response.
+ /// Indicates whether validation failure should result in an or not.
+ /// true if content is either an HTTP Request or an HTTP Response
+ internal static bool ValidateHttpMessageContent(HttpContent content, bool isRequest, bool throwOnError)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+
+ var contentType = content.Headers.ContentType;
+ if (contentType != null)
+ {
+ if (!contentType.MediaType.Equals(DefaultMediaType, StringComparison.OrdinalIgnoreCase))
+ {
+ if (throwOnError)
+ {
+ throw new ArgumentException(
+ Resources.HttpMessageInvalidMediaType.FormatCurrentCulture(contentType), nameof(content));
+ }
+
+ return false;
+ }
+
+ foreach (var parameter in contentType.Parameters)
+ {
+ if (parameter.Name.Equals(MsgTypeParameter, StringComparison.OrdinalIgnoreCase))
+ {
+ var msgType = parameter.Value.Trim('"');
+ if (!msgType.Equals(isRequest ? DefaultRequestMsgType : DefaultResponseMsgType,
+ StringComparison.OrdinalIgnoreCase))
+ {
+ if (throwOnError)
+ {
+ throw new ArgumentException(
+ Resources.HttpMessageInvalidMediaType.FormatCurrentCulture(msgType),
+ nameof(content));
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ if (throwOnError)
+ {
+ throw new ArgumentException(Resources.HttpMessageInvalidMediaType, nameof(content));
+ }
+
+ return false;
+ }
+
+ ///
+ /// Asynchronously serializes the object's content to the given .
+ ///
+ /// The to which to write.
+ /// The associated .
+ /// A instance that is asynchronously serializing the object's content.
+ protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ var header = SerializeHeader();
+ await stream.WriteAsync(header, 0, header.Length);
+
+ if (Content != null)
+ {
+ var readStream = await _streamTask.Value;
+ ValidateStreamForReading(readStream);
+ await Content.CopyToAsync(stream);
+ }
+ }
+
+ ///
+ /// Computes the length of the stream if possible.
+ ///
+ /// The computed length of the stream.
+ /// true if the length has been computed; otherwise false.
+ protected override bool TryComputeLength(out long length)
+ {
+ // We have four states we could be in:
+ // 1. We have content, but the task is still running or finished without success
+ // 2. We have content, the task has finished successfully, and the stream came back as a null or non-seekable
+ // 3. We have content, the task has finished successfully, and the stream is seekable, so we know its length
+ // 4. We don't have content (streamTask.Value == null)
+ //
+ // For #1 and #2, we return false.
+ // For #3, we return true & the size of our headers + the content length
+ // For #4, we return true & the size of our headers
+
+ var hasContent = _streamTask.Value != null;
+ length = 0;
+
+ // Cases #1, #2, #3
+ // We serialize header to a StringBuilder so that we can determine the length
+ // following the pattern for HttpContent to try and determine the message length.
+ // The perf overhead is no larger than for the other HttpContent implementations.
+ var header = SerializeHeader();
+ length += header.Length;
+ return true;
+ }
+
+ ///
+ /// Serializes the HTTP request line.
+ ///
+ /// Where to write the request line.
+ /// The HTTP request.
+ private static void SerializeRequestLine(StringBuilder message, HttpRequestMessage httpRequest)
+ {
+ Contract.Assert(message != null, "message cannot be null");
+ message.Append(httpRequest.Method + SP);
+ message.Append(httpRequest.RequestUri.PathAndQuery + SP);
+ message.Append($"HTTP/{(httpRequest.Version != null ? httpRequest.Version.ToString(2) : "1.1")}{CRLF}");
+
+ // Only insert host header if not already present.
+ if (httpRequest.Headers.Host == null)
+ {
+ message.Append($"HTTP{ColonSP}{httpRequest.RequestUri.Authority}{CRLF}");
+ }
+ }
+
+ ///
+ /// Serializes the HTTP status line.
+ ///
+ /// Where to write the status line.
+ /// The HTTP response.
+ private static void SerializeStatusLine(StringBuilder message, HttpResponseMessage httpResponse)
+ {
+ Contract.Assert(message != null, "message cannot be null");
+ message.Append($"HTTP/{(httpResponse.Version != null ? httpResponse.Version.ToString(2) : "1.1")}{SP}");
+ message.Append((int) httpResponse.StatusCode + SP);
+ message.Append(httpResponse.ReasonPhrase + CRLF);
+ }
+
+ ///
+ /// Serializes the header fields.
+ ///
+ /// Where to write the status line.
+ /// The headers to write.
+ private static void SerializeHeaderFields(StringBuilder message, HttpHeaders headers)
+ {
+ Contract.Assert(message != null, "message cannot be null");
+ if (headers != null)
+ {
+ foreach (var header in headers)
+ {
+ if (NeverSerializedHeaderFields.Contains(header.Key))
+ {
+ continue;
+ }
+
+ if (SingleValueHeaderFields.Contains(header.Key))
+ {
+ foreach (var value in header.Value)
+ {
+ message.Append(header.Key + ColonSP + value + CRLF);
+ }
+ }
+ else if (SpaceSeparatedValueHeaderFields.Contains(header.Key))
+ {
+ message.Append(header.Key + ColonSP + string.Join(SP, header.Value) + CRLF);
+ }
+ else
+ {
+ message.Append(header.Key + ColonSP + string.Join(CommaSeparator, header.Value) + CRLF);
+ }
+ }
+ }
+ }
+
+ private byte[] SerializeHeader()
+ {
+ var message = new StringBuilder(DefaultHeaderAllocation);
+ HttpHeaders headers;
+ HttpContent content;
+ if (HttpRequestMessage != null)
+ {
+ SerializeRequestLine(message, HttpRequestMessage);
+ headers = HttpRequestMessage.Headers;
+ content = HttpRequestMessage.Content;
+ }
+ else
+ {
+ SerializeStatusLine(message, HttpResponseMessage);
+ headers = HttpResponseMessage.Headers;
+ content = HttpResponseMessage.Content;
+ }
+
+ SerializeHeaderFields(message, headers);
+ if (content != null)
+ {
+ SerializeHeaderFields(message, content.Headers);
+ }
+
+ message.Append(CRLF);
+ return Encoding.UTF8.GetBytes(message.ToString());
+ }
+
+ private void ValidateStreamForReading(Stream stream)
+ {
+ // If the content needs to be written to a target stream a 2nd time, then the stream must support
+ // seeking (e.g. a FileStream), otherwise the stream can't be copied a second time to a target
+ // stream (e.g. a NetworkStream).
+ if (_contentConsumed)
+ {
+ if (stream != null && stream.CanRead)
+ {
+ stream.Position = 0;
+ }
+ else
+ {
+ throw new InvalidOperationException("HttpMessageContentAlreadyRead");
+ }
+ }
+
+ _contentConsumed = true;
+ }
+ }
+}
diff --git a/src/Authentication/Authentication/Helpers/HttpResponseException.cs b/src/Authentication/Authentication/Helpers/HttpResponseException.cs
new file mode 100644
index 00000000000..b99dea64b63
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/HttpResponseException.cs
@@ -0,0 +1,26 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System.Net.Http;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ public sealed class HttpResponseException : HttpRequestException
+ {
+ ///
+ /// Constructor for HttpResponseException.
+ ///
+ /// Message for the exception.
+ /// Response from the HTTP server.
+ public HttpResponseException(string message, HttpResponseMessage response) : base(message)
+ {
+ Response = response;
+ }
+
+ ///
+ /// HTTP error response.
+ ///
+ public HttpResponseMessage Response { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/InvokeGraphRequestAuthProvider.cs b/src/Authentication/Authentication/Helpers/InvokeGraphRequestAuthProvider.cs
new file mode 100644
index 00000000000..72326ce7c1f
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/InvokeGraphRequestAuthProvider.cs
@@ -0,0 +1,32 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Management.Automation.Language;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ public class InvokeGraphRequestAuthProvider : IAuthenticationProvider
+ {
+ private readonly GraphRequestSession _session;
+
+ public InvokeGraphRequestAuthProvider(GraphRequestSession session)
+ {
+ _session = session;
+ }
+
+ public Task AuthenticateRequestAsync(HttpRequestMessage request)
+ {
+ var authenticationHeader = new AuthenticationHeaderValue("Bearer", new NetworkCredential(string.Empty, _session.Token).Password);
+ request.Headers.Authorization = authenticationHeader;
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Authentication/Authentication/Helpers/InvokeGraphRequestUserAgent.cs b/src/Authentication/Authentication/Helpers/InvokeGraphRequestUserAgent.cs
new file mode 100644
index 00000000000..65c5724d2d3
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/InvokeGraphRequestUserAgent.cs
@@ -0,0 +1,93 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Management.Automation;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal class InvokeGraphRequestUserAgent
+ {
+ private readonly PSCmdlet _cmdLet;
+ private string _windowsUserAgent;
+
+ internal InvokeGraphRequestUserAgent(PSCmdlet cmdLet)
+ {
+ _cmdLet = cmdLet;
+ }
+ ///
+ /// Full UserAgent which Includes the Operating System, Current Culture
+ /// and full app name including powershell version and invoked command.
+ ///
+ internal string UserAgent
+ {
+ get
+ {
+ // format the user-agent string from the various component parts
+ var userAgent = string.Format(CultureInfo.InvariantCulture,
+ "{0} ({1}; {2}; {3}) {4}",
+ Compatibility, PlatformName, OS, Culture, App);
+ return userAgent;
+ }
+ }
+
+ internal static string Compatibility => ("Mozilla/5.0");
+ ///
+ /// Indicates the App which includes the PowerShell version
+ /// and the command name.
+ ///
+ internal string App
+ {
+ get
+ {
+ var app = string.Format(CultureInfo.InvariantCulture,
+ "PowerShell/{0} {1}", this._cmdLet.Host.Version, this._cmdLet.MyInvocation.MyCommand.Name);
+ return app;
+ }
+ }
+
+ internal string PlatformName
+ {
+ get
+ {
+ if (Platform.IsWindows)
+ {
+ // only generate the windows user agent once
+ if (_windowsUserAgent == null)
+ {
+ // find the version in the windows operating system description
+ var pattern = new Regex(@"\d+(\.\d+)+");
+ var versionText = pattern.Match(OS).Value;
+ var windowsPlatformVersion = new Version(versionText);
+ _windowsUserAgent = $"Windows NT {windowsPlatformVersion.Major}.{windowsPlatformVersion.Minor}";
+ }
+
+ return _windowsUserAgent;
+ }
+ else if (Platform.IsMacOS)
+ {
+ return "Macintosh";
+ }
+ else if (Platform.IsLinux)
+ {
+ return "Linux";
+ }
+ else
+ {
+ // unknown/unsupported platform
+ Debug.Assert(false, "Unable to determine Operating System Platform");
+ return string.Empty;
+ }
+ }
+ }
+
+ internal static string OS => RuntimeInformation.OSDescription.Trim();
+
+ internal static string Culture => (CultureInfo.CurrentCulture.Name);
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/PathUtils.cs b/src/Authentication/Authentication/Helpers/PathUtils.cs
new file mode 100644
index 00000000000..716aa4cdd87
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/PathUtils.cs
@@ -0,0 +1,42 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Management.Automation;
+using Microsoft.Graph.PowerShell.Authentication.Cmdlets;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ ///
+ /// Defines generic utilities and helper methods for PowerShell.
+ ///
+ internal static class PathUtils
+ {
+ public static string ResolveFilePath(string filePath, InvokeGraphRequest command, bool isLiteralPath)
+ {
+ string path = null;
+ try
+ {
+ var filePaths = new List();
+ if (isLiteralPath)
+ {
+ filePaths.Add(
+ command.SessionState.Path.GetUnresolvedProviderPathFromPSPath(filePath, out _, out _));
+ }
+ else
+ {
+ filePaths.AddRange(command.SessionState.Path.GetResolvedProviderPathFromPSPath(filePath, out _));
+ }
+
+ path = filePaths[0];
+ }
+ catch (ItemNotFoundException)
+ {
+ path = null;
+ }
+
+ return path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/StreamHelper.cs b/src/Authentication/Authentication/Helpers/StreamHelper.cs
new file mode 100644
index 00000000000..7329861cdb3
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/StreamHelper.cs
@@ -0,0 +1,128 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.IO;
+using System.Management.Automation;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Graph.PowerShell.Authentication.Cmdlets;
+using Microsoft.Graph.PowerShell.Authentication.Properties;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal static class StreamHelper
+ {
+ internal const int DefaultReadBuffer = 100000;
+
+ internal const int ChunkSize = 10000;
+
+ ///
+ /// Encode specified string to bytes using the provided encoding
+ ///
+ ///
+ ///
+ ///
+ internal static byte[] EncodeToBytes(this string str, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ // just use the default encoding if one wasn't provided
+ encoding = ContentHelper.GetDefaultEncoding();
+ }
+
+ return encoding.GetBytes(str);
+ }
+
+ internal static Stream GetResponseStream(this HttpResponseMessage response)
+ {
+ var responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
+ return responseStream;
+ }
+
+ internal static bool TryGetEncoding(this string characterSet, out Encoding encoding)
+ {
+ var result = false;
+ try
+ {
+ encoding = Encoding.GetEncoding(characterSet);
+ result = true;
+ }
+ catch (ArgumentException)
+ {
+ encoding = null;
+ }
+
+ return result;
+ }
+
+ internal static string DecodeStream(this BufferingStreamReader responseStream, ref Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ // Use the default encoding if one wasn't provided
+ encoding = ContentHelper.GetDefaultEncoding();
+ }
+ var content = responseStream.StreamToString(encoding);
+ return content;
+ }
+
+ internal static string StreamToString(this Stream stream, Encoding encoding)
+ {
+ using (var reader = new StreamReader(stream, encoding))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+
+ internal static void SaveStreamToFile(this Stream baseResponseStream, string filePath,
+ InvokeGraphRequest invokeGraphRequest, CancellationToken token)
+ {
+ // If the web cmdlet should resume, append the file instead of overwriting.
+ const FileMode fileMode = FileMode.Create;
+ using (var output = new FileStream(filePath, fileMode, FileAccess.Write, FileShare.Read))
+ {
+ baseResponseStream.WriteToStream(output, invokeGraphRequest, token);
+ }
+ }
+
+ private static void WriteToStream(this Stream input, Stream output, PSCmdlet cmdlet,
+ CancellationToken cancellationToken)
+ {
+ if (cmdlet == null)
+ {
+ throw new ArgumentNullException(nameof(cmdlet));
+ }
+
+ var copyTask = input.CopyToAsync(output);
+
+ var record = new ProgressRecord(
+ 000000000,
+ "WriteRequestProgressActivity",
+ "WriteRequestProgressStatus");
+ try
+ {
+ do
+ {
+ record.StatusDescription =
+ Resources.WriteRequestProgressStatus.FormatCurrentCulture(output.Position);
+ cmdlet.WriteProgress(record);
+
+ Task.Delay(1000, cancellationToken).Wait(cancellationToken);
+ } while (!copyTask.IsCompleted && !cancellationToken.IsCancellationRequested);
+
+ if (copyTask.IsCompleted)
+ {
+ record.StatusDescription = Resources.WriteRequestComplete.FormatCurrentCulture(output.Position);
+ cmdlet.WriteProgress(record);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/StringUtil.cs b/src/Authentication/Authentication/Helpers/StringUtil.cs
new file mode 100644
index 00000000000..b2774029a76
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/StringUtil.cs
@@ -0,0 +1,104 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Net;
+using System.Text;
+using Microsoft.Graph.PowerShell.Authentication.Properties;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal static class StringUtil
+ {
+ ///
+ /// Formats the specified string, using the current culture.
+ ///
+ ///
+ ///
+ ///
+ internal static string FormatCurrentCulture(this string formatSpec, params object[] args)
+ {
+ return string.Format(CultureInfo.CurrentCulture, formatSpec, args);
+ }
+
+ ///
+ /// Formats a Dictionary into a UrlEncoded string
+ ///
+ ///
+ ///
+ internal static string FormatDictionary(this IDictionary content)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+
+ var bodyBuilder = new StringBuilder();
+ foreach (string key in content.Keys)
+ {
+ if (0 < bodyBuilder.Length)
+ {
+ bodyBuilder.Append("&");
+ }
+
+ var value = content[key];
+
+ // URLEncode the key and value
+ var encodedKey = WebUtility.UrlEncode(key);
+ var encodedValue = string.Empty;
+ if (value != null)
+ {
+ encodedValue = WebUtility.UrlEncode(value.ToString());
+ }
+
+ bodyBuilder.AppendFormat("{0}={1}", encodedKey, encodedValue);
+ }
+
+ return bodyBuilder.ToString();
+ }
+
+ ///
+ /// Convert json string to object.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static bool TryConvert(this string jsonString, out object obj, ref Exception exRef)
+ {
+ var converted = false;
+ try
+ {
+ obj = JsonConvert.DeserializeObject(jsonString);
+ if (obj == null)
+ {
+ JToken.Parse(jsonString);
+ }
+ else
+ {
+ converted = true;
+ }
+ }
+ catch (JsonException ex)
+ {
+ var msg = Resources.JsonSerializationFailed.FormatCurrentCulture(ex.Message);
+ exRef = new ArgumentException(msg, ex);
+ obj = default;
+ }
+ catch (Exception jsonParseException)
+ {
+ exRef = jsonParseException;
+ obj = default;
+ }
+
+ return converted;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Helpers/WebResponseHelper.cs b/src/Authentication/Authentication/Helpers/WebResponseHelper.cs
new file mode 100644
index 00000000000..35fa71ca59b
--- /dev/null
+++ b/src/Authentication/Authentication/Helpers/WebResponseHelper.cs
@@ -0,0 +1,38 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
+// ------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net.Http;
+using Microsoft.Graph.PowerShell.Authentication.Cmdlets;
+
+namespace Microsoft.Graph.PowerShell.Authentication.Helpers
+{
+ internal static class WebResponseHelper
+ {
+ internal static Dictionary> GetHttpResponseHeaders(this HttpResponseMessage response)
+ {
+ var headers = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ foreach (var entry in response.Headers)
+ {
+ headers[entry.Key] = entry.Value;
+ }
+
+ // In CoreFX, HttpResponseMessage separates content related headers, such as Content-Type to
+ // HttpResponseMessage.Content.Headers. The remaining headers are in HttpResponseMessage.Headers.
+ // The keys in both should be unique with no duplicates between them.
+ // Added for backwards compatibility with PowerShell 5.1 and earlier.
+ if (response.Content != null)
+ {
+ foreach (var entry in response.Content.Headers)
+ {
+ headers[entry.Key] = entry.Value;
+ }
+ }
+
+ return headers;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj b/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj
index e6b8160de1c..f23d91f04ec 100644
--- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj
+++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj
@@ -27,12 +27,27 @@
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec
index 4f87002b4fd..79463ca7bd5 100644
--- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec
+++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec
@@ -1,7 +1,7 @@
- 0.5.0
+ 0.5.2
Microsoft.Graph.Authentication
Microsoft Graph PowerShell authentication module
Microsoft
diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1
index 0eef99381d2..e249bdf23e5 100644
--- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1
+++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1
@@ -72,7 +72,7 @@ FormatsToProcess = './Microsoft.Graph.Authentication.format.ps1xml'
FunctionsToExport = @()
# 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 = 'Connect-Graph', 'Disconnect-Graph', 'Get-MgContext', 'Get-MgProfile', 'Select-MgProfile'
+CmdletsToExport = 'Connect-Graph', 'Disconnect-Graph', 'Get-MgContext', 'Get-MgProfile', 'Select-MgProfile', 'Invoke-GraphRequest'
# Variables to export from this module
# VariablesToExport = @()
diff --git a/src/Authentication/Authentication/Models/GraphRequestAuthenticationType.cs b/src/Authentication/Authentication/Models/GraphRequestAuthenticationType.cs
new file mode 100644
index 00000000000..0b52ff12211
--- /dev/null
+++ b/src/Authentication/Authentication/Models/GraphRequestAuthenticationType.cs
@@ -0,0 +1,8 @@
+namespace Microsoft.Graph.PowerShell.Authentication.Models
+{
+ public enum GraphRequestAuthenticationType
+ {
+ Default = 0,
+ UserProvidedToken = 1
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Models/GraphRequestMethod.cs b/src/Authentication/Authentication/Models/GraphRequestMethod.cs
new file mode 100644
index 00000000000..360d8e1b764
--- /dev/null
+++ b/src/Authentication/Authentication/Models/GraphRequestMethod.cs
@@ -0,0 +1,14 @@
+namespace Microsoft.Graph.PowerShell.Authentication.Models
+{
+ ///
+ /// Contains the values of HttpVerbs
+ ///
+ public enum GraphRequestMethod
+ {
+ GET = 0,
+ POST = 1,
+ PUT = 2,
+ PATCH = 3,
+ DELETE = 4
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Models/RestReturnType.cs b/src/Authentication/Authentication/Models/RestReturnType.cs
new file mode 100644
index 00000000000..db4a7fb8b28
--- /dev/null
+++ b/src/Authentication/Authentication/Models/RestReturnType.cs
@@ -0,0 +1,21 @@
+namespace Microsoft.Graph.PowerShell.Authentication.Models
+{
+ public enum RestReturnType
+ {
+ ///
+ /// Return type not defined in response,
+ /// best effort detect.
+ ///
+ Detect = 0,
+
+ ///
+ /// Json return type.
+ ///
+ Json = 1,
+
+ ///
+ /// Xml return type.
+ ///
+ Xml = 2
+ }
+}
\ No newline at end of file
diff --git a/src/Authentication/Authentication/Properties/Resources.Designer.cs b/src/Authentication/Authentication/Properties/Resources.Designer.cs
new file mode 100644
index 00000000000..9489b3d6b04
--- /dev/null
+++ b/src/Authentication/Authentication/Properties/Resources.Designer.cs
@@ -0,0 +1,297 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Graph.PowerShell.Authentication.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Graph.PowerShell.Authentication.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Access to the path '{0}' is denied..
+ ///
+ internal static string AccessDenied {
+ get {
+ return ResourceManager.GetString("AccessDenied", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cmdlet cannot run because the following parameter is not specified: {0} The supplied Authentication type requires a {1} Specify {1}, then retry..
+ ///
+ internal static string AuthenticationCredentialNotSupplied {
+ get {
+ return ResourceManager.GetString("AuthenticationCredentialNotSupplied", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cmdlet cannot run because the following conflicting parameters are specified: {0} and Token. Specify either {0} or {1} then retry..
+ ///
+ internal static string AuthenticationTokenConflict {
+ get {
+ return ResourceManager.GetString("AuthenticationTokenConflict", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cmdlet cannot run because the following conflicting parameters are specified: {0} and {1} Specify either {0} or {1}, then retry..
+ ///
+ internal static string BodyConflict {
+ get {
+ return ResourceManager.GetString("BodyConflict", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} is required when Method is {1}.
+ ///
+ internal static string BodyMissingWhenMethodIsSpecified {
+ get {
+ return ResourceManager.GetString("BodyMissingWhenMethodIsSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Content encoding: {0}.
+ ///
+ internal static string ContentEncodingVerboseMessage {
+ get {
+ return ResourceManager.GetString("ContentEncodingVerboseMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cmdlet cannot run because the -ContentType parameter is not a valid Content-Type header. Specify a valid Content-Type for -ContentType, then retry. To suppress header validation, supply the -SkipHeaderValidation parameter..
+ ///
+ internal static string ContentTypeExceptionErrorMessage {
+ get {
+ return ResourceManager.GetString("ContentTypeExceptionErrorMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Path '{0}' resolves to a directory. Specify a path including a file name, and then retry the command..
+ ///
+ internal static string DirectoryPathSpecified {
+ get {
+ return ResourceManager.GetString("DirectoryPathSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cmdlet cannot run because the following conflicting parameters are specified: GraphRequestSession and SessionVariable. Specify either GraphRequestSession or SessionVariable, then retry..
+ ///
+ internal static string GraphRequestSessionConflict {
+ get {
+ return ResourceManager.GetString("GraphRequestSessionConflict", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Http Message Invalid Media Type {0}.
+ ///
+ internal static string HttpMessageInvalidMediaType {
+ get {
+ return ResourceManager.GetString("HttpMessageInvalidMediaType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The cmdlet cannot run because the following conflicting parameters are specified: {0} and {1}. Specify either {0} or {1} then retry..
+ ///
+ internal static string InferFileNameOutFilePathConflict {
+ get {
+ return ResourceManager.GetString("InferFileNameOutFilePathConflict", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Could not Infer File Name.
+ ///
+ internal static string InferredFileNameErrorMessage {
+ get {
+ return ResourceManager.GetString("InferredFileNameErrorMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Inferred File Name {0} Saving to {1}.
+ ///
+ internal static string InferredFileNameVerboseMessage {
+ get {
+ return ResourceManager.GetString("InferredFileNameVerboseMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invalid Host {0}.
+ ///
+ internal static string InvokeGraphRequestInvalidHostErrorMessage {
+ get {
+ return ResourceManager.GetString("InvokeGraphRequestInvalidHostErrorMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Empty string not allowed for {0}.
+ ///
+ internal static string InvokeGraphRequestInvalidUriErrorMessage {
+ get {
+ return ResourceManager.GetString("InvokeGraphRequestInvalidUriErrorMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Must specify {0}.
+ ///
+ internal static string InvokeGraphRequestMissingUriErrorMessage {
+ get {
+ return ResourceManager.GetString("InvokeGraphRequestMissingUriErrorMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} {1} with {2}-byte payload.
+ ///
+ internal static string InvokeGraphRequestVerboseMessage {
+ get {
+ return ResourceManager.GetString("InvokeGraphRequestVerboseMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to received {0}-byte response of content type {1}.
+ ///
+ internal static string InvokeGraphResponseVerboseMessage {
+ get {
+ return ResourceManager.GetString("InvokeGraphResponseVerboseMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Conversion from JSON failed with error: {0}.
+ ///
+ internal static string JsonSerializationFailed {
+ get {
+ return ResourceManager.GetString("JsonSerializationFailed", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Path '{0}' can be resolved to multiple paths..
+ ///
+ internal static string MultiplePathsResolved {
+ get {
+ return ResourceManager.GetString("MultiplePathsResolved", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Path '{0}' cannot be resolved to a file..
+ ///
+ internal static string NoPathResolved {
+ get {
+ return ResourceManager.GetString("NoPathResolved", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Path '{0}' is not a file system path. Please specify the path to a file in the file system..
+ ///
+ internal static string NotFileSystemPath {
+ get {
+ return ResourceManager.GetString("NotFileSystemPath", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} specified without {1}. If {0} is specified {1} must be specified as well..
+ ///
+ internal static string PassThruWithOutputFilePathMissing {
+ get {
+ return ResourceManager.GetString("PassThruWithOutputFilePathMissing", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Response status code does not indicate success: {0} ({1})..
+ ///
+ internal static string ResponseStatusCodeFailure {
+ get {
+ return ResourceManager.GetString("ResponseStatusCodeFailure", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Graph Request completed. (Number of bytes processed: {0}).
+ ///
+ internal static string WriteRequestComplete {
+ get {
+ return ResourceManager.GetString("WriteRequestComplete", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Number of bytes processed: {0}.
+ ///
+ internal static string WriteRequestProgressStatus {
+ get {
+ return ResourceManager.GetString("WriteRequestProgressStatus", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Authentication/Authentication/Properties/Resources.resx b/src/Authentication/Authentication/Properties/Resources.resx
new file mode 100644
index 00000000000..2329dc6b40b
--- /dev/null
+++ b/src/Authentication/Authentication/Properties/Resources.resx
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Access to the path '{0}' is denied.
+
+
+ The cmdlet cannot run because the following parameter is not specified: {0} The supplied Authentication type requires a {1} Specify {1}, then retry.
+
+
+ The cmdlet cannot run because the following conflicting parameters are specified: {0} and Token. Specify either {0} or {1} then retry.
+
+
+ The cmdlet cannot run because the following conflicting parameters are specified: {0} and {1} Specify either {0} or {1}, then retry.
+
+
+ {0} is required when Method is {1}
+
+
+ Content encoding: {0}
+
+
+ The cmdlet cannot run because the -ContentType parameter is not a valid Content-Type header. Specify a valid Content-Type for -ContentType, then retry. To suppress header validation, supply the -SkipHeaderValidation parameter.
+
+
+ Path '{0}' resolves to a directory. Specify a path including a file name, and then retry the command.
+
+
+ The cmdlet cannot run because the following conflicting parameters are specified: GraphRequestSession and SessionVariable. Specify either GraphRequestSession or SessionVariable, then retry.
+
+
+ Could not Infer File Name
+
+
+ Inferred File Name {0} Saving to {1}
+
+
+ Invalid Host {0}
+
+
+ Must specify {0}
+
+
+ {0} {1} with {2}-byte payload
+
+
+ received {0}-byte response of content type {1}
+
+
+ Path '{0}' can be resolved to multiple paths.
+
+
+ Path '{0}' cannot be resolved to a file.
+
+
+ Path '{0}' is not a file system path. Please specify the path to a file in the file system.
+
+
+ {0} specified without {1}. If {0} is specified {1} must be specified as well.
+
+
+ Response status code does not indicate success: {0} ({1}).
+
+
+ Http Message Invalid Media Type {0}
+ Http Message Invalid Media Type {0}
+
+
+ Conversion from JSON failed with error: {0}
+
+
+ Graph Request completed. (Number of bytes processed: {0})
+
+
+ Number of bytes processed: {0}
+
+
+ Empty string not allowed for {0}
+
+
+ The cmdlet cannot run because the following conflicting parameters are specified: {0} and {1}. Specify either {0} or {1} then retry.
+
+
\ No newline at end of file