(PS) Script to Split Reg/InI file into multiple files

Discussion in 'Scripting' started by KNARZ, Jan 18, 2020.

  1. KNARZ

    KNARZ MDL Addicted

    Joined:
    Oct 9, 2012
    Messages:
    896
    Likes Received:
    475
    Trophy Points:
    30
    Hey,
    unfortunatly I'm getting on my limits with Batch and I should have learned PowerShell long time ago but I'm not confident enough with PowerShell. So it would be nice if someone can help me starting some problem I have.

    I have several Reg files (Reg are similar InI files) which contain many different registry keys.
    I want to split every key with Value into a single file.

    example Source:
    regfile-blob:
    Code:
    [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System]
    "WallpaperStyle"="5"
    
    [HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\DriverSearching]
    "DontSearchCD"=dword:00000001
    "DontSearchFloppies"=dword:00000001
    "DontSearchWindowsUpdate"=dword:00000001
    
    I want to export this one file blob into a folder structure (like the reg key) and many flat files.
    So based on the example a File like/in should be generated: C:\User\Software\Policies\Microsoft\Windows\DriverSearching.reg
    DriverSearching than contains:.
    Code:
    [HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\DriverSearching]
    "DontSearchCD"=dword:00000001
    "DontSearchFloppies"=dword:00000001
    "DontSearchWindowsUpdate"=dword:00000001
    
    If file exisit (because auf mutiple files can contain same content) a number should be added (DriverSearching1.reg)
    C:\User\Software\Microsoft\Windows\CurrentVersion\Policies\System.reg
    Commented (;) lines should NOT be skipped and just added to the section / regkey

    So to clearify: Every full regkey should be one single file and can contain mutiple value and data, everything after [KEY] should just be added into the file (up till the next key)
    It may also shloud be possible to skip/ignore the prefix of HKLM / HKCU
     
  2. Thomas Dubreuil

    Thomas Dubreuil MDL Senior Member

    Joined:
    Aug 29, 2017
    Messages:
    357
    Likes Received:
    594
    Trophy Points:
    10
    #2 Thomas Dubreuil, Jan 19, 2020
    Last edited: Jan 19, 2020
    something like this should get you started, I added comments to make it easier to modify...
    Code:
    @echo off
    
    set "filetoparse=%~dpnx1"
    setlocal EnableDelayedExpansion
    REM parse registry file adding line number
        for /f "tokens=1* delims=:" %%a IN ('findstr /n "^" "%filetoparse%"') do (
            set "linenumber=%%a"
            set "regkey=%%b"
            if "!regkey:~0,3!"=="[HK" ( call :loopprocess )
        )
    pause
    exit /b
    
    :loopprocess
        set "var1=!regkey!"
        set "var2=!regkey!"
        set "tok=0"
    REM count tokens
    :loopprocess2
        for /F "tokens=1* delims=[\]" %%G in ( "!var1!" ) do (
          set /A "tok+=1"
          set var1=%%H
          goto loopprocess2
        )
    REM echo The string contains !tok! tokens.
    REM get last token name
        for /F "tokens=%tok% delims=[\]" %%J in ( "!var2!" ) do ( set "last=%%J" )
    REM create reg file with last token name
        echo Windows Registry Editor Version 5.00>"%~dp0%last%.reg"
        echo: >>"%~dp0%last%.reg"
        echo !regkey!>>"%~dp0%last%.reg"
    REM parse next lines
    :loopprocess3
        set /a "linenumber+=1"
        for /f "tokens=1* delims=:" %%S IN ('findstr /n "^" "%filetoparse%"') do (
            set "newlinenumber=%%S"
            set "linevalue=%%T"
            if "!newlinenumber!"=="!linenumber!" (
    REM exit loop if next line is a registry key
                if "!linevalue:~0,3!"=="[HK" ( goto :eof )
    REM print next line if line exist
                if defined linevalue (
                    echo !linevalue!>>"%~dp0%last%.reg"
                    goto :loopprocess3
        )))
    goto :eof
    for HKLM not sure what you meant...You can do string replacement before echoing the key, something like this
    REM create reg file with last token name
    set "regkey=!regkey:HKEY_CURRENT_USER=HKCU!"
    set "regkey=!regkey:HKEY_LOCAL_MACHINE=HKLM!"
     
  3. KNARZ

    KNARZ MDL Addicted

    Joined:
    Oct 9, 2012
    Messages:
    896
    Likes Received:
    475
    Trophy Points:
    30
    Thank you VERY much for the starting! Really appricated. I thought it would be easier in PS than Batch but I take what I get as this is only for internal Tools..
    I modded and tweaked the script in some areas (we have differnt coding style ^^ - I like to work will call and you seem a pro with recursiv loops)

    I figured 2 problems.
    1) Delete with [-HK is not yet supported (but I can solve this on my own (just skip loop 3)
    2) It works but I don't like that in loop3 every line starts a new instance of findstr. - Do you have some other solution in mind?

    (IncrementFileName isn't working yet.. it's more like a ToDo currently)

    I would have all done it very different ^^ but I like the approach!

    Code:
    @echo off
    setlocal EnableDelayedExpansion
    
    set "filetoparse=%~dpnx1"
    REM parse registry file adding line number
    for /f "tokens=1* delims=:" %%a IN ('findstr.exe /n "^" "%filetoparse%"') do (
        set "linenumber=%%a"
        set "regfileline=%%b"
        if "!regfileline:~0,3!"=="[HK"    (call :loopprocess %%a %%b)
        if "!regfileline:~0,4!"=="[-HK"    (call :delete)
    )
    exit /b
    
    :loopprocess
        set "regkey=!regfileline!"
        set "tok=0"
    REM count tokens
    :loopprocess2
    for /F "tokens=1* delims=[\]" %%G in ( "!regkey!" ) do (
        if !tok! EQU 0 (set reg_path=%%H)
        REM echo !tok! %%G
        set /A "tok+=1"
        set regkey=%%H
        set last=%%G
        goto loopprocess2
    )
    call :CleanUpRegPath
    call :FolderCheckAndCreate
    call :CreateFile
    call :loopprocess3
    goto :EOF
    
    
    REM parse next lines
    :loopprocess3
    set /a "linenumber+=1"
    for /f "tokens=1* delims=:" %%S IN ('findstr.exe /n "^" "%filetoparse%"') do (
        set "newlinenumber=%%S"
        set "linevalue=%%T"
        if "!newlinenumber!"=="!linenumber!" (
            REM exit loop if next line is a registry key
            if "!linevalue:~0,3!"=="[HK"    (goto :EOF)
            if "!linevalue:~0,4!"=="[-HK"    (goto :EOF)
            REM print next line if line exist
            if defined linevalue (
                echo !linevalue!>>"%reg_export%"
                goto :loopprocess3
            )
        )
    )
    goto :eof
    
    :CleanUpRegPath
    set "reg_path=!reg_path:[=!"
    set "reg_path=!reg_path:]=!"
    set "reg_path=!reg_path:\%last%=!"
    goto :EOF
    
    :FolderCheckAndCreate
    set "RegFullPath=%~dp0!reg_path!"
    if not exist "%RegFullPath%" (mkdir "%RegFullPath%")
    goto :EOF
    
    :CreateFile
    set "reg_export=%RegFullPath%\%last%.reg"
    if exist "%reg_export%" (call :IncrementFileName "%last%")
    echo Windows Registry Editor Version 5.00>"!reg_export!"
    echo: >>"!reg_export!"
    echo !regfileline!>>"!reg_export!"
    goto :EOF
    
    :IncrementFileName
    for /l %%i in (1 1 99) do (if NOT exist "%RegFullPath%\%last%_%%i.reg" (set "last=%last%_%%i" && goto :EOF))
    goto :EOF
     
  4. KNARZ

    KNARZ MDL Addicted

    Joined:
    Oct 9, 2012
    Messages:
    896
    Likes Received:
    475
    Trophy Points:
    30
    I am working on the problem 2 with all the findstr. instances. I pretty close but I have another problem I never got it right to solve.
    If I want to expand a variable which is part of a !! variable I don't know how the expand it.

    I need to resolve (get the data of): %section%_Line_!%section%_count! which "might result" in !%section%_Line_!%section%_count!!
    But double !!var!! does not work that way. I know this but never found a solution to it - any Idea?

    Current Version:
    Code:
    @echo off
    setlocal EnableDelayedExpansion
    set "filetoparse=%~dpnx1"
    
    REM parse registry file adding line number
    for /f "tokens=1* delims=:" %%a IN ('findstr.exe /n "^" "%filetoparse%"') do (
        set "linenumber=%%a"
        set "regfileline=%%b"
        if "!regfileline:~0,3!"=="[HK"    (call :loopprocess %%a %%b)
        if "!regfileline:~0,4!"=="[-HK"    (call :delete)
    )
    exit /b
    
    :loopprocess
        set "regkey=!regfileline!"
        set "tok=0"
    REM count tokens
    :loopprocess2
    for /F "tokens=1* delims=[\]" %%G in ( "!regkey!" ) do (
        if !tok! EQU 0 (set reg_path=%%H)
        REM echo !tok! %%G
        set /A "tok+=1"
        set regkey=%%H
        set last=%%G
        set section=%%G
        goto loopprocess2
    )
    call :CleanUpRegPath
    call :FolderCheckAndCreate
    call :CreateFile
    call :InitLoop
    call :ValueLoop
    goto :EOF
    
    :InitLoop
    set /a "%section%_Count=0"
    for /f "tokens=1* delims=:" %%S IN ('findstr.exe /n "^" "%filetoparse%"') do (
        set /a "%section%_Count+=1"
        REM echo Count: !%last%_count!
        set "%section%_Line_!%section%_count!=%%S"
        set "%section%_LineValue_!%section%_count!=%%T"
    )
    goto :EOF
    
    :ValueLoop
    set /a "linenumber+=1"
    echo Maxcount: !%section%_Count!
    echo "!%section%_Line_!%section%_count!!"
    echo  "%section%_Line_!%section%_count!"=="!linenumber!"
    for /l %%i in (1 1 !%section%_Count!) do (
        if "%section%_Line_!%section%_count!"=="!linenumber!" (
            REM exit loop if next line is a registry key
            if "!section%_LineValue_!%section%_count:~0,3!"=="[HK"    (goto :EOF)
            if "!section%_LineValue_!%section%_count:~0,4!"=="[-HK"    (goto :EOF)
            REM print next line if line exist
            if defined %section%_LineValue_!%section%_count! (
                REM echo !%Last%_linevalue!
                echo !%section%_LineValue_!%section%_count!!>>"%reg_export%"
                goto :ValueLoop
            )
    )
    goto :EOF
    
    :CleanUpRegPath
    set "reg_path=!reg_path:[=!"
    set "reg_path=!reg_path:]=!"
    set "reg_path=!reg_path:\%last%=!"
    goto :EOF
    
    :FolderCheckAndCreate
    set "RegFullPath=%~dp0!reg_path!"
    if not exist "%RegFullPath%" (mkdir "%RegFullPath%")
    goto :EOF
    
    :CreateFile
    set "reg_export=%RegFullPath%\%last%.reg"
    REM if exist "%reg_export%" (call :IncrementFileName "%last%")
    echo Windows Registry Editor Version 5.00>"!reg_export!"
    echo: >>"!reg_export!"
    echo !regfileline!>>"!reg_export!"
    goto :EOF
    
     
  5. GodHand

    GodHand MDL Addicted

    Joined:
    Jul 15, 2016
    Messages:
    534
    Likes Received:
    880
    Trophy Points:
    30
    If I have the time today, and a full solution has not been posted, I will post how this can be done in PowerShell by just using regular expressions within a single loop process.
     
  6. KNARZ

    KNARZ MDL Addicted

    Joined:
    Oct 9, 2012
    Messages:
    896
    Likes Received:
    475
    Trophy Points:
    30
    That would be great as this would be a nice challenge for me to learn from your code (have it side by side)
    I also would donate like 5-10$ via paypal to each of you or donate it to some NGO.
     
  7. KNARZ

    KNARZ MDL Addicted

    Joined:
    Oct 9, 2012
    Messages:
    896
    Likes Received:
    475
    Trophy Points:
    30
    #7 KNARZ, Jan 19, 2020
    Last edited: Jan 19, 2020
    (OP)
    Pretty much done.

    Code:
    @echo off
    setlocal EnableDelayedExpansion
    set "filetoparse=%~dpnx1"
    
    REM shift the file into variables
    for /f "tokens=1* delims=:" %%a IN ('findstr.exe /n "^" "%filetoparse%"') do (
        set /a "Count+=1"
        set "Line_!count!=%%a"
        set "LineValue_!count!=%%b"
    )
    REM parse registry file adding line number
        for /f "tokens=1* delims=:" %%a IN ('findstr.exe /n "^" "%filetoparse%"') do (
        set "linenumber=%%a"
        set "regfileline=%%b"
        if "!regfileline:~0,3!"=="[HK"    (call :loopprocess)
        if "!regfileline:~0,4!"=="[-HK"    (call :delete)
    )
    echo Done
    exit /b
    
    :loopprocess
        set "regkeypath=!regfileline!"
        set "tok=0"
    REM count tokens
    :loopprocess2
    for /F "tokens=1* delims=[\]" %%G in ( "!regkeypath!" ) do (
        if !tok! EQU 0 (set reg_path=%%H)
        set /A "tok+=1"
        set regkeypath=%%H
        set lastregkeypath=%%G
        goto loopprocess2
    )
    call :CleanUpRegPath reg_path
    call :FolderCheckAndCreate reg_path
    call :CreateBaseFile
    call :RegValueLoop !linenumber!
    goto :EOF
    exit /b
    
    :RegValueLoop
    set /a "linenumber+=1"
    for /l %%i in (%~1 1 %Count%) do (
        REM set "CurrentLine="
        REM set "CurrentLineValue=
        if "!Line_%%i!" == "!linenumber!" (
        REM exit loop if next line is a registry key
        if "!LineValue_%%i:~0,3!"=="[HK"    (goto :EOF)
        if "!LineValue_%%i:~0,4!"=="[-HK"    (goto :EOF)
        REM print next line if line exist
        if defined LineValue_%%i (
            echo !LineValue_%%i!>>"%reg_export%"
        ) else (
            echo.>>"%reg_export%"
        )
        goto :RegValueLoop
        )
    )
    goto :EOF
    
    :CleanUpRegPath
    set "%~1=!%~1:[=!"
    set "%~1=!%~1:]=!"
    set "%~1=!%~1:\%lastregkeypath%=!"
    goto :EOF
    
    :FolderCheckAndCreate
    set "RegFullPath=%~dp0!%~1!"
    if not exist "%RegFullPath%" (mkdir "%RegFullPath%")
    goto :EOF
    
    :CreateBaseFile
    set "reg_export=%RegFullPath%\%lastregkeypath%.reg"
    REM if exist "%reg_export%" (call :IncrementFileName "%last%")
    echo Windows Registry Editor Version 5.00>"!reg_export!"
    echo. >>"!reg_export!"
    echo !LineValue_%linenumber%!>>"%reg_export%"
    goto :EOF
    
     
  8. Thomas Dubreuil

    Thomas Dubreuil MDL Senior Member

    Joined:
    Aug 29, 2017
    Messages:
    357
    Likes Received:
    594
    Trophy Points:
    10
    #8 Thomas Dubreuil, Jan 19, 2020
    Last edited: Jan 20, 2020
    Increment file name "windows style" : name (1) name (2) etc...

    Code:
    :CreateBaseFile
        set "reg_export=%RegFullPath%\%lastregkeypath%.reg"
        if not exist "%reg_export%" ( call :Exportfile ) else ( call :IncrementFileName )
        goto :EOF
    
    :IncrementFileName
        setlocal EnableDelayedExpansion
        set "baseName=%lastregkeypath% ("
        set "n=0"
    :Incrementloop
        for /f "delims=" %%F in (
          '2^>nul dir /b /ad "%baseName%*)"^|findstr /xri /c:"%baseName%[0-9]*)"'
        ) do (
          set "name=%%F"
          set "name=!name:*%baseName%=!"
          set "name=!name:)=!"
          echo !name!
          if !name! gtr !n! set "n=!name!"
        )
        set /a n+=1
        set "newregkeypath=%baseName%%n%"
        set "reg_export=%RegFullPath%\%newregkeypath%).reg"
        if exist "%reg_export%" ( goto :Incrementloop )
    
    :Exportfile
        echo Windows Registry Editor Version 5.00>"!reg_export!"
        echo. >>"!reg_export!"
        echo !LineValue_%linenumber%!>>"%reg_export%"
        goto :EOF
    Full script with much less lines...
    Code:
    @echo off
    
    set "filetoparse=%~dpnx1"
    setlocal EnableDelayedExpansion
    for /f "tokens=1* delims=:" %%a IN ('findstr /n "^" "%filetoparse%"') do (
        set "linenumber=%%a"
        set "regkey=%%b"
        if "!regkey:~0,3!"=="[HK" ( call :loopprocess )
        if "!regkey:~0,4!"=="[-HK" ( call :delete )
    )
    <nul set /p DummyName=Done.
    timeout /t 2 /nobreak >nul 2>&1
    exit /b
    
    :loopprocess
    set "var1=!regkey!" &set "var2=!regkey!" &set "tok=0"
    :loopprocess2
    for /F "tokens=1* delims=[\]" %%G in ( "!var1!" ) do (
      set /A "tok+=1"
      set "var1=%%H"
      goto :loopprocess2
    )
    for /F "tokens=%tok% delims=[\]" %%J in ( "!var2!" ) do ( set "lastregkeypath=%%J" )
    set "RegFullPath=!regkey:HKEY_CURRENT_USER\=!" &set "RegFullPath=!RegFullPath:HKEY_LOCAL_MACHINE\=!"
    set "RegFullPath=!RegFullPath:HKCU\=!" &set "RegFullPath=!RegFullPath:HKLM\=!"
    set "RegFullPath=!RegFullPath:[=!" &set "RegFullPath=!RegFullPath:]=!"
    set "RegFullPath=!RegFullPath:%lastregkeypath%=!"
    set "RegFullPath=%~dp0!RegFullPath!"
    if not exist "%RegFullPath%" ( mkdir "%RegFullPath%" 2>nul )
    set "reg_export=%RegFullPath%\%lastregkeypath%.reg"
    if exist "!reg_export!" ( call :IncrementFileName )
    call :Exportfile
    :loopprocess3
    set /a "linenumber+=1"
    for /f "tokens=1* delims=:" %%S IN ('findstr /n "^" "%filetoparse%"') do (
        set "newlinenumber=%%S"
        set "linevalue=%%T"
        if "!newlinenumber!"=="!linenumber!" (
            if "!linevalue:~0,3!"=="[HK" ( goto :eof )
            if "!linevalue:~0,4!"=="[-HK" ( goto :eof )
            if defined linevalue (
                echo !linevalue!>>"!reg_export!"
                goto :loopprocess3
    )))
    goto :EOF
    
    :IncrementFileName
    set "baseName=%lastregkeypath% ("
    set "n=0"
    :Incrementloop
    for /f "delims=" %%F in (
      '2^>nul dir /b /ad "%baseName%*)"^|findstr /xri /c:"%baseName%[0-9]*)"'
    ) do (
      set "name=%%F"
      set "name=!name:*%baseName%=!"
      set "name=!name:)=!"
      echo !name!
      if !name! gtr !n! set "n=!name!"
    )
    set /a n+=1
    set "newregkeypath=%baseName%%n%"
    set "reg_export=%RegFullPath%\%newregkeypath%).reg"
    if exist "!reg_export!" ( goto :Incrementloop )
    goto :EOF
    
    :Exportfile
    echo Windows Registry Editor Version 5.00>"!reg_export!"
    echo: >>"!reg_export!"
    echo !regkey!>>"!reg_export!"
    goto :EOF
    Just saw now your comment about the findstr thingy...
    We can maybe use skip to skip "n" lines, but don't know if would be faster, you'll have to make a for /f loop anyway.

    ok last one :)
    Code:
    @echo off
    
    set "filetoparse=%~dpnx1"
    setlocal EnableDelayedExpansion
    for /f "tokens=1* delims=:" %%a IN ('findstr /n "^" "%filetoparse%"') do (
        set "linenumber=%%a"
        set "regfileline=%%b"
        if "!regfileline:~0,3!"=="[HK" ( call :loopprocess )
        if "!regfileline:~0,4!"=="[-HK" ( call :delete )
    )
    <nul set /p DummyName=Done.
    timeout /t 2 /nobreak >nul 2>&1
    exit /b
    
    :loopprocess
    set "var1=!regfileline!" &set "var2=!regfileline!" &set "tok=0"
    :loopprocess2
    for /F "tokens=1* delims=[\]" %%G in ( "!var1!" ) do (
      set /A "tok+=1"
      set "var1=%%H"
      goto :loopprocess2
    )
    for /F "tokens=%tok% delims=[\]" %%J in ( "!var2!" ) do ( set "lastregkeypath=%%J" )
    set "RegFullPath=!regfileline:HKEY_CURRENT_USER\=!" &set "RegFullPath=!RegFullPath:HKEY_LOCAL_MACHINE\=!"
    set "RegFullPath=!RegFullPath:HKCU\=!" &set "RegFullPath=!RegFullPath:HKLM\=!"
    set "RegFullPath=!RegFullPath:[=!" &set "RegFullPath=!RegFullPath:]=!"
    set "RegFullPath=!RegFullPath:%lastregkeypath%=!"
    set "RegFullPath=%~dp0!RegFullPath!"
    if not exist "%RegFullPath%" ( mkdir "%RegFullPath%" 2>nul )
    set "reg_export=%RegFullPath%\%lastregkeypath%.reg"
    if exist "!reg_export!" ( call :IncrementFileName )
    call :Exportfile
    :loopprocess3
    for /f "skip=%linenumber% usebackq delims=" %%S in ( "%filetoparse%" ) do (
        set "linevalue=%%S"
        if "!linevalue:~0,3!"=="[HK" ( goto :eof )
        if "!linevalue:~0,4!"=="[-HK" ( goto :eof )
        if defined linevalue (
            echo !linevalue!>>"!reg_export!"
            set /a "linenumber+=1"
            goto :loopprocess3
    ))
    goto :EOF
    
    :IncrementFileName
    set "baseName=%lastregkeypath% ("
    set "n=0"
    :Incrementloop
    for /f "delims=" %%F in (
      '2^>nul dir /b /ad "%baseName%*)"^|findstr /xri /c:"%baseName%[0-9]*)"'
    ) do (
      set "name=%%F"
      set "name=!name:*%baseName%=!"
      set "name=!name:)=!"
      echo !name!
      if !name! gtr !n! set "n=!name!"
    )
    set /a n+=1
    set "newregkeypath=%baseName%%n%"
    set "reg_export=%RegFullPath%\%newregkeypath%).reg"
    if exist "!reg_export!" ( goto :Incrementloop )
    goto :EOF
    
    :Exportfile
    echo Windows Registry Editor Version 5.00>"!reg_export!"
    echo: >>"!reg_export!"
    echo !regfileline!>>"!reg_export!"
    goto :EOF
    ps: I think you don't need to add a blank line, it should already add a CR at the end (unless you want the file to end with 2 blank lines)
    ps2: Later thought, to increment truely "Windows style" you can replace "n=0" with "n=1", as I think windows would not create "file (1)", but directly "file (2)".