Public/New-ADTShortcut.ps1

#-----------------------------------------------------------------------------
#
# MARK: New-ADTShortcut
#
#-----------------------------------------------------------------------------

function New-ADTShortcut
{
    <#
    .SYNOPSIS
        Creates a new .lnk or .url type shortcut.
 
    .DESCRIPTION
        Creates a new shortcut .lnk or .url file, with configurable options. This function allows you to specify various parameters such as the target path, arguments, icon location, description, working directory, window style, run as administrator, and hotkey.
 
    .PARAMETER Path
        Path to save the shortcut.
 
    .PARAMETER TargetPath
        Target path or URL that the shortcut launches.
 
    .PARAMETER Arguments
        Arguments to be passed to the target path.
 
    .PARAMETER IconLocation
        Location of the icon used for the shortcut.
 
    .PARAMETER IconIndex
        The index of the icon. Executables, DLLs, ICO files with multiple icons need the icon index to be specified. This parameter is an Integer. The first index is 0.
 
    .PARAMETER Description
        Description of the shortcut.
 
    .PARAMETER WorkingDirectory
        Working Directory to be used for the target path.
 
    .PARAMETER WindowStyle
        Windows style of the application. Options: Normal, Maximized, Minimized. Default is: Normal.
 
    .PARAMETER RunAsAdmin
        Set shortcut to run program as administrator. This option will prompt user to elevate when executing shortcut.
 
    .PARAMETER Hotkey
        Create a Hotkey to launch the shortcut, e.g. "CTRL+SHIFT+F".
 
    .INPUTS
        None
 
        You cannot pipe objects to this function.
 
    .OUTPUTS
        None
 
        This function does not return any output.
 
    .EXAMPLE
        New-ADTShortcut -Path "$env:ProgramData\Microsoft\Windows\Start Menu\My Shortcut.lnk" -TargetPath "$env:WinDir\System32\notepad.exe" -IconLocation "$env:WinDir\System32\notepad.exe" -Description 'Notepad' -WorkingDirectory "$env:HomeDrive\$env:HomePath"
 
        Creates a new shortcut for Notepad with the specified parameters.
 
    .NOTES
        An active ADT session is NOT required to use this function.
 
        Url shortcuts only support TargetPath, IconLocation and IconIndex. Other parameters are ignored.
 
        Tags: psadt
        Website: https://psappdeploytoolkit.com
        Copyright: (C) 2024 PSAppDeployToolkit Team (Sean Lillis, Dan Cunningham, Muhammad Mashwani, Mitch Richters, Dan Gough).
        License: https://opensource.org/license/lgpl-3-0
 
    .LINK
        https://psappdeploytoolkit.com
    #>


    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateScript({
                if (![System.IO.Path]::GetExtension($Path).ToLower().Equals('.lnk') -and ![System.IO.Path]::GetExtension($Path).ToLower().Equals('.url'))
                {
                    $PSCmdlet.ThrowTerminatingError((New-ADTValidateScriptErrorRecord -ParameterName Path -ProvidedValue $_ -ExceptionMessage 'The specified path does not have the correct extension.'))
                }
                return ![System.String]::IsNullOrWhiteSpace($_)
            })]
        [System.String]$Path,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$TargetPath,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Arguments,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [System.String]$IconLocation,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [System.Int32]$IconIndex,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Description,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [System.String]$WorkingDirectory,

        [Parameter(Mandatory = $false)]
        [ValidateSet('Normal', 'Maximized', 'Minimized')]
        [System.String]$WindowStyle,

        [Parameter(Mandatory = $false)]
        [System.Management.Automation.SwitchParameter]$RunAsAdmin,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Hotkey
    )

    begin
    {
        # Make this function continue on error.
        Initialize-ADTFunction -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -ErrorAction SilentlyContinue
    }

    process
    {
        # Make sure .NET's current directory is synced with PowerShell's.
        try
        {
            try
            {
                [System.IO.Directory]::SetCurrentDirectory((Get-Location -PSProvider FileSystem).ProviderPath)
                $FullPath = [System.IO.Path]::GetFullPath($Path)
            }
            catch
            {
                Write-Error -ErrorRecord $_
            }
        }
        catch
        {
            Invoke-ADTFunctionErrorHandler -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -ErrorRecord $_ -LogMessage "Specified path [$Path] is not valid."
            return
        }

        try
        {
            try
            {
                # Make sure directory is present before continuing.
                if (!($PathDirectory = [System.IO.Path]::GetDirectoryName($FullPath)))
                {
                    # The path is root or no filename supplied.
                    if (![System.IO.Path]::GetFileNameWithoutExtension($FullPath))
                    {
                        # No filename supplied.
                        $naerParams = @{
                            Exception = [System.ArgumentException]::new("Specified path [$FullPath] is a directory and not a file.")
                            Category = [System.Management.Automation.ErrorCategory]::InvalidArgument
                            ErrorId = 'ShortcutPathInvalid'
                            TargetObject = $FullPath
                            RecommendedAction = "Please confirm the provided value and try again."
                        }
                        throw (New-ADTErrorRecord @naerParams)
                    }
                }
                elseif (!(Test-Path -LiteralPath $PathDirectory -PathType Container))
                {
                    try
                    {
                        Write-ADTLogEntry -Message "Creating shortcut directory [$PathDirectory]."
                        $null = New-Item -LiteralPath $PathDirectory -ItemType Directory -Force
                    }
                    catch
                    {
                        Write-ADTLogEntry -Message "Failed to create shortcut directory [$PathDirectory].`n$(Resolve-ADTErrorRecord -ErrorRecord $_)" -Severity 3
                        throw
                    }
                }

                # Remove any pre-existing shortcut first.
                if (Test-Path -LiteralPath $FullPath -PathType Leaf)
                {
                    Write-ADTLogEntry -Message "The shortcut [$FullPath] already exists. Deleting the file..."
                    Remove-ADTFile -LiteralPath $FullPath
                }

                # Build out the shortcut.
                Write-ADTLogEntry -Message "Creating shortcut [$FullPath]."
                if ($Path -match '\.url$')
                {
                    [String[]]$URLFile = '[InternetShortcut]', "URL=$TargetPath"
                    if ($null -ne $IconIndex)
                    {
                        $URLFile += "IconIndex=$IconIndex"
                    }
                    if ($IconLocation)
                    {
                        $URLFile += "IconFile=$IconLocation"
                    }
                    [System.IO.File]::WriteAllLines($FullPath, $URLFile, [System.Text.UTF8Encoding]::new($false))
                }
                else
                {
                    $shortcut = [System.Activator]::CreateInstance([System.Type]::GetTypeFromProgID('WScript.Shell')).CreateShortcut($FullPath)
                    $shortcut.TargetPath = $TargetPath
                    if ($Arguments)
                    {
                        $shortcut.Arguments = $Arguments
                    }
                    if ($Description)
                    {
                        $shortcut.Description = $Description
                    }
                    if ($WorkingDirectory)
                    {
                        $shortcut.WorkingDirectory = $WorkingDirectory
                    }
                    if ($Hotkey)
                    {
                        $shortcut.Hotkey = $Hotkey
                    }
                    if ($IconLocation)
                    {
                        $shortcut.IconLocation = $IconLocation + ",$IconIndex"
                    }
                    $shortcut.WindowStyle = switch ($WindowStyle)
                    {
                        Normal { 1; break }
                        Maximized { 3; break }
                        Minimized { 7; break }
                        default { 1; break }
                    }

                    # Save the changes.
                    $shortcut.Save()

                    # Set shortcut to run program as administrator.
                    if ($RunAsAdmin)
                    {
                        Write-ADTLogEntry -Message 'Setting shortcut to run program as administrator.'
                        $fileBytes = [System.IO.FIle]::ReadAllBytes($FullPath)
                        $fileBytes[21] = $filebytes[21] -bor 32
                        [System.IO.FIle]::WriteAllBytes($FullPath, $fileBytes)
                    }
                }
            }
            catch
            {
                Write-Error -ErrorRecord $_
            }
        }
        catch
        {
            Invoke-ADTFunctionErrorHandler -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -ErrorRecord $_ -LogMessage "Failed to create shortcut [$Path]."
        }
    }

    end
    {
        Complete-ADTFunction -Cmdlet $PSCmdlet
    }
}