Similar to async, returning a yielded IEnumerable from RunAsUser<T> is not possible
ryancdotnet opened this issue · 3 comments
When executing the RunAsUser method, if T is IEnumerable, and the Func passed in as the function parameter performs yield return
, then the RunAsUser method immediately returns the IEnumerable, and the impersonated user context is already disposed by the time the first yield return
statement executes.
Consider the following example console app:
using System;
using System.Collections.Generic;
using SimpleImpersonation;
namespace SimpleImpersonationTests
{
class Program
{
private static UserCredentials _UserCredentials = new UserCredentials("TestUsername", "password");
private static LogonType _LogonType = LogonType.Interactive;
static void Main(string[] args)
{
Console.WriteLine($"Running {nameof(RunEnumerableTest1)}:");
WriteUsernamesToConsole(RunEnumerableTest1());
Console.WriteLine($"\nRunning {nameof(RunEnumerableTest2)}:");
WriteUsernamesToConsole(RunEnumerableTest2());
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
private static void WriteUsernamesToConsole(IEnumerable<string> usernames)
{
foreach (string username in usernames)
{
Console.WriteLine(username);
}
}
private static IEnumerable<string> GetCurrentUsers()
{
yield return Environment.UserName;
yield return Environment.UserName;
yield return Environment.UserName;
}
private static IEnumerable<string> RunEnumerableTest1() =>
Impersonation.RunAsUser(_UserCredentials, _LogonType, () =>
{
Console.WriteLine("Current user = " + Environment.UserName);
return GetCurrentUsers();
});
private static IEnumerable<string> RunEnumerableTest2() =>
Impersonation.RunAsUser(_UserCredentials, _LogonType, GetCurrentUsers);
// This is not possible, due to yield inside an anonymous function
//private static IEnumerable<string> RunEnumerableTest3() =>
// Impersonation.RunAsUser(_UserCredentials, _LogonType, () =>
// {
// Console.WriteLine("Current user = " + Environment.UserName);
// foreach (string username in GetCurrentUser())
// {
// yield return username;
// }
// });
}
}
Output of the above:
Running RunEnumerableTest1:
Current user = TestUsername
ryanc
ryanc
ryancRunning RunEnumerableTest2:
ryanc
ryanc
ryanc
Press enter to exit.
I wonder if a new method could be added to support yielded IEnumerable's? Where it would use a foreach
to execute the iterator and keep the context alive during iteration. Maybe like:
public IEnumerable<T> RunEnumerableAsUser<T>(UserCredentials userCredentials, LogonType logonType, Func<IEnumerable<T>> function)
Looks like it is not possible with the newer versions of .NET.
Seems like due to Windows impersonation changes in .NET Standard, line 94 would prevent it. That line would need to support yielded IEnumerables, and a yield inside an anonymous function there wouldn't work either. Pushing it out to another method call would be the same issue.
Even though it could be possible for older frameworks, it would seem kinda wonky to support yielded Enumerables for certain frameworks only.
Bummer.
It might be valuable to add a note to the official documentation about returning yielded IEnumerables from RunAsUser.
The "workaround" is to .ToList()
the yielded IEnumerable inside of the impersonated Func<T> function
call.
This isn't a true workaround because it negates the benefits of deferred execution. But for those who want to keep things as-is without a refactor, it might be helpful to note.
As of version 4.0.0, this library simply exposes a SafeAccessTokenHandle
that can be used with WindowsIdentity.RunImpersonated
and similar methods. Thus, any further discussion about what works within such impersonated code is better served at https://github.com/dotnet/runtime/issues. Thanks.