├── .gitattributes
├── JEAnalyzer
├── bin
│ ├── JEAnalyzer.dll
│ ├── JEAnalyzer.pdb
│ ├── readme.md
│ └── JEAnalyzer.xml
├── internal
│ ├── tepp
│ │ ├── assignment.ps1
│ │ ├── example.tepp.ps1
│ │ └── readme.md
│ ├── scriptblock
│ │ ├── validateServiceAccount.ps1
│ │ └── validatePath.ps1
│ ├── resources
│ │ ├── readme.md
│ │ ├── Register-JeaEndpointPublic.ps1
│ │ ├── jeamodule.psm1
│ │ ├── run.ps1
│ │ ├── Register-JeaEndpoint.ps1
│ │ └── bootstrap.ps1
│ ├── scripts
│ │ ├── preimport.ps1
│ │ ├── strings.ps1
│ │ ├── variables.ps1
│ │ ├── postimport.ps1
│ │ └── license.ps1
│ ├── configurations
│ │ ├── readme.md
│ │ └── configuration.ps1
│ └── functions
│ │ ├── Remove-SerializationLabel.ps1
│ │ ├── Read-Script.ps1
│ │ ├── Get-CommandMetaData.ps1
│ │ └── New-BootstrapScript.ps1
├── functions
│ ├── readme.md
│ ├── parsing
│ │ ├── Test-JeaCommand.ps1
│ │ ├── Read-JeaScriptFile.ps1
│ │ └── Read-JeaScriptblock.ps1
│ ├── construct
│ │ ├── Add-JeaModuleRole.ps1
│ │ ├── Add-JeaModuleScript.ps1
│ │ ├── New-JeaRole.ps1
│ │ ├── New-JeaCommand.ps1
│ │ ├── ConvertTo-JeaCapability.ps1
│ │ ├── New-JeaModule.ps1
│ │ └── Import-JeaScriptFile.ps1
│ ├── deploy
│ │ ├── Uninstall-JeaModule.ps1
│ │ ├── Install-JeaModule.ps1
│ │ └── Get-JeaEndpoint.ps1
│ └── write
│ │ ├── Export-JeaRoleCapFile.ps1
│ │ └── Export-JeaModule.ps1
├── tests
│ ├── functions
│ │ └── readme.md
│ ├── general
│ │ ├── FileIntegrity.Exceptions.ps1
│ │ ├── Help.Exceptions.ps1
│ │ ├── strings.Exceptions.ps1
│ │ ├── strings.Tests.ps1
│ │ ├── PSScriptAnalyzer.Tests.ps1
│ │ ├── Manifest.Tests.ps1
│ │ ├── FileIntegrity.Tests.ps1
│ │ └── Help.Tests.ps1
│ ├── readme.md
│ └── pester.ps1
├── snippets
│ └── help_par_EnableException.snippet
├── xml
│ ├── JEAnalyzer.Types.ps1xml
│ ├── readme.md
│ └── JEAnalyzer.Format.ps1xml
├── LICENSE
├── changelog.md
├── en-us
│ ├── about_JEAnalyzer.help.txt
│ └── strings.psd1
├── JEAnalyzer.psm1
└── JEAnalyzer.psd1
├── .github
├── workflows
│ ├── validate.yml
│ └── build.yml
└── FUNDING.yml
├── library
└── JEAnalyzer
│ ├── JEAnalyzer
│ ├── CapabilityType.cs
│ ├── Capability.cs
│ ├── CommandInfo.cs
│ ├── JEAnalyzer.csproj
│ ├── ScriptFile.cs
│ ├── CapabilityCommand.cs
│ ├── CapabilityScript.cs
│ ├── Module.cs
│ ├── Parameter.cs
│ └── Role.cs
│ └── JEAnalyzer.sln
├── .gitignore
├── LICENSE
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/JEAnalyzer/bin/JEAnalyzer.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSSecTools/JEAnalyzer/HEAD/JEAnalyzer/bin/JEAnalyzer.dll
--------------------------------------------------------------------------------
/JEAnalyzer/bin/JEAnalyzer.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSSecTools/JEAnalyzer/HEAD/JEAnalyzer/bin/JEAnalyzer.pdb
--------------------------------------------------------------------------------
/JEAnalyzer/internal/tepp/assignment.ps1:
--------------------------------------------------------------------------------
1 | Register-PSFTeppArgumentCompleter -Command Import-JeaScriptFile -Parameter Encoding -Name psframework-encoding
--------------------------------------------------------------------------------
/JEAnalyzer/internal/scriptblock/validateServiceAccount.ps1:
--------------------------------------------------------------------------------
1 | Set-PSFScriptblock -Name 'JEAnalyzer.Validate.ServiceAccount' -Scriptblock {
2 | $_ -match '^[^\\]+\\[^\\]+$'
3 | } -Global
--------------------------------------------------------------------------------
/JEAnalyzer/internal/resources/readme.md:
--------------------------------------------------------------------------------
1 | # Resources
2 |
3 | These files are used to copy & paste and possibly insert text into the copy.
4 |
5 | This makes it easier to centrally maintain micro-templates.
--------------------------------------------------------------------------------
/JEAnalyzer/internal/scripts/preimport.ps1:
--------------------------------------------------------------------------------
1 | # Add all things you want to run before importing the main code
2 |
3 | # Load the strings
4 | . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\strings.ps1"
--------------------------------------------------------------------------------
/JEAnalyzer/internal/tepp/example.tepp.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | # Example:
3 | Register-PSFTeppScriptblock -Name "JEAnalyzer.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' }
4 | #>
--------------------------------------------------------------------------------
/JEAnalyzer/functions/readme.md:
--------------------------------------------------------------------------------
1 | # Functions
2 |
3 | This is the folder where the functions go.
4 |
5 | Depending on the complexity of the module, it is recommended to subdivide them into subfolders.
6 |
7 | The module will pick up all .ps1 files recursively
--------------------------------------------------------------------------------
/JEAnalyzer/tests/functions/readme.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | This is where the function tests go.
4 |
5 | Make sure to put them in folders reflecting the actual module structure.
6 |
7 | It is not necessary to differentiate between internal and public functions here.
--------------------------------------------------------------------------------
/JEAnalyzer/internal/scripts/strings.ps1:
--------------------------------------------------------------------------------
1 | # Load English strings
2 | Import-PSFLocalizedString -Path "$script:ModuleRoot\en-us\strings.psd1" -Module JEAnalyzer -Language en-US
3 |
4 | # Obtain strings variable for in-script use
5 | $script:strings = Get-PSFLocalizedString -Module JEAnalyzer
--------------------------------------------------------------------------------
/JEAnalyzer/internal/scriptblock/validatePath.ps1:
--------------------------------------------------------------------------------
1 | Set-PSFScriptblock -Name 'JEAnalyzer.ValidatePath.Directory' -Scriptblock {
2 | Param ($Path)
3 | if (-not (Test-Path $Path)) { return $false }
4 | try { $null = Resolve-PSFPath -Path $Path -Provider FileSystem -SingleItem }
5 | catch { return $false }
6 | (Get-Item -Path $Path).PSIsContainer
7 | }
--------------------------------------------------------------------------------
/.github/workflows/validate.yml:
--------------------------------------------------------------------------------
1 | on: [pull_request]
2 |
3 | jobs:
4 | validate:
5 |
6 | runs-on: windows-latest
7 |
8 | steps:
9 | - uses: actions/checkout@v1
10 | - name: Install Prerequisites
11 | run: .\build\vsts-prerequisites.ps1
12 | shell: powershell
13 | - name: Validate
14 | run: .\build\vsts-validate.ps1
15 | shell: powershell
16 |
--------------------------------------------------------------------------------
/JEAnalyzer/bin/readme.md:
--------------------------------------------------------------------------------
1 | # bin folder
2 |
3 | The bin folder exists to store binary data. And scripts related to the type system.
4 |
5 | This may include your own C#-based library, third party libraries you want to include (watch the license!), or a script declaring type accelerators (effectively aliases for .NET types)
6 |
7 | For more information on Type Accelerators, see the help on Set-PSFTypeAlias
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/CapabilityType.cs:
--------------------------------------------------------------------------------
1 | namespace JEAnalyzer
2 | {
3 | ///
4 | /// List of supported capability types
5 | ///
6 | public enum CapabilityType
7 | {
8 | ///
9 | /// A cmdlet or function
10 | ///
11 | Command,
12 |
13 | ///
14 | /// An input script that should be deployed as part of the module.
15 | ///
16 | Script
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # ignore the settings folder and files for VSCode and PSS
3 | .vscode/*
4 | *.psproj
5 | *.psprojs
6 | *TempPoint*
7 |
8 | # Ignore staging info from Visual Studio
9 | library/JEAnalyzer/.vs/*
10 | library/JEAnalyzer/JEAnalyzer/bin/*
11 | library/JEAnalyzer/JEAnalyzer/obj/*
12 |
13 | # ignore PowerShell Studio MetaData
14 | JEAnalyzer/JEAnalyzer.psproj
15 | JEAnalyzer/JEAnalyzer.psproj.bak
16 | JEAnalyzer/JEAnalyzer.psprojs
17 | JEAnalyzer/JEAnalyzer.psproj
18 |
19 | # ignore the TestResults
20 | TestResults/*
21 |
22 | # ignore the publishing Directory
23 | publish/*
--------------------------------------------------------------------------------
/JEAnalyzer/internal/configurations/readme.md:
--------------------------------------------------------------------------------
1 | # Configurations
2 |
3 | Through the `PSFramework` you have a simple method that allows you to ...
4 |
5 | - Publish settings
6 | - With onboard documentation
7 | - Input validation
8 | - Scripts that run on change of settings
9 | - That can be discovered and updated by the user
10 | - That can be administrated by policy & DSC
11 |
12 | The configuration system is a bit too complex to describe in a help file, you can however visit us at http://psframework.org for detailed guidance.
13 |
14 | An example can be seen in the attached ps1 file
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 | - main
6 |
7 | jobs:
8 | build:
9 |
10 | runs-on: windows-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v1
14 | - name: Install Prerequisites
15 | run: .\build\vsts-prerequisites.ps1
16 | shell: powershell
17 | - name: Validate
18 | run: .\build\vsts-validate.ps1
19 | shell: powershell
20 | - name: Build
21 | run: .\build\vsts-build.ps1 -ApiKey $env:APIKEY
22 | shell: powershell
23 | env:
24 | APIKEY: ${{ secrets.ApiKey }}
25 |
--------------------------------------------------------------------------------
/JEAnalyzer/internal/resources/Register-JeaEndpointPublic.ps1:
--------------------------------------------------------------------------------
1 | function Register-JeaEndpointPublic
2 | {
3 | <#
4 | .SYNOPSIS
5 | Registers the module's JEA session configuration in WinRM.
6 |
7 | .DESCRIPTION
8 | Registers the module's JEA session configuration in WinRM.
9 | This effectively enables the module as a remoting endpoint.
10 |
11 | .EXAMPLE
12 | PS C:\> Register-JeaEndpointPublic
13 |
14 | Register this module in WinRM as a remoting target.
15 | #>
16 | [CmdletBinding()]
17 | param (
18 |
19 | )
20 |
21 | process
22 | {
23 | Register-JeaEndpoint
24 | }
25 | }
--------------------------------------------------------------------------------
/JEAnalyzer/internal/scripts/variables.ps1:
--------------------------------------------------------------------------------
1 | # List of potentially dangerous commands
2 | $script:dangerousCommands = @(
3 | '%'
4 | 'ForEach'
5 | 'ForEach-Object'
6 | '?'
7 | 'Where'
8 | 'Where-Object'
9 | 'iex'
10 | 'Add-LocalGroupMember'
11 | 'Add-ADGroupMember'
12 | 'net'
13 | 'net.exe'
14 | 'dsadd'
15 | 'dsadd.exe'
16 | 'Start-Process'
17 | 'New-Service'
18 | 'Invoke-Item'
19 | 'iwmi'
20 | 'Invoke-WmiMethod'
21 | 'Invoke-CimMethod'
22 | 'Invoke-Expression'
23 | 'Invoke-Command'
24 | 'New-ScheduledTask'
25 | 'Register-ScheduledJob'
26 | 'Register-ScheduledTask'
27 | '*.ps1'
28 | )
--------------------------------------------------------------------------------
/JEAnalyzer/internal/resources/jeamodule.psm1:
--------------------------------------------------------------------------------
1 | $script:ModuleRoot = $PSScriptRoot
2 |
3 | foreach ($scriptFile in (Get-ChildItem -Path "$($script:ModuleRoot)\internal\scriptsPre\" -Recurse -Filter *.ps1))
4 | {
5 | . $scriptFile.FullName
6 | }
7 |
8 | foreach ($functionFile in (Get-ChildItem -Path "$($script:ModuleRoot)\internal\functions\" -Recurse -Filter *.ps1))
9 | {
10 | . $functionFile.FullName
11 | }
12 | foreach ($functionFile in (Get-ChildItem -Path "$($script:ModuleRoot)\functions\" -Recurse -Filter *.ps1))
13 | {
14 | . $functionFile.FullName
15 | }
16 |
17 | foreach ($scriptFile in (Get-ChildItem -Path "$($script:ModuleRoot)\internal\scriptsPost\" -Recurse -Filter *.ps1))
18 | {
19 | . $scriptFile.FullName
20 | }
--------------------------------------------------------------------------------
/JEAnalyzer/tests/general/FileIntegrity.Exceptions.ps1:
--------------------------------------------------------------------------------
1 | # List of forbidden commands
2 | $global:BannedCommands = @(
3 | 'Write-Host',
4 | 'Write-Verbose',
5 | 'Write-Warning',
6 | 'Write-Error',
7 | 'Write-Output',
8 | 'Write-Information',
9 | 'Write-Debug'
10 | )
11 |
12 | <#
13 | Contains list of exceptions for banned cmdlets.
14 | Insert the file names of files that may contain them.
15 |
16 | Example:
17 | "Write-Host" = @('Write-PSFHostColor.ps1','Write-PSFMessage.ps1')
18 | #>
19 | $global:MayContainCommand = @{
20 | "Write-Host" = @()
21 | "Write-Verbose" = @()
22 | "Write-Warning" = @()
23 | "Write-Error" = @()
24 | "Write-Output" = @()
25 | "Write-Information" = @()
26 | "Write-Debug" = @()
27 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | FriedrichWeinmann
5 | patreon: # Replace with a single Patreon username
6 | open_collective: # Replace with a single Open Collective username
7 | ko_fi: # Replace with a single Ko-fi username
8 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
9 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
10 | liberapay: # Replace with a single Liberapay username
11 | issuehunt: # Replace with a single IssueHunt username
12 | otechie: # Replace with a single Otechie username
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/JEAnalyzer/tests/general/Help.Exceptions.ps1:
--------------------------------------------------------------------------------
1 | # List of functions that should be ignored
2 | $global:FunctionHelpTestExceptions = @(
3 |
4 | )
5 |
6 | <#
7 | List of arrayed enumerations. These need to be treated differently. Add full name.
8 | Example:
9 |
10 | "Sqlcollaborative.Dbatools.Connection.ManagementConnectionType[]"
11 | #>
12 | $global:HelpTestEnumeratedArrays = @(
13 |
14 | )
15 |
16 | <#
17 | Some types on parameters just fail their validation no matter what.
18 | For those it becomes possible to skip them, by adding them to this hashtable.
19 | Add by following this convention: = @()
20 | Example:
21 |
22 | "Get-DbaCmObject" = @("DoNotUse")
23 | #>
24 | $global:HelpTestSkipParameterType = @{
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/JEAnalyzer/snippets/help_par_EnableException.snippet:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | help_par_EnableException
5 |
6 | Help for the EnableException parameter
7 | Infernal Associates Ltd.
8 |
9 | Expansion
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/JEAnalyzer/internal/resources/run.ps1:
--------------------------------------------------------------------------------
1 | #requires -RunAsAdministrator
2 |
3 | <#
4 | .SYNOPSIS
5 | Bootstrap launch script that will install the wrapped JEA endpoint
6 |
7 | .DESCRIPTION
8 | Bootstrap launch script that will install the wrapped JEA endpoint
9 |
10 | .PARAMETER EndpointName
11 | Name of the JEA Endpoint to register
12 | #>
13 | [CmdletBinding()]
14 | param (
15 | [string]
16 | $EndpointName = '%name%'
17 | )
18 |
19 | if (-not (Test-Path -Path "$PSScriptRoot\Modules")) {
20 | throw "Package Error: No modules found!"
21 | }
22 |
23 | $moduleRoot = Join-Path -Path $env:ProgramFiles -ChildPath 'WindowsPowerShell\Modules'
24 | foreach ($moduleFolder in Get-ChildItem -Path "$PSScriptRoot\Modules" -Directory) {
25 | Copy-Item -LiteralPath $moduleFolder.FullName -Destination $moduleRoot -Recurse -Force
26 | }
27 |
28 | $module = Import-Module -Name $EndpointName -PassThru
29 | & $module { Register-JeaEndpoint }
--------------------------------------------------------------------------------
/JEAnalyzer/functions/parsing/Test-JeaCommand.ps1:
--------------------------------------------------------------------------------
1 | function Test-JeaCommand
2 | {
3 | <#
4 | .SYNOPSIS
5 | Tests, whether a command is safe to expose in JEA.
6 |
7 | .DESCRIPTION
8 | Tests, whether a command is safe to expose in JEA.
9 | Unsafe commands allow escaping the lockdown that JEA is supposed to provide.
10 | Safety check is a best effort initiative and not an absolute determination.
11 |
12 | .PARAMETER Name
13 | Name of the command to test
14 |
15 | .EXAMPLE
16 | PS C:\> Test-JeaCommand -Name 'Get-Command'
17 |
18 | Tests whether Get-Command is safe to expose in JEA (Hint: It is)
19 | #>
20 | [CmdletBinding()]
21 | param (
22 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
23 | [Alias('CommandName')]
24 | [string[]]
25 | $Name
26 | )
27 |
28 | process
29 | {
30 | foreach ($commandName in $Name)
31 | {
32 | Get-CommandMetaData -CommandName $commandName
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/JEAnalyzer/internal/scripts/postimport.ps1:
--------------------------------------------------------------------------------
1 | # Add all things you want to run after importing the main code
2 |
3 | # Load Configurations
4 | foreach ($file in (Get-ChildItem "$ModuleRoot\internal\configurations\*.ps1" -ErrorAction Ignore)) {
5 | . Import-ModuleFile -Path $file.FullName
6 | }
7 |
8 | # Load Tab Expansion
9 | foreach ($file in (Get-ChildItem "$ModuleRoot\internal\tepp\*.tepp.ps1" -ErrorAction Ignore))
10 | {
11 | . Import-ModuleFile -Path $file.FullName
12 | }
13 |
14 | # Load Scriptblocks
15 | foreach ($file in (Get-ChildItem "$ModuleRoot\internal\scriptblock\*.ps1" -ErrorAction Ignore))
16 | {
17 | . Import-ModuleFile -Path $file.FullName
18 | }
19 |
20 | # Load Tab Expansion Assignment
21 | . Import-ModuleFile -Path "$ModuleRoot\internal\tepp\assignment.ps1"
22 |
23 | # Load Variables
24 | . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\variables.ps1"
25 |
26 | # Load License
27 | . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\license.ps1"
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/Capability.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace JEAnalyzer
8 | {
9 | ///
10 | /// Base class for capabilities
11 | ///
12 | [Serializable]
13 | public abstract class Capability
14 | {
15 | ///
16 | /// The type of the capability. The set should do nothing, the get always return a static return.
17 | ///
18 | public abstract CapabilityType Type { get; set; }
19 |
20 | ///
21 | /// The name of the capability
22 | ///
23 | public string Name;
24 |
25 | ///
26 | /// Return a hashtable representation for use in a Role Capability File
27 | ///
28 | ///
29 | ///
30 | public abstract Hashtable ToHashtable(string ModuleName);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/JEAnalyzer/tests/readme.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | This is the folder, where all the tests go.
4 |
5 | Those are subdivided in two categories:
6 |
7 | - General
8 | - Function
9 |
10 | ## General Tests
11 |
12 | General tests are function generic and test for general policies.
13 |
14 | These test scan answer questions such as:
15 |
16 | - Is my module following my style guides?
17 | - Does any of my scripts have a syntax error?
18 | - Do my scripts use commands I do not want them to use?
19 | - Do my commands follow best practices?
20 | - Do my commands have proper help?
21 |
22 | Basically, these allow a general module health check.
23 |
24 | These tests are already provided as part of the template.
25 |
26 | ## Function Tests
27 |
28 | A healthy module should provide unit and integration tests for the commands & components it ships.
29 | Only then can be guaranteed, that they will actually perform as promised.
30 |
31 | However, as each such test must be specific to the function it tests, there cannot be much in the way of templates.
--------------------------------------------------------------------------------
/JEAnalyzer/internal/tepp/readme.md:
--------------------------------------------------------------------------------
1 | # Tab Expansion
2 |
3 | ## Description
4 |
5 | Modern Tab Expansion was opened to users with the module `Tab Expansion Plus Plus` (TEPP).
6 |
7 | It allows you to define, what options a user is offered when tabbing through input options. This can save a lot of time for the user and is considered a key element in user experience.
8 |
9 | The `PSFramework` offers a simplified way of offering just this, as the two example files show.
10 |
11 | ## Concept
12 |
13 | Custom tab completion is defined in two steps:
14 |
15 | - Define a scriptblock that is run when the user hits `TAB` and provides the strings that are his options.
16 | - Assign that scriptblock to the parameter of a command. You can assign the same scriptblock multiple times.
17 |
18 | ## Structure
19 |
20 | Import order matters. In order to make things work with the default scaffold, follow those rules:
21 |
22 | - All scriptfiles _defining_ completion scriptblocks like this: `*.tepp.ps1`
23 | - Put all your completion assignments in `assignment.ps1`
--------------------------------------------------------------------------------
/JEAnalyzer/tests/general/strings.Exceptions.ps1:
--------------------------------------------------------------------------------
1 | $exceptions = @{ }
2 |
3 | <#
4 | A list of entries that MAY be in the language files, without causing the tests to fail.
5 | This is commonly used in modules that generate localized messages straight from C#.
6 | Specify the full key as it is written in the language files, do not prepend the modulename,
7 | as you would have to in C# code.
8 |
9 | Example:
10 | $exceptions['LegalSurplus'] = @(
11 | 'Exception.Streams.FailedCreate'
12 | 'Exception.Streams.FailedDispose'
13 | )
14 | #>
15 | $exceptions['LegalSurplus'] = @(
16 | 'Validate.FileSystem.Directory.Fail'
17 | )
18 |
19 | $exceptions['NoTextNeeded'] = @(
20 | 'Validate.FSPath'
21 | 'Validate.FSPath.File'
22 | 'Validate.FSPath.FileOrParent'
23 | 'Validate.FSPath.Folder'
24 | 'Validate.Path'
25 | 'Validate.Path.Container'
26 | 'Validate.Path.Leaf'
27 | 'Validate.TimeSpan.Positive'
28 | 'Validate.Uri.Absolute'
29 | 'Validate.Uri.Absolute.File'
30 | 'Validate.Uri.Absolute.Https'
31 |
32 | 'FileSystem.Directory.Fail'
33 | 'Validate.FileSystem.Directory.Fail'
34 | )
35 |
36 | $exceptions
--------------------------------------------------------------------------------
/JEAnalyzer/xml/JEAnalyzer.Types.ps1xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Deserialized.Foo.Bar
6 |
7 |
8 | PSStandardMembers
9 |
10 |
11 |
12 | TargetTypeForDeserialization
13 |
14 |
15 | Foo.Bar
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Foo.Bar
24 |
25 |
26 | SerializationData
27 |
28 | PSFramework.Serialization.SerializationTypeConverter
29 | GetSerializationData
30 |
31 |
32 |
33 |
34 | PSFramework.Serialization.SerializationTypeConverter
35 |
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Miriam Wiesner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/JEAnalyzer/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Miriam Wiesner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/JEAnalyzer/tests/general/strings.Tests.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .DESCRIPTION
3 | This test verifies, that all strings that have been used,
4 | are listed in the language files and thus have a message being displayed.
5 |
6 | It also checks, whether the language files have orphaned entries that need cleaning up.
7 | #>
8 |
9 |
10 |
11 | Describe "Testing localization strings" {
12 | $moduleRoot = (Get-Module JEAnalyzer).ModuleBase
13 | $stringsResults = Export-PSMDString -ModuleRoot $moduleRoot
14 | $exceptions = & "$global:testroot\general\strings.Exceptions.ps1"
15 |
16 | foreach ($stringEntry in $stringsResults) {
17 | if ($stringEntry.String -eq "key") { continue } # Skipping the template default entry
18 | It "Should be used & have text: $($stringEntry.String)" -TestCases @{ stringEntry = $stringEntry; exceptions = $exceptions } {
19 | if ($exceptions.LegalSurplus -notcontains $stringEntry.String) {
20 | $stringEntry.Surplus | Should -BeFalse
21 | }
22 | if ($exceptions.NoTextNeeded -notcontains $stringEntry.String) {
23 | $stringEntry.Text | Should -Not -BeNullOrEmpty
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/JEAnalyzer/internal/functions/Remove-SerializationLabel.ps1:
--------------------------------------------------------------------------------
1 | function Remove-SerializationLabel {
2 | <#
3 | .SYNOPSIS
4 | Strips the "Deserialized." prefix out of the typenames of the specified objects.
5 |
6 | .DESCRIPTION
7 | Strips the "Deserialized." prefix out of the typenames of the specified objects.
8 | Use this if you want an object received from a remote session to look like a local object.
9 |
10 | .PARAMETER InputObject
11 | The object to fix the typenames of.
12 |
13 | .EXAMPLE
14 | PS C:\> $res = $res | Remove-SerializationLabel
15 |
16 | Renames the typenames of all objects in $res to no longer include the "Deserialized." prefix.
17 | #>
18 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
19 | [CmdletBinding()]
20 | param (
21 | [Parameter(ValueFromPipeline = $true)]
22 | $InputObject
23 | )
24 | process {
25 | if ($null -eq $InputObject) { return }
26 |
27 | $names = $($InputObject.PSObject.TypeNames)
28 | foreach ($name in $names) {
29 | $null = $InputObject.PSObject.TypeNames.Remove($name)
30 | $InputObject.PSObject.TypeNames.Add(($name -replace '^Deserialized\.'))
31 | }
32 | $InputObject
33 | }
34 | }
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2010
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{00A3AA73-E887-4EE6-AEAE-231AC4196FCB}") = "JEAnalyzer", "JEAnalyzer\JEAnalyzer.csproj", "{1437A71B-3DE4-4D26-ACDD-39AF521E9DB9}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {1437A71B-3DE4-4D26-ACDD-39AF521E9DB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {1437A71B-3DE4-4D26-ACDD-39AF521E9DB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {1437A71B-3DE4-4D26-ACDD-39AF521E9DB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {1437A71B-3DE4-4D26-ACDD-39AF521E9DB9}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {DC4A7F8E-605B-4308-A10A-D04C1D305216}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/JEAnalyzer/tests/general/PSScriptAnalyzer.Tests.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param (
3 | [switch]
4 | $SkipTest,
5 |
6 | [string[]]
7 | $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions")
8 | )
9 |
10 | if ($SkipTest) { return }
11 |
12 | $global:__pester_data.ScriptAnalyzer = New-Object System.Collections.ArrayList
13 |
14 | Describe 'Invoking PSScriptAnalyzer against commandbase' {
15 | $commandFiles = Get-ChildItem -Path $CommandPath -Recurse | Where-Object Name -like "*.ps1"
16 | $scriptAnalyzerRules = Get-ScriptAnalyzerRule
17 |
18 | foreach ($file in $commandFiles)
19 | {
20 | Context "Analyzing $($file.BaseName)" {
21 | $analysis = Invoke-ScriptAnalyzer -Path $file.FullName -ExcludeRule PSAvoidTrailingWhitespace, PSShouldProcess
22 |
23 | forEach ($rule in $scriptAnalyzerRules)
24 | {
25 | It "Should pass $rule" -TestCases @{ analysis = $analysis; rule = $rule } {
26 | If ($analysis.RuleName -contains $rule)
27 | {
28 | $analysis | Where-Object RuleName -EQ $rule -outvariable failures | ForEach-Object { $null = $global:__pester_data.ScriptAnalyzer.Add($_) }
29 |
30 | 1 | Should -Be 0
31 | }
32 | else
33 | {
34 | 0 | Should -Be 0
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/JEAnalyzer/internal/scripts/license.ps1:
--------------------------------------------------------------------------------
1 | New-PSFLicense -Product 'JEAnalyzer' -Manufacturer 'miwiesne' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2018-09-17") -Text @"
2 | Copyright (c) 2018 miwiesne
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 | "@
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/CommandInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Management.Automation;
4 |
5 | namespace JEAnalyzer
6 | {
7 | ///
8 | /// Information on a given PowerShell command
9 | ///
10 | [Serializable]
11 | public class CommandInfo
12 | {
13 | ///
14 | /// The name of the command
15 | ///
16 | public string CommandName;
17 |
18 | ///
19 | /// The full command object
20 | ///
21 | public System.Management.Automation.CommandInfo CommandObject;
22 |
23 | ///
24 | /// The file the command invocation was read from
25 | ///
26 | public string File;
27 |
28 | ///
29 | /// Whether the command is an alias
30 | ///
31 | public bool IsAlias
32 | {
33 | get { return CommandObject.CommandType == CommandTypes.Alias; }
34 | set { }
35 | }
36 |
37 | ///
38 | /// The name of the module the command comes from
39 | ///
40 | public string ModuleName
41 | {
42 | get { return CommandObject.ModuleName; }
43 | set { }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/JEAnalyzer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net4.5.2
5 |
6 |
7 |
8 | ..\..\..\JEAnalyzer\bin
9 | ..\..\..\JEAnalyzer\bin\JEAnalyzer.xml
10 |
11 |
12 |
13 | ..\..\..\JEAnalyzer\bin
14 | ..\..\..\JEAnalyzer\bin\JEAnalyzer.xml
15 |
16 |
17 |
18 | false
19 |
20 |
21 |
22 |
23 | ..\..\..\..\psframework\PSFramework\bin\PSFramework.dll
24 | false
25 |
26 |
27 | ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll
28 | false
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/JEAnalyzer/internal/resources/Register-JeaEndpoint.ps1:
--------------------------------------------------------------------------------
1 | function Register-JeaEndpoint
2 | {
3 | <#
4 | .SYNOPSIS
5 | Registers the module's JEA session configuration in WinRM.
6 |
7 | .DESCRIPTION
8 | Registers the module's JEA session configuration in WinRM.
9 | This effectively enables the module as a remoting endpoint.
10 |
11 | .EXAMPLE
12 | PS C:\> Register-JeaEndpoint
13 |
14 | Register this module in WinRM as a remoting target.
15 | #>
16 | [CmdletBinding()]
17 | Param (
18 |
19 | )
20 |
21 | process
22 | {
23 | $moduleName = (Get-Item -Path "$script:ModuleRoot\*.psd1").BaseName
24 | try {
25 | $null = Get-PSSessionConfiguration -Name $moduleName -ErrorAction Stop
26 | Unregister-PSSessionConfiguration -Name $moduleName -Force -Confirm:$false
27 | }
28 | catch { }
29 |
30 | # Plan to start WinRM in case it does not recover from registering the JEA session
31 | $taskname = "Start-WinRM-$(Get-Random)"
32 | $action = New-ScheduledTaskAction -Execute powershell.exe -Argument ('-Command Start-Sleep -Seconds 60; Start-Service WinRM -Confirm:$false; Unregister-ScheduledTask -TaskName {0} -Confirm:$false' -f $taskname)
33 | $principal = New-ScheduledTaskPrincipal -UserId SYSTEM -RunLevel Highest
34 | $null = Register-ScheduledTask -TaskName $taskname -Action $action -Principal $principal
35 | Start-ScheduledTask -TaskName $taskname
36 |
37 | Register-PSSessionConfiguration -Name $moduleName -Path "$script:ModuleRoot\sessionconfiguration.pssc" -Force
38 | }
39 | }
--------------------------------------------------------------------------------
/JEAnalyzer/xml/readme.md:
--------------------------------------------------------------------------------
1 | # XML
2 |
3 | This is the folder where project XML files go, notably:
4 |
5 | - Format XML
6 | - Type Extension XML
7 |
8 | External help files should _not_ be placed in this folder!
9 |
10 | ## Notes on Files and Naming
11 |
12 | There should be only one format file and one type extension file per project, as importing them has a notable impact on import times.
13 |
14 | - The Format XML should be named `JEAnalyzer.Format.ps1xml`
15 | - The Type Extension XML should be named `JEAnalyzer.Types.ps1xml`
16 |
17 | ## Tools
18 |
19 | ### New-PSMDFormatTableDefinition
20 |
21 | This function will take an input object and generate format xml for an auto-sized table.
22 |
23 | It provides a simple way to get started with formats.
24 |
25 | ### Get-PSFTypeSerializationData
26 |
27 | ```
28 | C# Warning!
29 | This section is only interest if you're using C# together with PowerShell.
30 | ```
31 |
32 | This function generates type extension XML that allows PowerShell to convert types written in C# to be written to file and restored from it without being 'Deserialized'. Also works for jobs or remoting, if both sides have the `PSFramework` module and type extension loaded.
33 |
34 | In order for a class to be eligible for this, it needs to conform to the following rules:
35 |
36 | - Have the `[Serializable]` attribute
37 | - Be public
38 | - Have an empty constructor
39 | - Allow all public properties/fields to be set (even if setting it doesn't do anything) without throwing an exception.
40 |
41 | ```
42 | non-public properties and fields will be lost in this process!
43 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JEAnalyzer
2 |
3 | ## Synopsis
4 |
5 | Simplifies the implementation and management of Just Enough Administration.
6 |
7 | It provides tools to:
8 |
9 | - Scan commands for potential danger when exposed in a JEA endpoint
10 | - Create JEA Endpoints in simple and convenient manner
11 |
12 | ## Getting Started
13 |
14 | To install the module, run:
15 |
16 | ```powershell
17 | Install-Module JEAnalyzer
18 | ```
19 |
20 | Then you are ready to create a new JEA module:
21 |
22 | ```powershell
23 | $module = New-JeaModule -Name ServerOperations -Description 'JEA Module for basic Server Operations' -Author 'Friedrich Weinmann' -Company Contoso -Version 1.0.0
24 | 'Restart-Computer', 'Get-ScheduledTask', 'Start-ScheduledTask', 'Stop-ScheduledTask' | Get-Command | New-JeaRole -Name 'ServerSystem' -Identity 'contoso\ServerSystemPermissions' -Module $module
25 | 'Send-RDUserMessage', 'Get-RDUserSession', 'Disconnect-RDUser' | New-JeaRole -Name 'RDSHelpDesk' -Identity 'contoso\RDSHelpDeskPermissions' -Module $module
26 | $module | Export-JeaModule -Path '.'
27 | ```
28 |
29 | This will create a module in the current folder that can be deployed using default package management tools.
30 |
31 | When installed on a target machine (under C:\Program Files\WindowsPowerShell\Modules), registering it as an endpoint is straightforward:
32 |
33 | ```powershell
34 | Register-JeaEndpoint_JEA_ServerOperations
35 | ```
36 |
37 | > Note: This requires elevation and must be run in the computer it is installed on.
38 |
39 | Registering a JEA endpoint will restart the WinRM service on the computer, disconnecting all sessions.
40 | Executing this command via remoting will thus lead to an error, but not affect the results.
41 |
--------------------------------------------------------------------------------
/JEAnalyzer/functions/construct/Add-JeaModuleRole.ps1:
--------------------------------------------------------------------------------
1 | function Add-JeaModuleRole
2 | {
3 | <#
4 | .SYNOPSIS
5 | Adds JEA roles to JEA Modules.
6 |
7 | .DESCRIPTION
8 | Adds JEA roles to JEA Modules.
9 |
10 | .PARAMETER Module
11 | The module to add roles to.
12 | Create a new module by using New-JeaModule command.
13 |
14 | .PARAMETER Role
15 | The role(s) to add.
16 | Create a new role by using the New-JeaRole command.
17 |
18 | .PARAMETER Force
19 | Enforce adding the role, overwriting existing roles of the same name.
20 |
21 | .PARAMETER EnableException
22 | This parameters disables user-friendly warnings and enables the throwing of exceptions.
23 | This is less user friendly, but allows catching exceptions in calling scripts.
24 |
25 | .EXAMPLE
26 | PS C:\> $roles | Add-JeaModuleRole -Module $module
27 |
28 | Adds the roles stored in $roles to the module stored in $module
29 | #>
30 | [CmdletBinding()]
31 | param (
32 | [Parameter(Mandatory = $true, Position = 0)]
33 | [JEAnalyzer.Module]
34 | $Module,
35 |
36 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
37 | [JEAnalyzer.Role[]]
38 | $Role,
39 |
40 | [switch]
41 | $Force,
42 |
43 | [switch]
44 | $EnableException
45 | )
46 |
47 | process
48 | {
49 | foreach ($roleItem in $Role)
50 | {
51 | if ($Module.Roles.ContainsKey($roleItem.Name) -and -not $Force)
52 | {
53 | Stop-PSFFunction -String 'Add-JeaModuleRole.RolePresent' -StringValues $roleItem.Name, $Module.Name -EnableException $EnableException -Continue -Cmdlet $PSCmdlet -Target $Role
54 | }
55 | Write-PSFMessage -String 'Add-JeaModuleRole.AddingRole' -StringValues $roleItem.Name, $Module.Name -Target $Role
56 | $Module.Roles[$roleItem.Name] = $roleItem
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/JEAnalyzer/internal/resources/bootstrap.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | This is a wrapper script around whatever data was injected into it when it was built.
4 |
5 | .DESCRIPTION
6 | This is a wrapper script around whatever data was injected into it when it was built.
7 | To inspect what is contained, run this script with the "-ExpandTo" parameter pointing at the folder where to extract it to.
8 | The "run.ps1" file in the root folder is what is being executed after unwrapping it if executed without parameters.
9 |
10 | .PARAMETER ExpandTo
11 | Expand the wrapped code, rather than execute it.
12 | Specify the folder you want it exported to.
13 |
14 | .EXAMPLE
15 | PS C:\> .\%scriptname%
16 |
17 | Execute the wrapped code.
18 |
19 | .EXAMPLE
20 | PS C:\> .\%scriptname% -ExpandTo C:\temp
21 |
22 | Export the wrapped code to C:\temp without executing it.
23 | #>
24 | [CmdletBinding()]
25 | param (
26 | [string]
27 | $ExpandTo
28 | )
29 |
30 | # The actual code to deploy
31 | $payload = '%data%'
32 |
33 | $tempPath = Join-Path -Path ([System.Environment]::GetFolderPath("LocalApplicationData")) -ChildPath 'Temp'
34 | $name = "Bootstrap-$(Get-Random)"
35 | $tempFile = Join-Path -Path $tempPath -ChildPath "$name.zip"
36 |
37 | $bytes = [Convert]::FromBase64String($payload)
38 | [System.IO.File]::WriteAllBytes($tempFile, $bytes)
39 |
40 | if ($ExpandTo) {
41 | Expand-Archive -Path $tempFile -DestinationPath $ExpandTo
42 | Remove-Item -Path $tempFile -Force
43 | return
44 | }
45 |
46 | $tempFolder = New-Item -Path $tempPath -Name $name -ItemType Directory -Force
47 | Expand-Archive -Path $tempFile -DestinationPath $tempFolder.FullName
48 |
49 | $launchPath = Join-Path -Path $tempFolder.FullName -ChildPath run.ps1
50 | try {
51 | & $launchPath
52 | }
53 | finally {
54 | Remove-Item -Path $tempFile -Force
55 | Remove-Item -Path $tempFolder.FullName -Force -Recurse
56 | }
--------------------------------------------------------------------------------
/JEAnalyzer/internal/configurations/configuration.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | This is an example configuration file
3 |
4 | By default, it is enough to have a single one of them,
5 | however if you have enough configuration settings to justify having multiple copies of it,
6 | feel totally free to split them into multiple files.
7 | #>
8 |
9 | <#
10 | # Example Configuration
11 | Set-PSFConfig -Module 'JEAnalyzer' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'"
12 | #>
13 |
14 | Set-PSFConfig -Module 'JEAnalyzer' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
15 | Set-PSFConfig -Module 'JEAnalyzer' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."
16 |
17 | Set-PSFConfig -Module 'JEAnalyzer' -Name 'Author' -Value $env:USERNAME -Initialize -Validation 'string' -SimpleExport -Description 'The default author name to use when creating role capability files'
18 | Set-PSFConfig -Module 'JEAnalyzer' -Name 'Company' -Value 'JEAnalyzer' -Initialize -Validation 'string' -SimpleExport -Description 'The default company name to use when creating role capability files'
19 | Set-PSFConfig -Module 'JEAnalyzer' -Name 'DefaultRepository' -Value 'PSGallery' -Initialize -Validation 'string' -SimpleExport -Description 'The PowerShell repository to use for module access'
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/ScriptFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Management.Automation;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace JEAnalyzer
10 | {
11 | ///
12 | /// A scriptfile deployed with the JEA module
13 | ///
14 | [Serializable]
15 | public class ScriptFile
16 | {
17 | ///
18 | /// Name of the file
19 | ///
20 | public string Name;
21 |
22 | ///
23 | /// Content of the file
24 | ///
25 | public string Text;
26 |
27 | ///
28 | /// Create an empty file object
29 | ///
30 | public ScriptFile()
31 | {
32 |
33 | }
34 |
35 | ///
36 | /// Create a file object based on an existing file
37 | ///
38 | /// Path to the file to load
39 | public ScriptFile(string Path)
40 | {
41 | string resolvedPath = (new SessionState()).Path.GetResolvedPSPathFromPSPath(Path)[0].Path;
42 | Text = File.ReadAllText(resolvedPath);
43 | FileInfo tempInfo = new FileInfo(resolvedPath);
44 | Name = tempInfo.Name.Substring(0, tempInfo.Name.Length - tempInfo.Extension.Length);
45 | }
46 |
47 | ///
48 | /// Create a file object from provided values
49 | ///
50 | /// Name of the file (should not include an extension)
51 | /// The text content of the file
52 | public ScriptFile(string Name, string Text)
53 | {
54 | this.Name = Name;
55 | this.Text = Text;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/CapabilityCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Management.Automation;
5 |
6 | namespace JEAnalyzer
7 | {
8 | ///
9 | /// A command to allow to the user
10 | ///
11 | [Serializable]
12 | public class CapabilityCommand : Capability
13 | {
14 | ///
15 | /// The type of the capability
16 | ///
17 | public override CapabilityType Type
18 | {
19 | get { return CapabilityType.Command; }
20 | set { }
21 | }
22 |
23 | ///
24 | /// The kind of command this is.
25 | ///
26 | public CommandTypes CommandType;
27 |
28 | ///
29 | /// Parameter constraints to apply to the command.
30 | ///
31 | public Dictionary Parameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
32 |
33 | ///
34 | /// Converts this capability into a hashtable needed for the export to a role capability file.
35 | ///
36 | ///
37 | ///
38 | public override Hashtable ToHashtable(string ModuleName)
39 | {
40 | Hashtable result = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
41 |
42 | result["Name"] = Name;
43 | if (Parameters.Count == 0)
44 | return result;
45 | List parameters = new List();
46 | foreach (Parameter param in Parameters.Values)
47 | parameters.Add(param.ToHashtable());
48 | result["Parameters"] = parameters.ToArray();
49 |
50 | return result;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/JEAnalyzer/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.3.19 (2025-05-07)
4 |
5 | - Upd: New-JeaModule - better service account validation and processing
6 | - Fix: Install-JeaModule - unexpected error when failing to connect to remote computer
7 |
8 | ## 1.3.17 (2023-06-18)
9 |
10 | - New: Get-JeaEndpoint - Retrieve JEA Endpoints and their capabilities from target computers.
11 | - New: Uninstall-JeaModule - Remove JEA Endpoints and - optionally - their backing code.
12 | - Upd: JEA Module - registering a JEA module now creates a scheduled task first to restore the WinRM service in case it fails its restart (as is its wont)
13 | - Upd: Export-JeaModule - able to export as selfcontained ps1 file.
14 | - Upd: New-JeaModule - added ModulesToImport parameter to import modules explicitly, without requiring them as a module dependency.
15 | - Fix: JEA Module - registering a new version fails without manually unregistering the previous JEA endpoint
16 | - Fix: New-JeaModule - using RequiredModules fails
17 |
18 | ## 1.2.10
19 |
20 | - New: Command Install-JeaModule - Installs a JEA module on the target computer
21 | - New: Command Add-JeaModuleScript - Adds a script to a JEA module
22 | - New: Command Test-JeaCommand - Test an individual command for safety to publish in an endpoint.
23 | - Upd: New-JeaModule - Added parameters for PreImport and PostImport scripts
24 | - Upd: New-JeaModule - New parameter `-RequiredModules` enables specifying prerequisites
25 | - Upd: New-JeaCommand - New parameter: `-CommandType` allows picking the type of command for unresolveable commands.
26 | - Upd: JeaModules - all roles will now automatically import the jea module, irrespective of commands used
27 | - Fix: Export-JeaModule - Does not write preimport and postimport scripts
28 | - Fix: New-JeaCommand - Fails for unknown commands
29 | - Fix: Export-JeaModule - New JEA modules will only try to load ps1 files on import.
30 |
31 | ## 1.1.0 (???)
32 |
33 | - Pre-History
34 |
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/CapabilityScript.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Management.Automation;
5 |
6 | namespace JEAnalyzer
7 | {
8 | ///
9 | /// A capability born of a script file.
10 | /// The file is stored as a function.
11 | ///
12 | [Serializable]
13 | public class CapabilityScript : Capability
14 | {
15 | ///
16 | /// The type of the capability
17 | ///
18 | public override CapabilityType Type
19 | {
20 | get { return CapabilityType.Script; }
21 | set { }
22 | }
23 |
24 | ///
25 | /// The actual function Definition
26 | ///
27 | public FunctionInfo Content;
28 |
29 | ///
30 | /// Parameter constraints to apply to the command.
31 | ///
32 | public Dictionary Parameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
33 |
34 | ///
35 | /// Converts this capability into a hashtable needed for the export to a role capability file.
36 | ///
37 | ///
38 | public override Hashtable ToHashtable(string ModuleName)
39 | {
40 | Hashtable result = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
41 |
42 | result["Name"] = String.Format("{0}\\{1}", ModuleName, Name);
43 | if (Parameters.Count == 0)
44 | return result;
45 | List parameters = new List();
46 | foreach (Parameter param in Parameters.Values)
47 | parameters.Add(param.ToHashtable());
48 | result["Parameters"] = parameters.ToArray();
49 |
50 | return result;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/JEAnalyzer/internal/functions/Read-Script.ps1:
--------------------------------------------------------------------------------
1 | function Read-Script {
2 | <#
3 | .SYNOPSIS
4 | Parse the content of a script
5 |
6 | .DESCRIPTION
7 | Uses the powershell parser to parse the content of a script or scriptfile.
8 |
9 | .PARAMETER ScriptCode
10 | The scriptblock to parse.
11 |
12 | .PARAMETER Path
13 | Path to the scriptfile to parse.
14 | Silently ignores folder objects.
15 |
16 | .EXAMPLE
17 | PS C:\> Read-PSMDScript -ScriptCode $ScriptCode
18 |
19 | Parses the code in $ScriptCode
20 |
21 | .EXAMPLE
22 | PS C:\> Get-ChildItem | Read-PSMDScript
23 |
24 | Parses all script files in the current directory
25 |
26 | .NOTES
27 | Additional information about the function.
28 | #>
29 | [CmdletBinding()]
30 | param (
31 | [Parameter(Position = 0, ParameterSetName = 'Script', Mandatory = $true)]
32 | [System.Management.Automation.ScriptBlock]$ScriptCode,
33 |
34 | [Parameter(Mandatory = $true, ParameterSetName = 'File', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
35 | [Alias('FullName')]
36 | [string[]]$Path
37 | )
38 |
39 | process {
40 | foreach ($file in $Path) {
41 | Write-PSFMessage -Level Verbose -Message "Processing $file" -Target $file
42 | $item = Get-Item $file
43 | if ($item.PSIsContainer) {
44 | Write-PSFMessage -Level Verbose -Message "is folder, skipping $file" -Target $file
45 | continue
46 | }
47 |
48 | $tokens = $null
49 | $errors = $null
50 | $ast = [System.Management.Automation.Language.Parser]::ParseFile($item.FullName, [ref]$tokens, [ref]$errors)
51 | [pscustomobject]@{
52 | PSTypeName = 'PSModuleDevelopment.Meta.ParseResult'
53 | Ast = $ast
54 | Tokens = $tokens
55 | Errors = $errors
56 | File = $item.FullName
57 | }
58 | }
59 |
60 | if ($ScriptCode) {
61 | $tokens = $null
62 | $errors = $null
63 | $ast = [System.Management.Automation.Language.Parser]::ParseInput($ScriptCode, [ref]$tokens, [ref]$errors)
64 | [pscustomobject]@{
65 | PSTypeName = 'PSModuleDevelopment.Meta.ParseResult'
66 | Ast = $ast
67 | Tokens = $tokens
68 | Errors = $errors
69 | Source = $ScriptCode
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/JEAnalyzer/functions/parsing/Read-JeaScriptFile.ps1:
--------------------------------------------------------------------------------
1 | function Read-JeaScriptFile
2 | {
3 | <#
4 | .SYNOPSIS
5 | Parses scriptfiles and returns qualified command objects of commands found.
6 |
7 | .DESCRIPTION
8 | Parses scriptfiles and returns qualified command objects of commands found.
9 |
10 | Note:
11 | The IsDangerous property is a best-effort thing.
12 | We TRY to find all dangerous commands, that might allow the user to escalate permissions on the Jea Endpoint.
13 | There is no guarantee for complete success however.
14 |
15 | .PARAMETER Path
16 | The path to scan.
17 | Will ignore folders, does not discriminate by extension.
18 |
19 | .PARAMETER EnableException
20 | This parameters disables user-friendly warnings and enables the throwing of exceptions.
21 | This is less user friendly, but allows catching exceptions in calling scripts.
22 |
23 | .EXAMPLE
24 | PS C:\> Get-ChildItem . -Filter *.ps1 -Recurse | Read-JeaScriptFile
25 |
26 | Scans all powershell script files in the folder and subfolder, then parses out command tokens.
27 | #>
28 | [CmdletBinding()]
29 | param (
30 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
31 | [Alias('FullName')]
32 | [string]
33 | $Path,
34 |
35 | [switch]
36 | $EnableException
37 | )
38 |
39 | begin
40 | {
41 | Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param'
42 | $filesProcessed = @()
43 | }
44 | process
45 | {
46 | foreach ($pathItem in $Path)
47 | {
48 | Write-PSFMessage -Level Verbose -Message "Processing $pathItem" -Target $pathItem
49 | try { $resolvedPaths = Resolve-PSFPath -Path $pathItem -Provider FileSystem }
50 | catch { Stop-PSFFunction -Message "Unable to resolve path: $pathItem" -Target $pathItem -EnableException $EnableException -Continue }
51 | foreach ($resolvedPath in $resolvedPaths)
52 | {
53 | $pathObject = Get-Item $resolvedPath
54 |
55 | if ($filesProcessed -contains $pathObject.FullName) { continue }
56 | if ($pathObject.PSIsContainer) { continue }
57 |
58 | $filesProcessed += $pathObject.FullName
59 | $commands = (Read-Script -Path $pathObject.FullName).Tokens | Where-Object TokenFlags -like "*CommandName*" | Group-Object Text | Select-Object -ExpandProperty Name | Where-Object { $_ }
60 | Write-PSFMessage -Level Verbose -Message "$($commands.Count) different commands found" -Target $pathItem
61 |
62 | if ($commands) { Get-CommandMetaData -CommandName $commands -File $pathObject.FullName }
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/JEAnalyzer/functions/construct/Add-JeaModuleScript.ps1:
--------------------------------------------------------------------------------
1 | function Add-JeaModuleScript
2 | {
3 | <#
4 | .SYNOPSIS
5 | Adds a script to a JEA module.
6 |
7 | .DESCRIPTION
8 | Adds a script to a JEA module.
9 | This script will be executed on import, either before or after loading functiosn contained in the module.
10 | Use this to add custom logic - such as logging - as users connect to the JEA endpoint.
11 |
12 | .PARAMETER Module
13 | The JEA module to add the script to.
14 | Use New-JeaModule to create such a module object.
15 |
16 | .PARAMETER Path
17 | Path to the scriptfile to add.
18 |
19 | .PARAMETER Text
20 | Script-Code to add.
21 |
22 | .PARAMETER Name
23 | Name of the scriptfile.
24 | This parameter is optional. What happens if you do NOT use it depends on other parameters:
25 | -Path : Uses the filename instead
26 | -Text : Uses a random guid
27 | This is mostly cosmetic, as you would generally not need to manually modify the output module.
28 |
29 | .PARAMETER Type
30 | Whether the script is executed before or after the functions of the JEA module are available.
31 | It needs to run BEFORE loading the functions if defining PowerShell classes, AFTER if it uses the functions.
32 | If neither: Doesn't matter.
33 | Defaults to: PostScript
34 |
35 | .EXAMPLE
36 | PS C:\> Add-JeaModuleScript -Module $Module -Path '.\connect.ps1'
37 |
38 | Adds the connect.ps1 scriptfile as a script executed after loading functions.
39 | #>
40 | [CmdletBinding(DefaultParameterSetName = 'File')]
41 | Param (
42 | [Parameter(Mandatory = $true, Position = 0)]
43 | [JEAnalyzer.Module]
44 | $Module,
45 |
46 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'File')]
47 | [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')]
48 | [Alias('FullName')]
49 | [string]
50 | $Path,
51 |
52 | [Parameter(Mandatory = $true, ParameterSetName = 'Text')]
53 | [string]
54 | $Text,
55 |
56 | [string]
57 | $Name,
58 |
59 | [ValidateSet('PreScript','PostScript')]
60 | [string]
61 | $Type = 'PostScript'
62 | )
63 |
64 | process
65 | {
66 | if ($Path)
67 | {
68 | $file = [JEAnalyzer.ScriptFile]::new($Path)
69 | if ($Name) { $file.Name = $Name }
70 | }
71 | else
72 | {
73 | if (-not $Name) { $Name = [System.Guid]::NewGuid().ToString() }
74 | $file = [JEAnalyzer.ScriptFile]::new($Name, $Text)
75 | }
76 | switch ($Type)
77 | {
78 | 'PreScript' { $Module.PreimportScripts[$file.Name] = $file }
79 | 'PostScript' { $Module.PostimportScripts[$file.Name] = $file }
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/JEAnalyzer/internal/functions/Get-CommandMetaData.ps1:
--------------------------------------------------------------------------------
1 | function Get-CommandMetaData
2 | {
3 | <#
4 | .SYNOPSIS
5 | Processes extra meta-information for a command
6 |
7 | .DESCRIPTION
8 | Processes extra meta-information for a command
9 |
10 | .PARAMETER CommandName
11 | The command to add information for.
12 |
13 | .PARAMETER File
14 | The file the command was read from.
15 |
16 | .EXAMPLE
17 | PS C:\> Get-CommandMetaData -CommandName 'Get-Help'
18 |
19 | Adds additional information for Get-Help and returns a useful data object.
20 | #>
21 | [CmdletBinding()]
22 | param (
23 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
24 | [string[]]
25 | $CommandName,
26 |
27 | [string]
28 | $File
29 | )
30 |
31 | begin
32 | {
33 | Write-PSFMessage -Level InternalComment -String 'General.BoundParameters' -StringValues ($PSBoundParameters.Keys -join ", ") -Tag 'debug', 'start', 'param'
34 |
35 | if (-not $script:allcommands)
36 | {
37 | # Cache known commands once
38 | Write-PSFMessage -Level Warning -Message "Gathering command information for the first time. This may take quite a while."
39 | [System.Collections.ArrayList]$script:allcommands = Get-Command | Group-Object Name | ForEach-Object { $_.Group | Sort-Object Version -Descending | Select-Object -First 1 }
40 | Get-Alias | Where-Object Name -NotIn $script:allcommands.Name | ForEach-Object { $null = $script:allcommands.Add($_) }
41 | }
42 | }
43 | process
44 | {
45 | foreach ($command in $CommandName)
46 | {
47 | Write-PSFMessage -Level Verbose -Message "Adding meta information for: $($command)"
48 | $commandObject = New-Object -TypeName 'JEAnalyzer.CommandInfo' -Property @{
49 | CommandName = $command
50 | File = $File
51 | }
52 | if ($object = $script:allcommands | Where-Object Name -EQ $command) { $commandObject.CommandObject = $object }
53 | $commandObject | Select-PSFObject -KeepInputObject -ScriptProperty @{
54 | IsDangerous = {
55 | # Parameters that accept scriptblocks are assumed to be dangerous
56 | if ($this.CommandObject.Parameters.Values | Where-Object { $_.ParameterType.FullName -eq 'System.Management.Automation.ScriptBlock' }) { return $true }
57 |
58 | # If the command is flagged as dangerous for JEA, mark it as such
59 | if ($this.CommandObject.Definition -match 'PSFramework\.PSFCore\.NoJeaCommand') { return $true }
60 |
61 | # If the command has a parameter flagged as dangerous for JEA, the command is a danger
62 | if ($this.CommandObject.Parameters.Values | Where-Object { $_.Attributes | Where-Object { $_ -is [PSFramework.PSFCore.NoJeaParameterAttribute] } }) { return $true }
63 |
64 | # Default: Is the command blacklisted?
65 | (& (Get-Module JEAnalyzer) { $script:dangerousCommands }) -contains $this.CommandName
66 | }
67 | }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/Module.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Management.Automation;
4 |
5 | namespace JEAnalyzer
6 | {
7 | ///
8 | /// A Jea Module package, to be export as powershell module and packaged to the target system.
9 | ///
10 | [Serializable]
11 | public class Module
12 | {
13 | ///
14 | /// The name of the module
15 | ///
16 | public string Name;
17 |
18 | ///
19 | /// A suitable description
20 | ///
21 | public string Description;
22 |
23 | ///
24 | /// THe version of the module
25 | ///
26 | public Version Version;
27 |
28 | ///
29 | /// The author of the module
30 | ///
31 | public string Author;
32 |
33 | ///
34 | /// The company the module is deployed by
35 | ///
36 | public string Company;
37 |
38 | ///
39 | /// The gMSA to operate the JEA endpoint under
40 | ///
41 | public string ServiceAccount;
42 |
43 | ///
44 | /// Modules required for this module
45 | ///
46 | public object RequiredModules;
47 |
48 | ///
49 | /// Modules that must be imported at load time, but should not be a required module.
50 | ///
51 | public string[] ModulesToImport;
52 |
53 | ///
54 | /// The roles contained in the module
55 | ///
56 | public Dictionary Roles = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
57 |
58 | ///
59 | /// Script code the module will execute before loading functions
60 | ///
61 | public Dictionary PreimportScripts = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
62 |
63 | ///
64 | /// Script code the module will execute after loading functions
65 | ///
66 | public Dictionary PostimportScripts = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
67 |
68 | ///
69 | /// Functions the module contains but are not published
70 | ///
71 | public Dictionary PrivateFunctions = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
72 |
73 | ///
74 | /// Functions the module contains and publishes.
75 | ///
76 | public Dictionary PublicFunctions = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/JEAnalyzer/tests/general/Manifest.Tests.ps1:
--------------------------------------------------------------------------------
1 | Describe "Validating the module manifest" {
2 | $moduleRoot = (Resolve-Path "$global:testroot\..").Path
3 | $manifest = ((Get-Content "$moduleRoot\JEAnalyzer.psd1") -join "`n") | Invoke-Expression
4 | Context "Basic resources validation" {
5 | $files = Get-ChildItem "$moduleRoot\functions" -Recurse -File | Where-Object Name -like "*.ps1"
6 | It "Exports all functions in the public folder" -TestCases @{ files = $files; manifest = $manifest } {
7 |
8 | $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '<=').InputObject
9 | $functions | Should -BeNullOrEmpty
10 | }
11 | It "Exports no function that isn't also present in the public folder" -TestCases @{ files = $files; manifest = $manifest } {
12 | $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '=>').InputObject
13 | $functions | Should -BeNullOrEmpty
14 | }
15 |
16 | It "Exports none of its internal functions" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } {
17 | $files = Get-ChildItem "$moduleRoot\internal\functions" -Recurse -File -Filter "*.ps1"
18 | $files | Where-Object BaseName -In $manifest.FunctionsToExport | Should -BeNullOrEmpty
19 | }
20 | }
21 |
22 | Context "Individual file validation" {
23 | It "The root module file exists" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } {
24 | Test-Path "$moduleRoot\$($manifest.RootModule)" | Should -Be $true
25 | }
26 |
27 | foreach ($format in $manifest.FormatsToProcess)
28 | {
29 | It "The file $format should exist" -TestCases @{ moduleRoot = $moduleRoot; format = $format } {
30 | Test-Path "$moduleRoot\$format" | Should -Be $true
31 | }
32 | }
33 |
34 | foreach ($type in $manifest.TypesToProcess)
35 | {
36 | It "The file $type should exist" -TestCases @{ moduleRoot = $moduleRoot; type = $type } {
37 | Test-Path "$moduleRoot\$type" | Should -Be $true
38 | }
39 | }
40 |
41 | foreach ($assembly in $manifest.RequiredAssemblies)
42 | {
43 | if ($assembly -like "*.dll") {
44 | It "The file $assembly should exist" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } {
45 | Test-Path "$moduleRoot\$assembly" | Should -Be $true
46 | }
47 | }
48 | else {
49 | It "The file $assembly should load from the GAC" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } {
50 | { Add-Type -AssemblyName $assembly } | Should -Not -Throw
51 | }
52 | }
53 | }
54 |
55 | foreach ($tag in $manifest.PrivateData.PSData.Tags)
56 | {
57 | It "Tags should have no spaces in name" -TestCases @{ tag = $tag } {
58 | $tag -match " " | Should -Be $false
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/JEAnalyzer/functions/construct/New-JeaRole.ps1:
--------------------------------------------------------------------------------
1 | function New-JeaRole
2 | {
3 | <#
4 | .SYNOPSIS
5 | Creates a new role for use in a JEA Module.
6 |
7 | .DESCRIPTION
8 | Creates a new role for use in a JEA Module.
9 |
10 | A role is a what maps a user or group identity to the resources it may use.
11 | Thus it consists of:
12 | - An Identity to apply to
13 | - Capabilities the user is granted.
14 | Capabilities can be any command or a custom script / command that will be embedded in the module.
15 |
16 | .PARAMETER Name
17 | The name of the role.
18 | On any given endpoint, all roles across ALL JEA Modules must have a unique name.
19 | To ensure this happens, all roles will automatically receive the modulename as prefix.
20 |
21 | .PARAMETER Identity
22 | Users or groups with permission to connect to an endpoint and receive this role.
23 | If left empty, only remote management users will be able to connect to this endpoint.
24 | Either use AD Objects (such as the output of Get-ADGroup) or offer netbios-domain-qualified names as string.
25 |
26 | .PARAMETER Capability
27 | The capabilities a role is supposed to have.
28 | This can be any kind of object - the name of a command, the output of Get-Command, path to a scriptfile or the output of any of the processing commands JEAnalyzer possesses (such as Read-JeaScriptFile).
29 |
30 | .PARAMETER Module
31 | A JEA module to which to add the role.
32 |
33 | .EXAMPLE
34 | PS C:\> New-JeaRole -Name 'Test'
35 |
36 | Creates an empty JEA Role named 'Test'
37 |
38 | .EXAMPLE
39 | PS C:\> New-JeaRole -Name 'Test' -Identity (Get-ADGroup JeaTestGroup)
40 |
41 | Creates an empty JEA Role named 'Test' that will grant remote access to members of the JeaTestGroup group.
42 |
43 | .EXAMPLE
44 | PS C:\> Read-JeaScriptFile -Path .\logon.ps1 | Where-Object CommandName -like "Get-AD*" | New-JeaRole -Name Logon -Identity (Get-ADGroup Domain-Users) | Add-JeaModuleRole -Module $module
45 |
46 | Parses the file logon.ps1 for commands.
47 | Then selects all of those commands that are used to read from Active Directory.
48 | It then creates a JEA Role named 'Logon', granting access to all AD Users to the commands selected.
49 | Finally, it adds the new role to the JEA Module object stored in $module.
50 | #>
51 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
52 | [CmdletBinding()]
53 | param (
54 | [Parameter(Mandatory = $true)]
55 | [string]
56 | $Name,
57 |
58 | [Parameter(Mandatory = $true)]
59 | [string[]]
60 | $Identity,
61 |
62 | [Parameter(ValueFromPipeline = $true)]
63 | $Capability,
64 |
65 | [JEAnalyzer.Module]
66 | $Module
67 | )
68 |
69 | begin
70 | {
71 | Write-PSFMessage -String 'New-JeaRole.Creating' -StringValues $Name
72 | $role = New-Object -TypeName 'JEAnalyzer.Role' -ArgumentList $Name, $Identity
73 | }
74 | process
75 | {
76 | $Capability | ConvertTo-JeaCapability | ForEach-Object {
77 | $null = $role.CommandCapability[$_.Name] = $_
78 | }
79 | }
80 | end
81 | {
82 | if ($Module) { $Module.Roles[$role.Name] = $role }
83 | else { $role }
84 | }
85 | }
--------------------------------------------------------------------------------
/JEAnalyzer/en-us/about_JEAnalyzer.help.txt:
--------------------------------------------------------------------------------
1 | TOPIC
2 | about_JEAnalyzer
3 |
4 | SHORT DESCRIPTION
5 | The JEAnalyzer Module is designed to analyze code, generate JEA Modules
6 | and manage JEA Deployments.
7 | It is your all-in-one go-to solution to your JEA deployments.
8 |
9 | LONG DESCRIPTION
10 | # Last updated for version 1.1.0
11 |
12 | #-----------------------------------------------------------------------#
13 | # Index #
14 | #-----------------------------------------------------------------------#
15 |
16 | 1) The JEA Module
17 | 2) Roles & Capabilities
18 | 3) Analytics
19 |
20 | #-----------------------------------------------------------------------#
21 | # 1) The JEA Module #
22 | #-----------------------------------------------------------------------#
23 |
24 | A JEA Module is a group of JEA Resources. These Include:
25 | - Capabilities: Individual actions a connected user may be able to perform.
26 | For example, the ability to run Restart-Service, but only
27 | with the parameter '-Name' and that only with the values
28 | 'Spooler' or 'dns'.
29 | - Roles: A role is a set of capabilities. These can be granted to groups
30 | or users and thus define, what kind of actions a user can perform
31 | on the endpoint.
32 | - Functions: A JEA Module can publish functions like any other module.
33 | These are specifically designed for use in capabilities.
34 | For example, using Import-JeaScriptFile will create both a
35 | capability and a function.
36 |
37 | When converting this into an actual PowerShell module, it will generate a
38 | package that in its entirety represents a PowerShell remoting endpoint in
39 | WinRM. It comes with its own installation routine and can be deployed using
40 | the common package management tools of PowerShell.
41 |
42 | Example of creating and writing a JEA Module:
43 |
44 | $module = New-JeaModule -Name ServerOperations -Description 'JEA Module for basic Server Operations' -Author 'Friedrich Weinmann' -Company Contoso -Version 1.0.0
45 | 'Restart-Computer', 'Get-ScheduledTask', 'Start-ScheduledTask', 'Stop-ScheduledTask' | Get-Command | New-JeaRole -Name 'ServerSystem' -Identity 'contoso\ServerSystemPermissions' -Module $module
46 | 'Send-RDUserMessage', 'Get-RDUserSession', 'Disconnect-RDUser' | New-JeaRole -Name 'RDSHelpDesk' -Identity 'contoso\RDSHelpDeskPermissions' -Module $module
47 | $module | Export-JeaModule -Path '.'
48 |
49 |
50 | #-----------------------------------------------------------------------#
51 | # 2) Roles & Capabilities #
52 | #-----------------------------------------------------------------------#
53 |
54 | # [TODO: Add Content]
55 |
56 |
57 | #-----------------------------------------------------------------------#
58 | # 3) Analytics #
59 | #-----------------------------------------------------------------------#
60 |
61 | # [TODO: Add Content]
62 |
63 | KEYWORDS
64 | JEAnalyzer
--------------------------------------------------------------------------------
/JEAnalyzer/internal/functions/New-BootstrapScript.ps1:
--------------------------------------------------------------------------------
1 | function New-BootstrapScript {
2 | <#
3 | .SYNOPSIS
4 | Take all contents of a folder and embed them into a bootstrap scriptfile.
5 |
6 | .DESCRIPTION
7 | Take all contents of a folder and embed them into a bootstrap scriptfile.
8 | The targeted folder must contain a run.ps1 file for executing the bootstrap logic.
9 |
10 | When executing the resulting file, it will:
11 | - Create a temp folder
12 | - Write all contents of the source folder into that temp folder
13 | - Execute run.ps1 within that temp folder
14 | - Remove the temp folder
15 |
16 | .PARAMETER Path
17 | The source folder containing the content to wrap up.
18 | Must contain a file named run.ps1, may contain subfolders.
19 |
20 | .PARAMETER OutPath
21 | The path where to write the bootstrap scriptfile to.
22 | Can be either a folder or the path to the ps1 file itself.
23 | If a folder is specified, it will create a "bootstrap.ps1" file in that folder.
24 |
25 | .EXAMPLE
26 | PS C:\> New-BootstrapScript -Path . -OutPath C:\temp
27 |
28 | Takes all items in the current folder, wraps them into a bootstrap script and writes that single file to C:\temp\bootstrap.ps1
29 | #>
30 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
31 | [CmdletBinding()]
32 | param (
33 | [Parameter(Mandatory = $true)]
34 | [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')]
35 | [string]
36 | $Path,
37 |
38 | [Parameter(Mandatory = $true)]
39 | [PsfValidateScript('PSFramework.Validate.FSPath.FileOrParent', ErrorString = 'PSFramework.Validate.FSPath.FileOrParent')]
40 | [string]
41 | $OutPath
42 | )
43 | process {
44 | $runFile = Join-Path -Path $Path -ChildPath 'run.ps1'
45 | if (-not (Test-Path -Path $runFile)) {
46 | Stop-PSFFunction -String 'New-BootstrapScript.Validation.NoRunFile' -StringValues $runFile -Target $Path -EnableException $true -Cmdlet $PSCmdlet -Category InvalidData
47 | }
48 |
49 | $tempFile = New-PSFTempFile -Name bootstrapzip -Extension zip -ModuleName JEAnalyzer
50 | Compress-Archive -Path (Join-Path -Path $Path -ChildPath '*') -DestinationPath $tempFile -Force
51 | $bytes = [System.IO.File]::ReadAllBytes($tempFile)
52 | $encoded = [convert]::ToBase64String($bytes)
53 | $bytes = $null
54 |
55 | $bootstrapCode = [System.IO.File]::ReadAllText("$script:ModuleRoot\internal\resources\bootstrap.ps1")
56 | $bootstrapCode = $bootstrapCode -replace '%data%', $encoded
57 | $encoded = $null
58 | Remove-PSFTempItem -Name bootstrapzip -ModuleName JEAnalyzer
59 |
60 | $outFile = Resolve-PSFPath -Path $OutPath -Provider FileSystem -SingleItem -NewChild
61 | if (Test-Path -Path $OutPath) {
62 | $item = Get-Item -Path $OutPath
63 | if ($item.PSIsContainer) {
64 | $outFile = Join-Path -Path $outFile -ChildPath 'bootstrap.ps1'
65 | }
66 | }
67 | $filename = Split-Path -Path $outFile -Leaf
68 | $bootstrapCode = $bootstrapCode -replace '%scriptname%', $filename
69 |
70 | $encoding = [System.Text.UTF8Encoding]::new($true)
71 | [System.IO.File]::WriteAllText($outFile, $bootstrapCode, $encoding)
72 | }
73 | }
--------------------------------------------------------------------------------
/JEAnalyzer/JEAnalyzer.psm1:
--------------------------------------------------------------------------------
1 | $script:ModuleRoot = $PSScriptRoot
2 | $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\JEAnalyzer.psd1").ModuleVersion
3 |
4 | # Detect whether at some level dotsourcing was enforced
5 | $script:doDotSource = Get-PSFConfigValue -FullName JEAnalyzer.Import.DoDotSource -Fallback $false
6 | if ($JEAnalyzer_dotsourcemodule) { $script:doDotSource = $true }
7 |
8 | <#
9 | Note on Resolve-Path:
10 | All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
11 | This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
12 | Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
13 | This is important when testing for paths.
14 | #>
15 |
16 | # Detect whether at some level loading individual module files, rather than the compiled module was enforced
17 | $importIndividualFiles = Get-PSFConfigValue -FullName JEAnalyzer.Import.IndividualFiles -Fallback $false
18 | if ($JEAnalyzer_importIndividualFiles) { $importIndividualFiles = $true }
19 | if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
20 | if ("" -eq '') { $importIndividualFiles = $true }
21 |
22 | function Import-ModuleFile
23 | {
24 | <#
25 | .SYNOPSIS
26 | Loads files into the module on module import.
27 |
28 | .DESCRIPTION
29 | This helper function is used during module initialization.
30 | It should always be dotsourced itself, in order to proper function.
31 |
32 | This provides a central location to react to files being imported, if later desired
33 |
34 | .PARAMETER Path
35 | The path to the file to load
36 |
37 | .EXAMPLE
38 | PS C:\> . Import-ModuleFile -File $function.FullName
39 |
40 | Imports the file stored in $function according to import policy
41 | #>
42 | [CmdletBinding()]
43 | Param (
44 | [string]
45 | $Path
46 | )
47 |
48 | if ($doDotSource) { . (Resolve-Path $Path).ProviderPath }
49 | else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText((Resolve-Path $Path).ProviderPath))), $null, $null) }
50 | }
51 |
52 | #region Load individual files
53 | if ($importIndividualFiles)
54 | {
55 | # Execute Preimport actions
56 | . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1"
57 |
58 | # Import all internal functions
59 | foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
60 | {
61 | . Import-ModuleFile -Path $function.FullName
62 | }
63 |
64 | # Import all public functions
65 | foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
66 | {
67 | . Import-ModuleFile -Path $function.FullName
68 | }
69 |
70 | # Execute Postimport actions
71 | . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1"
72 |
73 | # End it here, do not load compiled code below
74 | return
75 | }
76 | #endregion Load individual files
77 |
78 | #region Load compiled code
79 | ""
80 | #endregion Load compiled code
--------------------------------------------------------------------------------
/JEAnalyzer/functions/construct/New-JeaCommand.ps1:
--------------------------------------------------------------------------------
1 | function New-JeaCommand
2 | {
3 | <#
4 | .SYNOPSIS
5 | Creates a new command for use in a JEA Module's capability.
6 |
7 | .DESCRIPTION
8 | Creates a new command for use in a JEA Module's capability.
9 |
10 | .PARAMETER Name
11 | The name of the command.
12 |
13 | .PARAMETER Parameter
14 | Parameters to constrain.
15 | Specifying this will allow the end user to only use the thus listed parameters on the command.
16 | Valid input:
17 | - The string name of the parameter
18 | - A finished parameter object
19 | - A hashtable that contains further input value constraints. E.g.: @{ Name = 'Name'; ValidateSet = 'Dns', 'Spooler' }
20 |
21 | .PARAMETER Role
22 | A role to which to add the command.
23 | By default, the command object will just be returned by this function.
24 | If you specify a role, it will instead only be added to the role.
25 |
26 | .PARAMETER CommandType
27 | The type of command to add.
28 | Only applies when the command cannot be resolved.
29 | Defaults to function.
30 |
31 | .PARAMETER Force
32 | Override the security warning when generating an unsafe command.
33 | By default, New-JeaCommand will refuse to create a command object for commands deemed unsafe for use in JEA.
34 |
35 | .PARAMETER EnableException
36 | This parameters disables user-friendly warnings and enables the throwing of exceptions.
37 | This is less user friendly, but allows catching exceptions in calling scripts.
38 |
39 | .EXAMPLE
40 | PS C:\> New-JeaCommand -Name 'Restart-Service' -parameter 'Name'
41 |
42 | Generates a command object allowing the use of Get-Service, but only with the parameter "-Name"
43 | #>
44 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
45 | [CmdletBinding()]
46 | param (
47 | [Parameter(Mandatory = $true)]
48 | [string]
49 | $Name,
50 |
51 | [JEAnalyzer.Parameter[]]
52 | $Parameter,
53 |
54 | [JEAnalyzer.Role]
55 | $Role,
56 |
57 | [System.Management.Automation.CommandTypes]
58 | $CommandType = [System.Management.Automation.CommandTypes]::Function,
59 |
60 | [switch]
61 | $Force,
62 |
63 | [switch]
64 | $EnableException
65 | )
66 |
67 | process
68 | {
69 | $commandData = Get-CommandMetaData -CommandName $Name
70 | # Eliminate Aliases
71 | if ($commandData.CommandObject.CommandType -eq 'Alias')
72 | {
73 | $commandData = Get-CommandMetaData -CommandName $commandData.CommandObject.ResolvedCommand.Name
74 | }
75 | if ($commandData.IsDangerous -and -not $Force)
76 | {
77 | Stop-PSFFunction -String 'New-JeaCommand.DangerousCommand' -StringValues $Name -EnableException $EnableException.ToBool() -Target $Name
78 | return
79 | }
80 |
81 | $resultCommand = New-Object -TypeName 'JEAnalyzer.CapabilityCommand' -Property @{
82 | Name = $commandData.CommandName
83 | }
84 | if ($commandData.CommandObject) { $resultCommand.CommandType = $commandData.CommandObject.CommandType }
85 | else { $resultCommand.CommandType = $CommandType }
86 |
87 | foreach ($parameterItem in $Parameter)
88 | {
89 | $resultCommand.Parameters[$parameterItem.Name] = $parameterItem
90 | }
91 | # Add to role if specified, otherwise return
92 | if ($Role) { $null = $Role.CommandCapability[$commandData.CommandName] = $resultCommand }
93 | else { $resultCommand }
94 | }
95 | }
--------------------------------------------------------------------------------
/JEAnalyzer/JEAnalyzer.psd1:
--------------------------------------------------------------------------------
1 | @{
2 | # Script module or binary module file associated with this manifest
3 | RootModule = 'JEAnalyzer.psm1'
4 |
5 | # Version number of this module.
6 |
7 | ModuleVersion = '1.3.19'
8 |
9 | # ID used to uniquely identify this module
10 | GUID = '346caa76-534a-4651-88f5-359e85cd71c0'
11 |
12 | # Author of this module
13 | Author = 'Miriam Wiesner, Friedrich Weinmann'
14 |
15 | # Company or vendor of this module
16 | CompanyName = ' '
17 |
18 | # Copyright statement for this module
19 | Copyright = 'Copyright (c) 2018 Miriam Wiesner'
20 |
21 | # Description of the functionality provided by this module
22 | Description = 'Simplifies the implementation of Just Enough Administration by providing functions to convert Code, ScripBlocks or Scripts into JEA role capability files.'
23 |
24 | # Minimum version of the Windows PowerShell engine required by this module
25 | PowerShellVersion = '5.0'
26 |
27 | # Modules that must be imported into the global environment prior to importing
28 | # this module
29 | RequiredModules = @(
30 | @{ ModuleName = 'PSFramework'; ModuleVersion = '1.12.346' }
31 | )
32 |
33 | # Assemblies that must be loaded prior to importing this module
34 | RequiredAssemblies = @('bin\JEAnalyzer.dll')
35 |
36 | # Type files (.ps1xml) to be loaded when importing this module
37 | # TypesToProcess = @('xml\JEAnalyzer.Types.ps1xml')
38 |
39 | # Format files (.ps1xml) to be loaded when importing this module
40 | FormatsToProcess = @('xml\JEAnalyzer.Format.ps1xml')
41 |
42 | # Functions to export from this module
43 | FunctionsToExport = @(
44 | 'Add-JeaModuleRole'
45 | 'Add-JeaModuleScript'
46 | 'ConvertTo-JeaCapability'
47 | 'Export-JeaModule'
48 | 'Export-JeaRoleCapFile'
49 | 'Get-JeaEndpoint'
50 | 'Import-JeaScriptFile'
51 | 'Install-JeaModule'
52 | 'New-JeaCommand'
53 | 'New-JeaModule'
54 | 'New-JeaRole'
55 | 'Read-JeaScriptblock'
56 | 'Read-JeaScriptFile'
57 | 'Test-JeaCommand'
58 | 'Uninstall-JeaModule'
59 | )
60 |
61 | # Cmdlets to export from this module
62 | # CmdletsToExport = ''
63 |
64 | # Variables to export from this module
65 | # VariablesToExport = ''
66 |
67 | # Aliases to export from this module
68 | # AliasesToExport = ''
69 |
70 | # List of all modules packaged with this module
71 | ModuleList = @()
72 |
73 | # List of all files packaged with this module
74 | FileList = @()
75 |
76 | # Private data to pass to the module specified in ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
77 | PrivateData = @{
78 |
79 | #Support for PowerShellGet galleries.
80 | PSData = @{
81 |
82 | # Tags applied to this module. These help with module discovery in online galleries.
83 | Tags = @('jea')
84 |
85 | # A URL to the license for this module.
86 | LicenseUri = 'https://github.com/PSSecTools/JEAnalyzer/blob/master/LICENSE'
87 |
88 | # A URL to the main website for this project.
89 | ProjectUri = 'https://github.com/PSSecTools/JEAnalyzer'
90 |
91 | # A URL to an icon representing this module.
92 | # IconUri = ''
93 |
94 | # ReleaseNotes of this module
95 | # ReleaseNotes = ''
96 |
97 | } # End of PSData hashtable
98 |
99 | } # End of PrivateData hashtable
100 | }
--------------------------------------------------------------------------------
/JEAnalyzer/functions/parsing/Read-JeaScriptblock.ps1:
--------------------------------------------------------------------------------
1 | function Read-JeaScriptblock
2 | {
3 | <#
4 | .SYNOPSIS
5 | Reads a scriptblock and returns qualified command objects of commands found.
6 |
7 | .DESCRIPTION
8 | Reads a scriptblock and returns qualified command objects of commands found.
9 |
10 | .PARAMETER ScriptCode
11 | The string version of the scriptcode to parse.
12 |
13 | .PARAMETER ScriptBlock
14 | A scriptblock to parse.
15 |
16 | .PARAMETER EnableException
17 | This parameters disables user-friendly warnings and enables the throwing of exceptions.
18 | This is less user friendly, but allows catching exceptions in calling scripts.
19 |
20 | .EXAMPLE
21 | PS C:\> Get-SBLEvent | Read-JeaScriptblock
22 |
23 | Scans the local computer for scriptblock logging events and parses out the commands they use.
24 | #>
25 | [CmdletBinding()]
26 | param (
27 | [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
28 | [Alias('Code')]
29 | [string[]]
30 | $ScriptCode,
31 |
32 | [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
33 | [System.Management.Automation.ScriptBlock[]]
34 | $ScriptBlock,
35 |
36 | [switch]
37 | $EnableException
38 | )
39 |
40 | begin
41 | {
42 | Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param'
43 |
44 | $fromPipeline = Test-PSFParameterBinding -ParameterName ScriptCode, ScriptBlock -Not
45 | }
46 | process
47 | {
48 | #region Processing Scriptblock strings
49 | foreach ($codeItem in $ScriptCode)
50 | {
51 | if ($codeItem -eq 'System.Management.Automation.ScriptBlock') { continue }
52 | if ($ScriptBlock -and $fromPipeline) { continue }
53 |
54 | # Never log the full scriptblock, it might contain sensitive information
55 | Write-PSFMessage -Level Verbose -Message "Processing a scriptblock with $($codeItem.Length) characters"
56 | try { $codeBlock = [System.Management.Automation.ScriptBlock]::Create($codeItem) }
57 | catch { Stop-PSFFunction -Message "Failed to parse text as scriptblock, skipping" -EnableException $EnableException -ErrorRecord $_ -OverrideExceptionMessage -Continue }
58 | $commands = (Read-Script -ScriptCode $codeBlock).Tokens | Where-Object TokenFlags -like "*CommandName*" | Group-Object Text | Select-Object -ExpandProperty Name | Where-Object { $_ }
59 |
60 | Write-PSFMessage -Level Verbose -Message "$($commands.Count) different commands found" -Target $pathItem
61 |
62 | if ($commands) { Get-CommandMetaData -CommandName $commands }
63 | }
64 | #endregion Processing Scriptblock strings
65 |
66 | #region Processing Scriptblocks
67 | foreach ($codeItem in $ScriptBlock)
68 | {
69 | # Never log the full scriptblock, it might contain sensitive information
70 | Write-PSFMessage -Level Verbose -Message "Processing a scriptblock with $($codeItem.ToString().Length) characters"
71 | $commands = (Read-Script -ScriptCode $codeItem).Tokens | Where-Object TokenFlags -like "*CommandName*" | Group-Object Text | Select-Object -ExpandProperty Name | Where-Object { $_ }
72 |
73 | Write-PSFMessage -Level Verbose -Message "$($commands.Count) different commands found" -Target $pathItem
74 |
75 | if ($commands) { Get-CommandMetaData -CommandName $commands }
76 | }
77 | #endregion Processing Scriptblocks
78 | }
79 | }
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/Parameter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | using PSFramework.Localization;
7 |
8 | namespace JEAnalyzer
9 | {
10 | ///
11 | /// Represents a parameter constraint on a JEA capability's command
12 | ///
13 | [Serializable]
14 | public class Parameter
15 | {
16 | ///
17 | /// The name of the parameter to permit.
18 | ///
19 | public string Name;
20 |
21 | ///
22 | /// The set of valid values for the parameter.
23 | ///
24 | public string[] ValidateSet;
25 |
26 | ///
27 | /// A pattern input to this parameter must match.
28 | ///
29 | public string ValidatePattern;
30 |
31 | ///
32 | /// Creates an empty parameter (used for serialization)
33 | ///
34 | public Parameter()
35 | {
36 |
37 | }
38 |
39 | ///
40 | /// Creates a parameter without value constraints
41 | ///
42 | /// Name of the parameter
43 | public Parameter(string Name)
44 | {
45 | this.Name = Name;
46 | }
47 |
48 | ///
49 | /// Creates a parameter with value constraints
50 | ///
51 | /// Name of the parameter
52 | /// A set of legal values for the parameter
53 | /// A regex pattern input must match.
54 | public Parameter(string Name, string[] ValidateSet, string ValidatePattern)
55 | {
56 | this.Name = Name;
57 | this.ValidateSet = ValidateSet;
58 | this.ValidatePattern = ValidatePattern;
59 | }
60 |
61 | ///
62 | /// Parses out a parameter from an input hashtable
63 | ///
64 | /// The hashtable to interpret as parameter
65 | public Parameter(Hashtable Table)
66 | {
67 | if (!Table.ContainsKey("Name") || Table["Name"] == null)
68 | throw new ArgumentException(LocalizationHost.Read("JEAnalyzer.Assembly.Parameter.MissingName"));
69 |
70 | Name = (string)Table["Name"].ToString();
71 |
72 | if (Table.ContainsKey("ValidateSet"))
73 | ValidateSet = ((object[])Table["ValidateSet"]).Cast().ToArray();
74 | if (Table.ContainsKey("ValidatePattern"))
75 | ValidatePattern = (string)Table["ValidatePattern"];
76 | }
77 |
78 | ///
79 | /// Returns the hashtable needed for the role capability file
80 | ///
81 | /// A hashtable
82 | public Hashtable ToHashtable()
83 | {
84 | Hashtable result = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
85 |
86 | result["Name"] = Name;
87 | if (!String.IsNullOrEmpty(ValidatePattern))
88 | result["ValidatePattern"] = ValidatePattern;
89 | else if (ValidateSet != null)
90 | result["ValidateSet"] = ValidateSet;
91 |
92 | return result;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/JEAnalyzer/tests/general/FileIntegrity.Tests.ps1:
--------------------------------------------------------------------------------
1 | $moduleRoot = (Resolve-Path "$global:testroot\..").Path
2 |
3 | . "$global:testroot\general\FileIntegrity.Exceptions.ps1"
4 |
5 | Describe "Verifying integrity of module files" {
6 | BeforeAll {
7 | function Get-FileEncoding
8 | {
9 | <#
10 | .SYNOPSIS
11 | Tests a file for encoding.
12 |
13 | .DESCRIPTION
14 | Tests a file for encoding.
15 |
16 | .PARAMETER Path
17 | The file to test
18 | #>
19 | [CmdletBinding()]
20 | Param (
21 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
22 | [Alias('FullName')]
23 | [string]
24 | $Path
25 | )
26 |
27 | if ($PSVersionTable.PSVersion.Major -lt 6)
28 | {
29 | [byte[]]$byte = get-content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path
30 | }
31 | else
32 | {
33 | [byte[]]$byte = Get-Content -AsByteStream -ReadCount 4 -TotalCount 4 -Path $Path
34 | }
35 |
36 | if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) { 'UTF8 BOM' }
37 | elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) { 'Unicode' }
38 | elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) { 'UTF32' }
39 | elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) { 'UTF7' }
40 | else { 'Unknown' }
41 | }
42 | }
43 |
44 | Context "Validating PS1 Script files" {
45 | $allFiles = Get-ChildItem -Path $moduleRoot -Recurse | Where-Object Name -like "*.ps1" | Where-Object FullName -NotLike "$moduleRoot\tests\*"
46 |
47 | foreach ($file in $allFiles)
48 | {
49 | $name = $file.FullName.Replace("$moduleRoot\", '')
50 |
51 | It "[$name] Should have UTF8 encoding with Byte Order Mark" -TestCases @{ file = $file } {
52 | Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM'
53 | }
54 |
55 | It "[$name] Should have no trailing space" -TestCases @{ file = $file } {
56 | ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0}).LineNumber | Should -BeNullOrEmpty
57 | }
58 |
59 | $tokens = $null
60 | $parseErrors = $null
61 | $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$parseErrors)
62 |
63 | It "[$name] Should have no syntax errors" -TestCases @{ parseErrors = $parseErrors } {
64 | $parseErrors | Should -BeNullOrEmpty
65 | }
66 |
67 | foreach ($command in $global:BannedCommands)
68 | {
69 | if ($global:MayContainCommand["$command"] -notcontains $file.Name)
70 | {
71 | It "[$name] Should not use $command" -TestCases @{ tokens = $tokens; command = $command } {
72 | $tokens | Where-Object Text -EQ $command | Should -BeNullOrEmpty
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
79 | Context "Validating help.txt help files" {
80 | $allFiles = Get-ChildItem -Path $moduleRoot -Recurse | Where-Object Name -like "*.help.txt" | Where-Object FullName -NotLike "$moduleRoot\tests\*"
81 |
82 | foreach ($file in $allFiles)
83 | {
84 | $name = $file.FullName.Replace("$moduleRoot\", '')
85 |
86 | It "[$name] Should have UTF8 encoding" -TestCases @{ file = $file } {
87 | Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM'
88 | }
89 |
90 | It "[$name] Should have no trailing space" -TestCases @{ file = $file } {
91 | ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0 } | Measure-Object).Count | Should -Be 0
92 | }
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/JEAnalyzer/en-us/strings.psd1:
--------------------------------------------------------------------------------
1 | @{
2 |
3 | 'Add-JeaModuleRole.AddingRole' = 'Adding role {0} to module {1}' # $roleItem.Name, $Module.Name
4 | 'Add-JeaModuleRole.RolePresent' = 'Role {0} already exists in {1}! Use -Force to replace the existing role.' # $roleItem.Name, $Module.Name
5 |
6 | 'ConvertTo-Capability.CapabilityNotKnown' = 'Could not convert to capability: {0}' # $inputItem
7 |
8 | 'Export-JeaModule.File.Create' = 'Creating File: {0}' # $Path
9 | 'Export-JeaModule.Folder.Content' = 'Creating subfolder: {0}' # $folder
10 | 'Export-JeaModule.Folder.ModuleBaseExists' = 'The module''s base folder already exists: {0}' # $moduleBase.FullName
11 | 'Export-JeaModule.Folder.ModuleBaseNew' = 'Creating new module folder: {0}' # $moduleBase.FullName
12 | 'Export-JeaModule.Folder.RoleCapailities' = 'Creating the folder to store Role Capability Files: {0}\RoleCapabilities' # $rootFolder.FullName
13 | 'Export-JeaModule.Folder.VersionRoot' = 'Creating version specific module path: {0}\{1}' # $moduleBase.FullName, $moduleObject.Version
14 | 'Export-JeaModule.Role.NewRole' = 'Creating new Role: {0} ({1} Published Command Capabilities)' # $role.Name, $role.CommandCapability.Count
15 | 'Export-JeaModule.Role.VisibleCmdlet' = '[Role: {0}] Adding visible Cmdlet: {1}{2}' # $role.Name, $commandName, $parameterText
16 | 'Export-JeaModule.Role.VisibleFunction' = '[Role: {0}] Adding visible Function: {1}{2}' # $role.Name, $commandName, $parameterText
17 |
18 | 'Validate.FileSystem.Directory.Fail' = 'Not a directory: {0}' # ,
19 | 'Validate.ServiceAccount' = 'Invalid account name: Must be a valid NTAccount name notation (\samaccountname)' # ,
20 |
21 | 'General.BoundParameters' = 'Bound parameters: {0}' # ($PSBoundParameters.Keys -join ", ")
22 |
23 | 'Import-JeaScriptFile.ParsingError' = 'Parsing error for file: {0}' # $file
24 | 'Import-JeaScriptFile.ProcessingInput' = 'Processing file for import: {0}' # $file
25 | 'Import-JeaScriptFile.UnknownError' = 'Unknown error when processing file: {0}' # $file
26 |
27 | 'Install-JeaModule.Connecting.Sessions' = 'Connecting via WinRM to {0}' # ($ComputerName -join ", ")
28 | 'Install-JeaModule.Connections.Failed' = 'Failed to connect to {0}' # ($failedServers.TargetObject -join ", ")
29 | 'Install-JeaModule.Connections.NoSessions' = 'No successful sessions established, terminating.' #
30 | 'Install-JeaModule.Copying.Module' = 'Copying JEA module {0} to {1}' # $moduleObject.Name, $session.ComputerName
31 | 'Install-JeaModule.Exporting.Module' = 'Exporting JEA module {0}' # $moduleObject.Name
32 | 'Install-JeaModule.Install' = 'Installing JEA module {0}' # $moduleObject.Name
33 | 'Install-JeaModule.Installing.Module' = 'Installing JEA module {0}' # $moduleObject.Name
34 |
35 | 'New-BootstrapScript.Validation.NoRunFile' = 'Bootstrap folder does not contain a run.ps1 file, which is the start script without which there is no bootstrapping: {0}' # $runFile
36 |
37 | 'New-JeaCommand.DangerousCommand' = 'Dangerous command detected: {0}. Interrupting, use "-Force" to accept insecure commands.' # $Name
38 |
39 | 'New-JeaModule.Creating' = 'Creating JEA Module object for: {0} (v{1})' # $Name, $Version
40 |
41 | 'New-JeaRole.Creating' = 'Creating Role: {0}' # $Name
42 | }
--------------------------------------------------------------------------------
/library/JEAnalyzer/JEAnalyzer/Role.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace JEAnalyzer
7 | {
8 | ///
9 | /// A role in a JEA Module
10 | ///
11 | [Serializable]
12 | public class Role
13 | {
14 | ///
15 | /// The name of the role
16 | ///
17 | public string Name;
18 |
19 | ///
20 | /// The identity (user/group) being granted permission to the role.
21 | ///
22 | public string[] Identity;
23 |
24 | ///
25 | /// List of command capabilities
26 | ///
27 | public Dictionary CommandCapability = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
28 |
29 | ///
30 | /// Creates an empty role. Used for serialization.
31 | ///
32 | public Role()
33 | {
34 |
35 | }
36 |
37 | ///
38 | /// Create a role pre-seeded with name and identity
39 | ///
40 | /// The name of the role
41 | /// The Identity to be granted access to the role.
42 | public Role(string Name, string[] Identity)
43 | {
44 | this.Name = Name;
45 | this.Identity = Identity;
46 | }
47 |
48 | ///
49 | /// The list of Cmdlets that are part of the role.
50 | ///
51 | ///
52 | public object[] VisibleCmdlets()
53 | {
54 | List