Tag Archives: For

CSV Browser Links

Edit: At some point between publishing this post and now — 11:43 p.m. on Christmas Eve, Eve — I thought people consuming this post would benefit from seeing the menu open a CSV-based bookmark. A short gif has been added to the bottom of this post.

I do not know what my problem is, but for the last few years, I’ve been an anti-browser-bookmarks person. I don’t really have a good reason as to why, but I look forward to getting over it; I do. But until then, I have been saving all my links inside of a CSV file with the assumption that at some point I will write some PowerShell that will allow me to easily search my CSV file and then open links programmatically. Well, guess what, I finally wrote that.

The first thing sharing this project is going to require is a properly formatted CSV file we can both work from. No need to put mine in here; it would be full of links that are worthless to anyone but me. Copy and paste the comma-separated data below and save that off to a CSV file called Links.csv Be sure to note the path where you chose to save it, as we will work with it as a part of this post.

Title,Link,Note
PowerShell GitHub,https://github.com/PowerShell/PowerShell,
PowerShell GitHub Issues,https://github.com/PowerShell/PowerShell/issues,
PowerShell Docs GitHub,https://github.com/MicrosoftDocs/PowerShell-Docs,
Powershell Docs GitHub Issues,https://github.com/MicrosoftDocs/PowerShell-Docs/issues,
PowerShell Docs,https://docs.microsoft.com/en-us/powershell,
PowerShell Gallery,https://www.powershellgallery.com,
PowerShell Reddit,https://www.reddit.com/r/PowerShell,
Twitter Legends @jeffhicks,https://twitter.com/JeffHicks,
Twitter Legends @jsnover,https://twitter.com/jsnover,
Twitter Legends @concentrateddon, https://twitter.com/concentrateddon,
TechCrunch.com,https://techcrunch.com/,Tech News
Cnet.com,https://www.cnet.com/,Tech News
Gizmodo.com,https://gizmodo.com/,Tech News
9to5mac.com,https://9to5mac.com/,Tech News
Engadget.com,https://www.engadget.com/,Tech News
Wired.com,https://www.wired.com/,Tech News
TechRadar.com,https://www.techradar.com/,News
Axios.com,https://www.axios.com/,News

With our CSV in place, we can work through the below PowerShell and then take it for a test drive. Have a look and meet me below for a quick discussion, before we try it out.

function Find-Link {
    [CmdletBinding()]
    Param (
        [Parameter()]
        $Path = 'C:\Users\tommymaynard\Documents\CSVs\Links.csv',
        [Parameter()]$Title,[Parameter()]$Link,[Parameter()]$Note
    )

    #region Import CSV/filter.
    $AllLinks = Import-Csv -Path $Path
    $ FilterLinks = $AllLinks | Where-Object -FilterScript {
        $_.Title -like "*$Title*" -and $_.Link -like "*$Link*" -and $_.Note -like "*$Note*"
    }
    #endregion
    #region Create link menu.
    for ($i = 0; $i -lt $FilterLinks.Count; $i++) {
        "[$($i + 1)] $(($FilterLinks[$i]).Title)"
    } # for
    #endregion
    #region Prompt user.
    do {
        $Option = Read-Host -Prompt 'Link Number'
    } # do
    until ($Option -in (1..$FilterLinks.Count))
    Start-Process -FilePath "$($FilterLinks[$($Option - 1)].Link)"
    #endregion
}

The function is named Find-Link, and it includes a -Path parameter. This is the location of the CSV file. While the function contains a default path, it can be changed when the function is invoked by passing in a different value. The static entry in the function can also be permanently modified, as well — it is up to you.

Find-Link -Path '/users/landrews/Documents/bookmarks.csv'

When the file is imported, the entire CSV is assigned to the $AllLinks variable. Then, it runs a command against that variable, creating a new variable, to filter down the results using the value(s) potentially passed to three other parameters: -Title, -Link, and -Note. There is more than just the -Path parameter.  It does not check if any of these parameters were actually included, but it could have using $PSBoundParameters. Once we have a filtered list of links to display, we cycle through them using a for loop, which creates a menu of options. Here’s an example of one of the outputs created by invoking this command with the -Title parameter.

Find-Link -Title Twitter
[1] Twitter Legends @jeffhicks
[2] Twitter Legends @jsnover        
[3] Twitter Legends @concentrateddon
Link Number:

The final portion of the function is a do-until loop. This invokes Read-Host prompt until one of the available menu numbers is entered. When a number that is not included is entered, it will prompt the user again for a different value. Here is an example of that.

Find-Link -Title Twitter
[1] Twitter Legends @jeffhicks
[2] Twitter Legends @jsnover        
[3] Twitter Legends @concentrateddon
Link Number: 8
Link Number: 15
Link Number: 4
Link Number: 

When a value is selected that is included from the list, Start-Process invokes the corresponding link. Before we close out, here are a few more examples.

Find-Link -Link powershell
[1] PowerShell GitHub
[2] PowerShell GitHub Issues     
[3] PowerShell Docs GitHub       
[4] Powershell Docs GitHub Issues
[5] PowerShell Docs
[6] PowerShell Gallery
[7] PowerShell Reddit
Link Number:
Find-Link -Note News
[1] TechCrunch.com
[2] Cnet.com
[3] Gizmodo.com
[4] 9to5mac.com
[5] Engadget.com
[6] Wired.com
[7] TechRadar.com
[8] Axios.com
Link Number:
Find-Link -Note 'Tech News' 
[1] TechCrunch.com
[2] Cnet.com      
[3] Gizmodo.com   
[4] 9to5mac.com 
[5] Engadget.com
[6] Wired.com   
Link Number:   

We can combine the parameters too, to further filter the results.

Find-Link -Title GitHub -Link powershell
[1] PowerShell GitHub
[2] PowerShell GitHub Issues
[3] PowerShell Docs GitHub
[4] Powershell Docs GitHub Issues
Link Number: 

There may be a few things to add over time, but for now, this gives me what I wanted. It is better than navigating to the document and copying out a link — never. again.

Get-PSCallStack Demonstration Using Multiple Functions

I wrote a couple recent posts that used the Get-PSCallStack cmdlet. In my case, Get-PSCallStack helped one function determine what function invoked it, and used the information to further determine if that function was approved to invoke it, or not. In doing this, I wrote a series of small functions I’d like to share to help demonstrate this process.

In the below code example, I’ve defined a series of functions. The first function defined is called Get-Main, and it does a few things. It sets a variable $Index to zero, dumps the Get-PSCallStack commands into a global variable (that will exist after the function completes), and then loops through each of the command properties in the second variable, displaying its correctly assumed index, and the command name.

The following functions are in place to progressively call one another from the bottom up. Working downward, however, Get-OneUpOne invokes Get-Main (discussed above), Get-OneUpTwo invokes Get-OneUpOne, Get-OneUpThree invokes Get-OneUpTwo, etc. These multiple functions end up being listed in Get-PSCallStack, as we’ll soon see.

Function Get-Main {
    $Index = 0
    $Global:Var = (Get-PSCallStack).Command
    $Var | ForEach-Object {
        "Index $Index : $_"
        $Index ++
    }
}

Function Get-OneUpOne {
    Get-Main
}

Function Get-OneUpTwo {
    Get-OneUpOne
}

Function Get-OneUpThree {
    Get-OneUpTwo
}

Function Get-OneUpFour {
    Get-OneUpThree
}

Get-OneUpFour

The last line in the above example, starts the magic by invoking Get-OneUpFour, that invokes Get-OneUpThree, etc., moving upward. Here’s a gif that shows how these are invoked, working up from the bottom.

get-pscallstack-demonstration-using-multiple-functions01

When this chunk of code is dropped into the ISE and run, we get the results listed below. They indicate the index and value of Get-PSCallStack’s recorded commands, or steps, on the way to invoking the Get-Main function. Notice, that it’s backwards, and what I mean is this: The first index 0, is the last command recorded. The last index, 5, is the first command recorded.

Index 0 : Get-Main
Index 1 : Get-OneUpOne
Index 2 : Get-OneUpTwo
Index 3 : Get-OneUpThree
Index 4 : Get-OneUpFour
Index 5 : <ScriptBlock>

Since we created the global variable $Var, we can verify if the above results are correct — if our assumed index is accurate. We’ll check with a single index first, and then run $Var though a For loop to see each index.

$Var[2]

Get-OneUpTwo
For ($i = 0; $i -lt $Var.Length; $i++) {
    "Index $i -- $($Var[$i])"
}

Index 0 -- Get-Main
Index 1 -- Get-OneUpOne
Index 2 -- Get-OneUpTwo
Index 3 -- Get-OneUpThree
Index 4 -- Get-OneUpFour
Index 5 -- Get-PSCallStack_Example.ps1

Take a look at index 5 in the directly above example, and then the further above example. One says, “<ScriptBlock>,” and one says, “Get-PSCallStack_Example.ps1.” Any idea what changed between these two examples? If you guessed that I saved my code example, while writing this post, then you’d be correct.

The highest numbered index* — 5 in this example — is always going to be the starting point — whether it’s called from the prompt, or a saved file, and the lowest index — index 0 — will always be the currently invoked function. This means that index 1 will always be the calling function, of the last function that was invoked.

* The highest numbered index will also always be index -1, such as $Var[-1] would equal “<ScriptBlock>,” or “Get-PSCallStack_Example.ps1.” Knowing this, it’s possible to work backwards through these results, or any other array.

So it’s been said, the other time we see an array in reverse, is the $Error variable. $Error[0] is always the most recent error, not the first error. Thanks for reading, and we’ll do it again sometime soon.

Creating Multiple Credential Objects

Download the complete function: https://gist.github.com/tommymaynard/98031ccd5de67005bf3063db06a33851

There are times when you made need to use additional credentials, other than those used to begin the Windows PowerShell session. When I need to PSRemote to another domain’s computer, I quickly run a function I have stored in my $PROFILE to create a variable that contains a credential object for the second domain. It’s a bit more specific for my environment, so I won’t bother sharing that exact function. What I will do, however, is share and explain a function I’ve written to create up to 10 credential objects. You’ll soon see where that can be changed (if for some reason someone would want more than that many). Realistically, 10 seems much too high anyway. Moving on.

Now, it might be important to know a bit more about how this started. The unfortunate thing about that function (the one in the link) is that it is maxed out at three credential sets (and it continually used the word ‘domain’). As well, it wasn’t as flexible as it should’ve been and it didn’t have any comment-based help or any verbose statements. So, a couple of days after publishing that post, I copied the function back into the PowerShell ISE and started working on a “1.1” version. That’s what we’ll discuss in this post.

First, we’ll write some basic, structural code for the advanced function.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param ()

    Begin {
    } #End Begin

    Process {
    } #End Begin
} #End Function

Now, let’s add the parameter that will define how many credential objects the function will create. The variable we’ll use is $Set and we’ll cast it as an integer (Set will, therefore, also be the name of the parameter). In addition, we’ll add code to define the -Set parameter as being mandatory (it must be included when the function is run), and make the parameter positional (the value for -Set can be entered without providing the -Set parameter name). In addition, we’ll add the ValidateRange validation attribute that will require that the integer entered, as the value for the -Set parameter, must be 1 through 10. This can be changed if necessary.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
    } #End Begin
} #End Function

We won’t need to do anything in the Begin block, so we’ll focus on the Process block next. In there, we’ll need to prompt for a user name and password as many times as the value of the parameter -Set indicates. Since we know the number of times we’ll be looping (to prompt for the username and password), I recommend we use a for statement. For statements work this way: set a variable ($i in our case) as a counter variable, add a comparison to determine how many times to loop (while $i is less than or equal to $Set), and finally, include a way to increment the counter variable (that’s what $i++ does), so that we only loop the proper number of times.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
        For ($i = 1; $i -le $Set; $i++) {

        }
    } #End Begin
} #End Function

The final piece is adding the parts necessary to prompt for usernames and passwords, and creating variables to store each of the credential objects. We’ll do this as part of a try-catch.

Line 15 below, first runs the Get-Credential cmdlet. We know this because it is in parenthesis. These parentheses indicate that this cmdlet needs to run before it’s used as the value of the Set-Variable‘s -Value parameter. If for some reason the user presses Cancel, or presses the X in the top-right corner of the prompt dialog, the try portion of the try-catch will fail, and the catch portion will run. It will indicate that no credential was created for that iteration through the loop.

If the user enters, at minimum a username (because a password can be blank), then it will set a variable called $CredSet# (the hash mark (#) indicates a number). If we indicate we want to create two credential objects when we run the function (New-TMMultiCred 2), then $CredSet1 will be the variable that holds the first credential object, and $CredSet2 will hold the second.

Still on line 15, notice that the -Scope parameter is being used with the Set-Variable cmdlet. If we didn’t include this parameter and its value, Global, then the variables created (or modified, if the variable(s) already existed) by this command would not be available after the function was done executing.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
        For ($i = 1; $i -le $Set; $i++) {
            try {
                Set-Variable -Name CredSet$($i) -Value (Get-Credential -Credential $null) -Scope Global
                Write-Output -Verbose "SUCCESS: Credential Set $i stored in `$CredSet$($i)"
            } catch {
                Write-Warning -Verbose "No credential object was created for set $($i)."
            }
        }
    } #End Begin
} #End Function

Here’s a look at the function in progress. The first image shows that three credential objects were requested. The first credential object has already been created and is stored in $CredSet1, the second wasn’t created, since the user pressed Cancel on the second prompt, and the third credential object will be created when the user presses OK. The second image shows the end result.

Function for Creating Multiple Credential Objects-01

Function for Creating Multiple Credential Objects-02

Once this is complete, the user can run cmdlets that have an optional -Credential parameter and supply the fitting credential object, as seen in the example below. You can return all the cmdlets and functions that have the -Credential parameter using Get-Command: Get-Command -ParameterName Credential.

PS C:\> Invoke-Command -ComputerName dc01 -ScriptBlock {Get-Date} -Credential $CredSet3

Download the complete function: https://gist.github.com/tommymaynard/98031ccd5de67005bf3063db06a33851