Page 1 of 1

Translate ini-based portable AkelPad into the registry-based version at a glance (PowerShell script)

Posted: Thu Apr 10, 2025 12:05 am
by ewild
ini_toRegistry_AkelPad.ps1

Code: Select all

# https://akelpad.sourceforge.net/forum/viewtopic.php?p=36777#p36777
$time = [diagnostics.stopwatch]::StartNew()
$unicode = [Text.Encoding]::Unicode
$n = [Environment]::NewLine

# basics
$AppBrand  = 'HKCU:\SOFTWARE\Akelsoft'
$AppName   = 'AkelPad'
$AppINI    = 'AkelPad.ini'
$AppPath   = 'c:\literal\path\AppName'
# addons
$AppFiles  = 'AkelFiles'
$AppAddons = 'Plugs'
# items that require special treatment
$AppSubsection = 'QSearch';$subsection = 'FindHistory' # subsections
$AppExtra = 'Scripts';$extra = 'Settings' # keys with no value
$MultiString = 'Recent' # MultiString in Registry ~ Piped|String in ini
$StringBinary = 'Search' # Strings in Registry ~ Binary in ini
# placeholders
$options = 'Options' # default ini section name
$noname  = 'Noname' # temporary section name for ini with no sections
$dummy   = 'no_value_ini_key' # temporary value for keys with no values

# avoid ini within specified location, if needed
$avoid = 1 # pointer to avoid (1) or not (0)
switch ($avoid){
0 {$avoid = $null} # nothing to avoid
1 {$avoid = "*$AppAddons\$AppExtra\*"}} # avoid path like this

# get iniPath, trying consecutively: literal, current, parent, grandparent, great-grandparent paths
$literal=[IO.DirectoryInfo]$AppPath;$current=[IO.DirectoryInfo]$pwd.path
$try=$literal,$current,$current.Parent,$current.Parent.Parent,$current.Parent.Parent.Parent|foreach{if($_.exists){$_}}
switch ($try){{(Get-ChildItem -path $_ -file -recurse -force -filter $AppINI -EA:Silent|
sort LastWriteTime -bottom 1|Tee -var script:iniPath)}{break}}
if ($iniPath){$iniPath=$iniPath.DirectoryName}

# if no ini path, nothing to do here; otherwise process
if (-not($iniPath)){
'no ini data, nothing to do here...'|Write-Host -f Yellow} else {

'processing...'|Write-Host -f Yellow

# display iniPath
$iniPath|Write-Host -f Cyan

function Get-IniContent ($file){ # get ini file contents as hashtable

# get ini file raw contents
$read = [IO.StreamReader]::new($file) # create stream
$text = $read.ReadToEnd();$read.close();$read.dispose() # read, close and dispose

# regex patterns
$matchSections = '^\[(.+)\]'     # match ini section
$matchKeyValue = '(.+?)\s*=(.*)' # match ini key=value pair

# add [noname] section if not any
if ($text -notmatch $matchSections){$text = ('[{0}]' -f $noname)+$n+$text}

# initialize ordered hashtable
$ini = [ordered]@{}

# get ini sections, keys, and values
switch ($text -split $n){

# ini sections
{$_[0] -eq '[' -and $_[-1] -eq ']'}{
$section=$_.trim('[]')
$ini.$section = [ordered]@{};$i=1;continue}

# ini key=value pairs
{$_.contains('=')}{
$match = [regex]::matches($_,$matchKeyValue)
$key = $match.Groups[1].value
$value = $match.Groups[2].value
$ini.$section.$key = $value;continue}

# other ini keys, if any
default{
$key = $_;if ($key){
$ini.$section.$key = $dummy+$i;$i++}}

} # end of switch

$ini

} # end of function

# get ini files within iniPath
$files = Get-ChildItem -path $iniPath -file -recurse -force -filter *.ini|Where{$_.FullName -notlike $avoid}

# process each ini file
$list = foreach ($file in $files){

# get ini file path parts
$name = [IO.Path]::GetRelativePath($iniPath,$file)
$leaf = $file.DirectoryName|Split-Path -leaf

# get ini file contents
$ini = Get-IniContent $file

# get ini data section by section
foreach ($section in $ini.keys){

# get registry path for each section regarding app structure
$root = [IO.Path]::combine($AppBrand,$AppName)
switch ($leaf){$AppAddons{if ($section -notin $subsection){
$path = [IO.Path]::combine($root,$AppAddons,$file.BaseName)} if ($section -in $subsection -and $file.BaseName -in $AppSubsection){
$path = [IO.Path]::combine($root,$AppAddons,$file.BaseName,$section)}} $AppExtra {
$path = [IO.Path]::combine($root,$AppAddons,$leaf,$extra,$file.BaseName)} default {
$path = [IO.Path]::combine($root,$section)}} # end of switch

# process keys and values for each section
foreach ($key in $ini.$section.keys){

# reset loop variables
$value = $bytes = $type = $null

# get key property
$property = $ini.$section.$key

# get key value and type
switch ($property){

# Binary: if property fits specified match, is odd
{$_ -match '^[a-fA-F0-9]+$' -and $_.length % 2 -eq 0}{
$bytes = [convert]::fromHexString($_)
$value = [byte[]]$bytes
$type  = 'Binary'}

# DWord: if property fits specified match, maximum length, and magnitude
{$_ -match '^[0-9]+$' -and $_.length -le 10 -and $_/1 -le 4294967295}{
$value = [uint32]$property
$type  = 'DWord'}

# None: if property has no actual value
{$_ -match $dummy}{
$value = [byte[]]@()
$type  = 'None'}

default{

# MultiString: if key is in specific section
if ((-not ($value)) -and $section -eq $MultiString){
$value = [array]$_.split('|')
$type = 'MultiString'}

# String: if no property type defined so far
else{
$value = [string]$_
$type = 'String'}}

} # end of switch

# specific section: type=String, value=String via Binary bytes
if ($section -eq $StringBinary){
$type  = 'String'
$bytes = [convert]::fromHexString($property)
$value = $unicode.GetString($bytes)}

# data collection
[PSCustomObject][Ordered]@{
Ini     = $name
Section = $section
Path    = $path
Key     = $key
Type    = $type
Value   = $value}

}}} # end of keys, sections, files loops

# remove previous registry entries, if any
if ($AppBrand|Test-Path){Remove-Item $AppBrand -recurse -force -EA:Silent}

# write keys and values to the registry
foreach ($ini in $list){
if (-not ($ini.Path|Test-Path)){New-Item -path $ini.Path -force|Out-null}
Set-ItemProperty -path $ini.Path -name $ini.Key -value $ini.Value -type $ini.Type -force}

# display brief report
foreach ($ini in ($list|Select Ini,Section,Path|Sort * -unique|Group Ini|Sort Name -desc)){
''
$ini.Name|Write-Host -f Cyan
foreach ($section in $ini.group){
'[{0}] ' -f $section.Section|Write-Host -f DarkCyan -no;$section.Path|Write-Host -f Green}}
''
'{0} data entries written to the registry from {1} ini files ({2} sections)' -f
$list.count,$files.count,($list|Group Path).count|Write-Host -f DarkCyan

} # end of process

# finalizing
$time.Stop()
'{0:mm\:ss\.fff} by {1}' -f $time.Elapsed,
$MyInvocation.MyCommand.Name|Write-Host -f DarkCyan
''
if ($error){'errors {0}' -f $error.count;'';$error|foreach{'error {0}' -f ++$i;$_}}

sleep -s 33

For me, the script runs like a charm.
No significant errors or misconfigurations have been identified in my highly customized portable AkelPad.
For me script reports:
488 data entries written to the registry from 16 ini files (21 sections)
00:05.214 by ini_toRegistry_AkelPad.ps1
Notes:
  • The script can use a literal path to AkelPad.ini and/or define it by itself, from a location up to three levels below AkelPad.ini
  • The script can also transfer data from the AkelPad\AkelFiles\Plugs\Scripts\*.ini
    By default, this option is switched off.
    If needed, a user can switch this option on in the corresponding script section (see $avoid switch).
  • The script is written for the 64-bit AkelPad under 64-bit Windows.
  • The script is written for the cross-platform PowerShell (7+).
  • The sister script:
    PowerShell: translate registry-based AkelPad into the ini-based portable version at a glance
    https://akelpad.sourceforge.net/forum/v ... 803#p36803

Re: PowerShell: transferring ini-based portable AkelPad version into the registry-based settings version

Posted: Thu Apr 10, 2025 1:05 am
by ewild
Other useful neighboring scripts:

to backup AkelPad's registry data into a regular .reg file:
AkelPad_RegistryBackup.ps1

Code: Select all

$time = [diagnostics.stopwatch]::StartNew()
$stamp = Get-Date -format "yyyyMMdd_HHmmss"

$registry = @('HKCU:\SOFTWARE\Akelsoft')
$backup = 'Backup'
if (-not ($backup|Test-Path)){New-Item -path $backup -type directory -force|Out-null}

foreach ($path in $registry){
$file = [IO.Path]::combine($pwd,'Backup','AkelPad_RegistryBackup_'+(Split-Path $path -leaf)+'_'+$stamp+'.reg')
if ($path -like 'HKCU:*'){$hkey = $path.Replace(':','')}
reg export $hkey $file /y
}
''
$time.Stop()
'{0} registry entry(ies) backed up for {1:mm}:{1:ss}.{1:fff}' -f $registry.count,$time.Elapsed|Write-Host -f DarkCyan
''
sleep -s 3
to completely remove AkelPad's registry data:
AkelPad_RegistryRemove.ps1

Code: Select all

$time = [diagnostics.stopwatch]::StartNew()
$registry = @('HKCU:\SOFTWARE\Akelsoft')

foreach ($path in $registry){$path|Write-Host -f DarkGray;''
if ($path|Test-Path){Remove-Item $path -recurse -force -verbose -EA:Silent;''
if (!($path|Test-Path)){'registry cleanup performed successfully'|Write-Host -f Green}}
else {'does not exist; no action required'|Write-Host -f Yellow}}
''
$time.Stop()
'{0} registry entry(ies) processed for {1:mm}:{1:ss}.{1:fff}' -f $registry.count,$time.Elapsed|Write-Host -f DarkCyan
''
sleep -s 7

Re: PowerShell: translate ini-based portable AkelPad to the registry-based one at a glance

Posted: Wed Jul 09, 2025 11:24 am
by ewild
The main script has been rewritten.