Public/New-OutputObject.ps1

Function New-OutputObject {
    <#
 
    .SYNOPSIS
    Function intended for preparing a PowerShell object for output files like reports or logs.
 
    .DESCRIPTION
    Function intended for preparing a PowerShell custom object what contains e.g. file name for output/create files like reports or log.
    The name is prepared based on prefix, middle name part, suffix, date, etc. with verification if provided path exist and is it writable.
 
    Returned object contains properties
    - OutputObjectPath - to use it please check examples - as a [System.IO.FileInfo]
    - ExitCode
    - ExitCodeDescription
 
    Exit codes and descriptions
    - 0 = "Everything is fine :-)"
    - 1 = "Provided parent path <PATH> doesn't exist"
    - 2 = "The name not created due to unaccepatable chars"
    - 3 = "Provided patch <PATH> is not writable"
    - 4 = "The file\folder <PATH>\\<FILE_OR_FOLDER_NAME> already exist - can be overwritten"
    - 5 = "The file\folder <PATH>\\<FILE_OR_FOLDER_NAME> already exist - can't be overwritten"
 
    .PARAMETER ObjectType
    Type of object to prepare - file or folder
 
    .PARAMETER ParentPath
    The folder path what will be used as the parent path for the new created object.
    When non existing path will be provided the error code will be returned.
 
    By default output files are stored in the current path.
 
    .PARAMETER OutputObjectNamePrefix
    Prefix used for creating output files name
 
    .PARAMETER OutputObjectNameMidPart
    Part of the name which will be used in midle of output file name
 
    .PARAMETER OutputObjectNameSuffix
    Part of the name which will be used at the end of output file name
 
    .PARAMETER IncludeDateTimePartInOutputObjectName
    Set to TRUE if report file name should contains part based on date and time - format yyyyMMdd-HHmm is used
 
    .PARAMETER DateTimePartInOutputObjectName
    Set to date and time which should be used in output file name, by default current date and time is used
 
    .PARAMETER DateTimePartFormat
    Format string used to format date and time in output object name.
 
    .PARAMETER OutputFileNameExtension
    Set to extension which need to be used for output file, by default ".txt" is used
 
    .PARAMETER NamePartsSeparator
    A char used to separate parts in the name, by default "-" is used
 
    .PARAMETER BreakIfError
    Break function execution if parameters provided for output file creation are not correct or destination file path is not writables
 
    .EXAMPLE
 
    PS \> (Get-Item env:COMPUTERNAME).Value
    WXDX75
 
    PS \> $FileNeeded = @{
 
        ParentPath = 'C:\USERS\UserName\';
        OutputObjectNamePrefix = 'Messages';
        OutputObjectNameMidPart = (Get-Item env:COMPUTERNAME).Value;
        IncludeDateTimePartInOutputObjectName = $true;
        IncludeDateTimePartInOutputObjectName = $true;
 
        BreakIfError = $true
    }
 
    PS \> $PerServerReportFileMessages = New-OutputFile @FileNeeded
 
 
    PS \> $PerServerReportFileMessages | Format-List
 
 
    OutputObjectPath : C:\users\UserName\Messages-WXDX75-20151021-001205.txt
    ExitCode : 0
    ExitCodeDescription : Everything is fine :-)
 
    PS \> New-Item -Path $PerServerReportFileMessages.OutputObjectPath -ItemType file
 
    Directory: C:\USERS\UserName
 
    Mode LastWriteTime Length Name
    ---- ------------- ------ ----
    -a---- 21/10/2015 00:12 0 Messages-WXDX75-20151021-001205.txt
 
    The file created on provided parameters.
    Under preparation the file name is created, provided part of names are used, and availability of name (if the file exist now) is checked.
 
    .EXAMPLE
 
    $FileNeeded = @{
 
        ParentPath = 'C:\USERS\UserName\';
        OutputObjectNamePrefix = 'Messages';
        OutputObjectNameMidPart = 'COMPUTERNAME';
        IncludeDateTimePartInOutputObjectName = $false;
        OutputFileNameExtension = "csv";
        OutputObjectNameSuffix = "failed"
    }
 
    PS \> $PerServerReportFileMessages = New-OutputFile @FileNeeded
 
 
    PS \> $PerServerReportFileMessages.OutputObjectPath | Select-Object -Property Name,Extension,Directory | Format-List
 
    Name : Messages-COMPUTERNAME-failed.csv
    Extension : .csv
    Directory : C:\USERS\UserName
 
 
 
    PS \> ($PerServerReportFileMessages.OutputObjectPath).gettype()
 
    IsPublic IsSerial Name BaseType
    -------- -------- ---- --------
    True True FileInfo System.IO.FileSystemInfo
 
    PS \> Test-Path ($PerServerReportFileMessages.OutputObjectPath)
 
    False
 
    The funciton return object what contain the property named OutputObjectPath what is the object of type System.IO.FileSystemInfo.
 
    File is not created. Only the object in the memory is prepared.
 
    .OUTPUTS
    System.Object[]
 
    .LINK
    https://github.com/it-praktyk/New-OutputObject
 
    .LINK
    https://www.linkedin.com/in/sciesinskiwojciech
 
    .NOTES
    AUTHOR: Wojciech Sciesinski, wojciech[at]sciesinski[dot]net
    KEYWORDS: PowerShell, File, Folder, FileSystem
 
    CURRENT VERSION
    - 0.9.11- 2017-10-16
 
    HISTORY OF VERSIONS
    https://github.com/it-praktyk/New-OutputObject/CHANGELOG.md
 
    LICENSE
    Copyright (c) 2016 Wojciech Sciesinski
    This function is licensed under The MIT License (MIT)
    Full license text: https://opensource.org/licenses/MIT
 
    #>


    [cmdletbinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions','')]
    [OutputType([System.Object[]])]
    param (
        [parameter(Mandatory = $true)]
        [ValidateSet('File', 'Folder')]
        [Alias('ItemType')]
        [String]$ObjectType,
        [parameter(Mandatory = $false)]
        [String]$ParentPath = ".",
        [parameter(Mandatory = $false)]
        [String]$OutputObjectNamePrefix = "Output",
        [parameter(Mandatory = $false)]
        [String]$OutputObjectNameMidPart = $null,
        [parameter(Mandatory = $false)]
        [String]$OutputObjectNameSuffix = $null,
        [parameter(Mandatory = $false)]
        [Bool]$IncludeDateTimePartInOutputObjectName = $true,
        [parameter(Mandatory = $false)]
        [Nullable[DateTime]]$DateTimePartInOutputObjectName = $null,
        [parameter(Mandatory = $false)]
        [String]$DateTimePartFormat,
        [parameter(Mandatory = $false)]
        [String]$OutputFileNameExtension,
        [parameter(Mandatory = $false)]
        [alias("Separator")]
        [String]$NamePartsSeparator = "-",
        [parameter(Mandatory = $false)]
        [Switch]$BreakIfError

    )

    #Declare variable

    [Int]$ExitCode = 0

    $ExitCodesDescriptions = @{ 0= 'Everything is fine :-)';
                                1 = "Provided parent path {0} doesn't exist"; # $ParentPath
                                2 = 'The name not created due to unaccepatable chars';
                                3 = "Provided path {0} is not writable"; # $ParentPath
                                4 = "The {0} {1} already exist - can be overwritten" # $ItemTypeLowerCase, $OutputObjectPath.FullName
                                5 = "The {0} {1} already exist - can't be overwritten" # $ItemTypeLowerCase, $OutputObjectPath
    }

    [String]$ExitCodeDescription = 'Everything is fine :-)'

    $FinalNameParts = [ordered]@{NamePrefix=$OutputObjectNamePrefix;
                                NameMidPart=$OutputObjectNameMidPart;
                                DateTimePartInName='';
                                NameSuffix=$OutputObjectNameSuffix;
                                FileNameExtension=''
    }

    $Result = New-Object -TypeName PSObject

    If ( ($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -eq 'Core') -and ($ISLinux -or $IsMacOS)) {

        $PathSeparator = '/'

    }
    Else {

        $PathSeparator = '\'

    }

    If ($ObjectType -eq 'File') {

        $PathType = 'Leaf'

        $ItemTypeLowerCase = 'file'

        $ItemTypeCapitalized = 'File'

        $SkipInFinalName = @()

        If ([String]::IsNullOrEmpty($DateTimePartFormat)) {

            $DateTimePartFormat = 'yyyyMMdd-HHmmss'

        }
        Else {

            $TestCharsResult = Test-CharsInPath -Path $DateTimePartFormat -SkipCheckCharsInFolderPart -SkipDividingForParts

            If ( $TestCharsResult -eq 3) {

                 If ( $BreakIfError.IsPresent ) {

                    $MessageText = "Provided value for DateTimePartFormat contains char what is not allowed in a file name."

                    Throw $MessageText

                }
                Else {

                    [Int]$ExitCode = 2

                    [String]$ExitCodeDescription = $ExitCodesDescriptions[$ExitCode]

                }

            }

        }

        if ( [String]::IsNullOrEmpty($OutputFileNameExtension)) {

            $OutputFileNameExtension = '.txt'

            $FinalNameParts['FileNameExtension'] = '.txt'

        }
        Else {

            $FinalNameParts['FileNameExtension'] = ".{0}" -f $OutputFileNameExtension

        }

    }
    Else {

        $PathType = 'Container'

        $ItemTypeLowerCase = 'folder'

        $ItemTypeCapitalized = 'Folder'

        $SkipInFinalName = @('FileNameExtension')

        If ([String]::IsNullOrEmpty($DateTimePartFormat)) {

            $DateTimePartFormat = 'yyyyMMdd'

        }
        Else {

            $TestCharsResult = Test-CharsInPath -Path $DateTimePartFormat -SkipCheckCharsInFileNamePart -SkipDividingForParts

            If ( $DateTimePartFormat.Contains($PathSeparator) ) {

                $TestCharsResult = 5

            }

            If ( $TestCharsResult -eq 2 ) {

                If ( $BreakIfError.IsPresent ) {

                    $MessageText = "Provided value for DateTimePartFormat contains char what is not allowed in a folder name."

                    Throw $MessageText

                }
                Else {

                    [Int]$ExitCode = 2

                    [String]$ExitCodeDescription = $ExitCodesDescriptions[$ExitCode]

                }

            }
            ElseIf ( $TestCharsResult -eq 5 ) {

                If ( $BreakIfError.IsPresent ) {

                    $MessageText = "Provided value for DateTimePartFormat contains a char what is a path separator char."

                    Throw $MessageText

                }
                Else {

                    [Int]$ExitCode = 2

                    [String]$ExitCodeDescription = $ExitCodesDescriptions[$ExitCode]

                }

            }

        }

        if (-not [String]::IsNullOrEmpty($OutputFileNameExtension)) {

            [String]$MessageText = 'The value assigned to the parameter OutputFileNameExtension for a folder OutputType is ignored.'

            Write-Warning -Message $MessageText

        }

    }

    #Convert relative path to absolute path
    [String]$ParentPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ParentPath)

    #Assign value to the variable $IncludeDateTimePartInOutputObjectName if is not initialized
    If ($IncludeDateTimePartInOutputObjectName -and ($null -eq $DateTimePartInOutputObjectName)) {

        [String]$DateTimePartInObjectNameString = $(Get-Date -format $DateTimePartFormat)

        $FinalNameParts['DateTimePartInName'] = $DateTimePartInObjectNameString

    }
    elseif ($IncludeDateTimePartInOutputObjectName) {

        [String]$DateTimePartInObjectNameString = $(Get-Date -Date $DateTimePartInOutputObjectName -format $DateTimePartFormat)

        $FinalNameParts['DateTimePartInName'] = $DateTimePartInObjectNameString

    }

    #Check if Output directory exist
    If (-not (Test-Path -Path $ParentPath -PathType Container)) {

        [Int]$ExitCode = 1

        [String]$MessageText = $ExitCodesDescriptions[$ExitCode] -f $ParentPath

        [String]$ExitCodeDescription = $MessageText

    }

    #Try if Output directory is writable - a temporary object is created for that
    Else {

        #Try if Output directory is writable - a temporary file is created for that
        Try {

            [String]$TempObjectName = [System.IO.Path]::GetRandomFileName() -replace '.*\\', ''

            [String]$TempObjectPath = Join-Path -Path $ParentPath -ChildPath $TempObjectName

            New-Item -Path $TempObjectPath -type File -ErrorAction Stop | Out-Null

        }
        Catch {

            [Int]$ExitCode = 3

            [String]$MessageText = $ExitCodesDescriptions[$ExitCode] -f $ParentPath

            If ($BreakIfError.IsPresent) {

                Throw $MessageText

            }
            Else {

                [String]$ExitCodeDescription = $MessageText

            }

        }

        Remove-Item -Path $TempObjectPath -ErrorAction SilentlyContinue | Out-Null

    }

    $PartsToJoin =@("$ParentPath$PathSeparator")

    ForEach ( $NamePart in $FinalNameParts.Keys) {

        If ( $SkipInFinalName -notcontains $NamePart -and (-not [String]::IsNullOrEmpty( $FinalNameParts[$NamePart]))) {

            $PartsToJoin += $FinalNameParts[$NamePart]

        }

    }

    [String]$FinalName =  [string]::Join("$NamePartsSeparator",$PartsToJoin)

    $SequencesToReplace = @{'//' = '/';
                            '\\' = '\';
                            '..' = '.';
                            "$NamePartsSeparator." = '.';
                            "$NamePartsSeparator$NamePartsSeparator" = $NamePartsSeparator;
                            "$PathSeparator$NamePartsSeparator" = $PathSeparator
    }

    ForEach ( $SequenceKey in $SequencesToReplace.keys ) {

        $FinalName = "{0}{1}" -f $FinalName.Substring(0,2), (($FinalName.substring(2, $FinalName.length - 2)).Replace($SequenceKey, $SequencesToReplace[$SequenceKey]))

    }

    If ( $ExitCode -eq 2 ) {

      If ($ObjectType -eq 'File') {

            [System.IO.FileInfo]$OutputObjectPath = $null

        }
        Else {

            [System.IO.DirectoryInfo]$OutputObjectPath = $null

        }

    }
    Else {

        If ($ObjectType -eq 'File') {

            [System.IO.FileInfo]$OutputObjectPath = $FinalName

        }
        Else {

            [System.IO.DirectoryInfo]$OutputObjectPath = $FinalName

        }

        If (Test-Path -Path $OutputObjectPath -PathType $PathType) {

            $Answer = Get-OverwriteDecision -Path $OutputObjectPath -ItemType $ObjectType

            switch ($Answer) {

                0 {

                    [Int]$ExitCode = 4

                    [String]$MessageText = $ExitCodesDescriptions[$ExitCode] -f $ItemTypeLowerCase, $OutputObjectPath.FullName

                    [String]$ExitCodeDescription = $MessageText

                }

                1 {

                    [Int]$ExitCode = 5

                    [String]$MessageText = "The {0} {1} already exist - can't be overwritten" -f $ItemTypeLowerCase, $OutputObjectPath

                    [String]$ExitCodeDescription = $MessageText

                }

                2 {

                    [String]$MessageText = "The {0} {1} already exist - operation canceled by user" -f $ItemTypeLowerCase, $OutputObjectPath

                    Throw $MessageText

                }

            }

        }

    }

    $Result | Add-Member -MemberType NoteProperty -Name OutputObjectPath -Value $OutputObjectPath

    #$Result | Add-Member -MemberType AliasProperty -Name Path -Value OutputObjectPath

    If ($ObjectType -eq 'File') {

        $Result | Add-Member -MemberType AliasProperty -Name OutputFilePath -Value OutputObjectPath

    }
    Else {

        $Result | Add-Member -MemberType AliasProperty -Name OutputFolderPath -Value OutputObjectPath

    }

    $Result | Add-Member -MemberType NoteProperty -Name ExitCode -Value $ExitCode

    $Result | Add-Member -MemberType NoteProperty -Name ExitCodeDescription -Value $ExitCodeDescription

    Return $Result

}