After much experimentation I have found a work around.
1) Create a temp folder that both the process identity and impersonated identity have full rights to.
2) In the thread with impersonation wanting to create an extract. First de-impersonate. Second call CreateExtract specifying the desired file name, but use the temp folder (from #1). Finally re-impersonate after the extract is created.
3) Allow all other code to run as normal until the extract is complete and closed. I've had no problems doing anything to the extract once it is open no matter whom the thread is impersonating.
4) Finally, move the created extract to the destination the calling code actually desired (and was likely only write-able by the impersonated identity. Moving it (instead of copy) keeps the temp folder clean.
FYI, I ran into another place in my program where I ran a program and it was also a problem that it got the process identity. I found that there are 4 Windows API calls for started a process. The most commonly used one does allow you to specify credentials to "run as" a user, but my process doesn't know those. But there was one that would take an identity "token", the same thing used when un-impersonating and re-impersonating. I was able to use that call to launch a process as the impersonated user.
Therefore, I believe the proper fix for Tableau would be to check if the thread calling CreateExtract or OpenExtract is impersonation, and if so launch the external "server" executable just as I did, then the 2 sides of the pipe would be using the same (impersonated) identity and it should work without having to use my work around above. NOTE: the Tableau Extract API 2.0 has exactly the same problem.