PowerShell KeePass and saving time

Glad to be back from a 7-month dad leave. Let’s dive into some timesaving PowerShell!

The Problem

Password managers are very useful for anyone having more than one set of credentials, and most of us do.

They reduce the chance of credential leakage to unauthorized people and are vastly superior both post-it papers and notepad files.

However, I found myself using the graphical user interface (GUI) of my password manager daily to simply search copy and paste secret. The problem with navigating a GUI every day is that it’s time consuming, and there’s room for improvement, especially if you enjoy delving into some PowerShell and/or always have a terminal open.

Summary: Password managers GUIs are slow and tedious to work with. Let’s explore an alternative that is much faster!

My Solution

The solution to this problem that I went with was to create a custom script, which will install and configure my PowerShell session to easily access my password manager after typing in its master password. Together with a couple of functions to easily retrieve and copy-paste my password to the clipboard.

Setting something to your clipboard, especially a password, is a risk since other applications also can access the clipboard, thus the clipboard needs to be cleared by setting a sleep timer and overwriting the secret.

As a start, I will need to create a couple of parameters so the input becomes dynamic, so that I can use the script regardless of what filepath or database name I have on the computer.

param (
    [Parameter(Mandatory)]
    [string]$KeePassFilePath,
    [Parameter(Mandatory)]
    [string]$KeePassDataBaseName
)

The modules I will be using in my script is:

$Modules = "SecretManagement.KeePass", "Microsoft.PowerShell.SecretManagement"

Since I use KeePass, naturally this module comes in handy.

It’s an awesome module that I highly recommend for any KeePass & PowerShell user. I will use SecretManagement to enable the KeePass module and use it’s vault capabilities.

This will save me tons of time and I trust the sources that the modules originate from, to deliver secure and tested code. Much more then I trust myself to think of all security aspects of a something that would replace the modules already offered. Another great benefit of having PowerShell as a gateway to your password manager is that you don’t need to install the vendors application at all, this is a big plus if you’re (like I am) a fan of minimalism.

Next part of the code would be to install the modules: I set a condition to check if both modules are already present. If not, I try to install them. Since the Install-Module cmdlets Name parameter accepts a string array (look for String [] in the help files), I wont have to foreach loop through the modules.

$ExistingModules = Get-Module -Name $Modules -ListAvailable | Select-Object -ExpandProperty Name -Unique

if ($ExistingModules.count -ne 2) {
    Install-Module $Modules -Repository PSGallery -Verbose
}

I then have a condition to check of the vault name, if it’s not present already, I register the new KeePass vault, and sets it as the DefaultVault.

if ( -not (Get-SecretVault -Name $KeePassDataBaseName -ErrorAction SilentlyContinue)) {
    Register-SecretVault -Name $KeePassDataBaseName -Verbose -ModuleName 'SecretManagement.KeePass' -DefaultVault -VaultParameters @{
        Path = $KeePassFilePath
        UseMasterPassword = $true
    }
    Write-Verbose "$KeePassDataBaseName successfully installed." -Verbose
}
else {
    Write-Verbose "$KeePassDataBaseName was already configured." -Verbose
}

Function(s)

To speed things up even further, we want to create some smaller functions to wrap all the long cmdlets that we’d otherwise have to write, to get our secrets to the clipboard.

I say functions, because here’s where you can enable the work we’ve done even further, to work with PSCredentialObjects or start a process, wait, and send the password directly too it, thus generating a sort of custom single sign-on solution. However, sticking to the subject, my function will:

  1. Have a parameter that will be the secret that we’re looking for in our password manager
  2. Look for the secret, use Get-SecretInfo if the name is unknown
  3. Call the GetNetworkCredential method, and accessing the ‘Password’ property of the NetworkCredential object, essentially converting the SecureString to a String, and setting the value to clipboard
  4. Start a job with a ScriptBlock, which will replace the secret with a ‘Cleared!’ string.
function Find-FSecret {
    param (
        [parameter(mandatory)]
        [string]$Secret
    )
    $SecretLookup = Get-Secret -Name $Secret
    if ($SecretLookup) {
        Set-Clipboard -Value $SecretLookup.GetNetworkCredential().Password
        Write-Verbose "Secret found and set to clipboard. Will auto clear in 20 seconds." -Verbose
        $null = Start-Job -ScriptBlock {
            Start-Sleep -Seconds 20
            Set-Clipboard -Value 'Cleared!' -Verbose
        }
    }
}

I then add this function to my profile, which will load it on my users sessions, together with an alias declaration:

if ( -not (Get-Alias ffs -ErrorAction SilentlyContinue)) {
  New-Alias -Name 'ffs' -Value 'Find-FSecret'
}

Every time I get somewhat annoyed by yet another “SIGN IN” page, I simply tab over to PowerShell and vent out some frustration using my function:

ffs github
VERBOSE: Secret found and set to clipboard. Will auto clear in 20 seconds.

Discussion

In my example, I’m using KeePass, however this is very applicable to other password managers, in fact the PowerShell Gallery has tons of SecretManagement modules can be just as simple to use as in my examples.

Some examples:

  • BitWarden
  • LastPass
  • Keeper
  • CyberArk
  • Devolutions

Look yourself:

Find-Module *SecretManagement*

Another mention is, you want to make sure you’re not leaking your clipboard history. There’s 3rd party applications & settings built into Windows that might do so.

There’s also the possibility for a PowerShell Transcript to catch the output of your console, so make sure you never actually paste the credentials outside of the actual logon screen. You wouldn’t want to screen-share, or share a server with someone who could look into your command-line history and find a password in clear text.

Speaking of which, you can regularly look for passwords in clear text super easily using PSSecretScanner. I would recommend to look into it after completing a project like this.

Happy coding,

Emil

Analyze your Linux system using PowerShell

Install-Module linuxinfo

I am pleased to share that I have been working on a fun hobby project! A PowerShell module designed to facilitate Linux system analysis for PowerShell users. With its standardized noun-verb commands and object-based output, this module leverages the benefits of PowerShell to streamline analysis and information gathering on a Linux system.

Install it from the PowerShellGallery:

Install-Module linuxinfo -Verbose

View it’s functions:

Get-Command -Module linuxinfo
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Get-BatteryInfo                                    0.0.1      linuxinfo
Function        Get-ComputerInfo                                   0.0.1      linuxinfo
Function        Get-DisplayInfo                                    0.0.1      linuxinfo
Function        Get-FileSystemHelp                                 0.0.1      linuxinfo
Function        Get-NetworkInfo                                    0.0.1      linuxinfo
Function        Get-OSInfo                                         0.0.1      linuxinfo
Function        Get-SystemUptime                                   0.0.1      linuxinfo
Function        Get-USBInfo                                        0.0.1      linuxinfo

Get computer information:

Get-ComputerInfo
BiosDate        : 06/17/2022
BiosVendor      : INSYDE Corp.
BiosVerson      : 03.09
CPU             : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
CPUArchitecture : x86_64
CPUThreads      : 8
CPUCores        : 4
CPUSockets      : 1
DistName        : Fedora Linux
DistSupportURL  : https://fedoraproject.org/
DiskSizeGb      : {930, 16}
DiskFreeGb      : {848, 16}
DiskUsedGb      : 82
GPU             : Intel Corporation TigerLake-LP GT2 [Iris Xe Graphics] (rev 01)
DistVersion     : 37 (KDE Plasma)
KernelRelease   : 6.2.9-200.fc37.x86_64
OS              : GNU/Linux
RAM             : 31.9G

Getting the Operating system information:

Get-OSInfo
DistName      : Fedora Linux
DistVersion   : 37 (KDE Plasma)
SupportURL    : https://fedoraproject.org/
OS            : GNU/Linux
KernelRelease : 6.2.9-200.fc37.x86_64
OSInstallDate : 2023-03-25

Beyond Hardware Info

There’s more functions similar to the ones described above, where linuxinfo is parsing useful system information and displaying the output as a PSCustomObject. However, taking a look at a different kind of info:

Get-FileSystemHelp -All
Name                           Value
----                           -----
root                           root users home directory
etc                            system-global configuration files
mnt                            temporary mount points
dev                            device files for hardware access
bin                            essential user binaries
run                            stores runtime information
opt                            optional application software packages
media                          mount point for external / removable devices
lost+found                     stores corrupted filesystem files
usr                            user utilities and applications
tmp                            temporary files
var                            variable files
lib                            system libraries and kernel modules
boot                           boot loader files
proc                           procfs - process and kernel information
sys                            sysfs - devices and kernel information
srv                            services data directories
sbin                           essential system binaries
home                           users home directories

Get basic information about the linux filesystem using PowerShell. Can be very handy if you’re coming from a Windows background.

The function supports quick navigation using the -Go parameter, and displaying a richer help message with the -Full parameter.

Testing & Disclaimer

Currently the module has been tested on Ubuntu and Fedora, so I’m fairly confident that it works good on Debian & RHEL distributions.

However I’ve done no testing on arch linux, therefore I’m not sure how the experience is there. It’s also in an early stage (version 0.0.1), with improvement plans and new functionality. Be sure to hop on the GitHub repo to learn more.

Reason

I understand that the use-case for something like linuxinfo is a bit limited since Linux already has great tools for doing similar tasks. However this project is more of a personal journey into learning:

But most importantly, having some fun with PowerShell and extending the usefulness and value of PowerShell.

I’d be happy if you’d like to try it, and star the github repo if you feel it’s worth it.

Happy coding

PSCustomObject conditional loop-trick!

PSCustomObject, valuable skill

PSCustomObject is a feature in PowerShell that allows you to create structured data in a simple way.

There’s a ton to cover on the topic, but if your unfamiliar with it, first of all it’s probably one of the most important thing to spend time on understanding in PowerShell, secondly the PowerShell docs cover it very well.

In this blog post, I will cover a trick that I frequently use when generating structured data in form of objects that can later be piped to Export-CSV, or even better, Export-Excel

Looping & Conditions

This trick involves when you want to create a boolean (true or false) value in your PSCustomObject variable. Here’s an example of what I mean:

# Create an array object in a variable
$PSCustomObject = @()

# Get some data
$Process = Get-Process | Select-Object Name, Description -Unique

# Loop through data
foreach($p in $Process) {
    # Check if condition exists
    if ($p.Description) {
      # If it does, create the "true" version of the PSCustomObject
        $PSCustomObject += [PSCustomObject]@{
            Name = $p.Name
            ProcessHasDescription = $true
            Description = $p.Description
        }
    }
    else {
      # If it does not, create the "false" version
        $PSCustomObject += [PSCustomObject]@{
            Name = $p.Name
            ProcessHasDescription = $false
            Description = $null
        }
    }
}

# Show results
$PSCustomObject | Select-Object -First 10

Output:

Name             ProcessHasDescription Description
----             --------------------- -----------
audiodg                          False
Code                              True Visual Studio Code
CompPkgSrv                        True Component Package Support Server
concentr                          True Citrix Connection Center        
conhost                          False
conhost                           True Console Window Host
crashpad_handler                 False
csrss                            False
ctfmon                           False
dllhost                           True COM Surrogate

In this example, the Get-Process command is used to generate a list of system processes. The code then checks if a description is attached to each process. This technique can be applied to generate objects for all kinds of purposes. I’ve found it particularly useful for creating reports on Active Directory, computer hardware, access reports, or any other subject that requires a report with boolean values.

Some examples:

  • User changed password the last 30 days?
  • Computer disk less then 10gb left?
  • User has Full Control rights on network share?
  • Server answers on ping request?

Steps

  1. Generate an array of data
  2. Loop it and construct a condition
  3. If condition is met, create a PSCustomObject “true” block
  4. Else, create a PSCustomObject “false” block
  5. Export data

Closing thoughts

In this post, I aim to keep things short and concise by letting the example do the talking. The code is commented for easy understanding. This technique can be incredibly useful for generating reports or structured data that can inform decision-making in larger processes. I hope you find it helpful and please let me know if you do.

Wishing you a great day and, as always:

Happy coding

How to Learn Git, Markdown and PowerShell by Contributing to the PowerShell-Docs Repository

Intro

The PowerShell-Docs repository is the home of the official PowerShell documentation. It contains reference and conceptual content for various versions and modules of PowerShell. Contributing to this repository is a great way to learn Git, Markdown and PowerShell, as well as to help improve the quality and accuracy of the documentation.

In this blog post, I will show you how you can contribute to the PowerShell-Docs repository by doing quality contributions, and why it’s beneficial for your learning and development.

What are quality contributions?

Quality contributions are enhancements or fixes that improve the readability, consistency, style or accuracy of the documentation. They can include things like:

Quality contributions are different from content contributions, which involve adding new articles or topics, or making significant changes to existing ones. Content contributions require more discussion and approval from the PowerShell-Docs team before they can be merged.

How to make quality contributions?

Before we get into how to make quality contributions, I’d like to shamelessly plug my own module: PowerShell-Docs-CommunityModule It will help you pick out work that has not been done yet.

Install & try it, using the following code:

Set-Location $env:USERPROFILE

#  Make sure 'username' reflects your actual github username
git clone https://github.com/username/PowerShell-Docs

Install-Module PowerShell-Docs-CommunityModule

Find-MissingAliasNotes -Verbose

To make quality contributions, you need to have a GitHub account and some basic knowledge of Git and Markdown. You also need to install some tools that will help you edit and preview your changes locally. Here are some steps you can follow:

  1. Fork the PowerShell-Docs repository on GitHub.
  2. Clone your forked repository to your local machine using Git.
  3. Install Git, Markdown tools, Docs Authoring Pack (a VS Code extension), and Posh-Git (a PowerShell module).
  4. Check out the PowerShell Docs Quality Contributions project on GitHub. This project tracks all the open issues and PRs related to quality improvements.
  5. Pick an issue that interests you or create a new one if you find something that needs fixing. You can use PowerShell-Docs-CommunityModule to help you here.
  6. Assign yourself to the issue and start working on it locally using VS Code or your preferred editor. Make sure you create a new branch before editing any files. Making a new branch will ensure your edited files is clean in your upcoming Pull-Request.
  7. Preview your changes, make sure you’ve edited the ms.date at the top of the document to todays date (MM/dd/yyy), this way other contributors know when the document has been edited, it’s also required when doing an update, so the owners of the repository will ask you to do this if you miss it.
  8. Commit your changes using Git and push them to your forked repository on GitHub.
  9. Create a pull request (PR) from your forked repository to the original PowerShell-Docs repository on GitHub.
  10. Wait for feedback from reviewers or maintainers of the PowerShell-Docs team.
  11. Address any comments or suggestions they may have until your PR is approved and merged.

Why make quality contributions?

Making quality contributions has many benefits for both you and the PowerShell community.

For you:

  • You can learn Git, by contributing to a very friendly large code-base project. The owners are more then willing to help you with git related questions. You’ll grow a ton in this area once you start doing some PRs.
  • You will write/edit files in Markdown (.md), a very popular markup language.
  • Because you will be proof-reading the markdown documents, you will learn more PowerShell topics straight from the source of the documentation.
  • You can improve your writing skills by following Microsofts style guides and best practices for technical documentation.
  • You can get feedback from experts who work on PowerShell, Markdown and Git every day.
  • You can build your reputation as a contributor by having your name appear in commit history.

For the community:

  • You can help improve the clarity, consistency, style or accuracy of the documentation that many people rely on every day.
  • You can help reduce confusion, errors or frustration among users who read the documentation.
  • You can help keep the documentation up-to-date with changes in PowerShell features or functionality.
  • You will be listed in the Community Contributor Hall of Fame

Conclusion

In this post, I showed you how you can contribute to the PowerShell-Docs repository by doing quality contributions, and why it’s great for learning Git, Markdown and PowerShell, while at the same time using the PowerShell-Docs-CommunityModule to find out what to do first. We hope this blog post inspires you to join us in making the PowerShell documentation better for everyone.

If you have any questions, comments, or suggestions, please feel free to reach out on a DM either on twitter, email or mastodon! I hope to see a PR from you, and if you’ve successfully done so because of this post, make sure to notify me about it! 😃

Happy contributing

Using the PowerShell Module Z to Save Time in the Terminal

The module “Z” is a handy tool that can save you lots of time when navigating around your terminal. In this blog-post, we’ll cover how to install it to user scope, how to configure it by jumping around the terminal to different places, and how it saves lots of time in the long run.

Installing Z to your CurrentUser scope

To install Z, open a PowerShell terminal and run the following command:

Install-Module z -Scope CurrentUser

This will install Z for your current user only. If you want to install it for all users on your system, you can use -Scope AllUsers instead.

Configuring Z

Once you have Z installed, you can start configuring it by jumping around your terminal to different places. To do this, simply navigate to a directory that you frequently visit and then run z followed by a space and then part of the directory name. For example:

cd C:\Users\MyUser\Documents\Projects\MyProject
cd \
z MyProject

This will add C:\Users\MyUser\Documents\Projects\MyProject to Z’s list of directories. The more you use z with different directories, the more accurate it will become at predicting where you want to go.

Saving Time with Z

Once you have Z configured with your frequently visited directories, you can start saving time by using z instead of typing out long paths.

It may not seem that much of a timer-saver at first glance, however the more you get used to using z, instead of cd/tab-completion for navigation, the more time you will save. And I would imagine if you use your terminal daily, the time-savings are huge.

Z’s a bash fork

That’s right. The module is actually forked and ported from a bash script, what makes it super useful together with PowerShell is, you guessed it: PSProviders.

Not only can you navigate around your file-system, you can also visit your local reg hive, cert store, and other PSProviders, to save them to your directory history.

cd Cert:\LocalMachine\My\

z 'Cert:\LocalMachine\My'

z C:\

z my
  • Here, you start out visiting your local certificate store.
  • You save it to your z directory
  • You navigate back to C root
  • And back to your local cert store using only “z my”

This works with lots of different PSProviders, so try it out yourself!

To sum it up

By installing, using and configuring Z, you can save lots of time when navigating around your terminal. By installing it to user scope and configuring it by jumping around your terminal to different places, you’ll be able to quickly navigate to frequently visited directories and other PowerShell provider paths, with just a few keystrokes. Keep in mind that the more you use it, the better it gets, both from a functional standpoint but also the amount of time it saves you (So that you can drink coffee instead 😁 ).

Happy Coding

Active Directory Delegation done the PowerShell way!

Active Directory Access Granting

The following module is very important for a Active Directory operations or engineering type person. Most who have tried granting permissions outside of adding users to groups in Active Directory would probably agree that, access delegation can be a daunting task.

Your best bet is to use the Delegation wizard, but wheres the fun (PowerShell fun..) in that. And how well does it scale? Not very well.

The alternatives

So, the code-friendly alternatives we have are:

The first alternative, the native command DSACLS, can be a powerful way delegating, but is very frustrating to work with (or so I think).

This alternative is probably your best bet outside of the module DSACL. However creating the directory access rules and applying them can be a bit cumbersome.

Both alternatives are fine, but will require you to do some heavy lifting in terms of learning. I will not go into depth into the alternatives here, it’s just a good heads-up that these exists.

If you’ve learnt the basic of using a cmdlet in PowerShell, luckily for you, access delegation is just a module install away.

Using DSACL.

# Install the DSACL Module
Install-Module -Name DSACL  -Scope CurrentUser

# Build a splatting table with the desired groups
$ParamSplat = @{
    TargetDN = "CN=SalesUsers,DC=Contoso,DC=COM" # DistinguishedName of object to modify ACL on. Usually an OU.
    DelegateDN = "CN=SalesAdmins,OU=Groups,DC=Contoso,DC=COM" # DistinguishedName of group or user to give permissions to.
    DirectOnGroup = $true
}

Add-DSACLManageGroupMember @ParamSplat

This example creates a splatting table, and defines the objects to modify ACL (access control list), and user/group to give the permission to.

$ParamSplat = @{
    TargetDN = "OU=Users,DC=Contoso,DC=COM"
    DelegateDN = "CN=SalesAdmins,OU=Groups,DC=Contoso,DC=COM"
    AccessType = 'Allow'
    ObjectTypeName = 'User'
    NoInheritance = $True
}

Add-DSACLFullControl @ParamSplat

Gives the SalesAdmins group Full Control over ‘User’ object administrations in the Users OU. Disables Inheritance, making the ACL only apply on the given OU (Users).

The module contains some really useful cmdlets for managing AD delegation, to name a few:

  • Set-DSACLOwner
  • Add-DSACLJoinDomain
  • Add-DSACLResetPassword
Get-Command -Module DSACL

I recommend checking out the online documentation for each cmdlet, as well as running them in lab and verifying the ACL’s once executed.

If your into AD and PowerShell, it’s a fun and very learning experience to fire up some hyper-v VMs using Windows Server Evaluation ISO and a local client with some left-over RAM, and promote some domain controllers to test this module out. An awesome lab module that I recommend for very fast labs, are AutomatedLab. If you want to go a more step-by-step and less abstracted way of setting up a lab, I recommend my own Hyper-V script Labmil.

That’s it. Try it out yourself to discover how easy directory access delegation can be. The amount of AD automation is endless once you get a hang of it, combined with the standard ActiveDirectory module.

Happy coding

PowerShell Solution: Install PFX certificate on servers

Problem

As you may have guessed, this post will be about installing certificates using PowerShell.

Every year as someone working with identity topics, I am tasked with renewing certificates across the environment. Many services relies fully on valid certificates in order to function securely.

One critical service in particular that this scenario will cover is: Active Directory Federation Services, ADFS.

In most cases, you will have multiple ADFS servers, meaning, if your not automating already, you will need to install the SSL certificate manually (no fun experience on 10+ servers).

There’s more to say regarding specifically ADFS SSL certificates, that this post will not cover, however an installation will be needed in many of those scenarios as well.

Solution

This solution covers how one could do this for ADFS servers, however it carries over to other services that requires a valid certificate as well.

To generate an pfx file out of an external certificate, I recommend using The Digicert Cert Utility to generate the CSR (Certificate Signing Request) on the root server. Then simply import it using the digicert tool, and export the certificate to a .pfx file.

Here’s an example of how to export an already installed certificate as a PFX file:

$PfxPw = (Read-Host -Prompt 'Enter a password' -AsSecureString)

Get-ChildItem -Path cert:\localMachine\my\<thumbprint> | Export-PfxCertificate -FilePath C:\Cert\ssl_cert.pfx -Password $PfxPw

It’s important that the certificate gets imported on the server where the CSR was generated, in order to have a valid public/private keypair.

What we need to start out is:

  1. The ADFS Root server with the pfx certificate exported
  2. Access to all ADFS servers
  3. WinRM/PowerShell remoting enabled environment
# Local path to the certificate
$PFXPath = 'C:\Cert\ssl_cert.pfx'

# Credential object, we only use the password property
$Creds = Get-Credential -UserName 'Enter PFX password below' -Message 'Enter PFX password below'

# Path of the remote server we will copy to
$ServerCertPath = "C:\Cert\"

$InternalServers = "SERVER1", "SERVER2", "SERVER3"

foreach ($Server in $InternalServers) {

    # Creates a remote session
    $Session = New-PSSession -ComputerName $Server
    # Copies the certificate to the remote session
    Copy-Item -Path $PFXPath -ToSession $Session -Destination $ServerCertPath -Force -Verbose -ErrorAction Stop

    # Imports the pfx certificate using the credentials provided remotely
    Invoke-Command -Session $Session -ScriptBlock {

        Import-PfxCertificate -FilePath $using:ServerCertPath -CertStoreLocation Cert:\LocalMachine\My -Password $using:Creds.Password
        
    }
}

Small Talk

And just like that, you’ve saved truckloads of time every year using PowerShell.

I highly recommend checking out more cmdlets based from the pki and Microsoft.PowerShell.Security module. The script above displays how one can tackle a .pfx certificate, but using Import-Certificate, you could do similar things with .cer files.

Also, one could eliminate the need for generating a password with using something like Microsoft.PowerShell.SecretManagement. This module translates well into a lot of cmdlets in the pki/security module.

Stay safe & happy coding!

/Emil

PowerShell Solution: AGPM unable to take control of a GPO

Problem

If you enjoy the principle of least privileges, version control and doing big infrastructural changes in a safe manner, Advanced Group Policy Management or AGPM, is an amazing tool.

AGPM itself has a few years on its back, and as we sysadmins tend to get easier and easier systems now days, legacy systems can mean complexity.

When combined with new sysadmins that has not been introduced to the concept of AGPM, uncontrolled GPOs might become a problem and the built in error messages are sadly not the greatest.

(GPMC Error) could not take the ownership of the production GPO. Access is denied. (Exceptions from HRESULT : 0x80070005 (E_ACCESSDENIED)).

Access denied is caused by the AGPM service-account not having the permission to take control of the GPO (not having control of a GPO in AGPM really does ruin the point of AGPM). Solving this problem involves giving the service-account the permissions needed, however it’s a bit of a tricky thing to do.

Solution

As we’ve established, we must add the correct permissions for the service-account to the GPO, easy right? Luckily yes, because we know PowerShell!

To add the permissions, we need to understand how a GPO is stored. There’s two places a GPOs data resides in, ActiveDirectory (GPC) & Sysvol (GPT).

GPC

Group Policy Container (GPC), luckily the name is easy to remember because we already understand that AD consists of Organizational Units and… Containers. The GPC is stored in AD, under “CN=,Policies,CN=System,DC=x,DC=x”. Since it’s an AD object, logically it has attributes describing the object version etc.

GPT

Group Policy Template (GPT), is stored in the DC’s system volume (sysvol), under the ‘policies’ subfolder.

The GPT stores the majority of GPO data, it contains a folder structure of files that describes the GPOs functionality, meaning it stores script files, administrative template-based policies and various other security settings.

Replication

GPC uses AD replication, and GPT uses DFS-R since its in sysvol. This is important because we will edit the ACLs of both AD and sysvol in order to solve our issue.

Editing ACL for GPC

Editing its ACL requires generating an ActiveDirectoryRights object with the desired access. This can be done multiple ways, dsacls, using Set-ACL to name a few. In this case I had heard of an amazing module from Simon Wahlin called DSACL, so I can simply do the following:

$ADRights = "CreateChild", "DeleteChild", "Self", "WriteProperty", "DeleteTree", "Delete", "GenericRead", "WriteDacl", "WriteOwner", "AccessSystemSecurity"
Add-DSACLCustom -TargetDN $GPODN -DelegateDN $DelegateDN -ActiveDirectoryRights $ADRights  -InheritanceType Descendents -AccessControlType Allow

Add-DSACLCustom -TargetDN $GPODN -DelegateDN $DelegateDN -ActiveDirectoryRights $ADRights[0..8] -InheritanceType None -AccessControlType Allow

The ‘TargetDN’ in this case will be the GPCs distinguishedName, and the DelegateDN will be the distinguishedName of our AGPM service-account. We run the cmdlet twice to mimic the way AGPM edits the ACL in a controlled GPO. AccessSystemSecurity was not needed in the 2nd ACE and therefore I ended up selecting the first 9 (0..8) ADRights.

Editing the ACL for GPT

Since GPT is in sysvol, we now have the task of editing a filesystem ACL. This is different from a directory service ACL. There’s many ways of doing this as well, cacls, and Set-ACL works great. I ended up taking the easy way out and used NTFSSecurity, again another killer PowerShell module with 1,1mil downloads as of writing. And that’s quite understandable considering this is how one can grant full control on a filesystem:

Add-NTFSAccess -Path "\\DOMAIN\SYSVOL\DOMAIN.TEST\Policies\{$($GPOObject.Id)}" -AccessRights FullControl -Account 'DOMAIN\Service-Account-AGPM'

Almost ready to solve!

As we have learned, GPC and GPT is a bit different. Sysvol and AD does replicate, but in different ways. Key take-away is that most likely we need to wait for replication in order for the AGPM server to understand that the rights are in fact in place. This took me around 15m, this could have been avoided had I done the changes on the same server.

Using the AGPM module, we’re now ready to take control of the GPO, since we now have the access to do so.

Get-Gpo -Name "TheUncontrolledGPO" | Add-ControlledGpo -PassThru

In my case, I had more then 1 uncontrolled GPO, to say the least. Sadly the AGPM module doesn’t have something like ‘Get-UncontrolledGPO’.

What I ended up doing was to filter out all uncontrolled GPOs myself using Compare-Object, .

$ControlledGPOS = Get-ControlledGpo
$UncontrolledGPOS = (Compare-Object $ControlledGPOS.Name (Get-GPO -All).DisplayName).InputObject

foreach ($GPO in $UncontrolledGPOS) {
    Get-Gpo -Name $GPO | Add-ControlledGpo -PassThru
}

You can of course also navigate within GPMC > ChangeControl > Uncontrolled > select all GPOs, rightclick, Control.

Congratulations on having a fully controlled AGPM environment.

Discussion

Understanding where a GPO is stored is a nice way of understanding how they work. The reason behind having them stored in separate places most likely goes back to fact that AD is old, and back in the days, size mattered. Having the GPT files in the AD database (.dit) would simply mean a huge increase in data for AD. Splitting things up and having the DCs taking a bit of storage was probably a good idea back then.

On another note, notice my code in this solution was quite simple. Even thought we did some complex tasks. I was actively not trying to re-invent the wheel, and this is something that gets more important the ‘harder’ the task becomes. Using “blackbox” modules where we only follow the PowerShell standard way of typing out a cmdlet, can be a great way of achieving complex tasks with speed. It’s also important that when a “blackbox” module solves something for you, go back and try to dig deeper in what it actually did. I find this a good way of learning things in general.

Happy coding

/Emil

PowerShell: C Sharp for PowerShell Modules

My journey into C# so far

I’ve always been somewhat interested in programming, and PowerShell scripting and module making has put fire to that interest. The natural next language to learn for me has always been C#, reason being it’s easy to pick up if you already know PowerShell and it enables you to create binary PowerShell modules.

Some content I’ve devoured to increase my C# knowledge are:

  • C# Fundamentals course on PluralSight by Scott Allen
  • PowerShell to C# And Back, Book by Deepak Dhami & Prateek Singh

Various YouTube content and talks on building PowerShell Binary Modules using C#:

  • Building Cross Platform PowerShell Modules by Adam Driscoll
  • Writing Compiled PowerShell Cmdlets by Thomas Rayner

The above lists are things I’ve went through and I can honestly recommend.

What’s to come

I plan to further increase my knowledge with the books:

  • The C# Player’s Guide, Book by RB Whitaker
  • C# In Depth, Book by Jon Skeet

As well as writing more modules and other C# related projects.

The process of wrapping a binary module using a nuget package

  1. Install a modern version of the dotnet cli together with a dotnet sdk suitable for that version

  2. Init an empty git repo for the new code to live in

  3. Navigate to the folder; dotnet new classlib. This will generate a dotnet class library, once compiled it will generate a DLL file that will be our module

  4. In the csproj file, to make the module compatible with Win PowerShell & PowerShell, we set the TargetFramework to “netstandard2.0”

  5. Remove ImplicitUsings and Nullable Enabled. These are language features we do not need

  6. dotnet add package PowerShell Standard.Library

  7. dotnet add package thenugetpackage

  8. dotnet publish. We have now added the packages needed to start wrapping the nuget package into a PowerShell module

  9. To follow the official PowerShell repos naming standard, all the cmdlets are to be named: VerbNounCommand.cs

The following source code is commented to help one with a PowerShell background to understand it easier:

// Usings, similar to Import-Module
using System;
using System.Management.Automation;
using PasswordGenerator;

namespace PasswordGenerator
{
    // VerbsCommon contains a list of approved common verbs, the string Password is the Noun of the cmdlet
    [Cmdlet(VerbsCommon.New,"Password")]
    // Cmdletname : PSCmdlet, similar to Function Name {}
    public class GetGeneratedPasswordCommand : PSCmdlet
    {
        // [Parameter], default value is 16. If Get > Default, if set > set the value of the param
        private int _pwLengthDefault = 16;
        [Parameter]
        [ValidateRange(4,128)]
        public Int32 Length
        {
            get
            {
                return _pwLengthDefault;
            }
            set
            {
                _pwLengthDefault = value;
            }
        }

        private int _amountDefault = 1;
        [Parameter]
        public Int32 Amount
        {
            get
            {
                return _amountDefault;
            }
            set
            {
                _amountDefault = value;
            }
        }

        // Switch parameters, they turn true if specified
        [Parameter]
        public SwitchParameter IncludeSpecial { get; set; }
        [Parameter]
        public SwitchParameter IncludeNumeric { get; set; }
        [Parameter]
        public SwitchParameter IncludeUppercase { get; set; }
        [Parameter]
        public SwitchParameter IncludeLowercase { get; set; }

        protected override void ProcessRecord()
        {
            // for loop, same concept as in PowerShell
            for (int i = 0; i < Amount; i++)
            {
                if (!IncludeLowercase & !IncludeUppercase & IncludeSpecial & IncludeNumeric)
                {
                    var pwd = new Password(Length).IncludeSpecial().IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password); 
                }
                else if (IncludeNumeric & !IncludeSpecial & !IncludeUppercase & !IncludeLowercase)
                {
                    var pwd = new Password(Length).IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (IncludeSpecial & !IncludeNumeric & !IncludeUppercase & !IncludeLowercase)
                {
                    var pwd = new Password(Length).IncludeSpecial();
                    var password = pwd.Next();
                    WriteObject(password); 
                }
                else if (!IncludeNumeric & !IncludeSpecial & IncludeUppercase & IncludeLowercase)
                {
                    var pwd = new Password(Length).IncludeLowercase().IncludeUppercase();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeNumeric & !IncludeSpecial & !IncludeLowercase & IncludeUppercase)
                {
                    var pwd = new Password(Length).IncludeUppercase();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeNumeric & !IncludeSpecial & !IncludeUppercase & IncludeLowercase)
                {
                    var pwd = new Password(Length).IncludeLowercase();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeNumeric & IncludeLowercase & IncludeUppercase & IncludeSpecial)
                {
                    var pwd = new Password(Length).IncludeLowercase().IncludeUppercase().IncludeSpecial();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeNumeric & IncludeLowercase & IncludeUppercase & IncludeNumeric)
                {
                    var pwd = new Password(Length).IncludeLowercase().IncludeUppercase().IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeNumeric & !IncludeUppercase & IncludeSpecial & IncludeLowercase)
                {
                    var pwd = new Password(Length).IncludeLowercase().IncludeSpecial();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeSpecial & !IncludeUppercase & IncludeLowercase & IncludeNumeric)
                {
                    var pwd = new Password(Length).IncludeLowercase().IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeLowercase & !IncludeNumeric & IncludeUppercase & IncludeSpecial)
                {
                    var pwd = new Password(Length).IncludeUppercase().IncludeSpecial();
                    var password = pwd.Next();
                    WriteObject(password);
                }
                else if (!IncludeLowercase & !IncludeSpecial & IncludeUppercase & IncludeNumeric)
                {
                    var pwd = new Password(Length).IncludeUppercase().IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password); 
                }
                else if (!IncludeUppercase & IncludeLowercase & IncludeNumeric & IncludeSpecial)
                {
                    var pwd = new Password(Length).IncludeLowercase().IncludeSpecial().IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password); 
                }
                else if (!IncludeLowercase & IncludeUppercase & IncludeNumeric & IncludeSpecial)
                {
                    var pwd = new Password(Length).IncludeUppercase().IncludeSpecial().IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password); 
                }
                else
                {
                    var pwd = new Password(Length).IncludeLowercase().IncludeUppercase().IncludeSpecial().IncludeNumeric();
                    var password = pwd.Next();
                    WriteObject(password); 
                }
            }
            
        }

    }
}

The rest is a matter of figuring out how the package works, and what it supports. Make sure to try to get all the functionality of the package out in the PowerShell module. Obviously it might require you to make more cmdlets.

Next blog will be how I published my module to the PowerShell Gallery. Using Git, Github, PSGallery and PlatyPS.

Stay tuned!

Happy coding

/Emil

PowerShell for Security: PassWord Gen Part 2

Did it again

2021-05-10 - I wrote an article on Password Generators.

The goal of that pass-gen was to have a script in my $Profile that would simply work on both PowerShell 5.1 & PowerShell 7+. The goal was also to cover AD complexity rules, and it did just that.

However,

This time I’ve taken a whole new bull by the horn. While looking for a nuget package for password generators, out of curiosity on how a .net/C# developer would tackle the challenge that is coding a password generator, I stumbled upon “PasswordGenerator”.

To my surprise, the package has reached 1.6 million(!!!) downloads. I figured this package must be something special, some sort of holy grail of pass gens. And while I’m no C# expert, I’m always up for a challenge!

So I shamefully forked the repository and started working on a binary PowerShell cmdlet that would mimic the nuget package. 7 versions and 29 commits later, “BinaryPasswordGenerator” was born!

It’s fast…

Fast

It’s customizable

The cmdlet is highly customizable, just like the nuget package. This opens up a new usecase area that the former script did not cover:

  • Backend engine for generating passwords, in GUI/Web senarios (like a nuget package)
  • PIN/One Time Pass generations (usually 4-8 digit codes)
  • More user-friendly passwords (example: lowercase + numeric)
  • Supports up to 128 char length passwords
  • It’s wicked fast, meaning it scales better

Examples

# By default, all characters available for use and a length of 16

# Will return a random password with the default settings

New-Password
# Same as above but you can set the length. Must be between 4 and 128

# Will return a password which is 32 characters long

New-Password -Length 32
# Same as above but you can set the length. Must be between 4 and 128

# Will return a password which only contains lowercase and uppercase characters and is 21 characters long.

New-Password -IncludeLowercase -IncludeUppercase -Length 21
# You can build up your reqirements by adding parameters, like -IncludeNumeric

# This will return a password which is just numbers and has a default length of 16

New-Password -IncludeNumeric
# As above, here is how to get lower, upper and special characters using this approach

New-Password -IncludeLowercase -IncludeUppercase -IncludeSpecial
# This is the same as the above, but with a length of 128

New-Password -IncludeLowercase -IncludeUppercase -IncludeSpecial -Length 128
# One Time Passwords

# If you want to return a 4 digit number you can use this:

New-Password -IncludeNumeric -Length 4

Using together with other PowerShell modules:


# Convert to SecureString
$pw = New-Password | ConvertTo-SecureString -AsPlainText -Force

# Set a password in your SecretVault using Secret Store/Management
Set-Secret -Name 'User' -Secret (New-Password -Length 128) -Vault PSVault

Get-Secret User
System.Security.SecureString

Get-Secret User -AsPlainText
u%4EkQlMpVjPnO5VM5tYcnUE!F!D3wvhB8w595LXqIEAny1XC4OVn4\x!1Q79Nlj!QwK!zBVkFUAHVy44iEIO2icVE0meAz3YEWudP9UdKrjbrp8nJ8DECVll2Uq!kt5

Happy coding

/Emil