Commands/Get-Glass.ps1

function Get-Glass {
    <#
    .SYNOPSIS
        Gets invisible content
    .DESCRIPTION
        There are 16 invisible "variation selector" characters.

        This provides just enough information to encode a byte to an invisible character.

        Thus any series of bytes can be encoded in "glass".

        These selectors sometimes occur in the wild, but are always followed by a visible character.

        If you see two or more invisible characters in a row, you've got shards of glass.

        This script searches for unicode variation selectors in files and packages, and reports where they are found.
    .NOTES
        This unpacks any file it reads as an Open Packaging Convention archive, such as Visual Studio Extensions.
    .LINK
        https://github.com/StartAutomating/Glass
    .LINK
        https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)
    .LINK
        https://www.koi.ai/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace
    .LINK
        ConvertFrom-Glass
    .LINK
        ConvertTo-Glass
    #>

    [Alias('Glass')]
    param(
    # Looks for glass in a variety of different inputs
    [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [Alias('InputObject','FilePath','FullName')]
    [PSObject[]]
    $From
    )

    begin {
        # Glass can be identified by two or more invisible bytes.
        # (one invisible byte may be an actual variation selector)
        $glassPattern = @(            
            '[\ufe00-\ufe0f]{2,}'  # Match two or more characters in the selection range
        ) -join ''

        # Make our pattern into a real regex, for a bit of performance boost.
        $glassDetector = [Regex]::new($glassPattern) 
        
        $glassFound = @()
        $ToScan = @()
    }

    process {
        # Keep track of the total amount of content scanned
        $totalArchivePartsScanned = 0 
        $totalFilesScanned = 0    
        
        # Go over each potential file in from
        foreach ($file in $from) {            
            # for actual files
            if ($file -is [IO.FileInfo]) {
                # read it as bytes first
                $fileBytes = Get-Content -Raw -LiteralPath $file.FullName -AsByteStream                    
                try {
                    # and see if it is a package.
                    $filePackage = [IO.Packaging.Package]::Open($fileBytes, 'Open', 'Read')
                    # If it was get its parts.
                    $packageParts = @($filePackage.GetParts())
                    if ($packageParts) {
                        foreach ($part in $packageParts) {
                            # Read eac part as a string
                            $partStream = $part.GetStream()                            
                            $partStreamReader = [IO.StreamReader]::new($partStream)
                            $partText = $partStreamReader.ReadToEnd()
                            # and add them to our list of things to scan
                            $toScan += [PSCustomObject]@{
                                FilePath = $file.FullName
                                Part = $part.Uri
                                Text = $partText
                            }
                            $partStream.Close()
                        }
                    }
                    $filePackage.Close()
                } catch {
                    # If it was not a package, read it's text
                    $fileText = Get-Content -Raw -LiteralPath $file.FullName
                    # and add it to the list of things to scan.
                    $toScan += [PSCustomObject]@{
                        FilePath = $file.FullName
                        Text = $fileText
                    }                    
                }
                return
            }

            # If the input was a string
            if ($file -is [string]) {
                # see if it's a path
                if (Test-Path $file -ErrorAction Ignore) {
                    # If it was, get the file or directory and look for glass
                    Get-Item -Path $file | 
                        Get-Glass
                } else {
                    # otherwise, look for glass in the text blob
                    $toScan += [PSCustomObject]@{Text=$file}
                }
            }

            if ($file -is [psmoduleinfo]) {
                $file | Split-Path | Get-Item | Get-Glass 
                return
            }
            # If the input was a directory
            if ($file -is [IO.DirectoryInfo]) {
                # look for glass in all of the files (including hidden files)
                Get-ChildItem -Force -Recurse -File -LiteralPath $file.FullName | 
                    Get-Glass
                return
            }
            
        }
    }

    end {

        # at the end of the pipeline, scan all we need to scan.
        $GlassFound = foreach ($in in $ToScan) {
            # If there is no text, skip this object
            if (-not $in.Text) {continue }
            
            # Keep track of ow many parts/files we scan
            if ($in.Part) {
                $totalArchivePartsScanned++
            } else {
                $totalFilesScanned++
            }
            # find any shards.
            $shards = @($glassDetector.Matches($in.Text))
            # If we found any
            if ($shards) {
                # write a warning
                Write-Warning "$($in.FilePath)$(if ($in.Part) { "@$($in.Part)"}) contains glass shards at @ $(
                    foreach ($shard in $shards) {
                        $($shard.Index),'-',$($shard.Index + $shard.Length)
                    })"


                # If we found it in a file
                if ($in.FilePath) {
                    # Get the file
                    $fileAndInfo = 
                        Get-Item -LiteralPath $in.FilePath -Force
                            
                    # add on our shard information
                    $fileAndInfo |
                        Add-Member NoteProperty Shards $shards -Force
                    
                    # and optionally add on the part of the archive
                    if ($in.Part) {
                        $fileAndInfo | 
                            Add-Member NoteProperty Part $in.Part -Force                
                    }

                    # then emit the result.
                    $fileAndInfo
                } else {
                    # If we did not find it in a file, output the shards.
                    $shards
                }                
            }
        }

        # If we scanned anything, output our results
        if ($totalFilesScanned -or $totalArchivePartsScanned) {
            [PSCustomObject]@{
                FilesScanned = $totalFilesScanned
                PartsScanned = $totalArchivePartsScanned 
                Glass = $glassFound
            }
        }        
    }
}