diff --git a/documentation/Register-PnPAzureADApp.md b/documentation/Register-PnPAzureADApp.md index 3d9f27f4d..6cd71ed7e 100644 --- a/documentation/Register-PnPAzureADApp.md +++ b/documentation/Register-PnPAzureADApp.md @@ -41,6 +41,7 @@ Register-PnPAzureADApp -ApplicationName [-MicrosoftGraphEndPoint ] [-EntraIDLoginEndPoint ] [-SignInAudience ] + [-LaunchBrowser ] ``` ### Existing Certificate @@ -59,6 +60,7 @@ Register-PnPAzureADApp -CertificatePath [-CertificatePassword ] [-NoPopup] [-LogoFilePath ] + [-LaunchBrowser ] ``` ## DESCRIPTION @@ -436,6 +438,21 @@ Position: Named Accept pipeline input: False ``` +### -LaunchBrowser +Launch a browser automatically and copy the code to enter to the clipboard + +```yaml +Type: SwitchParameter +Parameter Sets: DeviceLogin +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ## RELATED LINKS [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/documentation/Register-PnPEntraIDAppForInteractiveLogin.md b/documentation/Register-PnPEntraIDAppForInteractiveLogin.md index f846eab2b..7b292cb01 100644 --- a/documentation/Register-PnPEntraIDAppForInteractiveLogin.md +++ b/documentation/Register-PnPEntraIDAppForInteractiveLogin.md @@ -28,6 +28,7 @@ Register-PnPEntraIDAppForInteractiveLogin -ApplicationName [-MicrosoftGraphEndPoint ] [-EntraIDLoginEndPoint ] [-SignInAudience ] + [-LaunchBrowser ] ``` ### Generate App using Device Login @@ -42,6 +43,7 @@ Register-PnPEntraIDAppForInteractiveLogin -ApplicationName [-NoPopup] [-LogoFilePath ] [-SignInAudience ] + [-LaunchBrowser ] ``` ## DESCRIPTION @@ -227,6 +229,21 @@ Position: Named Accept pipeline input: False ``` +### -LaunchBrowser +Launch a browser automatically and copy the code to enter to the clipboard + +```yaml +Type: SwitchParameter +Parameter Sets: DeviceLogin +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ## RELATED LINKS [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/src/Commands/AzureAD/RegisterAzureADApp.cs b/src/Commands/AzureAD/RegisterAzureADApp.cs index b6b7c4ad4..e0a9b2e36 100644 --- a/src/Commands/AzureAD/RegisterAzureADApp.cs +++ b/src/Commands/AzureAD/RegisterAzureADApp.cs @@ -19,6 +19,7 @@ using System.Diagnostics; using System.Dynamic; using PnP.PowerShell.Commands.Enums; +using TextCopy; namespace PnP.PowerShell.Commands.AzureAD { @@ -104,6 +105,9 @@ public class RegisterAzureADApp : BasePSCmdlet, IDynamicParameters [Parameter(Mandatory = false)] public EntraIDSignInAudience SignInAudience; + [Parameter(Mandatory = false, ParameterSetName = "DeviceLogin")] + public SwitchParameter LaunchBrowser; + protected override void ProcessRecord() { if (ParameterSpecified(nameof(Store)) && !OperatingSystem.IsWindows()) @@ -433,7 +437,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter) { Task.Factory.StartNew(() => { - token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, MicrosoftGraphEndPoint); + token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, MicrosoftGraphEndPoint, launchBrowser: LaunchBrowser); if (token == null) { messageWriter.WriteWarning("Operation cancelled or no token retrieved."); @@ -683,10 +687,8 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string if (OperatingSystem.IsWindows() && !NoPopup) { - if (!Stopping) { - if (ParameterSpecified(nameof(Interactive))) { using (var authManager = AuthenticationManager.CreateWithInteractiveLogin(azureApp.AppId, (url, port) => @@ -694,6 +696,21 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string BrowserHelper.OpenBrowserForInteractiveLogin(url, port, true, cancellationTokenSource); }, Tenant, "You successfully provided consent", "You failed to provide consent.", AzureEnvironment)) { + authManager.ClearTokenCache(); + authManager.GetAccessToken(resource, Microsoft.Identity.Client.Prompt.Consent); + } + } + else if (ParameterSpecified(nameof(DeviceLogin)) && LaunchBrowser) + { + using (var authManager = AuthenticationManager.CreateWithDeviceLogin(azureApp.AppId, Tenant, (deviceCodeResult) => + { + ClipboardService.SetText(deviceCodeResult.UserCode); + messageWriter.WriteWarning($"\n\nCode {deviceCodeResult.UserCode} has been copied to your clipboard and a new tab in the browser has been opened. Please paste this code in there and proceed.\n\n"); + BrowserHelper.OpenBrowserForInteractiveLogin(deviceCodeResult.VerificationUrl, BrowserHelper.FindFreeLocalhostRedirectUri(), false, cancellationTokenSource); + return Task.FromResult(0); + }, AzureEnvironment)) + { + authManager.ClearTokenCache(); authManager.GetAccessToken(resource, Microsoft.Identity.Client.Prompt.Consent); } } diff --git a/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs b/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs index 0c9f8fb4b..087ea3fa3 100644 --- a/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs +++ b/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs @@ -15,6 +15,7 @@ using System.Diagnostics; using System.Dynamic; using PnP.PowerShell.Commands.Enums; +using TextCopy; namespace PnP.PowerShell.Commands.AzureAD { @@ -53,6 +54,9 @@ public class RegisterEntraIDAppForInteractiveLogin : BasePSCmdlet, IDynamicParam [Parameter(Mandatory = false)] public EntraIDSignInAudience SignInAudience; + [Parameter(Mandatory = false, ParameterSetName = "DeviceLogin")] + public SwitchParameter LaunchBrowser; + protected override void ProcessRecord() { var redirectUri = "http://localhost"; @@ -354,7 +358,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter) { Task.Factory.StartNew(() => { - token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, MicrosoftGraphEndPoint); + token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, MicrosoftGraphEndPoint, launchBrowser: LaunchBrowser); if (token == null) { messageWriter.WriteWarning("Operation cancelled or no token retrieved."); @@ -497,13 +501,10 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string progressRecord.RecordType = ProgressRecordType.Completed; WriteProgress(progressRecord); - if (OperatingSystem.IsWindows() && !NoPopup) { - if (!Stopping) { - if (ParameterSpecified(nameof(Interactive))) { using (var authManager = AuthenticationManager.CreateWithInteractiveLogin(azureApp.AppId, (url, port) => @@ -511,6 +512,21 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string BrowserHelper.OpenBrowserForInteractiveLogin(url, port, true, cancellationTokenSource); }, Tenant, "You successfully provided consent", "You failed to provide consent.", AzureEnvironment)) { + authManager.ClearTokenCache(); + authManager.GetAccessToken(resource, Microsoft.Identity.Client.Prompt.Consent); + } + } + else if (ParameterSpecified(nameof(DeviceLogin)) && LaunchBrowser) + { + using (var authManager = AuthenticationManager.CreateWithDeviceLogin(azureApp.AppId, Tenant, (deviceCodeResult) => + { + ClipboardService.SetText(deviceCodeResult.UserCode); + messageWriter.WriteWarning($"\n\nCode {deviceCodeResult.UserCode} has been copied to your clipboard and a new tab in the browser has been opened. Please paste this code in there and proceed.\n\n"); + BrowserHelper.OpenBrowserForInteractiveLogin(deviceCodeResult.VerificationUrl, BrowserHelper.FindFreeLocalhostRedirectUri(), false, cancellationTokenSource); + return Task.FromResult(0); + }, AzureEnvironment)) + { + authManager.ClearTokenCache(); authManager.GetAccessToken(resource, Microsoft.Identity.Client.Prompt.Consent); } } diff --git a/src/Commands/Utilities/AzureAuthHelper.cs b/src/Commands/Utilities/AzureAuthHelper.cs index 2d6bd33e7..bf01e8f21 100644 --- a/src/Commands/Utilities/AzureAuthHelper.cs +++ b/src/Commands/Utilities/AzureAuthHelper.cs @@ -17,7 +17,7 @@ internal static async Task AuthenticateAsync(string tenantId, string use throw new ArgumentException($"{nameof(tenantId)} is required"); } - using (var authManager = PnP.Framework.AuthenticationManager.CreateWithCredentials(CLIENTID , username, password, azureEnvironment: azureEnvironment)) + using (var authManager = PnP.Framework.AuthenticationManager.CreateWithCredentials(CLIENTID, username, password, azureEnvironment: azureEnvironment)) { var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}"; if (azureEnvironment == AzureEnvironment.Custom) @@ -28,17 +28,22 @@ internal static async Task AuthenticateAsync(string tenantId, string use } } - internal static string AuthenticateDeviceLogin(CancellationTokenSource cancellationTokenSource, CmdletMessageWriter messageWriter, bool noPopup, AzureEnvironment azureEnvironment, string clientId = "1950a258-227b-4e31-a9cf-717495945fc2", string customGraphEndpoint = "") + internal static string AuthenticateDeviceLogin(CancellationTokenSource cancellationTokenSource, CmdletMessageWriter messageWriter, bool noPopup, AzureEnvironment azureEnvironment, string clientId = "1950a258-227b-4e31-a9cf-717495945fc2", string customGraphEndpoint = "", bool launchBrowser = false) { try - { - using (var authManager = PnP.Framework.AuthenticationManager.CreateWithDeviceLogin("1950a258-227b-4e31-a9cf-717495945fc2", (result) => + { + using (var authManager = PnP.Framework.AuthenticationManager.CreateWithDeviceLogin(CLIENTID, (result) => { - - if (Utilities.OperatingSystem.IsWindows() && !noPopup) + if (launchBrowser) + { + ClipboardService.SetText(result.UserCode); + messageWriter.WriteWarning($"Please login.\n\nWe opened a browser and navigated to {result.VerificationUrl}\n\nEnter code: {result.UserCode} (we copied this code to your clipboard)\n\nNOTICE: close the browser tab after you authenticated successfully to continue the process."); + BrowserHelper.OpenBrowserForInteractiveLogin(result.VerificationUrl, BrowserHelper.FindFreeLocalhostRedirectUri(), false, cancellationTokenSource); + } + else if (!noPopup) { ClipboardService.SetText(result.UserCode); - messageWriter.WriteWarning($"Please login.\n\nWe opened a browser and navigated to {result.VerificationUrl}\n\nEnter code: {result.UserCode} (we copied this code to your clipboard)\n\nNOTICE: close the popup after you authenticated successfully to continue the process."); + messageWriter.WriteWarning($"Please login.\n\nWe opened a popup window and navigated to {result.VerificationUrl}\n\nEnter code: {result.UserCode} (we copied this code to your clipboard)\n\nNOTICE: close the popup after you authenticated successfully to continue the process."); BrowserHelper.GetWebBrowserPopup(result.VerificationUrl, "Please login for PnP PowerShell", cancellationTokenSource: cancellationTokenSource, cancelOnClose: false); } else @@ -106,6 +111,6 @@ internal static string AuthenticateInteractive(CancellationTokenSource cancellat cancellationTokenSource.Cancel(); } return null; - } + } } } \ No newline at end of file