Category: Web

stuff about web design, development, trends, and tricks

  • How to make a PowerShell module

    Assuming you have a few PowerShell functions that you would like to gather into one package to easily distribute, you will want to make a module. This will allow you to load your functions on a specific computer and then call the functions without any further effort on your part.

    At work, I use Azure DevOps so I wrote this to take my code and bundle the functions into a module, test the module is operational, and save the module as an artifact that can be downloaded. If you don’t want to use ADO, you can adapt the code as necessary.

    I didn’t figure this all out on my own, here’s where I learned all this-

    These instructions are an amalgamation of those listed in the links and simplified for me.

    1. Make folder structure
      1. Add your functions and tests
    2. Make configuration file, from sample
    3. Copy build file, from GITHUB
    4. Copy Pester Tests, from GITHUB
    5. Copy YAML file to define build process, from GITHUB
    6. Create pipeline in Azure DevOps and output artifact
    7. Review code to download new artifact

    To hold the sample code, I have a GITHUB repo with sample files-

    https://github.com/chinkes5/PowerShellModule

    Step 1

    First is to organize what you have. The folder structure and a few key file names will drive a lot of how the module is found and used. All the folders described are relative to the same root folder in your Azure DevOps repository.

    The functions you have should be named with one file having a single function and the name of the file the same as the function within. Those that are public, that is functions for general consumption, should be put in a folder named ‘Public’. If you have private functions that you don’t want generally exposed, you can make a folder ‘Private’ as well.

    Make a folder ‘Tests’. There will be a few standard tests to make sure the module can be built. If you have written any Pester tests for your functions, add them here as well. The expected format will be [function-name].test.ps1

    Step 2

    In the root folder, make a json configuration file called ‘module-config.json’. Here is a sample to follow:

    {
        "name": "My_PS_Module",
        "description": "PowerShell module for use by my team for standard operations",
        "Author": "John Chinkes",
        "CompanyName": "Your Company Name",
        "CopyrightStartYear": 2024,
        "GUID": "your-guid-value",
        "PowerShellVersion": "5.0"
      }

    The build will use this information to make a PSM1, PSD1, and NuSpec files.

    Step 3

    You will want a build file to assemble all the functions and create your manifest file. Use the following, calling it ‘build.ps1’:

    Here’s the file in GITHUB- https://github.com/chinkes5/PowerShellModule/blob/main/build.ps1

    The values you put in the config file will be found and swapped out by this process. Also, the names of all the functions will be added. When you call the module, it will read the public and private folders to expose the public functions. To add new functions to your module, just re-run this build file and they will be added!

    If you want an explanation of what’s going on inside this file, see this post.

    Step 4

    Optionally, make Pester tests for your functions and the module. How to make Pester tests is outside the scope of this document but the following basic test is recommended:

    Here’s the file in GITHUB- https://github.com/chinkes5/PowerShellModule/blob/main/test.ps1. It will print out several paths so you can see where it’s working and then invoke-pester on the ‘Tests’ folder. The output will be an xml file that Azure DevOps will display with the build.

    Save this file as test.ps1 in the root of your module folder.

    Step 5

    The output of the pipeline will not actually be a module, but a NuGet file that can be loaded to on the destination computer.

    At noted at the beginning, I’m working with Azure DevOps for my CI/CD tool. Here is a sample YAML file to build the module. Please be careful with the spacing and formatting when you copy! You will want to swap out the path to your code in the repository. At the end of the file, swap out the name of your feed in Azure DevOps Artifacts.

    Here’s the file in GITHUB- https://github.com/chinkes5/PowerShellModule/blob/main/module.yaml. Save this as Module.YAML in the root of your module directory.

    Step 6

    Create the pipeline to output a new module to your Azure DevOps Artifacts. You will want to check in all the code and structure you have created above.

    1. Sign in to Azure DevOps:
    2. Navigate to Pipelines:
      • In the left-hand menu, click on Pipelines.
      • Click on New pipeline.
    3. Select the Repository:
      • Choose the location of your code (e.g., Azure Repos Git, GitHub, etc.).
      • Select the repository where your YAML file is located.
    4. Configure the Pipeline:
      • In the Configure step, select Existing Azure Pipelines YAML file.
      • Choose the Module.YAML file from your repository.
    5. Review and Run:
      • Review the YAML file content.
      • Click on Save and run to start the pipeline.

    After the pipeline has run, you should be able to check for a new artifact with your module.

    Step 7

    You will need a PAT with permissions to read and download packages or set the permissions to allow for public consumption. Use this code to register your Azure DevOps Artifacts on a destination computer, swapping out your PAT, a name for your repo, and the exact URL to your DevOps Artifact:

    $patToken = "Your PAT goes here" | ConvertTo-SecureString -AsPlainText -Force 
    $credsAzureDevopsServices = New-Object System.Management.Automation.PSCredential("username", $patToken) 
    
    $srePackageSource = "Name you call your repo" 
    packageSourceList = Get-PackageSource 
    if ($packageSourceList.Name -notcontains $srePackageSource) { 
        Write-Output "Adding package source to be able to get $srePackageSource..." 
        $regParam = @{ 
            Name               = $srePackageSource         SourceLocation     = "https://pkgs.dev.azure.com/[collection]/_packaging/youPathTo/nuget/v2"
            PublishLocation    = "https://pkgs.dev.azure.com/[collection]/_packaging/ youPathTo/nuget/v2"
            InstallationPolicy = 'Trusted' 
            Credential         = $credsAzureDevopsServices
            Verbose            = $true
        }
        Register-PSRepository @regParam 
    } 
    else { 
        Write-Output "Package source of $srePackageSource found." 
    } 
    
    $psRepoList = Get-PSRepository 
    if ($psRepoList.Name -notcontains $srePackageSource) { 
        Write-Output "Registering package source $srePackageSource..." 
        $pakParam = @{ 
            Name         = $srePackageSource 
            Location     = "https://pkgs.dev.azure.com/[collection]/_packaging/youPathTo/nuget/v2" 
            ProviderName = 'NuGet'
            Trusted      = $true 
            SkipValidate = $true 
            Credential   = $credsAzureDevopsServices 
        }
        Register-PackageSource @pakParam 
    }
    else {
        Write-Output "PowerShell repository $srePackageSource found." 
    }

    Then use PowerShellGet to find and download the module, note the reuse of the credential from prior script. Swap out the name used for your repo and the name of the module in this code:

    Write-Output "Finding Your Module..." 
    Find-Module -Name 'Name of Module' -Repository 'Name you call your repo' -Credential $credsAzureDevopsServices -Verbose x
    Write-Output "Downloading Your Module..." 
    Save-Module -Name 'Name of Module' -Repository 'Name you call your repo' -Path ($env:PSModulePath -split ';')[1] -Credential $credsAzureDevopsServices -Verbose 
    Write-Output "Importing Your Module..." 
    Import-Module -Name “Name of Module” -Force -Scope Global

    You now have a module and downloaded it on a destination computer!

  • pi-hole in a Container

    Man, what a struggle! I’ve been running close to the datacap for most of the year and figured, if I block the ads, and particularly the video ads, I gotta same some data. You can see how I’ve slowly been building out my containerized workload; this should be a snap!

    I found a ton of great resources that were collected into my final Docker Compose file:

    So I gathered snippets of this and that and made a configuration for myself. But when I tried to deploy the Stack, only errors! First was the volumes, totally messed up but reread my page on it and looked at the Wundertech example. The container would start but I couldn’t reach it. I learned that having both MACVLAN and Bridge network would allow Portainer/Docker to talk to the container while giving me the unique IP needed for the domain controller to point towards on Port 53. Then I watched this guy. He mentioned, in passing, how he selected the values for his MACVLAN network.

    Well, in the end, I was trying to be too fancy. When I wrote the subnet description to fit in between my Unifi gateway address on 192.168.1.1 and the majority of my servers on 192.168.1.100+, it worked. This matched the subnet already on eth0 of my docker host. This seems to explain the technical bits of why – https://github.com/moby/libnetwork/blob/master/docs/macvlan.md. When I was trying to use 192.168.60.0/24, it was not routable. There might have been a solution manually adding a route on the gateway, that’s outside of my knowledge now.

    If you look at the github readme in my repo, you can see a diagram of what I made. Once the container was up, could talk to the internet, could talk to the local network, a change to the DC to only use the PiHole was trivial. Last, I added a DNS entry so I can find the webpage for managing it. In the first day, it’s dropped 25-30% of all DNS lookups, mostly known ad and tracking URL. In a few weeks I’ll know if I actually get any data savings…

    The only thing to edit, once this was running, adding a jetpack URL to the white list so I could even get to the editor page in WordPress.

  • Password Generator

    I was playing with this a year ago when I started a new job. The boss said to use two words, a number, and a symbol- this would make a easy to remember password for users, including us! With that in mind, I grabbed a list of 4 and 5 letter words. Put them in an array and built a little something in PowerShell to spit out passwords.

    Then, I thought, why not put this on the web?! With Cloudflare workers, I can put some javascript out there, run it on their servers, fast connections, easy to manage- why not! So I built https://passwords.chinkes.com/. It was a page that pulled the password from a static list. There was some API access and you could chose to have symbols or not, numbers or not, or 6 letter words for a longer password.

    After 9 months, I returned to it and was able to add the slider to allow dynamic password lengths, as well as checkboxes to subtract symbols or numbers from the password.

    I hope you can make a better password with this!