Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.2.0] 2026-01-22

### Added

- Documentation for `ConvertFrom-JsonToHashtable` cmdlet with syntax,
parameters, and examples for PowerShell 5.1 and 7+ compatibility.
- CLAUDE.md project guidance document with module overview, architecture,
development commands, testing patterns, and key concepts.

### Changed

- Refactored logging script handling in configuration to support both file
paths and inline script blocks.
- Enhanced auditing functionality with detailed instructions for configuring
logging in Configuration.psd1.
- Improved logging configuration options and integrated cached configuration
in Import-GatekeeperConfig.
- Enhanced Test-FeatureFlag to execute logging scripts based on rule outcomes.
- Updated README with configuration details and logging behavior explanations.

## [0.1.1]

### Changed

- `Read-FeatureFile` uses a new static method to read the file and set the
FilePath.

Expand Down
3 changes: 2 additions & 1 deletion Gatekeeper/Classes/FeatureFlag.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
. $PSScriptRoot\..\Enums\Effect.ps1
. $PSScriptRoot\..\Public\ConvertFrom-JsonToHashtable.ps1

enum Operator {
Equals
Expand Down Expand Up @@ -159,7 +160,7 @@ class FeatureFlag {
# $json = Get-Content -Raw -Path 'd:\Gatekeeper\Gatekeeper\featureFlag.json'
# $featureFlag = [FeatureFlag]::FromJson($json)
static [FeatureFlag] FromJson([string]$json) {
$data = ConvertFrom-Json $json -AsHashtable
$data = ConvertFrom-JsonToHashtable -InputObject $json
return [FeatureFlag]::new($data)
}

Expand Down
6 changes: 4 additions & 2 deletions Gatekeeper/Classes/Property.ps1
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
. $PSScriptRoot\..\Public\ConvertFrom-JsonToHashtable.ps1

class PropertyValidation {
[int]$Minimum
[int]$Maximum
Expand Down Expand Up @@ -124,7 +126,7 @@ class PropertySet {
if (-not $validProperties) {
throw 'Properties file is not valid.'
}
$json = Get-Content $FilePath -Raw | ConvertFrom-Json -AsHashtable
$json = Get-Content $FilePath -Raw | ConvertFrom-JsonToHashtable
if ($json -isnot [hashtable]) {
throw 'Failed to create hashtable from json file'
}
Expand All @@ -135,7 +137,7 @@ class PropertySet {
}

static [PropertySet] FromJson([string]$json) {
$data = $json | ConvertFrom-Json -AsHashtable
$data = ConvertFrom-JsonToHashtable -InputObject $json
return [PropertySet]::new($data)
}

Expand Down
20 changes: 4 additions & 16 deletions Gatekeeper/Configuration.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,20 @@
Allow = @{
# We leave this disabled by default to avoid cluttering the console
Enabled = $false
Script = {
param($Rule)
Write-Host "✅ Rule [$($Rule.Name)] matched and is allowed."
}
Script = 'param($Rule); Write-Host "✅ Rule [$($Rule.Name)] matched and is allowed."'
}
Deny = @{
# We leave this disabled by default to avoid cluttering the console
Enabled = $false
Script = {
param($Rule)
Write-Host "⛔ Rule [$($Rule.Name)] matched and is denied."
}
Script = 'param($Rule); Write-Host "⛔ Rule [$($Rule.Name)] matched and is denied."'
}
Warning = @{
Enabled = $true
Script = {
param($Rule)
Write-Warning "⚠️ Rule [$($Rule.Name)] matched."
}
Script = 'param($Rule); Write-Warning "⚠️ Rule [$($Rule.Name)] matched."'
}
Audit = @{
Enabled = $true
Script = {
param($Rule)
Write-Host "Audit: $($Rule.Name)"
}
Script = 'param($Rule); Write-Host "Audit: $($Rule.Name)"'
}
}
}
36 changes: 18 additions & 18 deletions Gatekeeper/Gatekeeper.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@
@{

# Script module or binary module file associated with this manifest.
RootModule = 'Gatekeeper.psm1'
RootModule = 'Gatekeeper.psm1'

# Version number of this module.
ModuleVersion = '0.1.1'
ModuleVersion = '0.2.0'

# Supported PSEditions
# CompatiblePSEditions = @()

# ID used to uniquely identify this module
GUID = '7c2fb6fe-e024-4c39-b687-84fbe7473e3f'
GUID = '7c2fb6fe-e024-4c39-b687-84fbe7473e3f'

# Author of this module
Author = 'Gilbert Sanchez'
Author = 'Gilbert Sanchez'

# Company or vendor of this module
CompanyName = 'Gilbert Sanchez'
CompanyName = 'Gilbert Sanchez'

# Copyright statement for this module
Copyright = '(c) Gilbert Sanchez. All rights reserved.'
Copyright = '(c) Gilbert Sanchez. All rights reserved.'

# Description of the functionality provided by this module
Description = 'Helps implement feature flags in your PowerShell projects.'
Description = 'Helps implement feature flags in your PowerShell projects.'

# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''
Expand All @@ -51,9 +51,9 @@
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(
RequiredModules = @(
@{
ModuleName = 'Configuration'
ModuleName = 'Configuration'
ModuleVersion = '1.6.0'
}
)
Expand All @@ -62,7 +62,7 @@
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = @('Enums\Effect.ps1', 'Classes\Property.ps1', 'Classes\FeatureFlag.ps1')
ScriptsToProcess = @('Enums\Effect.ps1', 'Classes\Property.ps1', 'Classes\FeatureFlag.ps1')

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
Expand All @@ -77,13 +77,13 @@
FunctionsToExport = '*'

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = '*'
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = '*'
AliasesToExport = '*'

# DSC resources to export from this module
# DscResourcesToExport = @()
Expand All @@ -95,29 +95,29 @@
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PrivateData = @{

PSData = @{

# Tags applied to this module. These help with module discovery in online galleries.
Tags = @(
Tags = @(
'PSEdition_Core',
'Windows',
'Linux',
'MacOS'
)

# A URL to the license for this module.
LicenseUri = 'https://github.com/HeyItsGilbert/Gatekeeper/blob/master/LICENSE'
LicenseUri = 'https://github.com/HeyItsGilbert/Gatekeeper/blob/master/LICENSE'

# A URL to the main website for this project.
ProjectUri = 'https://github.com/HeyItsGilbert/Gatekeeper/'
ProjectUri = 'https://github.com/HeyItsGilbert/Gatekeeper/'

# A URL to an icon representing this module.
IconUri = 'https://raw.githubusercontent.com/HeyItsGilbert/Gatekeeper/main/static/icon.png'
IconUri = 'https://raw.githubusercontent.com/HeyItsGilbert/Gatekeeper/main/static/icon.png'

# ReleaseNotes of this module
ReleaseNotes = 'https://github.com/HeyItsGilbert/Gatekeeper/blob/master/CHANGELOG.md'
ReleaseNotes = 'https://github.com/HeyItsGilbert/Gatekeeper/blob/master/CHANGELOG.md'

# Prerelease string of this module
# Prerelease = ''
Expand Down
80 changes: 80 additions & 0 deletions Gatekeeper/Public/ConvertFrom-JsonToHashtable.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
function ConvertFrom-JsonToHashtable {
<#
.SYNOPSIS
Converts JSON to a hashtable with PowerShell 5.1 compatibility.

.DESCRIPTION
Provides a compatibility layer for converting JSON to hashtables that works

Check warning on line 7 in Gatekeeper/Public/ConvertFrom-JsonToHashtable.ps1

View workflow job for this annotation

GitHub Actions / Continuous Integration / Run Linters

Unknown word (hashtables) Suggestions: (hashables, hashtable, hashable, hatable, hashtags)
with both PowerShell 5.1 and PowerShell 7+. In PowerShell 7+, uses the native
-AsHashtable parameter. In PowerShell 5.1, manually converts PSCustomObject
to hashtable.

.PARAMETER InputObject
The JSON string to convert or pipeline input from Get-Content.

.EXAMPLE
$json = Get-Content -Path "file.json" -Raw | ConvertFrom-JsonToHashtable

.EXAMPLE
$data = ConvertFrom-JsonToHashtable -InputObject '{"key":"value"}'
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputObject
)

process {
# In PowerShell 7+, ConvertFrom-Json supports -AsHashtable
if ($PSVersionTable.PSVersion.Major -ge 7) {
return ($InputObject | ConvertFrom-Json -AsHashtable)
}

# For PowerShell 5.1, we need to manually convert PSCustomObject to Hashtable
$jsonObject = $InputObject | ConvertFrom-Json
return ConvertTo-Hashtable -InputObject $jsonObject
}
}

function ConvertTo-Hashtable {
<#
.SYNOPSIS
Recursively converts PSCustomObject to Hashtable.

.DESCRIPTION
Helper function that recursively converts PSCustomObject instances to hashtables.

Check warning on line 45 in Gatekeeper/Public/ConvertFrom-JsonToHashtable.ps1

View workflow job for this annotation

GitHub Actions / Continuous Integration / Run Linters

Unknown word (hashtables) Suggestions: (hashables, hashtable, hashable, hatable, hashtags)
Used for PowerShell 5.1 compatibility where ConvertFrom-Json doesn't support -AsHashtable.

.PARAMETER InputObject
The object to convert to a hashtable.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[object]$InputObject
)

if ($null -eq $InputObject) {
return $null
}

if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {
$collection = @(
foreach ($item in $InputObject) {
ConvertTo-Hashtable -InputObject $item
}
)
return $collection
}

if ($InputObject -is [PSCustomObject]) {
$hashtable = @{}
foreach ($property in $InputObject.PSObject.Properties) {
$hashtable[$property.Name] = ConvertTo-Hashtable -InputObject $property.Value
}
return $hashtable
}

# Return primitive types as-is
return $InputObject
}
22 changes: 13 additions & 9 deletions Gatekeeper/Public/Import-GatekeeperConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,21 @@ function Import-GatekeeperConfig {
Write-Verbose "Logging level '$level' is disabled, skipping."
continue
}
# Handle if the script is file or script block
if ($script:GatekeeperConfiguration.Logging[$level].Script -is [string]) {
$scriptPath = $script:GatekeeperConfiguration.Logging[$level].Script
if (-not (Test-Path -Path $scriptPath)) {
throw "Logging script file not found: $scriptPath"
# Handle if the script is a file path or string scriptblock
$scriptContent = $script:GatekeeperConfiguration.Logging[$level].Script
if ($scriptContent -is [string]) {
# Check if it's a file path
if (Test-Path -Path $scriptContent -ErrorAction SilentlyContinue) {
Write-Verbose "Loading logging script from file: $scriptContent"
$script:GatekeeperLogging[$level] = [scriptblock]::Create((Get-Content -Path $scriptContent -Raw))
} else {
# Treat it as a script string and convert to scriptblock
Write-Verbose "Converting string to script block for logging level: $level"
$script:GatekeeperLogging[$level] = [scriptblock]::Create($scriptContent)
}
Write-Verbose "Loading logging script from file: $scriptPath"
$script:GatekeeperLogging[$level] = [scriptblock]::Create((Get-Content -Path $scriptPath -Raw))
} elseif ($script:GatekeeperConfiguration.Logging[$level].Script -is [scriptblock]) {
} elseif ($scriptContent -is [scriptblock]) {
Write-Verbose "Using inline script block for logging level: $level"
$script:GatekeeperLogging[$level] = $script:GatekeeperConfiguration.Logging[$level].Script
$script:GatekeeperLogging[$level] = $scriptContent
} else {
Write-Warning "No valid script found for logging level: $level"
}
Expand Down
Loading