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
No significant errors or misconfigurations have been identified in my highly customized portable AkelPad.
For me script reports:
Notes:488 data entries written to the registry from 16 ini files (21 sections)
00:05.214 by ini_toRegistry_AkelPad.ps1
- 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