Functions/GenXdev.FileSystem/Expand-Path.ps1

################################################################################
<#
.SYNOPSIS
Expands any given file reference to a full pathname.
 
.DESCRIPTION
Expands any given file reference to a full pathname, with respect to the user's
current directory. Can optionally assure that directories or files exist.
 
.PARAMETER FilePath
The file path to expand to a full path.
 
.PARAMETER CreateDirectory
Will create directory if it does not exist.
 
.PARAMETER CreateFile
Will create an empty file if it does not exist.
 
.EXAMPLE
Expand-Path -FilePath ".\myfile.txt" -CreateFile
 
.EXAMPLE
ep ~\documents\test.txt -CreateFile
#>

function Expand-Path {

    [CmdletBinding()]
    [Alias("ep")]

    param(
        ########################################################################
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Path to expand"
        )]
        [ValidateNotNullOrEmpty()]
        [string] $FilePath,
        ########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Will create directory if it does not exist"
        )]
        [switch] $CreateDirectory,
        ########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Will create an empty file if it does not exist"
        )]
        [switch] $CreateFile,
        ########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Will delete the file if it already exists"
        )]
        [switch] $DeleteExistingFile
        ########################################################################
    )

    begin {

        # normalize path separators and remove double separators
        $normalizedPath = $FilePath.Trim().Replace("\", [IO.Path]::DirectorySeparatorChar).
        Replace("/", [IO.Path]::DirectorySeparatorChar).
        Replace([IO.Path]::DirectorySeparatorChar + [IO.Path]::DirectorySeparatorChar,
            [IO.Path]::DirectorySeparatorChar)

        # check if path ends with a directory separator
        $hasTrailingSeparator = $normalizedPath.EndsWith(
            [System.IO.Path]::DirectorySeparatorChar) -or
        $normalizedPath.EndsWith([System.IO.Path]::AltDirectorySeparatorChar)
    }

    process {

        # expand home directory if path starts with ~
        if ($normalizedPath.StartsWith("~")) {
            $normalizedPath = Join-Path (Resolve-Path ~).Path `
                $normalizedPath.Substring(1)
        }

        # handle absolute paths (drive letter or UNC)
        if ((($normalizedPath.Length -gt 1) -and
                ($normalizedPath.Substring(1, 1) -eq ":")) -or
            $normalizedPath.StartsWith("\\")) {

            try {
                $normalizedPath = [System.IO.Path]::GetFullPath($normalizedPath)
            }
            catch {
                Write-Verbose "Failed to normalize path, keeping original"
            }
        }
        else {
            # handle relative paths
            try {
                $normalizedPath = [System.IO.Path]::GetFullPath(
                    [System.IO.Path]::Combine($pwd, $normalizedPath))
            }
            catch {
                $normalizedPath = Convert-Path $normalizedPath
            }
        }

        # handle directory/file creation if requested
        if ($CreateDirectory -or $CreateFile) {

            # get directory path accounting for trailing separator
            $directoryPath = if ($hasTrailingSeparator) {
                [IO.Path]::TrimEndingDirectorySeparator($normalizedPath)
            }
            else {
                [IO.Path]::TrimEndingDirectorySeparator(
                    [System.IO.Path]::GetDirectoryName($normalizedPath))
            }

            # create directory if it doesn't exist
            if (-not [IO.Directory]::Exists($directoryPath)) {
                $null = [IO.Directory]::CreateDirectory($directoryPath)
                Write-Verbose "Created directory: $directoryPath"
            }
        }

        # delete existing file if requested
        if ($DeleteExistingFile -and [IO.File]::Exists($normalizedPath)) {

            # verify path doesn't point to existing directory
            if ([IO.Directory]::Exists($normalizedPath)) {
                throw "Cannot create file: Path refers to an existing directory"
            }

            if (-not (Remove-ItemWithFallback -Path $normalizedPath)) {

                throw "Failed to delete existing file: $normalizedPath"
            }

            Write-Verbose "Deleted existing file: $normalizedPath"
        }

        # handle file creation if requested
        if ($CreateFile) {

            # verify path doesn't point to existing directory
            if ([IO.Directory]::Exists($normalizedPath)) {
                throw "Cannot create file: Path refers to an existing directory"
            }


            # create empty file if it doesn't exist
            if (-not [IO.File]::Exists($normalizedPath)) {
                $null = [IO.File]::WriteAllText($normalizedPath, "")
                Write-Verbose "Created empty file: $normalizedPath"
            }
        }

        # clean up trailing separators except for root paths
        while ([IO.Path]::EndsInDirectorySeparator($normalizedPath) -and
            $normalizedPath.Length -gt 4) {
            $normalizedPath = [IO.Path]::TrimEndingDirectorySeparator($normalizedPath)
        }

        return $normalizedPath
    }

    end {
    }
}
################################################################################