Skip to content

UnGAC MSBuild Assemblies on Install or Update#5626

Merged
benvillalobos merged 24 commits intodotnet:masterfrom
benvillalobos:ungac-on-install
Sep 21, 2020
Merged

UnGAC MSBuild Assemblies on Install or Update#5626
benvillalobos merged 24 commits intodotnet:masterfrom
benvillalobos:ungac-on-install

Conversation

@benvillalobos
Copy link
Member

@benvillalobos benvillalobos commented Aug 7, 2020

Fixes #5183

@benvillalobos benvillalobos marked this pull request as draft August 7, 2020 17:45
Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just initial feedback

Comment on lines +52 to +59
string s = output.ReadLine();

// Store all instances of gacutil for now.
while (!String.IsNullOrEmpty(s))
{
allInstancesOfGACUtil.Add(s);
s = output.ReadLine();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
string s = output.ReadLine();
// Store all instances of gacutil for now.
while (!String.IsNullOrEmpty(s))
{
allInstancesOfGACUtil.Add(s);
s = output.ReadLine();
}
// Store all instances of gacutil for now.
for (string loc = output.ReadLine(); !String.IsNullOrEmpty(loc); loc = output.ReadLine())
{
allInstancesOfGACUtil.Add(s);
}

Does this terminate appropriately? If so, I think there's a ReadAllLines function that might do the same. (I might have made that up 😄.)

Also, do we really care about any gacutils after the first?

Copy link
Member Author

@benvillalobos benvillalobos Aug 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this terminate appropriately? If so, I think there's a ReadAllLines function that might do the same. (I might have made that up 😄.)

It does and has always, which kind of worries me. I would do ReadAllLines but have experienced lockups from that as well. Perhaps I can clear the buffer (as I do below) so I can safely pull everything.

Also, do we really care about any gacutils after the first?

Honestly I don't think so. I did it because "why not" while I had them there. The final version will almost certainly not store all instances.

input.WriteLine("where /F /R \"C:\\Program Files (x86)\\Microsoft SDKs\\Windows\" gacutil.exe");

// Throw away the first line - it's the command we just used
output.ReadLine();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would probably make it messier rather than cleaner, but you should be able to set echo to off to suppress this.

StreamReader output;
StreamWriter input;

Process proc = new Process
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems heavy. Is there a way to run it in-proc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of one. I considered using an exec task but we need this to be an exe. If someone knows I'd love to know!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ladipro?
He knows everything.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can access the GAC via the Fusion interface, for example:
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/fusion/iassemblycache-uninstallassembly-method

Not sure if a COM interop wrapper exists or you'll have to build one yourself. I guess a big reason to go this route would be if gacutil.exe is not guaranteed to be present on the machine. Otherwise, if you know for sure there is gacutil.exe and you can find it, it may be easier to just execute it. I'm assuming we don't care about performance here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. There are a few that exist, but either the links to them are dead or they're not supported anymore. I found https://github.com/EasyHook/EasyHook
http://blog.wibeck.org/2008/10/working-with-gac-from-code/ points to an API wrapper that unfortunately 404's today.
There's also https://www.codeproject.com/Articles/8285/GAC-API-Interface

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely of interest:

[ComImport, Guid("E707DCDE-D1CD-11D2-BAB9-00C04F8ECEAE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IAssemblyCache
{
/* Unused.
[PreserveSig]
int UninstallAssembly(uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, IntPtr pvReserved, int pulDisposition);
*/
int UninstallAssembly();

I don't think we can depend on gacutil.exe existing: the VS install may be what installs it. We could maybe express a dependency on "some .NET SDK" and use that, but it's probably easier to use the COM object.

it's probably easier to use the COM object

😲

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just spent a good chunk of time trying to get https://docs.microsoft.com/en-us/dotnet/api/system.enterpriseservices.internal.publish.gacremove?view=netframework-4.8 working to no avail (passing in file path, assembly name, pointing directly to dll, etc). MattCav suggested it was preferable to use some sort of API and keep the code simple so that's the route I'll go.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reusing NativeMethods was the way to go. The program does need to be running as admin for it to work, however.


proc.Close();
proc.Dispose();
Console.ReadKey();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The readkey is temporary so I can see the console output before the window closes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😕 --> 😄 --> 👍


namespace Microsoft.Build.UnGAC
{
class Program
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work, and is this the right place for this? I'm surprised first that this runs at install time rather than running every time the user builds and second that you had to make a new project to make something that runs automatically. Could you explain that a little more?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will allow it to run at install time is the exe being pointed to from src/Package/MSBuild.VSSetup/exe.swr, which src/Package/MSBuild.VSSetup/files.swr will point at. And files.swr is already being picked up by microbuild.

@benvillalobos
Copy link
Member Author

Another issue I've been seeing since adding the Microsoft.Build.UnGAC project:
image
I've tried removing the nuget packages, but can't seem to from this project.

Looks like they're taken from src/Directory.Build.targets

  <ItemGroup Condition="'$(IsUnitTestProject)' == 'true' And '$(TargetFrameworkIdentifier)' != '.NETFramework' ">
    <PackageReference Include="xunit.console" />
  </ItemGroup>

Presumably I shouldn't have this project under src, or somehow override that import for this particular project.

@elachlan elachlan mentioned this pull request Aug 11, 2020
@benvillalobos benvillalobos marked this pull request as ready for review August 21, 2020 22:52
@benvillalobos
Copy link
Member Author

Current issue:

.packages\microsoft.dotnet.arcade.sdk\1.0.0-beta.20411.9\tools\VisualStudio.BuildIbcTrainingInputs.targets#L21
.packages\microsoft.dotnet.arcade.sdk\1.0.0-beta.20411.9\tools\VisualStudio.BuildIbcTrainingInputs.targets(21,5): error MSB4018: The "GenerateTrainingInputFiles" task failed unexpectedly.
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Enumerable.Select[TSource,TResult](IEnumerable`1 source, Func`2 selector)
   at Microsoft.DotNet.Build.Tasks.VisualStudio.IbcEntry.GetEntriesFromVsixJsonManifest(JObject json) in /_/src/Microsoft.DotNet.Build.Tasks.VisualStudio/OptProf/IbcEntry.cs:line 85
   at Microsoft.DotNet.Build.Tasks.VisualStudio.GenerateTrainingInputFiles.ExecuteImpl() in /_/src/Microsoft.DotNet.Build.Tasks.VisualStudio/OptProf/GenerateTrainingInputFiles.cs:line 84
   at Microsoft.DotNet.Build.Tasks.VisualStudio.GenerateTrainingInputFiles.Execute() in /_/src/Microsoft.DotNet.Build.Tasks.VisualStudio/OptProf/GenerateTrainingInputFiles.cs:line 42
   at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()

VisualStudio.BuildIbcTrainingInputs.targets is imported in AfterSigning.proj.

MSBuild.Dev.sln Outdated
Comment on lines +3 to +4
# Visual Studio Version 16
VisualStudioVersion = 16.0.30320.27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Go ahead and revert this entirely since there's no meaningful change.

<TargetFramework>net45</TargetFramework>
<!-- Set as an exe because this project publishes its own output. -->
<OutputType>Exe</OutputType>
<!-- Forcing the 'vsix' output to be json output. -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add

... workaround for dotnet/arcade#xxxx

(and file the "support exe packages" bug there ;))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good!

<FileSignInfo Include="MessagePack.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="MessagePack.Annotations.dll" CertificateName="3PartySHA2" />
<ItemsToSign Include="$(VisualStudioSetupOutputPath)DevDivPackages\*.nupkg" />
<FileSignInfo Include="MessagePack.Annotations.dll" CertificateName="3PartySHA2" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused by this; do we not need to sign DevDivPackages*.nupkg anymore?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't sign it in the first place; this line was just in error.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Setup runs on net45 and may not have installed newer yet. -->
<TargetFramework>net45</TargetFramework>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean it doesn't work on mac/linux? Is there a way we can make it work on mac/linux?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GAC is a thing only on .NET Framework (on Windows). Things do not break because of the GAC on Mac/Linux, because there's no GAC.

/// It runs at VS install-time as well as repair-time.
/// It is intended to run as best effort. Meaning that if it fails, we avoid throwing and instead log it.
/// </summary>
class Program
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: generic name

{
string[] assembliesToUnGAC =
{
"Microsoft.Build, Version=15.1.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these the only versions people GAC?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're the only versions that we use, so they're the only ones that need to be removed from the GAC. Prior to VS 15, our assemblies were GACed by design; after they're not and they're all 15.1.0.0.

// If we hit an error with an assembly, keep trying the others.
if ((hresult >> 31) == 1)
{
Console.WriteLine($"Could not remove {assembly} from the GAC. HResult: {hresult}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it write this if the assembly just wasn't in the GAC?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should, but I'm modifying this now to not check whether it was successful. It will just print out that it tried, and what the result was.

Copy link
Member

@rainersigwald rainersigwald left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see your insertion-automation changes before signing off.

<FileSignInfo Include="MessagePack.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="MessagePack.Annotations.dll" CertificateName="3PartySHA2" />
<ItemsToSign Include="$(VisualStudioSetupOutputPath)DevDivPackages\*.nupkg" />
<FileSignInfo Include="MessagePack.Annotations.dll" CertificateName="3PartySHA2" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't sign it in the first place; this line was just in error.

@benvillalobos
Copy link
Member Author

benvillalobos commented Sep 21, 2020

@rainersigwald you can see it here: https://dev.azure.com/devdiv/DevDiv/_releaseProgress?releaseId=815300&_a=release-pipeline-progress
The only change was under Variables/InsertJsonValues. Changed

- Microsoft.Build.vsman{$(MSBuild_ExtApisPackageVersion)+$(Build.SourceVersion)}=https://vsdrop.corp.microsoft.com/file/v1/Products/DevDiv/dotnet/msbuild/$(Build.SourceBranchName)/$(Build.BuildNumber);Microsoft.Build.vsman
+ Microsoft.Build.vsman{$(MSBuild_ExtApisPackageVersion)+$(Build.SourceVersion)}=https://vsdrop.corp.microsoft.com/file/v1/Products/DevDiv/dotnet/msbuild/$(Build.SourceBranchName)/$(Build.BuildNumber);Microsoft.Build.vsman,Microsoft.Build.UnGAC.vsman=https://vsdrop.corp.microsoft.com/file/v1/Products/DevDiv/dotnet/msbuild/$(Build.SourceBranchName)/$(Build.BuildNumber);Microsoft.Build.UnGAC.vsman

Note that I removed the version from the ungac vsman, and there comma separating Microsoft.Build.vsman and Microsoft.Build.UnGAC. If you modify the Insert VS Payload step in the release and find the Component.json values field, there's a little guide there (see Example 2) that shows you can do this.

@rainersigwald
Copy link
Member

@benvillalobos that change looks good. I think this is good to go. Please change the real insertion job after you merge it.

@benvillalobos benvillalobos merged commit 4bbee8f into dotnet:master Sep 21, 2020
Forgind pushed a commit to Forgind/msbuild that referenced this pull request Sep 25, 2020
* Added .swr file. Try catching the program to ensure it doesn't fail and thus prevent MSBuild from installing

* Moved exe.swr to its own directory. Otherwise both .swr files are passed to arcade as a semicolon delimited list, which arcade can't deal with

* Sign UnGAC.exe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove MSBuild assemblies from the GAC at VSIX install time.

4 participants