PSShlink.psm1
#region Private functions function GetShlinkConnection { param ( [Parameter()] [String]$Server, [Parameter()] [SecureString]$ApiKey, [Parameter()] [Switch]$ServerOnly ) if (-not ("System.Web.HttpUtility" -as [Type])) { Add-Type -AssemblyName "System.Web" -ErrorAction "Stop" } if (-not $Script:ShlinkServer) { if ([String]::IsNullOrWhitespace($Server)) { $Script:ShlinkServer = Read-Host -Prompt "Enter your Shlink server URL (e.g. https://example.com)" } else { $Script:ShlinkServer = $Server } } if (-not $Script:ShlinkApiKey -And -not $ServerOnly.IsPresent) { if ([String]::IsNullOrWhitespace($ApiKey)) { $Script:ShlinkApiKey = Read-Host -Prompt "Enter your Shlink server API key" -AsSecureString } else { $Script:ShlinkApiKey = $ApiKey } } # Query the Shlink server for version, only on the first run of GetShlinkConnection, otherwise it # will enter an infinite loop of recursion and hit the PowerShell recursion limit. I want a user # experience of being warned each time they use a function on an unsupported Shlink server version. if (-not $Script:GetShlinkConnectionHasRun) { $Script:GetShlinkConnectionHasRun = $true $Script:ShlinkVersion = Get-ShlinkServer | Select-Object -ExpandProperty Version $Script:MinSupportedShlinkVersion = [Version]"2.4.0" if ([Version]$Script:ShlinkVersion -lt [Version]$Script:MinSupportedShlinkVersion) { $Script:ShlinkVersionIsSupported = $false } else { $Script:ShlinkVersionIsSupported = $true } } if ($Script:ShlinkVersionIsSupported -eq $false) { Write-Warning -Message ("PSShlink supports Shlink {0} or newer, your Shlink server is {1}. Some functions may not work as intended. Consider upgrading your Shlink instance." -f $Script:MinSupportedShlinkVersion, $Script:ShlinkVersion) } } function InvokeShlinkRestMethod { [CmdletBinding()] param ( [Parameter()] [String]$Server = $Script:ShlinkServer, [Parameter()] [SecureString]$ApiKey = $Script:ShlinkApiKey, [Parameter()] [Microsoft.PowerShell.Commands.WebRequestMethod]$Method = 'GET', [Parameter(Mandatory)] [String]$Endpoint, [Parameter()] [String]$Path, # Default value set where no Query parameter is passed because we still need the object for pagination later [Parameter()] [System.Collections.Specialized.NameValueCollection]$Query = [System.Web.HttpUtility]::ParseQueryString(''), [Parameter()] [hashtable]$Body, [Parameter()] [String[]]$PropertyTree, [Parameter()] [Int]$Page, [Parameter()] [String]$PSTypeName ) $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ApiKey) $Params = @{ Method = $Method Uri = "{0}/rest/v2/{1}" -f $Server, $Endpoint ContentType = "application/json" Headers = @{"X-Api-Key" = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)} ErrorAction = "Stop" ErrorVariable = "InvokeRestMethodError" } if ($PSBoundParameters.ContainsKey("Path")) { $Params["Uri"] = "{0}/{1}" -f $Params["Uri"], $Path } # Preserve the URI which does not contain any query parameters for the pagination URI building later $QuerylessUri = $Params["Uri"] if ($PSBoundParameters.ContainsKey("Query")) { $Params["Uri"] = "{0}?{1}" -f $Params["Uri"], $Query.ToString() } if ($PSBoundParameters.ContainsKey("Body")) { $Params["Body"] = $Body | ConvertTo-Json } $Result = do { try { $Data = Invoke-RestMethod @Params } catch { # The web exception class is different for Core vs Windows if ($InvokeRestMethodError.ErrorRecord.Exception.GetType().FullName -match "HttpResponseException|WebException") { $ExceptionMessage = $InvokeRestMethodError.Message | ConvertFrom-Json | Select-Object -ExpandProperty detail $ErrorId = "{0}{1}" -f [Int][System.Net.HttpStatusCode]$InvokeRestMethodError.ErrorRecord.Exception.Response.StatusCode, [String][System.Net.HttpStatusCode]$InvokeRestMethodError.ErrorRecord.Exception.Response.StatusCode switch -Regex ($InvokeRestMethodError.ErrorRecord.Exception.Response.StatusCode) { "BadRequest|Conflict" { $Exception = [System.ArgumentException]::new($ExceptionMessage) $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( $Exception, $ErrorId, [System.Management.Automation.ErrorCategory]::InvalidArgument, $Params['Url'] ) } "NotFound" { $Exception = [System.Management.Automation.ItemNotFoundException]::new($ExceptionMessage) $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( $Exception, $ErrorId, [System.Management.Automation.ErrorCategory]::ObjectNotFound, $Params['Uri'] ) } "ServiceUnavailable" { $Exception = [System.InvalidOperationException]::new($ExceptionMessage) $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( $Exception, $ErrorId, [System.Management.Automation.ErrorCategory]::ResourceUnavailable, $Params['Uri'] ) } default { $Exception = [System.InvalidOperationException]::new($ExceptionMessage) $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( $Exception, $ErrorId, [System.Management.Automation.ErrorCategory]::InvalidOperation, $Params['Uri'] ) } } $PSCmdlet.ThrowTerminatingError($ErrorRecord) } else { $PSCmdlet.ThrowTerminatingError($_) } } $PaginationData = if ($PropertyTree) { Write-Output $Data.($PropertyTree[0]).pagination } else { Write-Output $Data.pagination } if ($PaginationData) { $Query["page"] = $PaginationData.currentPage + 1 $Params["Uri"] = "{0}?{1}" -f $QuerylessUri, $Query.ToString() } Write-Output $Data } while ($PaginationData.currentPage -ne $PaginationData.pagesCount -And $PaginationData.pagesCount -ne 0) # Walk down the object's properties to return the desired property # e.g. sometimes the data is burried in tags.data or shortUrls.data etc foreach ($Property in $PropertyTree) { $Result = $Result.$Property } if ($PSBoundParameters.ContainsKey("PSTypeName")) { foreach ($item in $Result) { $item.PSTypeNames.Insert(0, $PSTypeName) } } Write-Output $Result [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) } #endregion #region Public functions function Get-ShlinkDomains { <# .SYNOPSIS Returns the list of all domains ever used, with a flag that tells if they are the default domain .DESCRIPTION Returns the list of all domains ever used, with a flag that tells if they are the default domain .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Get-ShlinkDomains Returns the list of all domains ever used, with a flag that tells if they are the default domain .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding()] param ( [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $Params = @{ Endpoint = "domains" PropertyTree = "domains", "data" ErrorAction = "Stop" } try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } } function Get-ShlinkServer { <# .SYNOPSIS Checks the healthiness of the service, making sure it can access required resources. .DESCRIPTION Checks the healthiness of the service, making sure it can access required resources. https://api-spec.shlink.io/#/Monitoring/health .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Get-ShlinkServer Returns the healthiness of the service. .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding()] param ( [Parameter()] [String]$ShlinkServer ) GetShlinkConnection -Server $ShlinkServer -ServerOnly $Uri = "{0}/rest/health" -f $Script:ShlinkServer try { Invoke-RestMethod -Uri $Uri -ErrorAction "Stop" } catch { Write-Error -ErrorRecord $_ } } function Get-ShlinkTags { <# .SYNOPSIS Returns the list of all tags used in any short URL, including stats and ordered by name. .DESCRIPTION Returns the list of all tags used in any short URL, including stats and ordered by name. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Get-ShlinkTags Returns the list of all tags used in any short URL, including stats and ordered by name. .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding()] param ( [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $QueryString = [System.Web.HttpUtility]::ParseQueryString('') $QueryString.Add("withStats", "true") $Params = @{ Endpoint = "tags" PropertyTree = "tags", "stats" } $Params["Query"] = $QueryString try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } } function Get-ShlinkUrl { <# .SYNOPSIS Get details of all short codes, or just one. .DESCRIPTION Get details of all short codes, or just one. Various filtering options are available from the API to ambigiously search for short codes. .PARAMETER ShortCode The name of the short code you wish to search for. For example, if the short URL is "https://example.com/new-url" then the short code is "new-url". .PARAMETER Domain The domain (excluding schema) associated with the short code you wish to search for. For example, "example.com" is an acceptable value. This is useful if your Shlink instance is responding/creating short URLs for multiple domains. .PARAMETER SearchTerm The search term to search for a short code with. .PARAMETER Tags One or more tags can be passed to find short codes using said tag(s). .PARAMETER OrderBy Order the results returned by "longUrl", "shortCode", "dateCreated", or "visits". The default sort order is in ascending order. .PARAMETER StartDate A datetime object to search for short codes where its start date is equal or greater than this value. If a start date is not configured for the short code(s), this filters on the dateCreated property. .PARAMETER EndDate A datetime object to search for short codes where its end date is equal or less than this value. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Get-ShlinkUrl Returns all short codes with no filtering applied. .EXAMPLE PS C:\> Get-ShlinkUrl -ShortCode "profile" Returns the short code "profile". .EXAMPLE PS C:\> Get-ShlinkUrl -ShortCode "profile" -Domain "example.com" Returns the short code "profile" using the domain "example.com". This is useful if your Shlink instance is responding/creating short URLs for multiple domains. .EXAMPLE PS C:\> Get-ShlinkUrl -Tags "oldwebsite", "evenolderwebsite" -OrderBy "dateCreated" Returns short codes which are associated with the tags "oldwebsite" and "evenolderwebsite". Ordered by the dateCreated property in ascending order. .EXAMPLE PS C:\> Get-ShlinkUrl -StartDate (Get-Date "2020-10-25 11:00:00") Returns short codes which have a start date of 25th October 2020 11:00:00 AM or newer. If a start date was not configured for the short code(s), this filters on the dateCreated property. .EXAMPLE PS C:\> Get-ShlinkUrl -SearchTerm "microsoft" Returns the short codes which match the search term "microsoft". .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject Objects have a PSTypeName of 'PSTypeName'. #> [CmdletBinding(DefaultParameterSetName="ListShortUrls")] param ( [Parameter(Mandatory, ParameterSetName="ParseShortCode")] [String]$ShortCode, [Parameter(ParameterSetName="ParseShortCode")] [String]$Domain, [Parameter(ParameterSetName="ListShortUrls")] [String]$SearchTerm, [Parameter(ParameterSetName="ListShortUrls")] [String[]]$Tags, [Parameter(ParameterSetName="ListShortUrls")] [ValidateSet("longUrl", "shortCode", "dateCreated", "visits")] [String]$OrderBy, [Parameter(ParameterSetName="ListShortUrls")] [datetime]$StartDate, [Parameter(ParameterSetName="ListShortUrls")] [datetime]$EndDate, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) # Using begin / process / end blocks due to the way PowerShell processes all # begin blocks in the pipeline first before the process blocks. If user passed # -ShlinkServer and -ShlinkApiKey in this function and piped to something else, # e.g. Set-ShlinkUrl, and they omitted those parameters from the piped function, # they will be prompted for -ShlinkServer and -ShlinkApiKey. This is not my intended # user experience. Hence the decision to implement begin/process/end blocks here. begin { GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $QueryString = [System.Web.HttpUtility]::ParseQueryString('') } process { $Params = @{ Endpoint = "short-urls" PSTypeName = "PSShlink" ErrorACtion = "Stop" } switch ($PSCmdlet.ParameterSetName) { "ParseShortCode" { switch ($PSBoundParameters.Keys) { "ShortCode" { $Params["Path"] = $ShortCode } "Domain" { $QueryString.Add("domain", $Domain) } } } "ListShortUrls" { $Params["PropertyTree"] = "shortUrls", "data" switch ($PSBoundParameters.Keys) { "Tags" { foreach ($Tag in $Tags) { $QueryString.Add("tags[]", $Tag) } } "SearchTerm" { $QueryString.Add("searchTerm", $SearchTerm) } "OrderBy" { $QueryString.Add("orderBy", $OrderBy) } "StartDate" { $QueryString.Add("startDate", (Get-Date $StartDate -Format "yyyy-MM-ddTHH:mm:sszzzz")) } "EndDate" { $QueryString.Add("endDate", (Get-Date $EndDate -Format "yyyy-MM-ddTHH:mm:sszzzz")) } } } } $Params["Query"] = $QueryString try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } } end { } } function Get-ShlinkVisits { <# .SYNOPSIS Get details of visits for a Shlink server, short codes or tags. .DESCRIPTION Get details of visits for a Shlink server, short codes or tags. .PARAMETER ShortCode The name of the short code you wish to return the visits data for. For example, if the short URL is "https://example.com/new-url" then the short code is "new-url". .PARAMETER Tag The name of the tag you wish to return the visits data for. .PARAMETER Domain The domain (excluding schema) associated with the short code you wish to search for. For example, "example.com" is an acceptable value. This is useful if your Shlink instance is responding/creating short URLs for multiple domains. .PARAMETER StartDate A datetime object to filter the visit data where the start date is equal or greater than this value. .PARAMETER EndDate A datetime object to filter the visit data where its end date is equal or less than this value. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Get-ShlinkVists Returns the overall visit count for your Shlink server .EXAMPLE PS C:\> Get-ShlinkVisits -ShortCode "profile" Returns all visit data associated with the short code "profile" .EXAMPLE PS C:\> Get-ShlinkVisits -Tag "oldwebsite" Returns all the visit data for all short codes asociated with the tag "oldwebsite" .EXAMPLE PS C:\> Get-ShlinkVisits -ShortCode "profile" -StartDate (Get-Date "2020-11-01") -EndDate (Get-Date "2020-11-30") Returns all visit data associated with the short code "profile" for the whole of November 2020 .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding(DefaultParameterSetName="Server")] param ( [Parameter(Mandatory, ParameterSetName="ShortCode")] [String]$ShortCode, [Parameter(Mandatory, ParameterSetName="Tag")] [String]$Tag, [Parameter(ParameterSetName="ShortCode")] [Parameter(ParameterSetName="Tag")] [String]$Domain, [Parameter(ParameterSetName="ShortCode")] [Parameter(ParameterSetName="Tag")] [datetime]$StartDate, [Parameter(ParameterSetName="ShortCode")] [Parameter(ParameterSetName="Tag")] [datetime]$EndDate, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $QueryString = [System.Web.HttpUtility]::ParseQueryString('') $Params = @{ PropertyTree = @("visits") } switch -Regex ($PSCmdlet.ParameterSetName) { "Server" { $Params["Endpoint"] = "visits" } "ShortCode|Tag" { $Params["PropertyTree"] += "data" $Params["PSTypeName"] = "PSShlinkVisits" switch ($PSBoundParameters.Keys) { "Domain" { $QueryString.Add("domain", $Domain) } "StartDate" { $QueryString.Add("startDate", (Get-Date $StartDate -Format "yyyy-MM-ddTHH:mm:sszzzz")) } "EndDate" { $QueryString.Add("endDate", (Get-Date $EndDate -Format "yyyy-MM-ddTHH:mm:sszzzz")) } } } "ShortCode" { $Params["Endpoint"] = "short-urls/{0}/visits" -f $ShortCode } "Tag" { $Params["Endpoint"] = "tags/{0}/visits" -f $Tag } } $Params["Query"] = $QueryString try { $Result = InvokeShlinkRestMethod @Params # I figured it would be nice to add the Server property so it is immediately clear # the server's view count is returned when no parameters are used if ($PSCmdlet.ParameterSetName -eq "Server") { [PSCustomObject]@{ Server = $Script:ShlinkServer visitsCount = $Result.visitsCount } } else { $Result } } catch { Write-Error -ErrorRecord $_ } } function New-ShlinkTag { <# .SYNOPSIS Creates one or more new tags on your Shlink server .DESCRIPTION Creates one or more new tags on your Shlink server .PARAMETER Tags Name(s) for your new tag(s) .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> New-ShlinkTag -Tags "oldwebsite","newwebsite","misc" Creates the following new tags on your Shlink server: "oldwebsite","newwebsite","misc" .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding()] param ( [Parameter(Mandatory)] [String[]]$Tags, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $Params = @{ Endpoint = "tags" Method = "POST" Body = @{ tags = @($Tags) } PropertyTree = "tags", "data" ErrorAction = "Stop" } try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } finally { Write-Warning -Message "As of Shlink 2.4.0, this endpoint is deprecated. New tags are automatically created when you specify them in the -Tags parameter with New-ShlinkUrl. At some point, this function may be removed from PSShlink." } } function New-ShlinkUrl { <# .SYNOPSIS Creates a new Shlink short code on your Shlink server. .DESCRIPTION Creates a new Shlink short code on your Shlink server. .PARAMETER LongUrl Define the long URL for the new short code. .PARAMETER CustomSlug Define a custom slug for the new short code. .PARAMETER Tags Associate tag(s) with the new short code. .PARAMETER ValidSince Define a "valid since" date with the new short code. .PARAMETER ValidUntil Define a "valid until" date with the new short code. .PARAMETER MaxVisits Set the maximum number of visits allowed for the new short code. .PARAMETER Domain Associate a domain with the new short code to be something other than the default domain. This is useful if your Shlink instance is responding/creating short URLs for multiple domains. .PARAMETER ShortCodeLength Set the length of your new short code other than the default. .PARAMETER FindIfExists Specify this switch to first search and return the data about an existing short code that uses the same long URL if one exists. .PARAMETER DoNotValidateUrl Disables long URL validation while creating the short code. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> New-ShlinkUrl -LongUrl "https://google.com" Will generate a new short code with the long URL of "https://google.com", using your Shlink server's default for creating new short codes, and return all the information about the new short code. .EXAMPLE PS C:\> New-ShlinkUrl -LongUrl "https://google.com" -CustomSlug "mygoogle" -Tags "search-engine" -ValidSince (Get-Date "2020-11-01") -ValidUntil (Get-Date "2020-11-30") -MaxVisits 99 -FindIfExists Will generate a new short code with the long URL of "https://google.com" using the custom slug "search-engine". The default domain for the Shlink server will be used. The link will only be valid for November 2020. The link will only work for 99 visits. If a duplicate short code is found using the same long URL, another is not made and instead data about the existing short code is returned. .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$LongUrl, [Parameter()] [String]$CustomSlug, [Parameter()] [String[]]$Tags, [Parameter()] [datetime]$ValidSince, [Parameter()] [datetime]$ValidUntil, [Parameter()] [Int]$MaxVisits, [Parameter()] [String]$Domain, [Parameter()] [Int]$ShortCodeLength, [Parameter()] [Switch]$FindIfExists, [Parameter()] [Switch]$DoNotValidateUrl, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $Params = @{ Endpoint = "short-urls" Method = "POST" Body = @{ longUrl = $LongUrl validateUrl = -not $DoNotValidateUrl.IsPresent } ErrorAction = "Stop" } switch ($PSBoundParameters.Keys) { "CustomSlug" { $Params["Body"]["customSlug"] = $CustomSlug } "Tags" { $Params["Body"]["tags"] = @($Tags) } "ValidSince" { $Params["Body"]["validSince"] = (Get-Date $ValidSince -Format "yyyy-MM-ddTHH:mm:sszzzz") } "ValidUntil" { $Params["Body"]["validUntil"] = (Get-Date $ValidSince -Format "yyyy-MM-ddTHH:mm:sszzzz") } "MaxVisits" { $Params["Body"]["maxVisits"] = $MaxVisits } "Domain" { $Params["Body"]["domain"] = $Domain } "ShortCodeLength" { $Params["Body"]["shortCodeLength"] = $ShortCodeLength } "FindIfExists" { $Params["Body"]["findIfExists"] = "true" } } try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } } function Remove-ShlinkTag { <# .SYNOPSIS Remove a tag from an existing Shlink server. .DESCRIPTION Remove a tag from an existing Shlink server. .PARAMETER Tags Name(s) of the tag(s) you want to remove. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Remove-ShlinkTag -Tags "oldwebsite" -WhatIf Reports what would happen if the command was invoked, because the -WhatIf parameter is present. .EXAMPLE PS C:\> Remove-ShlinkTag -Tags "oldwebsite", "newwebsite" Removes the following tags from the Shlink server: "oldwebsite", "newwebsite" .EXAMPLE PS C:\> "tag1","tag2" | Remove-ShlinkTag Removes "tag1" and "tag2" from your Shlink instance. .EXAMPLE PS C:\> Get-ShlinkUrl -ShortCode "profile" | Remove-ShlinkTag Removes all the tags which are associated with the short code "profile" from the Shlink instance. .INPUTS System.String[] Used for the -Tags parameter. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]]$Tags, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) begin { GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $QueryString = [System.Web.HttpUtility]::ParseQueryString('') # Gather all tags and check if any of the user's desired tag(s) to delete # are currently an existing tag within the process / for loop later. # This is because the REST API does not produce any kind of feedback if the # user attempts to delete a tag which does not exist. $AllTags = Get-ShlinkTags } process { foreach ($Tag in $Tags) { if ($AllTags.tag -notcontains $Tag) { $WriteErrorSplat = @{ Message = "Tag '{0}' does not exist on Shlink server '{1}'" -f $Tag, $Script:ShlinkServer Category = "ObjectNotFound" TargetObject = $Tag } Write-Error @WriteErrorSplat continue } else { $QueryString.Add("tags[]", $Tag) } $Params = @{ Endpoint = "tags" Method = "DELETE" Query = $QueryString ErrorAction = "Stop" } if ($PSCmdlet.ShouldProcess( ("Would delete tag '{0}' from Shlink server '{1}'" -f ([String]::Join("', '", $Tags)), $Script:ShlinkServer), "Are you sure you want to continue?", ("Removing tag '{0}' from Shlink server '{1}'" -f ([String]::Join("', '", $Tags)), $Script:ShlinkServer))) { try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } } } } end { } } function Remove-ShlinkUrl { <# .SYNOPSIS Removes a short code from the Shlink server .DESCRIPTION Removes a short code from the Shlink server .PARAMETER ShortCode The name of the short code you wish to remove from the Shlink server. .PARAMETER Domain The domain associated with the short code you wish to remove from the Shlink server. This is useful if your Shlink instance is responding/creating short URLs for multiple domains. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Remove-ShlinkUrl -ShortCode "profile" -WhatIf Reports what would happen if the command was invoked, because the -WhatIf parameter is present. .EXAMPLE PS C:\> Remove-ShlinkUrl -ShortCode "profile" -Domain "example.com" Removes the short code "profile" associated with the domain "example.com" from the Shlink server. .EXAMPLE PS C:\> Get-ShlinkUrl -SearchTerm "oldwebsite" | Remove-ShlinkUrl Removes all existing short codes which match the search term "oldwebsite". .EXAMPLE PS C:\> "profile", "house" | Remove-ShlinkUrl Removes the short codes "profile" and "house" from the Shlink instance. .INPUTS System.String[] Used for the -ShortCode parameter. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]]$ShortCode, [Parameter()] [String]$Domain, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) begin { GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey } process { foreach ($Code in $ShortCode) { $Params = @{ Endpoint = "short-urls" Path = $Code Method = "DELETE" ErrorAction = "Stop" } $WouldMessage = "Would delete short code '{0}' from Shlink server '{1}'" -f $Code, $Script:ShlinkServer $RemovingMessage = "Removing short code '{0}' from Shlink server '{1}'" -f $Code, $Script:ShlinkServer if ($PSBoundParameters.ContainsKey("Domain")) { $QueryString = [System.Web.HttpUtility]::ParseQueryString('') $QueryString.Add("domain", $Domain) $Params["Query"] = $QueryString $WouldMessage = $WouldMessage -replace "from Shlink server", ("for domain '{0}'" -f $Domain) $RemovingMessage = $RemovingMessage -replace "from Shlink server", ("for domain '{0}'" -f $Domain) } if ($PSCmdlet.ShouldProcess( $WouldMessage, "Are you sure you want to continue?", $RemovingMessage)) { try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } } } } end { } } function Save-ShlinkUrlQrCode { <# .SYNOPSIS Save a QR code to disk for a short code. .DESCRIPTION Save a QR code to disk for a short code. The default size of images is 300x300 and the default file type is png. The default folder for files to be saved to is $HOME\Downloads. The naming convention for the saved files is as follows: ShlinkQRCode_<shortCode>_<domain>_<size>.<format> .EXAMPLE PS C:\> Save-ShlinkUrlQrCode -ShortCode "profile" -Domain "example.com" -Size 1000 -Format svg -Path "C:\temp" Saves a QR code to disk in C:\temp named "ShlinkQRCode_profile_example-com_1000.svg". It will be saved as 1000x1000 pixels and of SVG type. .EXAMPLE PS C:\> Get-ShlinkUrl -SearchTerm "someword" | Save-ShlinkUrlQrCode -Path "C:\temp" Saves QR codes for all short URLs returned by the Get-ShlinkUrl call. All files will be saved as the default values for size (300x300) and type (png). All files will be saved in "C:\temp" using the normal naming convention for file names, as detailed in the description. .INPUTS System.Management.Automation.PSObject[] Expects PSObjects with PSTypeName of 'PSTypeName', typically from Get-ShlinkUrl. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline, ParameterSetName="InputObject")] [PSTypeName('PSShlink')] [PSCustomObject[]]$InputObject, [Parameter(Mandatory, ParameterSetName="SpecifyProperties")] [String]$ShortCode, [Parameter(ParameterSetName="SpecifyProperties")] [String]$Domain, [Parameter()] [Int]$Size = 300, [Parameter()] [ValidateSet("png","svg")] [String]$Format = "png", [Parameter()] [String]$Path = "{0}\Downloads" -f $home, [Parameter(ParameterSetName="SpecifyProperties")] [String]$ShlinkServer, [Parameter(ParameterSetName="SpecifyProperties")] [SecureString]$ShlinkApiKey ) begin { $QueryString = [System.Web.HttpUtility]::ParseQueryString('') $QueryString.Add("format", $Format) if ($PSCmdlet.ParameterSetName -ne "InputObject") { $Params = @{ ShortCode = $ShortCode ShlinkServer = $ShlinkServer ShlinkApiKey = $ShlinkApiKey ErrorAction = "Stop" } if ($PSBoundParameters.ContainsKey("Domain")) { $Params["Domain"] = $Domain } # Force result to be scalar, otherwise it returns as a collection of 1 element. # Thanks to Chris Dent for this being a big "ah-ha!" momemnt for me, especially # when piping stuff to Get-Member try { $Object = Get-ShlinkUrl @Params | ForEach-Object { $_ } } catch { Write-Error -ErrorRecord $_ } if ([String]::IsNullOrWhiteSpace($Object.Domain)) { # We can safely assume the ShlinkServer variable will be set due to the Get-ShlinkUrl call # i.e. if it is not, then Get-ShlinkUrl will prompt the user for it and therefore set the variable $Object.Domain = [Uri]$Script:ShlinkServer | Select-Object -ExpandProperty "Host" } $InputObject = $Object } } process { foreach ($Object in $InputObject) { if ([String]::IsNullOrWhiteSpace($Object.Domain)) { $Object.Domain = [Uri]$Script:ShlinkServer | Select-Object -ExpandProperty "Host" } $Params = @{ OutFile = "{0}\ShlinkQRCode_{1}_{2}_{3}.{4}" -f $Path, $Object.ShortCode, ($Object.Domain -replace "\.", "-"), $Size, $Format Uri = "{0}/qr-code/{1}?{2}" -f $Object.ShortUrl, $Size, $QueryString.ToString() ErrorAction = "Stop" } try { Invoke-RestMethod @Params } catch { Write-Error -ErrorRecord $_ } } } end { } } function Set-ShlinkTag { <# .SYNOPSIS Renames an existing tag to a new value on the Shlink server. .DESCRIPTION Renames an existing tag to a new value on the Shlink server. .PARAMETER OldTagName The name of the old tag you want to change the name of. .PARAMETER NewTagName The name fo the new tag you want to the new name to be. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Set-ShlinkTag -OldTagName "oldwebsite" -NewTagName "veryoldwebsite" Updates the tag with the name "oldwebsite" to have a new name of "veryoldwebsite". .INPUTS This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$OldTagName, [Parameter(Mandatory)] [String]$NewTagName, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $Params = @{ Endpoint = "tags" Method = "PUT" Body = @{ oldName = $OldTagName newName = $NewTagName } ErrorAction = "Stop" } try { InvokeShlinkRestMethod @Params } catch { Write-Error -ErrorRecord $_ } } function Set-ShlinkUrl { <# .SYNOPSIS Update an existing short code on the Shlink server. .DESCRIPTION Update an existing short code on the Shlink server. .PARAMETER ShortCode The name of the short code you wish to update. .PARAMETER LongUrl The new long URL to associate with the existing short code. .PARAMETER Tags The name of one or more tags to associate with the existing short code. Due to the architecture of Shlink's REST API, this parameter can only be used in its own parameter set. .PARAMETER ValidSince Define a new "valid since" date with the existing short code. .PARAMETER ValidUntil Define a new "valid until" date with the existing short code. .PARAMETER MaxVisits Set a new maximum visits threshold for the existing short code. .PARAMETER Domain The domain which is associated with the short code you wish to update. This is useful if your Shlink instance is responding/creating short URLs for multiple domains. .PARAMETER ShlinkServer The URL of your Shlink server (including schema). For example "https://example.com". It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .PARAMETER ShlinkApiKey A SecureString object of your Shlink server's API key. It is not required to use this parameter for every use of this function. When it is used once for any of the functions in the PSShlink module, its value is retained throughout the life of the PowerShell session and its value is only accessible within the module's scope. .EXAMPLE PS C:\> Set-ShlinkUrl -ShortCode "profile" -LongUrl "https://github.com/codaamok" -ValidSince (Get-Date "2020-11-01") -ValidUntil (Get-Date "2020-11-30") -MaxVisits 99 Update the existing short code "profile", associated with the default domain of the Shlink server, to point to URL "https://github.com/codaamok". The link will only be valid for November 2020. The link will only work for 99 visits. .EXAMPLE PS C:\> Set-ShlinkUrl -ShortCode "profile" -Tags "powershell","pwsh" Update the existing short code "profile" to have the tags "powershell" and "pwsh" associated with it. .EXAMPLE PS C:\> Get-ShlinkUrl -SearchTerm "preview" | Set-ShlinkUrl -Tags "preview" Updates all existing short codes which match the search term "preview" to have the tag "preview". .INPUTS System.String[] Used for the -ShortCode parameter. .OUTPUTS System.Management.Automation.PSObject #> [CmdletBinding(DefaultParameterSetName="EditUrl")] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName="EditUrlTag")] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName="EditUrl")] [String[]]$ShortCode, [Parameter(Mandatory, ParameterSetName="EditUrl")] [String]$LongUrl, [Parameter(Mandatory, ParameterSetName="EditUrlTag")] [String[]]$Tags, [Parameter(ParameterSetName="EditUrl")] [datetime]$ValidSince, [Parameter(ParameterSetName="EditUrl")] [datetime]$ValidUntil, [Parameter(ParameterSetName="EditUrl")] [Int]$MaxVisits, [Parameter(ParameterSetName="EditUrlTag")] [Parameter(ParameterSetName="EditUrl")] [String]$Domain, [Parameter(ParameterSetName="EditUrl")] [Switch]$DoNotValidateUrl, [Parameter()] [String]$ShlinkServer, [Parameter()] [SecureString]$ShlinkApiKey ) begin { GetShlinkConnection -Server $ShlinkServer -ApiKey $ShlinkApiKey $QueryString = [System.Web.HttpUtility]::ParseQueryString('') } process { foreach ($Code in $ShortCode) { $GetShlinkUrlParams = @{ ShortCode = $Code } switch ($PSCmdlet.ParameterSetName) { "EditUrl" { $Params = @{ Endpoint = "short-urls" Path = $Code Method = "PATCH" Body = @{ longUrl = $LongUrl validateUrl = -not $DoNotValidateUrl.IsPresent } } switch ($PSBoundParameters.Keys) { "ValidSince" { $Params["Body"]["validSince"] = (Get-Date $ValidSince -Format "yyyy-MM-ddTHH:mm:sszzzz") } "ValidUntil" { $Params["Body"]["validUntil"] = (Get-Date $ValidSince -Format "yyyy-MM-ddTHH:mm:sszzzz") } "MaxVisits" { $Params["Body"]["maxVisits"] = $MaxVisits } } } "EditUrlTag" { $Params = @{ Endpoint = "short-urls/{0}/tags" -f $Code Method = "PUT" Query = $QueryString Body = @{ tags = @($Tags) } } } } # The Domain parameter can be used in both parameter sets if ($PSBoundParameters.ContainsKey("Domain")) { $QueryString.Add("domain", $Domain) $GetShlinkUrlParams["Domain"] = $Domain } $Params["Query"] = $QueryString try { # Note: when using -Tags the API endpoint returns a successful message / object, # whereas with everything else no success message / object is returned... $null = InvokeShlinkRestMethod @Params # ... as a result I want to create a user experience where another call is made # to Get-ShlinkUrl to show the user their new changes, viewing the whole object # for each short code. Get-ShlinkUrl @GetShlinkUrlParams } catch { Write-Error -ErrorRecord $_ } } } end { } } #endregion |