8000 PS should try to load the assembly from file path first before loading from assemblyName by chunqingchen · Pull Request #4196 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

PS should try to load the assembly from file path first before loading from assemblyName #4196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 29, 2017

Conversation

chunqingchen
Copy link
Contributor
@chunqingchen chunqingchen commented Jul 7, 2017

Fix issue #3325

Summary of the issue:

visual studio cannot import the right assembly contains in their test vsix package through package manage console. It will always load the assembly contains in the vs location

Root cause of the issue:
loadassembly funtion will always try to load the assembly based on the assembly name first, then try the filename. In this case, no matter what assembly path psd1 file gives, ps will ignore and load the assembly in the vs cache.

Fix:
Since the filename is given, we should try it first before going to the assembly name.

The test scenario of this issue is very specific and does not contains in our ps enviroment. Attached the screen shot before/after the fix.

Before the fix:
image

After the fix:
image

@SteveL-MSFT
Copy link
Member

Can you add a synthetic test case?

@chunqingchen chunqingchen force-pushed the bugfix1 branch 4 times, most recently from 0e8289f to 0f4d8e8 Compare August 1, 2017 00:20
@chunqingchen chunqingchen changed the title PS should try to load the assembly from file path first before find a… PS should try to load the assembly from file path first before from assemblyName only Aug 1, 2017
@chunqingchen chunqingchen changed the title PS should try to load the assembly from file path first before from assemblyName only PS should try to load the assembly from file path first before loading from assemblyName Aug 1, 2017
@chunqingchen
Copy link
Contributor Author

@SteveL-MSFT test is added

$path = (Get-ChildItem $TESTDRIVE\System.Management.Automation.dll).FullName
[System.AppDomain]::CurrentDomain.GetAssemblies().Location.Contains($path) | Should be $true
}
}
Copy link
Member

Choose a reason for hiding this comment

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

add newline at end of file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

public class TestBinaryModuleCmdlet1Command
{
}

Copy link
Member

Choose a reason for hiding this comment

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

delete this empty line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved


if (!String.IsNullOrEmpty(filename))
{
error = null;
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 redundant from line 1329

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

}
catch (FileNotFoundException fileNotFound)
{
error = fileNotFound;
Copy link
Member

Choose a reason for hiding this comment

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

if we were given a filepath and it doesn't exist, it doesn't seem like we should try to load something that happens to have the same name but different path

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

}
catch (FileLoadException fileLoadException)
{
error = fileLoadException;
Copy link
Member

Choose a reason for hiding this comment

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

if we were given a filepath and it doesn't exist, it doesn't seem like we should try to load something that happens to have the same name but different path

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

return null;
}
}

#if !CORECLR// Assembly.LoadWithPartialName is not in CoreCLR. In CoreCLR, 'LoadWithPartialName' can be replaced by Assembly.Load with the help of AssemblyLoadContext.
Copy link
Member

Choose a reason for hiding this comment

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

We are removing FullCLR code, so you should remove this since you're touching this file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

}
}

if (loadedAssembly != null)
Copy link
Member

Choose a reason for hiding this comment

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

be consistent with line 1338 and return loadedAssembly on line 1375

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

}
}

string fixedName = null;
Copy link
Member

Choose a reason for hiding this comment

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

since the FullCLR code should be removed fixedName is only used within this if statement so move it inside the if statement

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

catch (BadImageFormatException badImage)
{
error = badImage;
return null;
Copy link
Member

Choose a reason for hiding this comment

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

if you didn't successfully load the assembly, loadedAssembly should still be null, so I think you can get rid of the return null; statements and just return loadedAssemb 67E6 ly at the end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved.

}

string fixedName = null;
if (!String.IsNullOrEmpty(name))
Copy link
Member

Choose a reason for hiding this comment

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

make this else if

A3E2 Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

@chunqingchen
Copy link
Contributor Author

@SteveL-MSFT your comments are resolved. thank you

error = securityException;
}
}

Copy link
Member

Choose a reason for hiding this comment

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

Delete blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

@chunqingchen
Copy link
Contributor Author

@SteveL-MSFT your comment is resolved thanks

Copy link
Member
@SteveL-MSFT SteveL-MSFT left a comment

Choose a reason for hiding this comment

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

LGTM


#Ignore slash format difference under windows/Unix
$path = (Get-ChildItem $TESTDRIVE\System.Management.Automation.dll).FullName
[System.AppDomain]::CurrentDomain.GetAssemblies().Location.Contains($path) | Should be $true
Copy link
Member

Choose a reason for hiding this comment

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

This works only because the actual assembly name of the assembly generated from Add-Type is not the same as the file name.

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of this, you can use Import-Module -Passthru and have the exact Assembly instance to validate.

We also want to avoid Should Be $true because the logs are less useful, so I'd like to see something like:

$assem = Import-Module -PassThru ...
$assem.Location | Should Be $Path

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

@daxian-dbw
Copy link
Member

This change seems risky to me.

  1. If an assembly is already loaded, and you try to load another same assembly file of a different version, Assembly.LoadFrom will throw; if you want to load another assembly file with the same version, then the already loaded assembly will be returned. See an example here:
PS:6> [System.Reflection.AssemblyName]::GetAssemblyName("F:\tmp\System.Management.Automation.dll")

Version        Name
-------        ----
6.0.1.0        System.Management.Automation

PS:7> [psobject].Assembly.GetName()

Version        Name
-------        ----
6.0.0.0        System.Management.Automation

PS:8> [System.Reflection.Assembly]::LoadFrom("F:\tmp\System.Management.Automation.dll")
Exception calling "LoadFrom" with "1" argument(s): "Could not load file or assembly 'System.Management.Automation,
Version=6.0.1.0, Culture=neutral, PublicKeyToken=null'."
At line:1 char:1
+ [System.Reflection.Assembly]::LoadFrom("F:\tmp\System.Management.Auto ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FileLoadException
  1. The original code prefers to assemblies in GAC, while the altered code prefers to file name. This is a behavior change, and since we now looking for GAC assemblies in powershell core for Assembly.Load call, this change could cause problems.

Add @lzybkr to reviewers to get more inputs on this change.

@daxian-dbw daxian-dbw requested review from lzybkr and removed request for adityapatwardhan August 4, 2017 19:26
using System;
namespace ModuleCmdlets
{
public class TestBinaryModuleCmdlet1Command
Copy link
Contributor

Choose a reason for hiding this comment

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

You should make this a complete cmdlet that can be called. That is the real scenario that needs to be tested.

@chunqingchen chunqingchen removed the request for review from vors August 31, 2017 07:08
@chunqingchen
Copy link
Contributor Author

@daxian-dbw
According to dongbo's concern about the exception. If user has specified the file path of the assembly, we should load the assembly according to user's willing. If an exception is thrown as assembly is already loaded, user should correct the file path or just give assembly name instead.

Loading assembly in GAC regardless user's given file path is exactly where the bug scenario comes from.

@lzybkr your comments are resolved.


Describe "Import-Module for Binary Modules" -Tags 'CI' {

It "PS should try to load the assembly from file path first" {
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure this test validates the fix? It passes even without your fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

previously when system.automation.management is used loading error will be occured.
Now we have to check the actual cmdlet is loaded.
Resolved.

@daxian-dbw
Copy link
Member

@chunqingchen please don't override existing commit history. Instead, push new commits to address comments.
Also, please make sure you add [Feature] label to your next commit message so that all tests can be validated with your fix.

@daxian-dbw
Copy link
Member

@chunqingchen if we decide to take this breaking change, then it'd better go in as soon as possible so that we have more time to validate the potential impact. Can you please address the comments as soon as you get a chance?

@chunqingchen
Copy link
Contributor Author

@daxian-dbw thanks for your comments. your comment is resolved.

"@

Add-Type -TypeDefinition $src -OutputAssembly $TESTDRIVE\System.dll
$assembly = Import-Module $TESTDRIVE\System.dll -Force -PassThru
Copy link
Member

Choose a reason for hiding this comment

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

Once the dll loaded, you will get errors when Pester tries to remove $TestDrive. I suggest you do import-module, $module.Path and Test-BinaryModuleCmdlet1 in a new powershell instance, and use the returned value for validation, so that there won't be any errors when Pester removes $TestDrive.

Copy link
Member

Choose a reason for hiding this comment

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

Plus, Import-Module -PassThru returns a PSModuleInfo object, so the value name should be $module instead of $assembly.


#Ignore slash format difference under windows/Unix
$path = (Get-ChildItem $TESTDRIVE\System.dll).FullName
$assembly.Path | Should be $path
Copy link
Member

Choose a reason for hiding this comment

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

I think your actual intent is to use $module.ImplementingAssembly.Location.

@daxian-dbw
Copy link
Member

@chunqingchen Thanks for the update, I left a few more comments. Please push new commits to address the commits without overriding the commit history.

@@ -109,12 +109,17 @@ namespace ModuleCmdlets
"@

Add-Type -TypeDefinition $src -OutputAssembly $TESTDRIVE\System.dll
$assembly = Import-Module $TESTDRIVE\System.dll -Force -PassThru
## Create a new Runspace and define helper functions in it
$powershell = [powershell]::Create()
Copy link
Member

Choose a reason for hiding this comment

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

Sorry that maybe I wasn't clear on my comment. I suggest you use powershell.exe to gather the information you want to test. In that way, the generated system.dll won't be loaded into the current app domain and thus Pester can remove it properly later.

It should be something like this:

$results = powershell -noprofile -c "`$module = Import-Module $TESTDRIVE\System.dll -Pssthru; `$module.ImplementingAssembly.Location; Test-BinaryModuleCmdlet1"
$results[0] | Should Be $path
$results[1] | Should BeExactly "BinaryModuleCmdlet1 exported by the ModuleCmdlets module."

@daxian-dbw
Copy link
Member

@chunqingchen Please fix the test failure.

@chunqingchen
Copy link
Contributor Author

thanks @daxian-dbw , checks are passed.

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.

6 participants
0