├── Assets ├── ContextMenu.jpg ├── Migration.jpg ├── ProjectSample.jpg └── Registration.jpg ├── CSProjTemplates.ps1 ├── ITV.util.ps1 ├── LICENSE ├── MigrateThisC#ProjectToCore.ps1 └── README.md /Assets/ContextMenu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSchwichtenberg/.NETFrameworkTo.NETCoreMigrationPowerShellScript/bd7b18f570b1f42a3f4c536bf5361430efd2db55/Assets/ContextMenu.jpg -------------------------------------------------------------------------------- /Assets/Migration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSchwichtenberg/.NETFrameworkTo.NETCoreMigrationPowerShellScript/bd7b18f570b1f42a3f4c536bf5361430efd2db55/Assets/Migration.jpg -------------------------------------------------------------------------------- /Assets/ProjectSample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSchwichtenberg/.NETFrameworkTo.NETCoreMigrationPowerShellScript/bd7b18f570b1f42a3f4c536bf5361430efd2db55/Assets/ProjectSample.jpg -------------------------------------------------------------------------------- /Assets/Registration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSchwichtenberg/.NETFrameworkTo.NETCoreMigrationPowerShellScript/bd7b18f570b1f42a3f4c536bf5361430efd2db55/Assets/Registration.jpg -------------------------------------------------------------------------------- /CSProjTemplates.ps1: -------------------------------------------------------------------------------- 1 | #region -------------------------- Templates 2 | $projEXETemplate = 3 | @" 4 | 5 | 6 | 7 | 8 | 9 | exe 10 | [TFM] 11 | 12 | 13 | 14 | 15 | [icon] 16 | false 17 | false 18 | [rootnamespace] 19 | 20 | 21 | 22 | 23 | true 24 | true 25 | true 26 | embedded 27 | false 28 | Link 29 | true 30 | 31 | 32 | 33 | 34 | [AssetsRef] 35 | 36 | 37 | 38 | 39 | [NugetReference] 40 | 41 | 42 | 43 | 44 | [ProjectReference] 45 | 46 | 47 | 48 | 49 | [LibReference] 50 | 51 | 52 | "@ 53 | 54 | $projWPFTemplate = 55 | @" 56 | 57 | 58 | 59 | 60 | 61 | [TFMWINDOWS] 62 | winexe 63 | true 64 | true 65 | 66 | 67 | 68 | 69 | false 70 | false 71 | [rootnamespace] 72 | [icon] 73 | 74 | 75 | 76 | 77 | false 78 | true 79 | true 80 | embedded 81 | false 82 | Link 83 | true 84 | 85 | 86 | 87 | 88 | [AssetsRef] 89 | 90 | 91 | 92 | 93 | [NugetReference] 94 | 95 | 96 | 97 | 98 | [ProjectReference] 99 | 100 | 101 | 102 | 103 | [LibReference] 104 | 105 | 106 | "@ 107 | 108 | $projLibTemplate = 109 | @" 110 | 111 | 112 | 113 | 114 | Library 115 | [TFM] 116 | false 117 | false 118 | [rootnamespace] 119 | 120 | 121 | 122 | 123 | 124 | [AssetsRef] 125 | 126 | 127 | 128 | 129 | [NugetReference] 130 | 131 | 132 | 133 | 134 | [ProjectReference] 135 | 136 | 137 | 138 | 139 | [LibReference] 140 | 141 | 142 | "@ 143 | 144 | $projTestTemplate = 145 | @" 146 | 147 | 148 | 149 | 150 | [TFM] 151 | false 152 | false 153 | [rootnamespace] 154 | win-x64 155 | 156 | 157 | 158 | 159 | [AssetsRef] 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | [NugetReference] 169 | 170 | 171 | 172 | 173 | [ProjectReference] 174 | 175 | 176 | 177 | 178 | [LibReference] 179 | 180 | 181 | "@ 182 | 183 | $NugetRefTemplate= 184 | @" 185 | 186 | "@ 187 | 188 | $ProjRefTemplate= 189 | @" 190 | 191 | "@ 192 | 193 | $LibRefTemplate= 194 | @" 195 | 196 | ..\_Libs\TODO.dll 197 | 198 | "@ 199 | 200 | $AssetTemplate= 201 | @" 202 | 203 | "@ 204 | #endregion # Templates -------------------------------------------------------------------------------- /ITV.util.ps1: -------------------------------------------------------------------------------- 1 | # A few small PowerShell Utilities / Colorful output, RegEx utilities etc. 2 | # Dr. Holger Schwichtenberg, www.IT-Visions.de, 2007-2020 3 | 4 | 5 | function Exit-ReturnKey($message = "Press RETURN key to exit.") 6 | { 7 | <# 8 | .SYNOPSIS 9 | Wait for RETURN key and than exit script 10 | #> 11 | Write-host $message 12 | read-host 13 | exit 14 | } 15 | 16 | function Get-RegExFromFile($path, $pattern) 17 | <# 18 | .SYNOPSIS 19 | Get a tag oder value from a project file 20 | #> 21 | { 22 | $content = get-content $path 23 | $matches = ($content | select-string -pattern $pattern).Matches 24 | if ( $matches -eq $null) { return $null } 25 | $matches | foreach { $_.Groups[1].Value } 26 | } 27 | 28 | function Replace-TextInFile($path, $oldtext, $newtext) 29 | { 30 | <# 31 | .SYNOPSIS 32 | Replace a text in a file 33 | #> 34 | ((Get-Content -path $path -Raw).Replace($oldtext, $newtext)) | Set-Content -Path $path 35 | } 36 | 37 | function Replace-RegExInFile($path, $reg, $newtext) 38 | { 39 | <# 40 | .SYNOPSIS 41 | Replace a text in a file 42 | #> 43 | (Get-Content -path $path -Raw) -replace $reg,$newtext | Set-Content -Path $path -Encoding UTF8 44 | } 45 | 46 | function head() 47 | { 48 | [CmdletBinding()] 49 | Param( [Parameter(ValueFromPipeline)]$s) 50 | Write-Host $s -ForegroundColor Blue -BackgroundColor White 51 | } 52 | 53 | function print() 54 | { 55 | [CmdletBinding()] 56 | Param( [Parameter(ValueFromPipeline)]$s) 57 | Write-Host $s 58 | } 59 | 60 | function h1() 61 | { 62 | [CmdletBinding()] 63 | Param( [Parameter(ValueFromPipeline)]$s) 64 | Write-Host $s -ForegroundColor black -BackgroundColor Yellow 65 | } 66 | 67 | function h2() 68 | { 69 | [CmdletBinding()] 70 | Param( [Parameter(ValueFromPipeline)]$s) 71 | Write-Host $s -ForegroundColor White -BackgroundColor Green 72 | } 73 | 74 | function h3() 75 | { 76 | [CmdletBinding()] 77 | Param( [Parameter(ValueFromPipeline)]$s) 78 | Write-Host $s -ForegroundColor White -BackgroundColor DarkBlue 79 | } 80 | 81 | function error() 82 | { 83 | [CmdletBinding()] 84 | Param( [Parameter(ValueFromPipeline)]$s) 85 | Write-Host $s -ForegroundColor white -BackgroundColor red 86 | } 87 | 88 | function warning() 89 | { 90 | [CmdletBinding()] 91 | Param( [Parameter(ValueFromPipeline)]$s) 92 | Write-Host $s -ForegroundColor yellow 93 | } 94 | 95 | function info() 96 | { 97 | [CmdletBinding()] 98 | Param( [Parameter(ValueFromPipeline)]$s) 99 | Write-Host $s -ForegroundColor cyan 100 | } 101 | 102 | function success() 103 | { 104 | [CmdletBinding()] 105 | Param( [Parameter(ValueFromPipeline)]$s) 106 | Write-Host $s -ForegroundColor green 107 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 dotnetdoktor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MigrateThisC#ProjectToCore.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Inquire" 2 | 3 | . "$PSScriptRoot\ITV.util.ps1" 4 | . "$PSScriptRoot\CSProjTemplates.ps1" 5 | 6 | Head "A PowerShell Script for the Migration of C#-based .NET Framework projects to .NET 8.0 or .NET 9.0" 7 | Head "Dr. Holger Schwichtenberg, www.IT-Visions.de 2019-2025" 8 | Head "Skript-Version: 0.9.0 (2025-03-01)" 9 | Head "Using .NET SDK Version: $(dotnet --version)" 10 | # ****************************************************** 11 | 12 | $TFM = "net9.0" # or "net8.0" 13 | $TFMWindows = "net9.0-windows" # or "net8.0" 14 | $defaultNugets = @{ 15 | "Microsoft.Windows.Compatibility"="9.0.*" # or: "Microsoft.Windows.Compatibility"="8.0.*" 16 | } 17 | 18 | #region -------------------------- Register "Migrate this C#-Project to .NET Core" command for .csproj 19 | if ((New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) 20 | { 21 | New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -ea SilentlyContinue | out-null 22 | $regPath = "HKCR:PROGID\shell\Migrate this C#-Project to modern .NET" 23 | $progIds = (Get-ItemProperty HKCR:.csproj\OpenWithProgids | gm | where name -Like "VisualStudio*").Name 24 | 25 | foreach($progID in $progIds) 26 | { 27 | $classRoot = $regPath -replace "PROGID", $progID 28 | Write-Host "Registering this script in Registry $classRoot ..." 29 | md "$classRoot" -Force | Out-Null 30 | md "$classRoot\command" -Force | Out-Null 31 | $registryPath = "$classRoot\Command" 32 | $Name = "(Default)" 33 | $value = 'powershell.exe -File "' + $PSScriptRoot +"\" + $MyInvocation.MyCommand.Name + '" "%1"' 34 | New-ItemProperty -Path $registryPath -Name $name -Value $value -PropertyType String -Force | Out-Null 35 | } 36 | success "DONE! Run script as normal user for migrating C# projects to modern .NET" 37 | Exit-ReturnKey 38 | } 39 | #endregion 40 | 41 | # -------------------------------------------------------------------------------- 42 | function Migrate-Project($projectfile, $newParentFolderName, $template, $projects, $libs, $nugets) 43 | { 44 | <# 45 | .SYNOPSIS 46 | convert a .NET Framework project to modern .NET 47 | #> 48 | 49 | h1 "Converting .NET Framework project to modern .NET: $projectFile" 50 | 51 | #region -------------------------- Paths 52 | h2 "Creating paths..." 53 | $filename = [System.IO.Path]::GetFileNameWithoutExtension($projectfile) 54 | $projectFolder = [System.IO.Path]::GetDirectoryName($projectfile) 55 | $folderObj = (get-item $projectFolder) 56 | $parentfolder = $folderObj.Parent.FullName 57 | $newProjectFolder = [System.IO.Path]::Combine($newParentFolderName, $folderObj.Name) 58 | $newProjectName = [System.IO.Path]::GetFileNameWithoutExtension((get-item $projectfile).Name) 59 | $newProjectFilePath = [System.IO.Path]::Combine($newProjectFolder,"$($newProjectName).csproj") 60 | print "Source path: $projectFolder" 61 | print "Source Project file: $projectfile" 62 | print "Target Solution folder: $newParentFolderName" 63 | print "Target path: $newProjectFolder" 64 | print "Target project: $newProjectFilePath" 65 | print "Template: $template" 66 | 67 | # Create target Solution folder, if it does not exists 68 | if (-not (test-path $newParentFolderName)) { md $newParentFolderName } 69 | 70 | # Remove target project folder, if it DOES exists 71 | Remove-Item $newProjectFolder/* -Recurse -Force -ea SilentlyContinue 72 | md $newProjectFolder -ea SilentlyContinue 73 | #region 74 | 75 | #region -------------------------- Getting data from existing project file 76 | h2 "Getting data from existing project file..." 77 | if (-not (test-path $projectfile)) { throw "Project file not found!" } 78 | $rootnamespace = Get-RegExFromFile $projectfile '(.*)' 79 | if ($rootnamespace -eq $null) { $rootnamespace = $newProjectName } 80 | print "Rootnamespace: $rootnamespace" 81 | 82 | if ($projects -eq $null) { 83 | $projects = Get-RegExFromFile $projectfile ' 92 | { 93 | [Hashtable] $e = @{ } 94 | $xml = [xml] (Get-Content $path) 95 | $prSet = $xml.SelectNodes("//*[local-name()='PackageReference']") 96 | foreach($pr in $prSet) 97 | { 98 | $name = ($pr.Attributes["Include"]).value 99 | $vers = ($pr.SelectSingleNode("//*[local-name()='Version']")).innerText 100 | #print "$name = $vers" 101 | $e.Add($name, $vers) 102 | } 103 | #print $e.Count 104 | $e 105 | } 106 | 107 | $nugetsFromProject += (Get-PackageReferencesFromFile $projectfile ) 108 | print $nugetsFromProject.Gettype().fullname 109 | $nugets += $nugetsFromProject 110 | if ($nugets -ne $null) { $nugets.keys | ForEach-Object { print "PackageReference: $_"; } } 111 | 112 | $applicationicon = Get-RegExFromFile $projectfile '(.*)' 113 | print "applicationicon: $applicationicon" 114 | #endregion 115 | 116 | #region -------------------------- Remove Destination Folder" 117 | h2 "Clean Destination Folder $newProjectFolder" 118 | if (test-path $newProjectFolder) { 119 | warning "Removing existing files in $newProjectFolder..." 120 | rd $newProjectFolder -Force -Recurse 121 | } 122 | #endregion 123 | 124 | #region -------------------------- Copy Code 125 | h2 "Copy Code $newProjectName to $newProjectFolder" 126 | Copy-Item $projectFolder $newProjectFolder -Recurse -Force 127 | dir $newProjectFolder | out-default 128 | 129 | h2 "Make writeable" 130 | Get-ChildItem -Path $newProjectFolder -Recurse -File | ForEach-Object { 131 | $_.IsReadOnly = $false 132 | } 133 | 134 | h2 "Remove unused files in $newProjectName..." 135 | if (Test-Path $newProjectFolder\bin) {rd $newProjectFolder\bin -Recurse } 136 | if (Test-Path $newProjectFolder\obj) {rd $newProjectFolder\obj -Recurse } 137 | remove-item $newProjectFolder\*.vspscc 138 | remove-item $newProjectFolder\*.sln 139 | remove-item $newProjectFolder\*.csproj 140 | dir $newProjectFolder -Recurse | Set-ItemProperty -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue | out-default 141 | dir $newProjectFolder | out-default 142 | #endregion 143 | 144 | #region -------------------------- Creating new project file 145 | h2 "Creating new project file ($newProjectFilePath )..." 146 | $csproj = $template 147 | $csproj = $csproj.Replace("[DATE]",(get-Date)) 148 | $csproj = $csproj.Replace("[rootnamespace]",$rootnamespace) 149 | $csproj = $csproj.Replace("[icon]",$applicationicon) 150 | $csproj = $csproj.Replace("[TFM]",$TFM) 151 | $csproj = $csproj.Replace("[TFMWINDOWS]",$TFMWINDOWS) 152 | $projRef = "" 153 | foreach($r in $projects) 154 | { 155 | print "Project: $r" 156 | $projRef += " " + $ProjRefTemplate.Replace("TODO",$r) + "`n" 157 | } 158 | $csproj = $csproj.Replace("[ProjectReference]",$projRef.TrimEnd()) 159 | 160 | $assetRef = "" 161 | if (test-path $newProjectFolder\assets) 162 | { 163 | $assets = dir $newProjectFolder\assets 164 | foreach($a in $assets) 165 | { 166 | print "Asset: $a" 167 | $assetRef += " " + $AssetTemplate.Replace("FILE","assets\$($a.name)") + "`n" 168 | } 169 | } 170 | $csproj = $csproj.Replace("[AssetsRef]",$assetRef.TrimEnd()) 171 | 172 | $nugetref = "" 173 | foreach($n in $nugets.keys) 174 | { 175 | print "Nuget: $n $($nugets[$n])" 176 | $nugetref += " " + $NugetRefTemplate.Replace("NAME",$n).Replace("VERSION",$nugets[$n]) + "`n" 177 | } 178 | $csproj = $csproj.Replace("[NugetReference]",$nugetref.TrimEnd()) 179 | 180 | $libref = "" 181 | foreach($l in $libs) 182 | { 183 | print "DLL: $l" 184 | $libref += " " + $LibRefTemplate.Replace("TODO",$l) 185 | } 186 | $csproj = $csproj.Replace("[LibReference]",$libref.TrimEnd()) 187 | 188 | #print $csproj 189 | $csproj | Set-Content $newProjectFilePath -Force 190 | 191 | #endregion 192 | 193 | #region -------------------------- Build 194 | h2 "Build $newProjectName..." 195 | cd $newProjectFolder 196 | #dotnet restore | out-default 197 | dotnet build | out-default 198 | #endregion 199 | return $newProjectFolder 200 | } 201 | 202 | #region ############################ Main 203 | 204 | $sourceproject = $args[0] # Get path to .csproj from Script arguments 205 | if ($sourceproject -eq $null) { Write-Error "No path! :-("; Read-Host; exit; } 206 | 207 | $projectFolder = [System.IO.Path]::GetDirectoryName($sourceproject) 208 | $projektName = [System.IO.Path]::GetFileName(($sourceproject)) 209 | $folderObj = (get-item $projectFolder) 210 | $parentfolder = $folderObj.Parent.FullName 211 | $newParentFolderName = $parentfolder + "#" + $TFM.Replace(".","") 212 | 213 | print "Selected Source Folder: $projectFolder" 214 | print "Selected Project: $projektName" 215 | 216 | if (test-path ([System.IO.Path]::Combine($projectfolder, "packages.config")) ) 217 | { 218 | warning "There is still a packages.config in this project. You should move to -Tags using Visual Studio. Continue anyway? (Y/N)" 219 | $c = Read-Host 220 | if ($c -ine "y" ) { exit } 221 | } 222 | 223 | $template = "" 224 | $c = read-host "Template: C=Console, W=WPF/WinForms, L=Library (DLL), U=Unit Tests Other=exit?" 225 | switch($c.toupper()) 226 | { 227 | "W" { $template = $projwpftemplate; } 228 | "C" { $template = $projEXETemplate; } 229 | "U" { $template = $projTestTemplate; } 230 | "L" { $template = $projlibtemplate; } 231 | default { return } 232 | } 233 | 234 | print "Target Folder: Press enter to accept the default [$($newParentFolderName)]" 235 | $defaultValue = 'default' 236 | $prompt = Read-Host "Press enter to accept the default [$($newParentFolderName)]" 237 | $newParentFolderName = ($newParentFolderName,$prompt)[[bool]$prompt] 238 | 239 | $newProjectFolder = Migrate-Project $sourceproject $newParentFolderName $template $null $null $defaultNugets 240 | success "DONE: $newProjectFolder" 241 | Exit-ReturnKey 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # .NET Framework to modern .NET (.NET 8.0 / .NET 9.0) Migration PowerShell Script 2 | A PowerShell Script for the Migration of C#-based .NET Framework projects to .NET 8.0 or .NET 9.0 3 | - creates a clean .NET SDK-style project file (.csproj) in a new folder 4 | - customizable templates for project file 5 | - copies all files from the original folder except /bin, /ob, .sln, .csproj and .vspscc 6 | - handles all files in the /Assets folder as Resource 7 | - migrates icon, rootnamespace, project references and packages references from original project 8 | - includes the Windows Compatibility Pack by default 9 | - does NOT set any build action (e.g. for EDMX, Typed DataSets) 10 | - does NOT exclude any files 11 | - does NOT integrate any other project settings e.g. Build Events 12 | - you can easily change these defaults or add additional settings 13 | 14 | # Supported Project Types 15 | - WPF 16 | - Windows Forms 17 | - Console 18 | - Library 19 | - Unit Tests 20 | 21 | # Not supported yet 22 | - Webprojects (ASP.NET) 23 | 24 | # Installation 25 | 1. Make sure you enabled PowerShell scripts on your system (e.g using: set-executionpolicy unrestricted) 26 | 2. Running the script as administrator will register the script in the Windows Explorer context menu for .csproj files. 27 | 28 | ![Register Script](Assets/Registration.jpg) 29 | 30 | # Running the script 31 | 32 | Select a .csproj file in Windows Explorer and choose "Migrate this C#-Project to .NET Core/.NET 5" in the context menu. 33 | 34 | ![Context Menu](Assets/ContextMenu.jpg) 35 | 36 | ![Migration of a project](Assets/Migration.jpg) 37 | 38 | ![Migration of a project](Assets/ProjectSample.jpg) 39 | --------------------------------------------------------------------------------