microsoft/artifacts-credprovider

MsalInteractiveTokenProvider breaks if no console window handle available

TomKuhn opened this issue · 4 comments

We have a Windows Forms app (it's basically a setup wizard type application)
At some point, this program uses powershell.exe to run a script which does a NuGet restore from a private ADO feed.
This invokes CredentialProvider.Microsoft.exe to get a token for the Nuget ADO feed

However, because this wizard is a Windows Forms app, and we don't want random cmd console windows popping up all over the place as we run external programs, we always launch them with CreateNoWindow, therefor the child processes get NO ConsoleWindow.

This causes the provider to not be able to use Interactive flow because:

AzureArtifacts.cs
IntPtr consoleHandle = GetConsoleWindow();
returns IntPtr.Zero.

Do you have any solution for a use case like this? CredentialProvider.Microsoft.exe launched from a Forms app, and no console window is available?

I've hacked around and added the following code to CredentialProvider, but I'd rather not have to modify this code and have our own private copy!

private static IntPtr GetConsoleOrTerminalWindow()
{
IntPtr consoleHandle = GetConsoleWindow();
IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner);

if (handle == IntPtr.Zero)
{
// Cannot get handle to console window, walk up parent processes until we find one that has a MainWindowHandle
var parent = ParentProcessUtilities.GetParentProcess();
while (parent != null)
{
if (parent.MainWindowHandle != IntPtr.Zero)
{
handle = parent.MainWindowHandle;
break;
}
parent = ParentProcessUtilities.GetParentProcess(parent.Handle);
}
}

return handle;
}

///

/// A utility class to determine a process parent.
///

[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{
// These members must match PROCESS_BASIC_INFORMATION
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength);

///


/// Gets the parent process of the current process.
///

/// An instance of the Process class.
public static Process GetParentProcess()
{
return GetParentProcess(Process.GetCurrentProcess().Handle);
}

///


/// Gets the parent process of specified process.
///

/// The process id.
/// An instance of the Process class.
public static Process GetParentProcess(int id)
{
Process process = Process.GetProcessById(id);
return GetParentProcess(process.Handle);
}

///


/// Gets the parent process of a specified process.
///

/// The process handle.
/// An instance of the Process class.
public static Process GetParentProcess(IntPtr handle)
{
ParentProcessUtilities pbi = new ParentProcessUtilities();
int returnLength;
int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength);
if (status != 0)
throw new Win32Exception(status);

try
{
	return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
catch (ArgumentException)
{
	// not found
	return null;
}

}
}

@TomKuhn - to make sure I understand your use case:

You want the cred provider interactive prompt pop-up windows, and device code flow is not possible or wanted?
But the parent process does not have access to the console window, but the parent process of the parent process does?

@embetten - Some clarification for you

  • We want to avoid device flow authentication if at all possible as it requires copy/paste, going to a website, entering a code, and then doesn't seem to work with SilentAuth in subsequent tries, so it means our end users have to constantly repeat this.

  • The MSAL's publicclientapplicationbuilder WithBroker now requires you pass in a windows handle so it can center itself

  • CredentialProvider.Microsoft has implemented this by getting a handle to the ConsoleWindow

  • In our case we don't always have a console window, e.g we run CredentialProvider.Microsoft.exe directly from a Windows Forms application, and don't show a console window, which means GetConsoleWindow() returns zero, and the interactive popup we require doesn't work and we move onto the next auth method, which is DeviceFlow.

My PR just was a trial piece of code that works for us, that if we don't have a console window, then maybe we can get the window handle from the process which launched us. It could probably be refactored better into a list of IParentWindowHandleProvider type classes, trying each one in turn until one returns an HWND.

To answer your questions above directly:
You want the cred provider interactive prompt pop-up windows, and device code flow is not possible or wanted? Yes, exactly

But the parent process does not have access to the console window, but the parent process of the parent process does?
The current process might have no ConsoleWindow(), but the parent process (or parent of parent etc) does have a window (not necessarily a Console, might be a WinForm, might be a native app, but all you need is the hWnd)

@TomKuhn

Thanks for the additional information. Looking into the area more, it looks like the dotnet auth team is moving some of this logic into MSAL see. In addition, looking at the PR, #473, we have concerns about prompting users from the wrong handler in non-windows form scenarios.

We are considering an alternate approach to allow users to pass the appropriate handle as an input and will keep this issue open as an enhancement to track that work. For this approach, please let us know if you are running the cred provider from a nuget.exe, dotnet, or in standalone mode.

This issue has had no activity in 90 days. Please comment if it is not actually stale.