├── 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 | 
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 | 
35 |
36 | 
37 |
38 | 
39 |
--------------------------------------------------------------------------------