Public/New-rwDirPath.ps1
|
<# .SYNOPSIS Creates a directory safely with validation and optional interactive prompts. .DESCRIPTION New-rwDirPath ensures a new folder is created under the specified parent, validating names, honoring reserved characters, and asking interactively when no name is supplied. Supports pipeline input, -WhatIf/-Confirm, and returns either paths, names, or DirectoryInfo objects. .PARAMETER Path Parent folder for the new directory. Defaults to the current location and accepts pipeline input. .PARAMETER DirName Name of the directory to create. If omitted, the helper prompts interactively (supports retries). .PARAMETER Name Return just the directory name instead of the path. .PARAMETER Object Return the DirectoryInfo object for the created folder. .EXAMPLE New-rwDirPath -Path C:\Temp -DirName "NewFolder" -Object # Creates folder and returns DirectoryInfo metadata. .EXAMPLE New-rwDirPath -DirName "Temp" -Name # Returns just the folder name after creation. .NOTES Validates against invalid characters and Windows reserved names, and respects ShouldProcess for safe execution. #> Function New-rwDirPath { [CmdletBinding(SupportsShouldProcess)] [OutputType([string])] param( [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string]$Path = (Get-Location).Path, [Parameter()] [ValidateNotNullOrEmpty()] [string]$DirName, [switch]$Name, [switch]$Object ) process { Write-Verbose "New-rwDirPath: Path='$Path' DirName='$DirName'" if ($Name -and $Object) { throw [System.ArgumentException] "`-Name` and `-Object` are mutually exclusive." } $folderName = if ($DirName) { $DirName.Trim() } else { Read-rwNewDirName } if (-not $folderName) { return @() } $validation = Get-rwDirPathValidation -Path $Path -FolderName $folderName if (-not $validation.Success) { Write-Warning $validation.Message return @() } $creation = New-rwDirName -Path $Path -FolderName $validation.CanonicalName if ($creation.Result -ne 'Created' -or -not $creation.DirectoryInfo) { return @() } Write-Verbose "Before Out-rwDirSelection: Type=$($creation.DirectoryInfo.GetType().FullName)" return Out-rwDirSelection -Selection @($creation.DirectoryInfo) -ReturnName:$Name -ReturnObject:$Object } } function Get-rwDirPathValidation { param( [string]$Path, [string]$FolderName ) if (-not (Test-Path -LiteralPath $Path -PathType Container)) { return [pscustomobject]@{ Success = $false; Message = "Path '$Path' does not exist or is not a directory."; CanonicalName = $null } } if ($FolderName) { $name = $FolderName.Trim() } else { $name = $null } if (-not $name) { return [pscustomobject]@{ Success = $false; Message = 'Folder name cannot be empty.'; CanonicalName = $null } } $invalidChars = [System.IO.Path]::GetInvalidFileNameChars() if ($name.IndexOfAny($invalidChars) -ne -1 -or $name.Contains('\') -or $name.Contains('/')) { return [pscustomobject]@{ Success = $false; Message = 'Folder name contains invalid characters.'; CanonicalName = $null } } if ($name -in @('.', '..')) { return [pscustomobject]@{ Success = $false; Message = "Folder name '$name' is reserved."; CanonicalName = $null } } if (Test-IsWindows) { $base = $name.TrimEnd('.', ' ').ToUpperInvariant() if ($script:WindowsReservedDeviceNames -contains $base -or ($script:ReservedNamePattern -and $name -match $script:ReservedNamePattern)) { return [pscustomobject]@{ Success = $false; Message = "Folder name '$name' is reserved on Windows."; CanonicalName = $null } } } return [pscustomobject]@{ Success = $true; Message = ''; CanonicalName = $name } } function New-rwDirName { [CmdletBinding(SupportsShouldProcess)] param( [string]$Path, [string]$FolderName ) $folderPath = Join-Path -Path $Path -ChildPath $FolderName if (Test-Path -LiteralPath $folderPath -PathType Container) { return [pscustomobject]@{ Result = 'Exists'; DirectoryInfo = (Get-Item $folderPath); Message = 'Already exists' } } $automationEnabled = Get-rwDirToolsAutomation Write-Verbose "Automation mode: $automationEnabled for path: $folderPath" if (-not $automationEnabled) { if (-not $PSCmdlet.ShouldProcess($folderPath, 'Create directory')) { return [pscustomobject]@{ Result = 'Cancelled'; DirectoryInfo = $null; Message = 'Operation cancelled' } } } try { $confirmParam = if ($automationEnabled) { $false } else { $ConfirmPreference } $dir = New-Item -Path $folderPath -ItemType Directory -WhatIf:$WhatIfPreference -Confirm:$confirmParam -ErrorAction Stop return [pscustomobject]@{ Result = 'Created'; DirectoryInfo = $dir; Message = '' } } catch { return [pscustomobject]@{ Result = 'Failed'; DirectoryInfo = $null; Message = $_.Exception.Message } } } function Read-rwNewDirName { param([int]$MaxRetries = 5) for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { if (-not $Host.UI.RawUI -or $env:CI) { Write-Warning 'Interactive input is not available in this session.' return $null } $input = Read-Host -Prompt "`nEnter New Folder Name" if (-not $input) { if ($attempt -lt $MaxRetries -and (Show-rwPromptYesNo -Title 'Nothing was entered. Try again?')) { continue } return $null } if (Show-rwPromptYesNo -Title "Use this folder name? $input") { return $input } } Write-Warning "Maximum retry attempts ($MaxRetries) reached." return $null } |