Call Update-Package for different solution file

149 Views Asked by At

I have a powershell script that I launch from Package Manager Console in Visual studio. In particular I have a question related to this command:

 Update-Package -ProjectName $ProjectName -reinstall

This command installs packages from packages.config (I'm working mainly with legacy non sdk projects) into specified project.

All is good when I run this command for projects in the currently opened solution. But I don't want opening many solutions to update packages, instead I wrote a single script (called inside package manager in VS) that just goes into specific solution folder and update projects there. It works, but the generated paths to packages folder in .csproj file are created relative to the initially opened solution, not the solution where the particular project belongs to. Which leads to paths like:

   ..\..\..\..\SomeNotCurrentlyOpenedSolution\packages\..

instead just

   ..\packages\..

Is there a way to fix it? Maybe somehow it's possible to force VS thinking that a particular command is launched for specified solution, not always currently opened

UPDATE1: Please see commands log and description:

To illustrate the issue I have 2 newly created empty solutions: ClassLibrary1.sln and ClassLibrary2.sln. ClassLibrary1.csproj (a single project inside solution) has a reference on newtonsoft package. I've opened Package Manager Console in ClassLibrary2.sln and type the below commands:

PM> ls
    Directory: C:\test\ClassLibrary2
Mode                 LastWriteTime         Length Name                                                                              
----                 -------------         ------ ----                                                                              
d-----          3/9/2024   9:53 PM                ClassLibrary2                                                                     
-a----          3/9/2024   9:53 PM           1145 ClassLibrary2.sln                                                                 

PM> Get-Content ..\ClassLibrary1\ClassLibrary1\ClassLibrary1.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
...
    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
    </Reference>
...

As we can see, the ClassLibrary1.csproj has a reference on Newtonesoft.Json package with expected path.

As suggested by @jdweng, I've cded into ClassLibrary1.csproj.

PM> cd ..\ClassLibrary1\ClassLibrary1

PM> ls
    Directory: C:\test\ClassLibrary1\ClassLibrary1
Mode                 LastWriteTime         Length Name                                                                              
----                 -------------         ------ ----                                                                              
d-----          3/9/2024   9:28 PM                bin                                                                               
d-----          3/9/2024   9:28 PM                obj                                                                               
d-----          3/9/2024   9:28 PM                Properties                                                                        
-a----          3/9/2024   9:28 PM            196 Class1.cs                                                                         
-a----          3/9/2024   9:30 PM           2528 ClassLibrary1.csproj                                                              
-a----          3/9/2024   9:30 PM            144 packages.config   

and try to update packages in ClassLibrary1.csproj. It doesn't work until I add a ClassLibrary1.csproj project into ClassLibrary2 solution (that's fine to me).

PM> Update-Package -ProjectName ClassLibrary1 -reinstall
Update-Package : Project 'ClassLibrary1' is not found.
At line:1 char:1
+ Update-Package -ProjectName ClassLibrary1 -reinstall
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (ClassLibrary1:String) [Update-Package], ItemNotFoundException
    + FullyQualifiedErrorId : NuGetProjectNotFound,NuGet.PackageManagement.PowerShellCmdlets.UpdatePackageCommand

after adding the ClassLibrary1.csproj project into the ClassLibrary2 solution, the command has succeded:

PM> ls
    Directory: C:\test\ClassLibrary1\ClassLibrary1
Mode                 LastWriteTime         Length Name                                                                              
----                 -------------         ------ ----                                                                              
d-----          3/9/2024  10:02 PM                bin                                                                               
d-----          3/9/2024   9:28 PM                obj                                                                               
d-----          3/9/2024   9:28 PM                Properties                                                                        
-a----          3/9/2024   9:28 PM            196 Class1.cs                                                                         
-a----          3/9/2024  10:12 PM           2278 ClassLibrary1.csproj                                                              
-a----          3/9/2024  10:12 PM            144 packages.config                                                                   

PM> Update-Package -ProjectName ClassLibrary1 -reinstall

Attempting to gather dependency information for multiple packages with respect to project 'ClassLibrary1', targeting '.NETFramework,Version=v4.5.2'
Gathering dependency information took 216 ms
Attempting to resolve dependencies for multiple packages.
Resolving dependency information took 0 ms
Resolving actions install multiple packages
Retrieving package 'Newtonsoft.Json 13.0.3' from 'nuget.org'.
Removed package 'Newtonsoft.Json 13.0.3' from 'packages.config'
Successfully uninstalled 'Newtonsoft.Json 13.0.3' from ClassLibrary1
Package 'Newtonsoft.Json.13.0.3' already exists in folder 'C:\test\ClassLibrary2\packages'
Added package 'Newtonsoft.Json.13.0.3' to 'packages.config'
Successfully installed 'Newtonsoft.Json 13.0.3' to ClassLibrary1
Executing nuget actions took 718 ms
Time Elapsed: 00:00:00.9501830

PM> Get-Content .\ClassLibrary1.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <HintPath>..\..\ClassLibrary2\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
    </Reference> 

you may see that the path to Newtonsoft.Json binaries has been changed. How can I prevent it?

For the context, what I want to do is to install packages mentioned in packages.config into the project file.

UPDATE2:

I tried approach suggested by @VonC related to nuget.exe binary:

nuget restore YourSolution.sln

or

nuget update YourProject.csproj

Unfortunatelly, it doesn't update neither a project file nor package.config. For now, using package manager console, I install a new package via:

  Install-Package Newtonsoft.Json -Version 7.0.1

of if the package is already in packages.config run (yeah, it's weird case):

  Update-Package -ProjectName $ProjectName -reinstall      

As far as I can tell, nuget restore or nuget update does nothing with packages.config and project file

1

There are 1 best solutions below

8
VonC On BEST ANSWER

The Update-Package command in the Package Manager Console operates within the context of the currently loaded solution in Visual Studio. When you add a project from another solution and update its packages, NuGet resolves the paths based on the current solution's directory, which leads to the undesired path changes you have observed.

Unfortunately, directly manipulating the solution context from within the Package Manager Console to trick Visual Studio into thinking a different solution is loaded for the scope of the command is not straightforward or supported by existing NuGet PowerShell commands.


That means you would have to consider, as an alternative, to automate package updates with the NuGet Command Line Interface (CLI) or MSBuild. Both tools can be used outside of Visual Studio and allow you to specify the solution or project files explicitly. That would give you more control over the process and avoids the context issue with Visual Studio's Package Manager Console.

For example, using the NuGet CLI, you can restore packages for a specific solution or project file without opening it in Visual Studio:

nuget restore YourSolution.sln

Or update packages for a project:

nuget update YourProject.csproj

As a more straightforward workaround, after updating the packages, you could manually adjust the HintPath in the .csproj files using a script. While not ideal, this would allow you to correct the paths without manually editing each file. You could write a PowerShell script to search and replace incorrect path segments in your project files.

Get-ChildItem -Recurse -Filter *.csproj | ForEach-Object {
    (Get-Content $_.FullName).replace('..\..\ClassLibrary2\packages', '..\packages') | Set-Content $_.FullName
}

That script finds all .csproj files, reads their content, replaces the incorrect relative path with the correct one, and saves the file.


Marco Merola's comment expands on the last workaround, changing the HintPath to use $(SolutionDir) instead of relative paths directly in your .csproj files: your project would reference the NuGet packages relative to the solution directory, regardless of where the project is being built from.

For existing references in your .csproj files, you would need to manually edit them or write a script to replace the relative paths with $(SolutionDir)packages\. For example, a <HintPath> that looks like ..\..\packages\MyPackage\lib\net45\MyLib.dll would be changed to $(SolutionDir)packages\MyPackage\lib\net45\MyLib.dll.

The script mentioned in the blog post "Identifying Nuget package references which are using relative paths across whole solution" from yer.ac can be a starting point for automating the process of identifying and updating project references that do not use $(SolutionDir).

param (
    [string]$solutionDir
)

$solutionDir = $solutionDir -replace '\\$', '' # Make sure no trailing backslash
$csprojFiles = Get-ChildItem -Path $solutionDir -Recurse -Filter *.csproj

foreach ($file in $csprojFiles) {
    $content = Get-Content $file.FullName
    $updated = $false

    for ($i = 0; $i -lt $content.Length; $i++) {
        if ($content[$i] -match '<HintPath>[^$](.*?packages\\)') {
            $content[$i] = $content[$i] -replace '<HintPath>[^$].*?packages\\', "<HintPath>$(`$SolutionDir)packages\\"
            $updated = $true
        }
    }

    if ($updated) {
        $content | Set-Content $file.FullName
        Write-Output "Updated HintPath in $($file.FullName)"
    }
}

You can run this script for a specific solution directory by providing the path to the solution directory as an argument. It searches for .csproj files, looks for HintPath entries without $(SolutionDir), and replaces them with paths that start with $(SolutionDir)packages\.

To make this process even easier, you can incorporate the PowerShell script as an External Tool in Visual Studio, as described in the blog post. That allows you to run the script directly from Visual Studio for the currently opened solution, helping you make sure all project references use the $(SolutionDir) variable for package paths.