rubberman
Goto Top

CodeBlocks Portable with MinGW 32 and 64 bit on Windows

cbplwico_en

back-to-top1. Introduction


Hello community

Besides VC++, I'm reasonably happy with Code::Blocks as an IDE for C and C++ on Windows. While you probably can't convince VC++ to work portable, Code::Blocks can do this with just a few tricks. There are already portable versions, but they haven't been suitable enough for me. For this reason, I started developing my own implementation. A VBScript initially seemed to me to be the easiest way to get to my goal, as it was comfortable to edit and had the required XML support.
More than 10 years after I first published this guide ( (obsolet) CodeBlocks Portable mit MinGW 32 und 64 Bit unter Windows, German only ), VBScript has been deprecated by Microsoft. Thus the launcher now has a graphical user interface based on PowerShell and WPF. There are now also MinGW-based compiler builds with an LLVM/CLang toolset to support other processor architectures, which I cite in this guide.
I hope this solution will be of interest to you.

BR
Steffen

back-to-top2. Preconditions

  • Windows 10 operating system or newer (tested under Win10 x64 and Win11 x64)
  • Windows PowerShell 5.1 or PowerShell Core
  • > 1.1 GB storage space

back-to-top3. Preparation

Of course, you need an appropriate directory structure for the whole thing to work. Because compilers are controlled via command line parameters, it is safer if none of the directories and subdirectories contain spaces or special characters such as & or %.
  • First create a new directory, it's reasonable to name it "CodeBlocksPortable". (Create it where you have unrestricted writing access to the directory structure!)
  • In this directory, a subdirectory "CodeBlocks" to contain the IDE,
  • a subdirectory "Projects", which later contains the projects,
  • and another subdirectory "Additional_Include". Self-created header files can be stored there that are frequently used, or, for example, the popular "Boost" headers.

(Note: If a subdirectory "_archive_" is created in the "Projects" directory to where finished projects can be moved, those projects are excluded from displaying in the "Recent projects" of the Code::Blocks start window.)

The folder structure should now look like this:
CodeBlocksPortable
    ├─ Additional_Include
    ├─ CodeBlocks
    └─ Projects
You can also give these directories other names, as after all you have to be able to work with them.

back-to-top4. Installations

back-to-top4.1. Code::Blocks (IDE)
Download codeblocks-NN.MM-nosetup.zip here (www.codeblocks.org/downloads/binaries). NN.MM is for the version number. (Recently codeblocks-20.03-nosetup.zip)
IMORTANT: Do not download the mingw-containig archive! You would get a GCC version with incomplete Windows headers.

Extract the contents of the ZIP archive into the "CodeBlocks" subdirectory.

In the CodeBlocks subdirectory you should now find both the "share" directory and "codeblocks.exe" file, among others:
CodeBlocksPortable
    ├─ Additional_Include
    ├─ CodeBlocks
    │      ├─ share
    │      └─ (codeblocks.exe)
    │
    └─ Projects
(Please never start codeblocks.exe by double-clicking it. The PowerShell script will do this later.)

back-to-top4.2. Compiler
Now that the IDE is in place, you still need a compiler. To be more precise, at least two compilers in order to be able to use the feature to create both 32-bit and 64-bit applications.
You can find them here (github.com/mstorsjo/llvm-mingw/releases).
The compilers that can be used under Windows are ZIP archives with names that match the following pattern:
"llvm-mingw-<YYYYMMDD>-<C runtime library>-<target processor>.zip"
  • <YYYYMMDD> is for year, month and day of the release date.
  • <C runtime library> is the Name of the DLL that contains the implementation of the C functions. "ucrt" is only preinstalled from Win10 onwards, while apps linked to "msvcrt" are also compatible with older Windows versions.
  • <target processor> refers to the processor family for which your apps are compiled. "i686" stands for 32-bit Intel processors, "armv7" for 32-bit ARM processors, "x86_64" for 64-bit AMD or Intel processors and "aarch64" for 64-bit ARM64 processors.

If you are in doubt which compilers are suitable for your computer - the probability is high that you will make the right choice with the following two:
"llvm-mingw-<YYYYMMDD>-msvcrt-i686.zip" for compiling 32-bit apps
"llvm-mingw-<YYYYMMDD>-msvcrt-x86_64.zip" for compiling 64-bit apps

Choose the latest version. The archives contain a directory with the same name as the ZIP file each.

Extract the *.zip files into "CodeBlocksPortable\CodeBlocks". The new directory structure could now look like this:
CodeBlocksPortable
    ├─ Additional_Include
    ├─ CodeBlocks
    │      ├─ llvm-mingw-20231031-msvcrt-i686
    │      ├─ llvm-mingw-20231031-msvcrt-x86_64
    │      ├─ share
    │      └─ (codeblocks.exe)
    │
    └─ Projects

back-to-top5. CB-Portable-Launcher.ps1

back-to-top5.1. The script code
The following PowerShell script starts "codeblocks.exe" with the appropriate settings. So "Code::Blocks" becomes "Code::Blocks Portable" face-wink
Save the script as "CB-Portable-Launcher.ps1" in the "CodeBlocksPortable" folder.
Add-Type -AssemblyName 'PresentationFramework', 'System.Drawing'  
Add-Type -Namespace 'P' -Name 'Invoke' -MemberDefinition @'  
  [StructLayout(LayoutKind.Sequential)]
  struct PropertyKey {
    Guid fmtId;
    int pId;
    internal PropertyKey(int propId) {
      fmtId = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"); // format ID of all System.AppUserModel properties  
      pId = propId;
    }
  }
  [StructLayout(LayoutKind.Explicit)]
  struct PropVariant {
    [FieldOffset(0)] VarEnum vt;
    [FieldOffset(8), MarshalAs(UnmanagedType.LPWStr)] string val;
    internal PropVariant(VarEnum varType) {
      vt = varType;
      val = "Public.CB_Portable_Launcher_ps1"; // bodgy: if vt is VT_BOOL, the string pointer is intended to be interpreted as boolean TRUE  
    }
  }
  [ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]  
  interface IPropertyStore {
    int GetCount(ref int cProps);
    int GetAt(int iProp, ref PropertyKey pkey);
    int GetValue(ref PropertyKey key, PropVariant pv);
    int SetValue(ref PropertyKey key, PropVariant pv);
    int Commit();
  }
  [DllImport("shell32.dll")]  
  static extern int SHGetPropertyStoreForWindow(IntPtr wnd, ref Guid riid, out IPropertyStore ppv);
  static void SetPropVal(IPropertyStore propStore, int propId, VarEnum varType) {
    var propKey = new PropertyKey(propId);
    propStore.SetValue(ref propKey, new PropVariant(varType));
  }
  public static void UpdateAppUserModel(IntPtr wnd) {
    var iid = typeof(IPropertyStore).GUID;
    IPropertyStore propStore;
    SHGetPropertyStoreForWindow(wnd, ref iid, out propStore);
    SetPropVal(propStore, 9, VarEnum.VT_BOOL); // .PreventPinning --> TRUE
    SetPropVal(propStore, 5, VarEnum.VT_LPWSTR); // .ID --> custom string
  }
  [DllImport("dwmapi.dll")]  
  public static extern int DwmSetWindowAttribute(IntPtr wnd, int attrId, ref int attr, int attrSize);
  [DllImport("kernel32.dll")]  
  public static extern IntPtr GetConsoleWindow();
  [DllImport("kernel32.dll")]  
  public static extern void GetNativeSystemInfo(Int16[] sysInf);
  [DllImport("shcore.dll")]  
  public static extern int SetProcessDpiAwareness(int value);
  [DllImport("user32.dll")]  
  public static extern int ShowWindowAsync(IntPtr wnd, int state);
'@  

#region fallback strings
$script:sId_0 = 'CodeBlocks and compiler directory'  
$script:sId_1 = 'Projects directory'  
$script:sId_2 = 'Directory for additional header files'  
$script:sId_3 = 'Compiler'  
$script:sId_4 = 'Start'  
$script:sId_5 = 'Cancel'  
$script:sId_6 = 'Please select the Code::Blocks directory.'  
$script:sId_7 = 'Please select the Projects directory.'  
$script:sId_8 = 'Please select the Additional_Include directory.'  
$script:sId_9 = '"Code::Blocks" has already been started in another process.  
Close the "Code::Blocks" window and try again.'  
$script:sId_10 = 'Error! Select the "CodeBlocks and compiler directory".'  
$script:sId_11 = 'Error! Select the "Projects directory".'  
$script:sId_12 = 'Error! Select the "Directory for additional header files".'  
$script:sId_13 = 'The file "codeblocks.exe" was not found.  
Check your selection in the "CodeBlocks and compiler directory" box.'  
$script:sId_14 = 'Error! No compiler found. Visit'  
$script:sId_15 = 'Ready. - There is a new compiler version. Visit'  
$script:sId_16 = 'Ready. - If necessary, select the compiler for the target platform.'  
#endregion fallback strings

#region predefined locations
$script:scriptDir = Split-Path $MyInvocation.MyCommand.Path
$script:prtblConf = Join-Path $script:scriptDir 'CB-Portable-Config.xml'  
$script:i18nSpec = Join-Path $script:scriptDir 'CB-Portable-I18n.xml'  
$script:urlLatest = 'https://github.com/mstorsjo/llvm-mingw/releases/latest'  
#endregion predefined locations

#region error flags
& {
  $flg = 1
  'errCbDir', 'errProjDir', 'errAInclDir', 'errCbExe', 'errComp' | ForEach-Object { Set-Variable $_ $flg -Scope 'Script'; $flg *= 2 }  
  $script:errFlags = $flg - 1 # initially all defined flags are set and later conditionally cleared bit by bit to enable the 'Start' button  
}
function Set-ErrFlag([int]$flg) { $script:errFlags = $script:errFlags -bOr $flg }
function Clear-ErrFlag([int]$flg) { $script:errFlags = $script:errFlags -bAnd (-bNot $flg) }
function Test-ErrFlag([int]$flg) { ($script:errFlags -bAnd $flg) -ne 0 }
#endregion error flags

function New-PrtblConf {
  [xml]$script:xmlConf = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><CB_Portable_Launcher_Config version="2"><relative_paths><CBDir><![CDATA[|]]></CBDir><ProjDir><![CDATA[|]]></ProjDir><AdditionalIncludeDir><![CDATA[|]]></AdditionalIncludeDir></relative_paths></CB_Portable_Launcher_Config>'  
  $script:xmlConf.Save($script:prtblConf)
}

function New-I18nFile {
  ([xml]@"  
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><lang><!--  
  To add a new translation, use either the long or short form of the language
   code as child-node name of the 'lang' root node. Each of the forms must be  
   based on a 'CurrentUICulture.Name' property of the .NET class  
   'System.Globalization.CultureInfo'.  
   - The long form consists of the combination of the 2 letters for the
      language, an underscore, and the country/region code.
      NOTE: The hyphen of the 'CurrentUICulture.Name' property is replaced with  
      an underscore to get a valid node name.
      Example: 'zh_Hans' or 'zh_Hant'  
   - The short form is only the 2 letters for the language. Prefer this form if
      there are no differences in translation for countries or regions that use
      the same language.
      Example: 'de' (instead of 'de_AT', 'de_DE' etc.)  
  Search order:
   - The long form is found.
   - Else, the short form is found.
   - Else, a long form with the same language but different country/region code
      is found.
   - Else, fall back to English.
  Make sure
   - to use CDATA elemets for translated strings,
   - to save the file UTF-8 encoded.
  --><en><s id="0"><![CDATA[${script:sId_0}]]></s><s id="1"><![CDATA[${script:sId_1}]]></s><s id="2"><![CDATA[${script:sId_2}]]></s><s id="3"><![CDATA[${script:sId_3}]]></s><s id="4"><![CDATA[${script:sId_4}]]></s><s id="5"><![CDATA[${script:sId_5}]]></s>  
<s id="6"><![CDATA[${script:sId_6}]]></s><s id="7"><![CDATA[${script:sId_7}]]></s><s id="8"><![CDATA[${script:sId_8}]]></s><s id="9"><![CDATA[${script:sId_9}]]></s><s id="10"><![CDATA[${script:sId_10}]]></s><s id="11"><![CDATA[${script:sId_11}]]></s>  
<s id="12"><![CDATA[${script:sId_12}]]></s><s id="13"><![CDATA[${script:sId_13}]]></s><s id="14"><![CDATA[${script:sId_14}]]></s><s id="15"><![CDATA[${script:sId_15}]]></s><s id="16"><![CDATA[${script:sId_16}]]></s></en></lang>  
"@).Save($script:i18nSpec)  
}

function New-ConfFile {
  param(
    [Parameter(Mandatory = $True)][string]$confFile
  )
  PROCESS {
    ([xml]@'  
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><CodeBlocksConfig version="1"><app><environment><CHECK_ASSOCIATIONS bool="0" /></environment><SHOW_TIPS bool="0" /><RECENT_PROJECTS><astr /></RECENT_PROJECTS>  
<locale><ENABLE bool="1" /></locale></app><project_manager><DEFAULT_PATH><str /></DEFAULT_PATH></project_manager><compiler><build_progress /><SETTINGS_VERSION><str><![CDATA[0.0.3]]></str></SETTINGS_VERSION>  
<sets><gcc><NAME><str><![CDATA[GNU GCC Compiler]]></str></NAME><COMPILER_OPTIONS><str><![CDATA[-Wall;-std=c++20;-std=c17;-s;-ffunction-sections;-fdata-sections;]]></str></COMPILER_OPTIONS>
<LINKER_OPTIONS><str><![CDATA[-O3;-static-libstdc++;-static-libgcc;-Wl,-Bstatic -lstdc++ -lpthread;-Wl,--gc-sections;]]></str></LINKER_OPTIONS><MASTER_PATH><str /></MASTER_PATH><C_COMPILER><str><![CDATA[gcc.exe]]></str></C_COMPILER>
<CPP_COMPILER><str><![CDATA[g++.exe]]></str></CPP_COMPILER><LINKER><str><![CDATA[g++.exe]]></str></LINKER><MAKE><str><![CDATA[make.exe]]></str></MAKE></gcc></sets><DEFAULT_COMPILER><str><![CDATA[gcc]]></str></DEFAULT_COMPILER></compiler>
<debugger_common><sets><gdb_debugger><conf1><NAME><str><![CDATA[Default]]></str></NAME><values><EXECUTABLE_PATH><str /></EXECUTABLE_PATH></values></conf1></gdb_debugger></sets></debugger_common><scripts><generic_wizard><WANT_DEBUG bool="1" /><DEBUG_NAME><str><![CDATA[Debug]]></str></DEBUG_NAME>  
<DEBUG_OUTPUT><str /></DEBUG_OUTPUT><DEBUG_OBJECTS_OUTPUT><str /></DEBUG_OBJECTS_OUTPUT><WANT_RELEASE bool="1" /><RELEASE_NAME><str><![CDATA[Release]]></str></RELEASE_NAME><RELEASE_OUTPUT><str /></RELEASE_OUTPUT><RELEASE_OBJECTS_OUTPUT><str /></RELEASE_OBJECTS_OUTPUT></generic_wizard></scripts></CodeBlocksConfig>  
'@).Save($confFile)  
  }
}

function Update-CData {
  param(
    [Parameter(Mandatory = $True, Position = 0)][xml]$oXML,
    [Parameter(Mandatory = $True, Position = 1)][string]$baseNode,
    [Parameter(Mandatory = $True, Position = 2)][string]$data
  )
  PROCESS {
    $oNode = $oXML.DocumentElement.SelectSingleNode("${baseNode}/str")  
    if ($oNode) { $oNode.ParentNode.RemoveChild($oNode) | Out-Null }
    $oItem = $oXML.CreateElement('str')  
    $oNode = $oXML.DocumentElement.SelectSingleNode($baseNode)
    $oNode.AppendChild($oItem) | Out-Null
    $oNode = $oXML.DocumentElement.SelectSingleNode("${baseNode}/str")  
    $oItem = $oXML.createCDATASection($data)
    $oNode.AppendChild($oItem) | Out-Null
  }
}

function Clear-Node {
  param(
    [Parameter(Mandatory = $True, Position = 0)][xml]$oXML,
    [Parameter(Mandatory = $True, Position = 1)][string]$baseNode,
    [Parameter(Mandatory = $True, Position = 2)][string]$node
  )
  PROCESS {
    $oNode = $oXML.DocumentElement.SelectSingleNode("${baseNode}/$node")  
    if ($oNode) { $oNode.ParentNode.RemoveChild($oNode) | Out-Null }
    $oNode = $oXML.DocumentElement.SelectSingleNode($baseNode)
    $oItem = $oXML.CreateElement($node)
    $oNode.AppendChild($oItem) | Out-Null
  }
}

function Update-Recents {
  param(
    [Parameter(Mandatory = $True, Position = 0)][xml]$oXML,
    [Parameter(Mandatory = $True, Position = 1)][string[]]$recentProjList
  )
  PROCESS {
    $i = 0
    $oNode = $oXML.DocumentElement.SelectSingleNode('app/RECENT_PROJECTS/astr')  
    $recentProjList | ForEach-Object {
      $oItem = $oXML.CreateElement('s')  
      $oNode.AppendChild($oItem) | Out-Null
      $oItem = $oXML.createCDATASection($_)
      $oNode.ChildNodes[$i++].AppendChild($oItem) | Out-Null
    }
  }
}

function Update-Conf {
  param(
    [Parameter(Mandatory = $True, Position = 0)][string]$fileName,
    [Parameter(Mandatory = $True, Position = 1)][string]$mingwDir,
    [Parameter(Mandatory = $True, Position = 2)][string]$gdb,
    [Parameter(Mandatory = $True, Position = 3)][string]$compilerName,
    [Parameter(Mandatory = $True, Position = 4)][AllowNull()][string[]]$recentProjList
  )
  PROCESS {
    $oXML = New-Object 'xml'  
    $oXML.Load($fileName)
    Update-CData $oXML 'compiler/sets/gcc/MASTER_PATH' $mingwDir  
    Update-CData $oXML 'debugger_common/sets/gdb_debugger/conf1/values/EXECUTABLE_PATH' $gdb  
    Update-CData $oXML 'project_manager/DEFAULT_PATH' "${script:projDir}\"  
    Clear-Node $oXML 'compiler/sets/gcc' 'INCLUDE_DIRS'  
    Update-CData $oXML 'compiler/sets/gcc/INCLUDE_DIRS' "${mingwDir}\mingw\include;${script:aInclDir};"  
    Clear-Node $oXML 'compiler/sets/gcc' 'LIBRARY_DIRS'  
    Update-CData $oXML 'compiler/sets/gcc/LIBRARY_DIRS' "${mingwDir}\mingw\lib;"  
    Update-CData $oXML 'scripts/generic_wizard/DEBUG_OUTPUT' "bin\Debug_${compilerName}\"  
    Update-CData $oXML 'scripts/generic_wizard/DEBUG_OBJECTS_OUTPUT' "obj\Debug_${compilerName}\"  
    Update-CData $oXML 'scripts/generic_wizard/RELEASE_OUTPUT' "bin\Release_${compilerName}\"  
    Update-CData $oXML 'scripts/generic_wizard/RELEASE_OBJECTS_OUTPUT' "obj\Release_${compilerName}\"  
    Clear-Node $oXML 'app/RECENT_PROJECTS' 'astr'  
    if ($recentProjList) { Update-Recents $oXML $recentProjList }
    $oXML.Save($fileName)
  }
}

function Update-ProjFile {
  param(
    [Parameter(Mandatory = $True, Position = 0)][string]$fileName,
    [Parameter(Mandatory = $True, Position = 1)][string]$compilerName
  )
  PROCESS {
    $xml = New-Object 'xml'  
    $xml.Load($fileName)
    $buildTarget = $xml.CodeBlocks_project_file.Project.Build.Target
    $debug = $buildTarget | Where-Object { $_.title -eq 'Debug' }  
    $debugOptionOutput = $debug.Option | Where-Object { $_.HasAttribute('output') }  
    $debugOptionOutput.output = "bin/Debug_${compilerName}/$(Split-Path $debugOptionOutput.output -Leaf)"  
    $debugOptionObject_output = $debug.Option | Where-Object { $_.HasAttribute('object_output') }  
    $debugOptionObject_output.object_output = "obj/Debug_${compilerName}/"  
    $release = $buildTarget | Where-Object { $_.title -eq 'Release' }  
    $releaseOptionOutput = $release.Option | Where-Object { $_.HasAttribute('output') }  
    $releaseOptionOutput.output = "bin/Release_${compilerName}/$(Split-Path $releaseOptionOutput.output -Leaf)"  
    $releaseOptionObject_output = $release.Option | Where-Object { $_.HasAttribute('object_output') }  
    $releaseOptionObject_output.object_output = "obj/Release_${compilerName}/"  
    $xml.Save($fileName)
  }
}

function Get-RecentProjects {
  $projFiles = [Collections.Generic.List[string]]::new()
  Get-ChildItem $script:projDir -Exclude '_archive_' -Directory -ErrorAction 'SilentlyContinue' | Get-ChildItem -Filter '*.layout' -Recurse -File -ErrorAction 'SilentlyContinue' | Sort-Object -Property 'LastWriteTime' -Descending -ErrorAction 'SilentlyContinue' | ForEach-Object { $projFiles.Add([IO.Path]::ChangeExtension($_.FullName, 'cbp')) }  
  $projFiles
}

function Get-CompilerList {
  $compilers = [Collections.Generic.SortedList[string, object]]::new()
  Get-ChildItem $script:cbDir -Directory | Sort-Object -Property 'Name' | Where-Object { $_.Name -match '^llvm-mingw-(\d{8})-(.+)' } | ForEach-Object { $compilers[$matches[2]] = @{ 'timestamp' = $matches[1]; 'path' = $_.FullName } }  
  $compilers
}

function Get-MatchingCompilerKey {
  param(
    [Parameter(Mandatory = $True)][string[]]$compilerKeys
  )
  PROCESS {
    $archPatterns = @{ '0' = '-i686$'; '5' = '-armv7$'; '9' = '-x86_64$'; '12' = '-aarch64$' }  
    $systemInfo = [Int16[]]::new(32) # more than enough memory to alias a SYSTEM_INFO structure
    [P.Invoke]::GetNativeSystemInfo($systemInfo)
    $pattern = $archPatterns[[string]$systemInfo[0]] # the first element contains the SYSTEM_INFO::wProcessorArchitecture member
    if (-not $pattern) { return $compilerKeys | Select-Object -First 1 }
    $matching = $compilerKeys | Where-Object { $_ -match $pattern } | Select-Object -First 1
    if (-not $matching) { return $compilerKeys | Select-Object -First 1 }
    $matching
  }
}

function Get-LatestOnlineCompilerTimestamp {
  try {
    $oHttpReq = [Net.HttpWebRequest]::Create($script:urlLatest)
    $oHttpReq.AllowAutoRedirect = $False
    $oHttpReq.Method = 'HEAD'  
    $response = $oHttpReq.GetResponse()
    if ($response.StatusCode -eq 'Redirect') { $timeStamp = Split-Path $response.Headers['Location'] -Leaf }  
    $oHttpReq.Abort()
    $response.Close()
    $timeStamp
  }
  catch {}
}

function Resolve-RelativePath {
  param(
    [Parameter(Mandatory = $True)][string]$relPath
  )
  PROCESS {
    $relPath = $relPath.Trim()
    if (-not (Test-Path $relPath)) { return '' }  
    (Resolve-Path $relPath).Path
  }
}

function Get-PickedFolder {
  param(
    [Parameter(Mandatory = $True)][string]$msg
  )
  PROCESS {
    $oDir = (New-Object -ComObject 'Shell.Application').BrowseForFolder($script:hWnd, $msg, 0x201 <# BIF_RETURNONLYFSDIRS | BIF_NONEWFOLDERBUTTON #>, $PWD.Path)  
    if (-not $oDir) { return '' }  
    $oDir.Self.Path
  }
}

function Update-GUI {
  if (Test-ErrFlag $script:errCbDir) { $script:cbTxtBx.Text = '' }  
  else {
    $script:cbTxtBx.Text = Resolve-Path $script:cbDir -Relative
    if (Test-ErrFlag $script:errComp) {
      $script:compilerList = Get-CompilerList
      if ($script:compilerList.Count) {
        Clear-ErrFlag $script:errComp
        $script:compCoBx.ItemsSource = $script:compilerList.Keys
        $script:compCoBx.SelectedItem = Get-MatchingCompilerKey $script:compilerList.Keys
        $script:latestTimestamp = Get-LatestOnlineCompilerTimestamp
      }
    }
    if (Test-ErrFlag $script:errCbExe) {
      $script:cbExe = Join-Path $script:cbDir 'codeblocks.exe'  
      if (Test-Path $script:cbExe) {
        Clear-ErrFlag $script:errCbExe
        $script:mainWnd.Icon = [Windows.Interop.Imaging]::CreateBitmapSourceFromHIcon([Drawing.Icon]::ExtractAssociatedIcon($script:cbExe).Handle, [Windows.Int32Rect]::Empty, [Windows.Media.Imaging.BitmapSizeOptions]::FromEmptyOptions())
      }
    }
  }
  $script:projTxtBx.Text = $(if (Test-ErrFlag $script:errProjDir) { '' } else { Resolve-Path $script:projDir -Relative })  
  $script:aInclTxtBx.Text = $(if (Test-ErrFlag $script:errAInclDir) { '' } else { Resolve-Path $script:aInclDir -Relative })  
  $script:stsLnk.Text = ''  
  if ($script:errFlags) {
    $script:startBtn.IsEnabled = $False
    $script:stsTxt.Foreground = 'Red'  
    if (Test-ErrFlag $script:errCbDir) { $script:stsTxt.Text = $script:sId_10 }
    elseif (Test-ErrFlag $script:errProjDir) { $script:stsTxt.Text = $script:sId_11 }
    elseif (Test-ErrFlag $script:errAInclDir) { $script:stsTxt.Text = $script:sId_12 }
    elseif (Test-ErrFlag $script:errCbExe) { $script:stsTxt.Text = $script:sId_13 }
    else {
      $script:stsTxt.Text = "${script:sId_14}`n"  
      $script:stsLnk.Text = $script:urlLatest
    }
  }
  else {
    $script:startBtn.IsEnabled = $True
    $script:stsTxt.Foreground = 'White'  
    if ($script:latestTimestamp -gt $script:compilerList[$script:compCoBx.SelectedItem].timestamp) {
      $script:stsTxt.Text = "${script:sId_15}`n"  
      $script:stsLnk.Text = $script:urlLatest
    }
    else { $script:stsTxt.Text = $script:sId_16 }
  }
}

#region housekeeping
[void][P.Invoke]::ShowWindowAsync([P.Invoke]::GetConsoleWindow(), 0 <#SW_HIDE#>) # Try to hide the CLI as we only use the GUI.
try { [void][P.Invoke]::SetProcessDpiAwareness(2 <#PROCESS_PER_MONITOR_DPI_AWARE#>) } catch {} # Avoid blurry text.
Set-Location $script:scriptDir # However this script is invoked, make sure the current working directory is the script directory.
if (-not (Test-Path $script:prtblConf)) { New-PrtblConf }
$script:xmlConf = New-Object 'xml'  
$script:xmlConf.Load($script:prtblConf)
if ($script:xmlConf.CB_Portable_Launcher_Config.Version -ne '2') { New-PrtblConf }  
$script:cbDir = Resolve-RelativePath $script:xmlConf.CB_Portable_Launcher_Config.relative_paths.CBDir.'#cdata-section'  
if ($script:cbDir) { Clear-ErrFlag $script:errCbDir }
$script:projDir = Resolve-RelativePath $script:xmlConf.CB_Portable_Launcher_Config.relative_paths.ProjDir.'#cdata-section'  
if ($script:projDir) { Clear-ErrFlag $script:errProjDir }
$script:aInclDir = Resolve-RelativePath $script:xmlConf.CB_Portable_Launcher_Config.relative_paths.AdditionalIncludeDir.'#cdata-section'  
if ($script:aInclDir) { Clear-ErrFlag $script:errAInclDir }
& {
  if (Test-Path $script:i18nSpec) {
    $langName = [Globalization.CultureInfo]::CurrentUICulture.Name -replace '-', '_'  
    $xmlLocl = New-Object 'xml'  
    $xmlLocl.Load($script:i18nSpec)
    $sNodes = $xmlLocl.lang.$langName | Select-Object -First 1 | ForEach-Object { $_.s }
    if (-not $sNodes) { $sNodes = $xmlLocl.lang.($langName -replace '_.*') | Select-Object -First 1 | ForEach-Object { $_.s } }  
    if (-not $sNodes) { $sNodes = $xmlLocl.lang.ChildNodes | Where-Object { $_.get_name() -like ($langName -replace '_.*', '_*') } | Select-Object -First 1 | ForEach-Object { $_.s } }  
    if ($sNodes) { $sNodes | ForEach-Object { Set-Variable "sId_$($_.id)" $_.'#cdata-section' -Scope 'Script' } }  
  }
  else { New-I18nFile }
}
#endregion housekeeping

#region GUI elements
$script:mainWnd = [Windows.Markup.XamlReader]::Parse(@"  
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        Title="Code::Blocks Portable Launcher" FocusManager.FocusedElement="{Binding ElementName=compCoBx}" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">  
  <Window.Resources>
    <x:Double x:Key="CSpc">20</x:Double><x:Double x:Key="C1">180</x:Double><x:Double x:Key="C3">95</x:Double><x:Double x:Key="C6">75</x:Double>  
    <x:Double x:Key="RSpcN">3</x:Double><x:Double x:Key="RSpcW">8</x:Double><x:Double x:Key="RStc">30</x:Double><x:Double x:Key="RBox">26</x:Double><x:Double x:Key="RSts">55</x:Double>  
    <x:Double x:Key="LabelFontSize">12</x:Double><x:Double x:Key="BoxFontSize">14</x:Double>  
    <Color x:Key="AnthraciteBlack">#0f1012</Color><Color x:Key="AnthraciteShade">#1f2023</Color><Color x:Key="AnthraciteTint">#3f4144</Color>  
    <SolidColorBrush x:Key="AnthraciteBlackBrush" Color="{StaticResource AnthraciteBlack}" />  
    <SolidColorBrush x:Key="AnthraciteShadeBrush" Color="{StaticResource AnthraciteShade}" />  
    <LinearGradientBrush x:Key="AnthraciteGradientBrush" StartPoint="0,0" EndPoint="1,1">  
      <GradientStop Color="{StaticResource AnthraciteShade}" Offset="0.0" /><GradientStop Color="{StaticResource AnthraciteTint}" Offset="1.0" />  
    </LinearGradientBrush>
    <Style x:Key="LabelStyle" TargetType="Label">  
      <Setter Property="FontSize" Value="{StaticResource LabelFontSize}" />  
      <Setter Property="Foreground" Value="White" />  
      <Setter Property="VerticalAlignment" Value="Bottom" />  
    </Style>
    <Style x:Key="TextBoxStyle" TargetType="TextBox">  
      <Setter Property="FontSize" Value="{StaticResource BoxFontSize}" />  
      <Setter Property="Background" Value="WhiteSmoke" />  
      <Setter Property="VerticalContentAlignment" Value="Center" />  
      <Setter Property="IsReadOnly" Value="True" />  
      <Setter Property="IsTabStop" Value="False" />  
    </Style>
    <Style x:Key="ButtonStyle" TargetType="Button">  
      <Setter Property="FontSize" Value="{StaticResource BoxFontSize}" />  
      <Setter Property="FontWeight" Value="Bold" />  
      <Setter Property="Foreground" Value="White" />  
      <Setter Property="Background" Value="{StaticResource AnthraciteShadeBrush}" />  
      <Style.Resources>
        <Style TargetType="Border"><Setter Property="CornerRadius" Value="5" /></Style>  
      </Style.Resources>
      <Style.Triggers>
        <Trigger Property="IsFocused" Value="True"><Setter Property="BorderBrush" Value="WhiteSmoke" /></Trigger>  
        <Trigger Property="IsMouseOver" Value="True"><Setter Property="Foreground" Value="Black" /></Trigger>  
      </Style.Triggers>
    </Style>
    <Style x:Key="StatusStyle" TargetType="Label">  
      <Setter Property="FontSize" Value="{StaticResource LabelFontSize}" />  
      <Setter Property="Foreground" Value="White" />  
      <Setter Property="Background" Value="{StaticResource AnthraciteBlackBrush}" />  
      <Setter Property="VerticalContentAlignment" Value="Center" />  
      <Setter Property="Padding" Value="20,0,20,0" />  
    </Style>
  </Window.Resources>
  <Grid Background="{StaticResource AnthraciteGradientBrush}">  
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="{Binding Source={StaticResource CSpc}}" />  
      <ColumnDefinition Width="{Binding Source={StaticResource C1}}" />  
      <ColumnDefinition Width="{Binding Source={StaticResource CSpc}}" />  
      <ColumnDefinition Width="{Binding Source={StaticResource C3}}" />  
      <ColumnDefinition Width="{Binding Source={StaticResource CSpc}}" />  
      <ColumnDefinition Width="{Binding Source={StaticResource CSpc}}" />  
      <ColumnDefinition Width="{Binding Source={StaticResource C6}}" />  
      <ColumnDefinition Width="{Binding Source={StaticResource CSpc}}" />  
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="{Binding Source={StaticResource RStc}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcN}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RBox}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcW}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RStc}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcN}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RBox}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcW}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RStc}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcN}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RBox}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcW}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RStc}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcN}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RBox}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcW}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSpcW}}" />  
      <RowDefinition Height="{Binding Source={StaticResource RSts}}" />  
    </Grid.RowDefinitions>
    <Label Content="$script:sId_0" Grid.Column="1" Grid.ColumnSpan="6" Style="{StaticResource LabelStyle}" />  
    <TextBox x:Name="cbTxtBx" Grid.Column="1" Grid.Row="2" Grid.ColumnSpan="4" Style="{StaticResource TextBoxStyle}" />  
    <Button x:Name="cbBtn" Content="..." Grid.Column="6" Grid.Row="2" Style="{StaticResource ButtonStyle}" />  
    <Label Content="$script:sId_1" Grid.Column="1" Grid.Row="4" Grid.ColumnSpan="6" Style="{StaticResource LabelStyle}" />  
    <TextBox x:Name="projTxtBx" Grid.Column="1" Grid.Row="6" Grid.ColumnSpan="4" Style="{StaticResource TextBoxStyle}" />  
    <Button x:Name="projBtn" Content="..." Grid.Column="6" Grid.Row="6" Style="{StaticResource ButtonStyle}" />  
    <Label Content="$script:sId_2" Grid.Column="1" Grid.Row="8" Grid.ColumnSpan="6" Style="{StaticResource LabelStyle}" />  
    <TextBox x:Name="aInclTxtBx" Grid.Column="1" Grid.Row="10" Grid.ColumnSpan="4" Style="{StaticResource TextBoxStyle}" />  
    <Button x:Name="aInclBtn" Content="..." Grid.Column="6" Grid.Row="10" Style="{StaticResource ButtonStyle}" />  
    <Label Content="$script:sId_3" Grid.Column="1" Grid.Row="12" Grid.ColumnSpan="2" Style="{StaticResource LabelStyle}" />  
    <ComboBox x:Name="compCoBx" Grid.Column="1" Grid.Row="14" FontSize="{StaticResource BoxFontSize}" VerticalContentAlignment="Center" />  
    <Button x:Name="startBtn" Content="$script:sId_4" IsDefault="True" Grid.Column="3" Grid.Row="13" Grid.RowSpan="3" Style="{StaticResource ButtonStyle}" />  
    <Button x:Name="cancelBtn" Content="$script:sId_5" IsCancel="True" Grid.Column="5" Grid.Row="13" Grid.ColumnSpan="2" Grid.RowSpan="3" Style="{StaticResource ButtonStyle}" />  
    <Label Grid.Row="17" Grid.ColumnSpan="8" Style="{StaticResource StatusStyle}">  
      <TextBlock TextWrapping="Wrap"><Run x:Name="stsTxt" /><Hyperlink Focusable="False"><Run x:Name="stsLnk" /></Hyperlink></TextBlock>  
    </Label>
  </Grid>
</Window>
"@)  
$script:hWnd = [Windows.Interop.WindowInteropHelper]::new($script:mainWnd).EnsureHandle()
'cbTxtBx', 'cbBtn', 'projTxtBx', 'projBtn', 'aInclTxtBx', 'aInclBtn', 'compCoBx', 'startBtn', 'cancelBtn', 'stsTxt', 'stsLnk' | ForEach-Object { Set-Variable $_ $script:mainWnd.FindName($_) -Scope 'Script' }  
Update-GUI
#endregion GUI elements

#region GUI event handling
$updateAppearance = {
  $Win32_TRUE = 1
  [void][P.Invoke]::DwmSetWindowAttribute($script:hWnd, 20 <#DWMWA_USE_IMMERSIVE_DARK_MODE#>, [ref]$Win32_TRUE, 4 <#SizeOf($Win32_TRUE)#>) # dark title bar
  [P.Invoke]::UpdateAppUserModel($script:hWnd) # equip the window with an own AppUserModel ID so that the window icon becomes the taskbar icon
}

$pickFolder = {
  switch ($this.Name) {
    'cbBtn' {  
      $script:cbDir = Get-PickedFolder $script:sId_6
      Set-ErrFlag ($script:errCbExe -bOr $script:errComp)
      $script:compilerList = $script:compCoBx.ItemsSource = $null
      if ($script:cbDir) { Clear-ErrFlag $script:errCbDir } else { Set-ErrFlag $script:errCbDir }
      break
    }
    'projBtn' {  
      $script:projDir = Get-PickedFolder $script:sId_7
      if ($script:projDir) { Clear-ErrFlag $script:errProjDir } else { Set-ErrFlag $script:errProjDir }
      break
    }
    'aInclBtn' {  
      $script:aInclDir = Get-PickedFolder $script:sId_8
      if ($script:aInclDir) { Clear-ErrFlag $script:errAInclDir } else { Set-ErrFlag $script:errAInclDir }
    }
  }
  Update-GUI
}

$execMain = {
  if ((Get-Process 'codeblocks' -ErrorAction 'SilentlyContinue').Count) {  
    $script:stsTxt.Foreground = 'Red'  
    $script:stsTxt.Text = $script:sId_9
    $script:stsLnk.Text = ''  
  }
  else {
    $script:xmlConf.CB_Portable_Launcher_Config.relative_paths.CBDir.'#cdata-section' = $script:cbTxtBx.Text  
    $script:xmlConf.CB_Portable_Launcher_Config.relative_paths.ProjDir.'#cdata-section' = $script:projTxtBx.Text  
    $script:xmlConf.CB_Portable_Launcher_Config.relative_paths.AdditionalIncludeDir.'#cdata-section' = $script:aInclTxtBx.Text  
    $script:xmlConf.Save($script:prtblConf)
    $appdataDir = Join-Path $script:cbDir 'AppData'  
    $confFile = Join-Path $appdataDir 'codeblocks\default.conf'  
    if (-not (Test-Path $confFile)) {
      New-Item (Join-Path $appdataDir 'codeblocks') -ItemType 'Directory' -Force  
      New-ConfFile $confFile
    }
    $selectedComp = $script:compCoBx.SelectedItem
    $mingwDir = $script:compilerList[$selectedComp].path
    $gdb = 'gdb.exe'  
    if ($selectedComp -match '-i686$') { $gdb = Join-Path $script:cbDir 'gdb\i686\bin\gdb.exe' }  
    elseif ($selectedComp -match '-x86_64$') { $gdb = Join-Path $script:cbDir 'gdb\x86_64\bin\gdb.exe' }  
    $recentProjs = Get-RecentProjects
    Update-Conf $confFile $mingwDir $gdb $selectedComp ($recentProjs | Select-Object -First 20)
    $env:APPDATA = $appdataDir
    Start-Process $script:cbExe
    $recentProjs | ForEach-Object { Update-ProjFile $_ $selectedComp }
    $script:mainWnd.Close()
  }
}

$script:mainWnd.Add_Loaded($updateAppearance)
$script:cbBtn.Add_Click($pickFolder)
$script:projBtn.Add_Click($pickFolder)
$script:aInclBtn.Add_Click($pickFolder)
$script:startBtn.Add_Click($execMain)
$script:cancelBtn.Add_Click({ $script:mainWnd.Close() })
$script:stsLnk.Add_PreviewMouseLeftButtonDown({ Start-Process $this.Text })
#endregion GUI event handling

[void]$script:mainWnd.ShowDialog()

CodeBlocksPortable
    ├─ Additional_Include
    ├─ CodeBlocks
    │      ├─ llvm-mingw-20231031-msvcrt-i686
    │      ├─ llvm-mingw-20231031-msvcrt-x86_64
    │      ├─ share
    │      └─ (codeblocks.exe)
    │
    ├─ Projects
    └─ (CB-Portable-Launcher.ps1)

back-to-top5.1.1. How to save the script
As I learned, some people find it difficult to save the code from the forum as a PowerShell script.
  • Select and copy the entire code ([Ctrl]+[C]).
  • Open Windows Notepad. For example like this:
    • Key combination [Windows]+[R],
    • write notepad into the opened "Run" dialog,
    • Click OK.
  • Paste the copied code ([Ctrl]+[V]).
  • Menu File -> Save as...
  • Navigate to the CodeBlocksPortable directory
  • Select file type All files (*.*), file name CB-Portable-Launcher.ps1
  • Save.

back-to-top5.2. The first start
Now everything is ready for the first run. Depending on local settings, the script is started either from the context menu of the file or by double-clicking on "CB-Portable-Launcher.ps1". This will first check the environment and will certainly find that some files and directories are still missing. Don't worry, you will be guided through the next steps.
Click on the first [...] button. You will be asked for the "CodeBlocks" directory. Select it and confirm. The same with the two additional [...] buttons for the "Projects" and "Additional_Include" directories.
Click on the [Start] button.
Code::Blocks will now open and bother you again to confirm the default compiler "GNU GCC Compiler".
Attention! This message may be hidden behind other open windows, making it appear like Code::Blocks is not starting. In this case, minimize all open windows one after the other (or navigate through the windows with [Tab] while holding down the [Alt] key)...
(This procedure really only occurs at the first start.)

Done! Now you can finally get started ... face-wink

back-to-top6. CB-Portable-I18n.xml

This file was automatically created in the CodeBlocksPortable directory. The "I18n" in the file name is the usual abbreviation for "Internationalization". In addition to the English names for input fields, buttons, prompts and status messages, it contains a short explanation of how further translations can be added. So, if you are not a native English, feel free to add your own translation for the GUI.

I created a GitHub repository for this file. Users are welcome to contribute their translation strings in a pull request. Or just add your translation to this forum thread and I'll take care of the rest face-smile In any case, it might be worth checking whether there is already a translation in your mother tongue.

back-to-top7. Further information

back-to-top7.1. How does the script actually work?
Code::Blocks requires the file "default.conf", an XML file that contains all of the IDE's settings. This file would normally be created in the AppData directory of your user profile. The script first creates a prototype of this file in "CodeBlocksPortable\CodeBlocks\AppData\codeblocks" and saves the location of the compiler, your projects and other settings there. These settings are updated every time the script is started. Furthermore, the environment variable %AppData% for the codeblocks.exe process is changed so that Code::Blocks loads the modified "default.conf" instead of looking for it in your user profile. Code::Blocks is started in this environment and thus mutates into a "portable app".
Besides that, compiler-dependent target paths are used for the compiled apps. In addition to changes in the "default.conf", the corresponding target paths are also updated in all project files (*.cbp) in the "Projects" directory. This has the advantage that there are no errors for files that have not been recompiled for a different processor architecture, and that executables in the "bin" directory are also stored in different, appropriately named subdirectories for different architectures. In order for the project files to be updated, the projects must already be in the "Projects" directory when the script is started. Projects in the "_archive_" subdirectory (see bullet point 3.) are excluded from this update.

back-to-top7.2. Why am I not able to create a 64-bit program on a 32-bit system?
The compiler for 64-bit applications is itself a 64-bit program. Of course this doesn't run on a 32-bit system.
But it works the other way around. You can compile 32-bit programs on a 64-bit operating system. For this reason, you can select which compiler should be used in the launcher dialog.

back-to-top7.3. How can I keep the compiler up to date?
There is no automatic update function. However, the status line of the launcher dialog informs you when a new compiler version is available. Then proceed as described in bullet point 4.2. to add directories with the latest version. The old compiler directories can be deleted. The script only looks for the latest version anyway.

back-to-top7.4. When using an LLVM toolset, why is GCC selected as the default compiler?
I have to admit that this is actually contradictory. Please treat "GCC" as the profile name, rather than the compiler name in this case.
Based on the original guide, there are probably people who have been using this solution for years. In order to remain compatible with an existing "default.conf", I decided to stick with the name "GCC" for this profile. This is possible because the LLVM toolsets use lightweight wrapper apps that just have different names. Also included are the names of the GCC toolset. The command line options are also largely compatible, so I accept this flaw in favor of retaining the settings of long-term users.

back-to-top7.5. How can I debug my app in Code::Blocks?
The debugger is a notable exception of the above mentioned tool names. There is no gdb.exe available. The LLVM debugger is lldb.exe. However, this debugger is incompatible with Code::Blocks. For i686 and x86_64 processors are builds of GDB for Windows available.
I have chosen to use these (github.com/ssbssa/gdb/releases/latest).
Download the archives that meet patterns "gdb-<version>-i686.7z" and "gdb-<version>-x86_64.7z", respectively.
(e.g. gdb-14.1.90.20231204-i686.7z and gdb-14.1.90.20231204-x86_64.7z)
Create folder CodeBlocksPortable\CodeBlocks\gdb. Then create the two subfolders i686 and x86_64.
Copy the included bin directories of the downloaded 7z archives to the appropriate subfolders.
The folder structure should look like this now:
CodeBlocksPortable
    ├─ Additional_Include
    ├─ CodeBlocks
    │      ├─ AppData
    │      ├─ gdb
    │      │    ├─ i686
    │      │    │    └─ bin
    │      │    │         └─ (gdb.exe)
    │      │    └─ x86_64
    │      │         └─ bin
    │      │              └─ (gdb.exe)
    │      ├─ llvm-mingw-20231031-msvcrt-i686
    │      ├─ llvm-mingw-20231031-msvcrt-x86_64
    │      ├─ share
    │      └─ (codeblocks.exe)
    │
    ├─ Projects
    └─ (CB-Portable-Launcher.ps1)
Based on the selected compiler, the CB-Portable-Launcher script automatically updates the path to the debugger in the Code::Blocks configuration.


Change log:
  • 23.02.2024 Script: Make sure encoding of XML files is respected.
  • 04.02.2024 Script: Make sure the codeblocks.exe path is updated properly. Use the C::B icon.
  • 18.12.2023 Item 7.5. added for a debugging option. Script updated to make it work.
  • 16.11.2023 Script: Make sure the compiler list is updated properly.
  • 15.11.2023 Script: Slightly updated column widths and row heights to reflect the text lengths of translations.
  • 14.11.2023 Script: Automatic text wrapping added to the status bar because longer text is expected in translations
  • 12.11.2023 Item 6 GitHub link added, item 7.4. new
  • 11.11.2023 Initial release as a logical successor of (obsolet) CodeBlocks Portable mit MinGW 32 und 64 Bit unter Windows (German only)

Content-Key: 8746519955

Url: https://administrator.de/contentid/8746519955

Printed on: April 27, 2024 at 05:04 o'clock

Member: rubberman
rubberman Feb 23, 2024 at 21:30:32 (UTC)
Goto Top
A couple of minor updates have been made since I published this first. None of them were worth informing you about in a new post. Today, however, I fixed a bug that occurred if both the script was run in PowerShell 5.1 and updated XML files contained UTF-8 multibyte characters. These characters were corrupted in such a way that the file size was bloated every time more.

BR
Steffen