How can i improve this script to make it better?

DEREK CHUA 0 Reputation points
2025-11-07T05:46:07.9166667+00:00

Add-Type -AssemblyName System.Windows.Forms

$dictDialog = New-Object System.Windows.Forms.OpenFileDialog

$dictDialog.Title = "Select dictionary.txt"

$dictDialog.Filter = "All files (.)|."

$null = $dictDialog.ShowDialog()

$dictionaryPath = $dictDialog.FileName

$pseudoDialog = New-Object System.Windows.Forms.OpenFileDialog

$pseudoDialog.Title = "Select pseudonymized.txt"

$pseudoDialog.Filter = "All files (.)|."

$null = $pseudoDialog.ShowDialog()

$pseudonymPath = $pseudoDialog.FileName

$targetDialog = New-Object System.Windows.Forms.FolderBrowserDialog

$targetDialog.Description = "Select folder to scan"

$null = $targetDialog.ShowDialog()

$targetFolder = $targetDialog.SelectedPath

$outputDialog = New-Object System.Windows.Forms.FolderBrowserDialog

$outputDialog.Description = "Select output folder"

$null = $outputDialog.ShowDialog()

$outputFolder = $outputDialog.SelectedPath

$dictionary = Get-Content $dictionaryPath

$pseudonyms = Get-Content $pseudonymPath

$map = @{}

for ($i = 0; $i -lt $dictionary.Count; $i++) {

$map[$dictionary[$i]] = $pseudonyms[$i]

}

$changesLogPath = Join-Path $outputFolder "_Changes.txt"

if (Test-Path $changesLogPath) { Remove-Item $changesLogPath }

New-Item -ItemType File -Path $changesLogPath -Force | Out-Null

function Is-TextFile($path) {

try {

Get-Content $path -TotalCount 1 -ErrorAction Stop | Out-Null

return $true

} catch {

return $false

}

}

Get-ChildItem -Path $targetFolder -Recurse -File | Where-Object { Is-TextFile($_.FullName) } | ForEach-Object {

$filePath = $_.FullName

$relativePath = $filePath.Substring($targetFolder.Length).TrimStart('')

$outputPath = Join-Path $outputFolder $relativePath

$content = @(Get-Content $filePath)

$newContent = @($content)

$changes = @()

for ($i = 0; $i -lt $content.Count; $i++) {

$line = $content[$i]

$modified = $line

foreach ($key in $map.Keys) {

$modified = $modified -replace [regex]::Escape($key), $map[$key]

}

if ($line -ne $modified) {

$changes += "File: $filePathnOriginal: $linenModified: $modified`n"

}

$newContent[$i] = $modified

}

if ($changes.Count -gt 0) {

$outputDir = Split-Path $outputPath

if (!(Test-Path $outputDir)) {

New-Item -ItemType Directory -Path $outputDir -Force | Out-Null

}

$newContent | Set-Content $outputPath

Add-Content -Path $changesLogPath -Value ($changes -join "`n")

}

}

Get-ChildItem -Path $targetFolder -Recurse -Directory | Sort-Object -Property FullName -Descending | ForEach-Object {

$originalName = $_.Name

$newName = $originalName

foreach ($key in $map.Keys) {

$newName = $newName -replace [regex]::Escape($key), $map[$key]

}

if ($originalName -ne $newName) {

$parent = Split-Path $_.FullName -Parent

$newPath = Join-Path $parent $newName

Rename-Item -Path $_.FullName -NewName $newName

Add-Content -Path $changesLogPath -Value "Folder renamed:nOriginal: $($_.FullName)nNew: $newPath`n"

}

}

Developer technologies | C#
Developer technologies | C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
{count} votes

2 answers

Sort by: Most helpful
  1. Q&A Assist
    2025-11-07T05:46:27.36+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    To improve and shorten your PowerShell script, you can consider the following suggestions:

    1. Consolidate File Dialogs: You can create a function to handle the file and folder selection dialogs to avoid repeating similar code for each dialog.
    2. Use ForEach-Object for Hashtable Creation: Instead of using a for loop to create the hashtable, you can use ForEach-Object to make it more concise.
    3. Combine File Operations: You can combine the operations that check for the existence of the output directory and create it into a single command using New-Item -Force.
    4. Optimize the Log Writing: Instead of checking for changes and writing to the log file in multiple places, consider collecting all changes and writing them at once after processing all files.
    5. Use -replace with a single command: You can use a single -replace command with a loop to handle all replacements in one go, which can simplify the logic.

    Here’s a revised version of your script incorporating these suggestions:

    Add-Type -AssemblyName System.Windows.Forms
    
    function Select-FileOrFolder($dialogType, $title, $filter = '') {
        if ($dialogType -eq 'File') {
            $dialog = New-Object System.Windows.Forms.OpenFileDialog
            $dialog.Title = $title
            $dialog.Filter = $filter
            $dialog.ShowDialog() | Out-Null
            return $dialog.FileName
        } else {
            $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
            $dialog.Description = $title
            $dialog.ShowDialog() | Out-Null
            return $dialog.SelectedPath
        }
    }
    
    $dictionaryPath = Select-FileOrFolder 'File' 'Select Dictionary File' 'Text files (*.txt)|*.txt|All files (*.*)|*.*'
    $pseudonymPath = Select-FileOrFolder 'File' 'Select Pseudonym File' 'Text files (*.txt)|*.txt|All files (*.*)|*.*'
    $targetFolder = Select-FileOrFolder 'Folder' 'Select Folder for Scanning'
    $outputFolder = Select-FileOrFolder 'Folder' 'Select Scan Output Folder'
    
    Write-Host "Dictionary Path: $dictionaryPath"
    Write-Host "Pseudonym Path: $pseudonymPath"
    Write-Host "Target Folder: $targetFolder"
    Write-Host "Output Folder: $outputFolder"
    
    $dictionary = Get-Content $dictionaryPath
    $pseudonyms = Get-Content $pseudonymPath
    $map = @{}
    $dictionary | ForEach-Object { $map[$_] = $pseudonyms[$dictionary.IndexOf($_)] }
    
    New-Item -ItemType Directory -Path $outputFolder -Force | Out-Null
    $changesLogPath = Join-Path $targetFolder "changes.txt"
    Remove-Item $changesLogPath -ErrorAction SilentlyContinue
    New-Item -ItemType File -Path $changesLogPath -Force | Out-Null
    
    Get-ChildItem -Path $targetFolder -Recurse -Filter *.log | ForEach-Object {
        $fileContent = Get-Content $_.FullName
        $newContent = $fileContent -replace ([regex]::Escape($map.Keys -join '|')), { $map[$_]} 
        $changes = Compare-Object -ReferenceObject $fileContent -DifferenceObject $newContent
    
        if ($changes) {
            $changes | ForEach-Object { Add-Content -Path $changesLogPath -Value "File: $($_.InputObject)`nOriginal: $($_.InputObject)`nModified: $newContent`n" }
        }
    
        $outputPath = Join-Path $outputFolder ($_.FullName.Substring($targetFolder.Length))
        New-Item -ItemType Directory -Path (Split-Path $outputPath) -Force | Out-Null
        $newContent | Set-Content $outputPath
    }
    

    This version is more concise and maintains the same functionality as your original script.

    0 comments No comments

  2. Adiba Khan 1,440 Reputation points Microsoft External Staff
    2025-12-05T09:52:10+00:00

    Thanks for reaching out. This script appears to be performing a deidentification/pseudonymization task: reading a dictionary and a list of pseudonymization, creating a mapping and then scanning a target folder to replace original text/filenames with their pseudonyms, logging the changes and saving the results to an output folder.

    The current script is functional but can be improved in terms of readability, efficiency, error handling, and robustness.

    here are breakdown of the key improvements and the revised script:

    1. Script structure and Initialization The beginning of the script can be made cleaner and more robust by using a param() block for optional input parameters and using better practices for adding assemblies and defining functions.
      1. param() Block: This allows the script to be run non-interactively by-passing paths as arguments, which is great for automation. If the paths aren't provided, the script falls back to using GUI prompt.
      2. Clear dialog filters: the file filters for the Open Dialogs are made more specific.
      3. Centralized Configurations: All necessary modules/assemblies are loaded upfront.
              # Requires -Version 5.1
              ## PARAMETERS ##
              param(
              	[Parameter(Mandatory=$false)]
              	[string]$DictionaryPath,
              
              	[Parameter(Mandatory=$false)]
              	[string]$PseudonymPath,
              
              	[Parameter(Mandatory=$false)]
              	[string]$TargetFolder,
              
              	
              	[string]$POutputFolder
              )
              Add-Type -AssemblyName
              System.Windows.Forms
              $ErrorActionPreference = "Stop" # Stop on errors for better debugging
              
              # Default log file name
              $ChangeLogFileName = "changes.txt"
              
              
        
    2. Input/Output Handling (GUI Prompt) The repeated code for the folder and file dialogs is consolidated, and error handling for the dialogs is added.
      1. Helper Functions: Creating functions for selecting files/folders improves readability and avoids repeating the dialog codes.
      2. Path Validation: Basic check to ensure the user selected something before continuing.
               ## HELPER FUNCTIONS FOR GUI INPUT ##
              function Select-File{
              	param(
              		[string]$Title = "Select File"
              		[string]$Filter = "Text files (*.txt)|.txt|All files (*.*)|*.*"
              	)
              	$dialog = New-Object
              	System.Windows.Forms.OpenFileDialog
              	$dialog.Title = $Title
              	$dialog.Filter = $Filter
              	
              	if($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
              	{
              		return $dialog.FileName
              	}
              	return $null
              	}
              	function Select-Folder {
              		param(
              			[string]$Description = "Select Folder"
              		)
              		$dialog = New-Object
              		System-Windows.Forms.FolderBrowserDialog
              		$dialog.Description= $description
              
              	if( $dialog.ShowDialog() -eq [System.Windows.Forms.DialogueResult::OK]
              	{
              		return $dialog.SelectedPath
              	}
              	return $null
              }
              # --- Collect/Validate Paths (using GUI if needed) ---
              if (-not $DictionaryPath)
              {
              $DictioaryPath = Select-File -Title "Select Dictionary file (Original Trems)" -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
              }
              if (-not $DictionaryPath)
              { Write-Error "Dictionary path not selected. Ecisting.";exit }
              Write-Host "Dictionary Path: $DictionaryPath"
              
              if (-not $PseudonymPath)
              {
              $PseudonymPath= Select-File-Title   "Select Pseudonym File (Replacement Terms)" -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
              }
              if (-not $pseudonymPath)
              { Write-Error "Pseudonym path not selected. Existing.";
              exit}
              Write-Host "Pseudonym Path:
              $PseudonymPath"
              
              if (-not TargetFolder)
              {
              $TargetFolder = Select-Folder-Description "Select Folder to Scan (Source)"
              }
              if (-not $TargetFolder)
              {Write-Error "Target folder not selected. Existing.";
              exit}
              Write-Host "Target Folder: $TargetFolder"
              
              if (-not $OutputFolder)
              {
              $OutputFolder = Select-Folder-Description "Select Output Folder (Destination)"
              }
              if (-not $OutputFolder)
              {
              Write-Host "Output folder not selected. Existing.";
              exit}
              Write-Host "Output Folder: $OutputFolder"
              
              # create output folder if it doesn't exist
              if (-not (Test-Path $OutputFolder))
              {
              Write-Host "Creating output directory: $OutputFolder"
              New-Item -Path $OutputFolder -ItemType Directory -Force | Out-Null
              }
              
              
        
    3. Map Creation and Validation The method for creating the map is already efficient, but validation is added to ensure the dictionary and pseudonym files have the same number of lines, which is critical for a one-to-one mapping.
         ## CREATE PSEUDONYM MAP ##
         	Write-Host "Reading dictionary and pseudonym files....
         $dictionary = Get-Content $DictionaryPath
         $Pseudonyms = Get-Content $PseudonymPath
         
         if ($dictionary.Count -ne $Pseudonyms.Count)
         {
         Write-Error "Dictionary and pseudonym files must have the same number of lines for a 1:1 mapping."
         exit
         }
         $map = @{}
         for ($i =0 ; $i -lt $dictionary.Count; $i++)
         {
         # Trim to avoid mapping issues due to leading/ trailing whitespace
         	$key = $dictionary[$i.Trim()
         	$value = $pseudonyms[$i].Trim()
         	if (-not [string]::IsNullOrWhiteSpace($key)){
         		$map[$key] = $value
         	}
         }
         Write-Host "Map created with $ ($map.Count) enties."
         #the log file path is defined here, based on the output folder
         $ChangesLogPath = Join-Path $OutputFolder $ChangeLogFileName
         
         # clear previous log file for a clean run 
         if (Test-Path $ChangeLogPath)
         {
         Remove-Item $ChangeLogPath -Force
         }
         
      
    4. Renaming and Content Processing This section contains the core logic. The file processing loops are made more efficient and robust.
      1. File Type Check: The original script attempted a file type using Get-Content within a try/catch block, ehich is overly complex and slow. A better approach is often to filter by file extension. The provided script uses an external is-TextFile function, which is kept, but it's important to note this can be simplified if you only care about a few extensions.
      2. Path Manipulation: The use of $_.FullName.Substring($TargetFolder.Length) is correct for getting the relative path but is slightly prone to issues with trailing slashes. Using [System.IO.Path]::HetRelativePath() in newer PowerShell versions (Core/7+) is more robust.
      3. Consistent File Writing: using set-content and add-content ensures proper encoding and handling.
      4. Regex Escaping: The use [regex]::Escape($key) is essential and correctly implemented in the original script to handle special characters in the dictionary keys.

    Please let us know if you require any further assistance we’re happy to help. If you found this information useful, kindly mark this as "Accept Answer".

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.