Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
150857 stories
·
33 followers

When Santa brings you a work request: Searching GitHub Repos and Gists for Text Patterns with PowerShell

1 Share

This is my feeble attempt at blogging again. Now that blogging is out of fashion and we trust AI to tell us what to think, drink and eat, it’s probably as good a time to do it as ever. So, this was prompted (pardon the pun) by a need to find any and all references in my sprawling warehouse of festering code (GitHub) that mention “Invoke-WebRequest” which might be used on a Windows machine. The goal being to make sure to include -UseBasicParsing for safety reasons.

As Matthew Dowst explains in his recent YouTube video, this is related to CVE-2025-54100 and only affects Windows PowerShell 5.1. It does not apply to PowerShell 7, and therefore doesn’t apply to PowerShell running on MacOS or Linux.

Assumptions

  • You are familiar with GitHub and have a GitHub account
  • You are familiar with PowerShell (on Windows, Mac or Linux)
  • You need to search through a bunch of old code, like me.
  • You have GitHub CLI installed and operational
  • You have no life and are too broke to do something more fun
  • Your kids are grown and moved out, so you have nothing else going on
  • You haven’t won the lottery or inherited a fortune from a dead relative
  • You are addicted to some sort of caffeinated liquid substances like me

If you meet these conditions, you’re good to go.

Why?

Aside from having no life and being addicted to coffee, sometimes you might need to quickly search through a lot of source code, which may or may not be cloned to a local place (hard drive). Matt covers this scenario in the video linked above. All my code is hiding in old GitHub repositories and gists. Oh, and I don’t have a life.

Caveates, Disclaimers, etc.

This code was tested with Windows PowerShell 5.1 and PowerShell 7.5.4 on Linux. Why both? Because it was either that or crawl in the attic to get Christmas stuff down, and it’s cold up there today. I also don’t have a life.

Your results may vary. Test thoroughly in non-production environments on non-production code for non-production use. Or test it on mission critical life-supporting systems in production, because you ignore warnings like this anyway. Don’t blame me if it crashes anything important.

Searching through Code

There’s quite a few options for searching content within GitHub Repositories and Gists, from the obvious web portal search tools, to various PowerShell scripts and modules (sort of), REST API, and my recent favorite: GitHub CLI.

After sampling a dozen or so scripts, which were close, but not quite what I was looking for, I searched PowerShell Gallery (using AI assistance) but using Find-PsResource -Tag 'github' didn’t find more than a handful, but still not what I was looking for. That’s not to say there aren’t any, but
in a time crunch, I wasn’t able to locate something close enough. So I started playing with GitHub CLI.

PowerShell: Searching Repos

The source code: Get it here instead of copying from this blog.

function Search-GitHubRepository {
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)][string]$SearchValue,
        [parameter(Mandatory = $false)][string]$Owner,
        [parameter(Mandatory = $false)][switch]$Summary
    )
    try {
        if ([string]::IsNullOrEmpty($SearchValue)) {
            throw "No SearchValue was provided"
        }
        if (-not (Get-Command "gh")) {
            throw "Install GitHub CLI first."
        }
        if ($IsLinux) {
            $cmd = "gh"
        } else {
            $cmd = "gh.exe"
        }

        $ghArgs = @('search', 'code', $SearchValue)

        if (![string]::IsNullOrEmpty($Owner)) {
            $ghArgs += "--owner=$Owner"
        }
        $ghArgs += @('--json', 'repository,path,url,textMatches')

        Write-Verbose "Command: $cmd $($arglist -join ' ')"
        $textmatches = & $cmd $ghArgs | ConvertFrom-Json
        Write-Verbose "$($textmatches.count) matches found" -ForegroundColor Cyan
        if ($Summary.IsPresent) {
            $textmatches | Select-Object -Property @{l = 'Repository'; e = { $_.repository.nameWithOwner } } |
                Select-Object -Property Repository | Sort-Object -Unique -Property Repository
        } else {
            $textmatches |
                Select-Object -Property path,url,@{l = 'Repository'; e = { $_.repository.nameWithOwner } },
                    @{l = 'Text'; e = { $_.textMatches.fragment } } |
                Sort-Object -Property Repository
        }
    } catch {
        [pscustomobject]@{
            Status   = "Error"
            Message  = $_.Exception.Message
            Trace    = $_.Exception.ScriptStackTrace
            Category = $_.Exception.CategoryInfo.Activity
        }
    }
}

Example:

Search-GitHubRepository -SearchValue "Invoke-WebRequest" -Owner "username" -Summary

This returns names of Repositories and matching items to provide a summary of matches.

Search-GitHubRepository -SearchValue "Invoke-WebRequest" -Owner "username"

This returns repository and file names and matching content as well.

PowerShell: Search Gists

The source: Get it here

function Search-GitHubGist {
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$true)][string]$SearchValue,
        [parameter(Mandatory=$false)][switch]$IncludeContent,
        [parameter(Mandatory=$false)][int]$Limit = 100
    )

    try {
        if ([string]::IsNullOrEmpty($SearchValue)) {
            throw "No SearchValue was provided"
        }
        if (-not (Get-Command "gh")) {
            throw "Install GitHub CLI first."
        }
        if ($IsLinux) {
            $cmd = "gh"
        } else {
            $cmd = "gh.exe"
        }

        $ghArgs = @('gist', 'list', '--filter', $SearchValue, '--include-content', '--limit', $Limit)
        $gists = & $cmd @ghArgs
        <#
        Filter results to map lines to properties as follows:

        b5db0c256f73f300eaea8c50d7973f9d boxstarter_sample2.txt
            BoxStarter Examples
                Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | iex

        No spaces at the beginning of the line = id and filename
        4 spaces at the beginning of the line = description
        8 spaces at the beginning of the line = matching content
        #>
        $results = @()
        for ($i = 0; $i -lt $gists.Count; $i++) {
            $line = $gists[$i]
            if (![string]::IsNullOrEmpty($line)) {
                if (-not $line.StartsWith(" ")) {
                    # Line with no leading spaces = id and filename
                    $gistId      = $line.Substring(0, 32)
                    $filename    = $line.Substring(33)
                    $description = ""
                    $content     = ""

                    # Check next lines for description (4 spaces) and content (8 spaces)
                    if ($i + 1 -lt $gists.Count -and $gists[$i + 1].StartsWith("    ") -and -not $gists[$i + 1].StartsWith("        ")) {
                        $description = $gists[$i + 1].Trim()
                        if ($i + 2 -lt $gists.Count -and $gists[$i + 2].StartsWith("        ")) {
                            $content = $gists[$i + 2].Trim()
                        }
                    }

                    $results += [pscustomobject]@{
                        id          = $gistId
                        filename    = $filename
                        gistname    = $description
                        content     = $content
                    }
                }
            }
        }

        $results | foreach-object {
            $gistId   = $_.id
            $filename = $_.filename
            Write-Verbose "gist id: $gistId - filename: $filename"
            $gistContent = gh gist view $gistId --raw
            if ($IncludeContent.IsPresent) {
                Write-Verbose "Including content in results"
                $gistContent | select-string -Pattern $SearchValue -List |
                    select-object -Property @{l='gistId';e={$gistId}}, @{l='filename';e={$filename}}, @{l='line';e={$_.LineNumber}}, @{l='match';e={$_.Line}}
            } else {
                $gistContent | select-string -Pattern $SearchValue -List |
                    select-object -Property @{l='gistId';e={$gistId}}, @{l='filename';e={$filename}}, @{l='line';e={$_.LineNumber}}
            }
        }
    } catch {
        [pscustomobject]@{
            Status   = "Error"
            Message  = $_.Exception.Message
            Trace    = $_.Exception.StackTrace
            Category = $_.Exception.CategoryInfo.Activity
        }
    }
}

Example:

Search-GitHubGist -SearchValue "Invoke-WebRequest"

This returns a summary of Gists and filenames with matching content, but not the actual matching content in the output.

Search-GitHubGist -SearchValue "Invoke-WebRequest" -IncludeContent

This returns all matching Gists and filenames as well as matching line numbers and content portions for each.

Conclusion

You might find some mistakes, or notice that I conveniently forgot to include support for filtering Gists by Public or Secret (GitHub CLI supports that, by the way). If you think this blows chunks, please be kind, but any feedback is welcome, especially if it includes winning lottery numbers.

You can get a lot done when you don’t have enough money to do something else. Coffee helps too.
Go to the following link for more information on the GitHub CLI Comment Reference



Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

AI Can Write Your Code. It Can’t Do Your Job.

1 Share

In May, OpenAI agreed to pay $3 billion for Windsurf, the AI coding assistant formerly known as Codeium. Three billion dollars. For a VSCode fork.

The deal eventually fell apart, but what matters is that they wanted to do it in the first place.

Last week, Anthropic made an interesting acquisition: they bought Bun, the JavaScript runtime. Bun is open source and MIT-licensed. Anthropic could have forked it and built on top of it for free. They have Claude Code, an excellent code-writing tool.

Instead, they bought the company. Because they wanted Jarred Sumner and his team.

This is what I keep coming back to when I see another “Programming is dead” post go viral. The companies building AI, the ones who supposedly know exactly what it can and can’t do, are spending billions to acquire engineering talent. Not fire them, acquire them.

If OpenAI believed GPT could replace software engineers, why wouldn’t they build their own VS Code fork for a fraction of that cost? If Anthropic thought Claude could do the work, why make an acquisition at all?

Programming isn’t the job

Here’s my take: AI can replace most of programming, but programming isn’t the job.

Programming is a task. It’s one of many things you do as part of your work. But if you’re a software engineer, your actual job is more than typing code into an editor.

The mistake people make is conflating the task with the role. It’s like saying calculators replaced accountants. Calculators automated arithmetic, but arithmetic was never the job. The job was understanding financials, advising clients, making judgment calls, etc. The calculator just made accountants faster at the mechanical part.

AI is doing something similar for us.

What the job is

Think about what you actually do in a given week.

You sit in a meeting where someone describes a vague problem, and you’re the one who figures out what they actually need. You look at a codebase and decide which parts to change and which to leave alone. You push back on a feature request because you know it’ll create technical debt that’ll haunt the team for years. You review a colleague’s PR and catch a subtle bug that would’ve broken production. You make a call on whether to ship now or wait for more testing.

None of that is programming, but it’s all your job.

Some concerns

I’m not going to pretend nothing is changing.

Will some companies use AI as an excuse to cut headcount? Absolutely. Some already have. There will be layoffs blamed on “AI efficiency gains” that are really just cost-cutting dressed up as something else.

But think about who stays and who goes in that scenario. It’s not random. The engineers who understand that programming isn’t the job, the ones who bring judgment, context, and the ability to figure out what to build, those are the ones who stay. The ones who only brought code output might be at risk

A common worry is that juniors will get left behind. If AI handles the “doing” part, how do they build judgment? I actually think the opposite is true. AI compresses the feedback loop. What used to take days of flipping through books or waiting for Stack Overflow answers now takes seconds. The best juniors aren’t skipping steps, but getting through them faster.

Now think about your own situation. Say you were hired two years ago, before the current AI wave. Your company wanted you. They saw value in what you bring. Now, with AI tools, you’re significantly more productive. You ship faster. You handle more complexity. You’re better at your job than ever before.

“You got way more productive, so we’re letting you go” is not a sentence that makes a lot of sense.

What to do about it

If you’re reading this, you’re already thinking about this stuff. That puts you ahead. Here’s how to stay there:

  1. Get hands-on with AI tools. Learn what they’re actually useful for. Figure out where they save you time and where they waste it. The engineers who are doing this now will be ahead.
  2. Practice the non-programming parts. Judgment, trade-offs, understanding requirements, communicating with stakeholders. These skills matter more now, not less.
  3. Build things end-to-end. The more you understand the full picture, from requirements to deployment to maintenance, the harder you are to replace.
  4. Document your impact, not your output. Frame your work in terms of problems solved, not lines of code written.
  5. Stay curious, not defensive. The engineers who will struggle are the ones who see AI as a threat to defend against rather than a tool to master.

The shape of the work is changing: some tasks that used to take hours now take minutes, some skills matter less, others more.

But different isn’t dead. The engineers who will thrive understand that their value was never in the typing, but in the thinking, in knowing which problems to solve, in making the right trade-offs, in shipping software that actually helps people.

OpenAI and Anthropic could build their own tools. They have the best AI in the world. Instead, they’re spending billions on engineers. That should tell you something.





Read the whole story
alvinashcraft
26 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Why you shouldn’t use INs in SQL and what to use instead

1 Share

Starting Point

In our application, TimeRocket, we frequently run into a scenario where after an initial pre-filtering phase we issue another query for the elements identified beforehand. A concrete example: we want to fetch calendar events, but first need to determine which calendars to query.

It typically looks something like this:

public async Task<Row> GetEreignisse(
    EmployeeGuid[] employeeIds,
    Workday workday)
{
    var calendarIds = await GetCalendarIdsForEmployees(calendars);
    return await this.Query<Row>(
        @"SELECT * FROM calendarEntries e
                WHERE e.tenantId = @tenantId
                    AND e.workday = @workday
                    AND e.calendarId IN @calendarIds",
        new
        {
            calendarIds = calendarIds,
            tenantId = tenantId,
            workday = ToDateTime(workday)
        });
}

We use Dapper in our software, which is why we write e.calendarId IN @calendarIds instead of something like e.calendarId IN (@calendarIds). But that’s merely an implementation detail.

So the question is: what’s actually wrong with this approach?

Well, let’s assume calendarIds contains three IDs. On SQL Server the statement ultimately becomes something like:

SELECT *
FROM calendarEntries e
WHERE e.tenantId = @tenantId
  AND e.workday = @workday
  AND e.calendarId IN (@calendarId1, @calendarId2, @calendarId3)

This leads to two issues:

1. Parameter Limit

Because SQL Server requires one parameter per element, you hit the limit relatively quickly. On MS SQL, the maximum number of parameters is 2100. This may sound absurdly high, but we have absolutely encountered places where we ran into this limit.

Our “solution” back then involved hideous ideas such as splitting the items into chunks (say, batches of 1000), executing a query per chunk, and then stitching the results back together. Yeah, I’m actually guilty of implementing this…

2. Constantly Changing Query Plans

Even worse: every time the number of elements changes, SQL Server generates a completely new query plan. Technically speaking, a query with two elements ends with:

e.calendarId IN (@calendarId1, @calendarId2)

while another ends with

e.calendarId IN (@calendarId1, @calendarId2, @calendarId3)

These are not the same queries, which means SQL Server treats them as different plans.

This uses lots of disk space and causes SQL Server to frequently re-calculate query plans, which is an extremely expensive operation.

Solution

Our solution to this problem is called Table-Valued Parameters, or TVPs.

As far as I know, this feature exists only on MS SQL (since SQL Server 2008). Other SQL servers may offer something similar, but I’m not familiar with those.

What Are TVPs and How Do They Work?

MS SQL allows us to define our own data types, called “user-defined types”, in addition to built-in types like NVARCHAR or INT. TVPs extend this concept by allowing us to define entire tables as a type.

A TVP consists of rows (our elements) and columns (in our case, just one GUID column). When executing a query, we populate a table on the backend side (e.g., an ADO.NET DataTable) and pass the entire table as a single parameter.

Inside the SQL statement, we now have access to this table parameter and can simply JOIN it.

Let’s take a look at the code:

public async Task<Row> GetEreignisse(
    EmployeeGuid[] employeeIds,
    Workday workday)
{
    var calendarIds = await GetCalendarIdsForEmployees(calendars);

    var dataTable = new System.Data.DataTable();
    dataTable.Columns.Add("Id", typeof(Guid));
    foreach (var guid in calendarIds)
    {
        dataTable.Rows.Add(guid);
    }
    var tvp = dataTable.AsTableValuedParameter("GuidList");

    return await this.Query<Row>(
        @"SELECT * FROM calendarEntries e
                JOIN @calendarIds c ON e.calendarId = c.Id
                WHERE e.tenantId = @tenantId
                    AND e.workday = @workday",
        new
        {
            calendarIds = tvp,
            tenantId = tenantId,
            workday = ToDateTime(workday)
        });
}

In the example above, we used two “magic” strings: "Id" and "GuidList". Where do these come from?

Before we can use a TVP at all, the type must be created once on SQL Server:

CREATE TYPE GuidList AS TABLE (Id UNIQUEIDENTIFIER NOT NULL);

Here, we define a user-defined type GuidList. It’s a table type (AS TABLE) with a single GUID column named Id.

Next, we need to grant access:

GRANT EXECUTE ON TYPE::dbo.GuidList TO public;

Of course, you could restrict this to specific users or groups, but since this is merely a type and not actual data, I personally don’t see why it shouldn’t just be available publicly.

After these two steps, our GuidList type can now be used anywhere on the SQL Server. And yes you understood correctly: this type needs to be created only once. Not per query. And it can be reused across entirely different queries on entirely different tables.

Naturally, converting our GUIDs into a TVP can be encapsulated in a helper function:

public static SqlMapper.ICustomQueryParameter ToGuidListTvp(IEnumerable<Guid> guids)
    {
        var dt = new System.Data.DataTable();
        dt.Columns.Add("Id", typeof(Guid));
        foreach (var guid in guids.Distinct())
        {
            dt.Rows.Add(guid);
        }
        return dt.AsTableValuedParameter("GuidList");
    }

Personally, I really like this solution because it doesn’t distract from the actual query and -once set up- can be reused effortlessly.
What do you think? Let me know your thoughts in the comments!

Read the whole story
alvinashcraft
42 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Building Secure Enterprise APIs with .NET, Azure API Management, and Entra ID (Azure AD)

1 Share

## 1 Introduction: The Zero Trust Mandate for Modern APIs

Modern enterprise systems depend on APIs to connect business processes, exchange data, and support distributed applications. These APIs operate in environments where users, workloads, and network boundaries shift constantly—across cloud platforms, on-prem systems, partner integrations, and remote access. The real challenge today isn’t building APIs; it’s securing them reliably and consistently in this changing landscape.

Using *.NET, Azure API Management (APIM), and Entra ID* together provides a strong, predictable foundation for implementing Zero Trust principles across your entire API surface.

This section sets the stage by defining the security problem and the architectural model used throughout the article.

### 1.1 The Shift from Perimeter to Identity-Centric Security

For a long time, organizations relied on firewalls and network segmentation to protect internal systems. If traffic came from inside the network, it was assumed to be trustworthy. That assumption no longer holds. Cloud adoption, mobile users, partner integrations, and public-facing APIs have blurred the concept of a “safe internal network.”

Read the whole story
alvinashcraft
56 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Responding to "The highest quality codebase"

1 Share
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Announcing Rust 1.92.0

1 Share

The Rust team is happy to announce a new version of Rust, 1.92.0. Rust is a programming language empowering everyone to build reliable and efficient software.

If you have a previous version of Rust installed via rustup, you can get 1.92.0 with:

$ rustup update stable

If you don't have it already, you can get rustup from the appropriate page on our website, and check out the detailed release notes for 1.92.0.

If you'd like to help us out by testing future releases, you might consider updating locally to use the beta channel (rustup default beta) or the nightly channel (rustup default nightly). Please report any bugs you might come across!

What's in 1.92.0 stable

Deny-by-default never type lints

The language and compiler teams continue to work on stabilization of the never type. In this release the never_type_fallback_flowing_into_unsafe and dependency_on_unit_never_type_fallback future compatibility lints were made deny-by-default, meaning they will cause a compilation error when detected.

It's worth noting that while this can result in compilation errors, it is still a lint; these lints can all be #[allow]ed. These lints also will only fire when building the affected crates directly, not when they are built as dependencies (though a warning will be reported by Cargo in such cases).

These lints detect code which is likely to be broken by the never type stabilization. It is highly advised to fix them if they are reported in your crate graph.

We believe there to be approximately 500 crates affected by this lint. Despite that, we believe this to be acceptable, as lints are not a breaking change and it will allow for stabilizing the never type in the future. For more in-depth justification, see the Language Team's assessment.

unused_must_use no longer warns about Result<(), UninhabitedType>

Rust's unused_must_use lint warns when ignoring the return value of a function, if the function or its return type is annotated with #[must_use]. For instance, this warns if ignoring a return type of Result, to remind you to use ?, or something like .expect("...").

However, some functions return Result, but the error type they use is not actually "inhabited", meaning you cannot construct any values of that type (e.g. the ! or Infallible types).

The unused_must_use lint now no longer warns on Result<(), UninhabitedType>, or on ControlFlow<UninhabitedType, ()>. For instance, it will not warn on Result<(), Infallible>. This avoids having to check for an error that can never happen.

use core::convert::Infallible;
fn can_never_fail() -> Result<(), Infallible> {
    // ...
    Ok(())
}

fn main() {
    can_never_fail();
}

This is particularly useful with the common pattern of a trait with an associated error type, where the error type may sometimes be infallible:

trait UsesAssocErrorType {
    type Error;
    fn method(&self) -> Result<(), Self::Error>;
}

struct CannotFail;
impl UsesAssocErrorType for CannotFail {
    type Error = core::convert::Infallible;
    fn method(&self) -> Result<(), Self::Error> {
        Ok(())
    }
}

struct CanFail;
impl UsesAssocErrorType for CanFail {
    type Error = std::io::Error;
    fn method(&self) -> Result<(), Self::Error> {
        Err(std::io::Error::other("something went wrong"))
    }
}

fn main() {
    CannotFail.method(); // No warning
    CanFail.method(); // Warning: unused `Result` that must be used
}

Emit unwind tables even when -Cpanic=abort is enabled on linux

Backtraces with -Cpanic=abort previously worked in Rust 1.22 but were broken in Rust 1.23, as we stopped emitting unwind tables with -Cpanic=abort. In Rust 1.45 a workaround in the form of -Cforce-unwind-tables=yes was stabilized.

In Rust 1.92 unwind tables will be emitted by default even when -Cpanic=abort is specified, allowing for backtraces to work properly. If unwind tables are not desired then users should use -Cforce-unwind-tables=no to explicitly disable them being emitted.

Validate input to #[macro_export]

Over the past few releases, many changes were made to the way built-in attributes are processed in the compiler. This should greatly improve the error messages and warnings Rust gives for built-in attributes and especially make these diagnostics more consistent among all of the over 100 built-in attributes.

To give a small example, in this release specifically, Rust became stricter in checking what arguments are allowed to macro_export by upgrading that check to a "deny-by-default lint" that will be reported in dependencies.

Stabilized APIs

These previously stable APIs are now stable in const contexts:

Other changes

Check out everything that changed in Rust, Cargo, and Clippy.

Contributors to 1.92.0

Many people came together to create Rust 1.92.0. We couldn't have done it without all of you. Thanks!

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories