├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── validate.yml ├── .gitignore ├── JEAnalyzer ├── JEAnalyzer.psd1 ├── JEAnalyzer.psm1 ├── LICENSE ├── bin │ ├── JEAnalyzer.dll │ ├── JEAnalyzer.pdb │ ├── JEAnalyzer.xml │ └── readme.md ├── changelog.md ├── en-us │ ├── about_JEAnalyzer.help.txt │ └── strings.psd1 ├── functions │ ├── construct │ │ ├── Add-JeaModuleRole.ps1 │ │ ├── Add-JeaModuleScript.ps1 │ │ ├── ConvertTo-JeaCapability.ps1 │ │ ├── Import-JeaScriptFile.ps1 │ │ ├── New-JeaCommand.ps1 │ │ ├── New-JeaModule.ps1 │ │ └── New-JeaRole.ps1 │ ├── deploy │ │ ├── Get-JeaEndpoint.ps1 │ │ ├── Install-JeaModule.ps1 │ │ └── Uninstall-JeaModule.ps1 │ ├── parsing │ │ ├── Read-JeaScriptFile.ps1 │ │ ├── Read-JeaScriptblock.ps1 │ │ └── Test-JeaCommand.ps1 │ ├── readme.md │ └── write │ │ ├── Export-JeaModule.ps1 │ │ └── Export-JeaRoleCapFile.ps1 ├── internal │ ├── configurations │ │ ├── configuration.ps1 │ │ └── readme.md │ ├── functions │ │ ├── Get-CommandMetaData.ps1 │ │ ├── New-BootstrapScript.ps1 │ │ ├── Read-Script.ps1 │ │ └── Remove-SerializationLabel.ps1 │ ├── resources │ │ ├── Register-JeaEndpoint.ps1 │ │ ├── Register-JeaEndpointPublic.ps1 │ │ ├── bootstrap.ps1 │ │ ├── jeamodule.psm1 │ │ ├── readme.md │ │ └── run.ps1 │ ├── scriptblock │ │ ├── validatePath.ps1 │ │ └── validateServiceAccount.ps1 │ ├── scripts │ │ ├── license.ps1 │ │ ├── postimport.ps1 │ │ ├── preimport.ps1 │ │ ├── strings.ps1 │ │ └── variables.ps1 │ └── tepp │ │ ├── assignment.ps1 │ │ ├── example.tepp.ps1 │ │ └── readme.md ├── snippets │ └── help_par_EnableException.snippet ├── tests │ ├── functions │ │ └── readme.md │ ├── general │ │ ├── FileIntegrity.Exceptions.ps1 │ │ ├── FileIntegrity.Tests.ps1 │ │ ├── Help.Exceptions.ps1 │ │ ├── Help.Tests.ps1 │ │ ├── Manifest.Tests.ps1 │ │ ├── PSScriptAnalyzer.Tests.ps1 │ │ ├── strings.Exceptions.ps1 │ │ └── strings.Tests.ps1 │ ├── pester.ps1 │ └── readme.md └── xml │ ├── JEAnalyzer.Format.ps1xml │ ├── JEAnalyzer.Types.ps1xml │ └── readme.md ├── LICENSE ├── README.md ├── build ├── filesAfter.txt ├── filesBefore.txt ├── vsts-build.ps1 ├── vsts-prerequisites.ps1 └── vsts-validate.ps1 ├── install.ps1 └── library └── JEAnalyzer ├── JEAnalyzer.sln └── JEAnalyzer ├── Capability.cs ├── CapabilityCommand.cs ├── CapabilityScript.cs ├── CapabilityType.cs ├── CommandInfo.cs ├── JEAnalyzer.csproj ├── Module.cs ├── Parameter.cs ├── Role.cs └── ScriptFile.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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/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/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/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/bin/JEAnalyzer.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PSSecTools/JEAnalyzer/702f8e5bbad92e74eac0ae0df98bb635708a9965/JEAnalyzer/bin/JEAnalyzer.dll -------------------------------------------------------------------------------- /JEAnalyzer/bin/JEAnalyzer.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PSSecTools/JEAnalyzer/702f8e5bbad92e74eac0ae0df98bb635708a9965/JEAnalyzer/bin/JEAnalyzer.pdb -------------------------------------------------------------------------------- /JEAnalyzer/bin/JEAnalyzer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JEAnalyzer 5 | 6 | 7 | 8 | 9 | Base class for capabilities 10 | 11 | 12 | 13 | 14 | The type of the capability. The set should do nothing, the get always return a static return. 15 | 16 | 17 | 18 | 19 | The name of the capability 20 | 21 | 22 | 23 | 24 | Return a hashtable representation for use in a Role Capability File 25 | 26 | 27 | 28 | 29 | 30 | 31 | A command to allow to the user 32 | 33 | 34 | 35 | 36 | The type of the capability 37 | 38 | 39 | 40 | 41 | The kind of command this is. 42 | 43 | 44 | 45 | 46 | Parameter constraints to apply to the command. 47 | 48 | 49 | 50 | 51 | Converts this capability into a hashtable needed for the export to a role capability file. 52 | 53 | 54 | 55 | 56 | 57 | 58 | A capability born of a script file. 59 | The file is stored as a function. 60 | 61 | 62 | 63 | 64 | The type of the capability 65 | 66 | 67 | 68 | 69 | The actual function Definition 70 | 71 | 72 | 73 | 74 | Parameter constraints to apply to the command. 75 | 76 | 77 | 78 | 79 | Converts this capability into a hashtable needed for the export to a role capability file. 80 | 81 | 82 | 83 | 84 | 85 | List of supported capability types 86 | 87 | 88 | 89 | 90 | A cmdlet or function 91 | 92 | 93 | 94 | 95 | An input script that should be deployed as part of the module. 96 | 97 | 98 | 99 | 100 | Information on a given PowerShell command 101 | 102 | 103 | 104 | 105 | The name of the command 106 | 107 | 108 | 109 | 110 | The full command object 111 | 112 | 113 | 114 | 115 | The file the command invocation was read from 116 | 117 | 118 | 119 | 120 | Whether the command is an alias 121 | 122 | 123 | 124 | 125 | The name of the module the command comes from 126 | 127 | 128 | 129 | 130 | A Jea Module package, to be export as powershell module and packaged to the target system. 131 | 132 | 133 | 134 | 135 | The name of the module 136 | 137 | 138 | 139 | 140 | A suitable description 141 | 142 | 143 | 144 | 145 | THe version of the module 146 | 147 | 148 | 149 | 150 | The author of the module 151 | 152 | 153 | 154 | 155 | The company the module is deployed by 156 | 157 | 158 | 159 | 160 | The gMSA to operate the JEA endpoint under 161 | 162 | 163 | 164 | 165 | Modules required for this module 166 | 167 | 168 | 169 | 170 | Modules that must be imported at load time, but should not be a required module. 171 | 172 | 173 | 174 | 175 | The roles contained in the module 176 | 177 | 178 | 179 | 180 | Script code the module will execute before loading functions 181 | 182 | 183 | 184 | 185 | Script code the module will execute after loading functions 186 | 187 | 188 | 189 | 190 | Functions the module contains but are not published 191 | 192 | 193 | 194 | 195 | Functions the module contains and publishes. 196 | 197 | 198 | 199 | 200 | Represents a parameter constraint on a JEA capability's command 201 | 202 | 203 | 204 | 205 | The name of the parameter to permit. 206 | 207 | 208 | 209 | 210 | The set of valid values for the parameter. 211 | 212 | 213 | 214 | 215 | A pattern input to this parameter must match. 216 | 217 | 218 | 219 | 220 | Creates an empty parameter (used for serialization) 221 | 222 | 223 | 224 | 225 | Creates a parameter without value constraints 226 | 227 | Name of the parameter 228 | 229 | 230 | 231 | Creates a parameter with value constraints 232 | 233 | Name of the parameter 234 | A set of legal values for the parameter 235 | A regex pattern input must match. 236 | 237 | 238 | 239 | Parses out a parameter from an input hashtable 240 | 241 | The hashtable to interpret as parameter 242 | 243 | 244 | 245 | Returns the hashtable needed for the role capability file 246 | 247 | A hashtable 248 | 249 | 250 | 251 | A role in a JEA Module 252 | 253 | 254 | 255 | 256 | The name of the role 257 | 258 | 259 | 260 | 261 | The identity (user/group) being granted permission to the role. 262 | 263 | 264 | 265 | 266 | List of command capabilities 267 | 268 | 269 | 270 | 271 | Creates an empty role. Used for serialization. 272 | 273 | 274 | 275 | 276 | Create a role pre-seeded with name and identity 277 | 278 | The name of the role 279 | The Identity to be granted access to the role. 280 | 281 | 282 | 283 | The list of Cmdlets that are part of the role. 284 | 285 | 286 | 287 | 288 | 289 | The list of Functions that are part of the role. 290 | 291 | 292 | 293 | 294 | 295 | Transcribes all function definitions to the specified module object. 296 | 297 | The object to receive the function definitions 298 | 299 | 300 | 301 | A scriptfile deployed with the JEA module 302 | 303 | 304 | 305 | 306 | Name of the file 307 | 308 | 309 | 310 | 311 | Content of the file 312 | 313 | 314 | 315 | 316 | Create an empty file object 317 | 318 | 319 | 320 | 321 | Create a file object based on an existing file 322 | 323 | Path to the file to load 324 | 325 | 326 | 327 | Create a file object from provided values 328 | 329 | Name of the file (should not include an extension) 330 | The text content of the file 331 | 332 | 333 | 334 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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/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/functions/construct/ConvertTo-JeaCapability.ps1: -------------------------------------------------------------------------------- 1 | function ConvertTo-JeaCapability 2 | { 3 | <# 4 | .SYNOPSIS 5 | Converts the input into JEA Capabilities. 6 | 7 | .DESCRIPTION 8 | Converts the input into JEA Capabilities. 9 | This is a multitool conversion command, accepting a wide range of input objects. 10 | Whether it is a simple command name, the output of Get-Command, items returned by Read-JeaScriptFile or a complex hashtable. 11 | Example hashtable: 12 | @{ 13 | 'Get-Service' = @{ 14 | Name = 'Restart-Service' 15 | Parameters = @{ 16 | Name = 'Name' 17 | ValidateSet = 'dns', 'spooler' 18 | } 19 | } 20 | } 21 | 22 | .PARAMETER InputObject 23 | The object(s) to convert into a capability object. 24 | 25 | .EXAMPLE 26 | PS C:\> Get-Command Get-AD* | ConvertTo-JeaCapability 27 | 28 | Retrieves all ad commands that read data and converts them into capabilities. 29 | #> 30 | [CmdletBinding()] 31 | param ( 32 | [Parameter(ValueFromPipeline = $true)] 33 | $InputObject 34 | ) 35 | 36 | process 37 | { 38 | foreach ($inputItem in $InputObject) 39 | { 40 | # Skip empty entries 41 | if ($null -eq $inputItem) { continue } 42 | 43 | # Pass through finished capabilities 44 | if ($inputItem -is [JEAnalyzer.Capability]) 45 | { 46 | $inputItem 47 | continue 48 | } 49 | 50 | #region Decide based on input type 51 | switch ($inputItem.GetType().FullName) 52 | { 53 | #region Get-Command data 54 | 'System.Management.Automation.AliasInfo' 55 | { 56 | New-Object -TypeName JEAnalyzer.CapabilityCommand -Property @{ 57 | Name = $inputItem.ResolvedCommand.Name 58 | CommandType = 'Alias' 59 | } 60 | break 61 | } 62 | 'System.Management.Automation.FunctionInfo' 63 | { 64 | New-Object -TypeName JEAnalyzer.CapabilityCommand -Property @{ 65 | Name = $inputItem.Name 66 | CommandType = 'Function' 67 | } 68 | break 69 | } 70 | 'System.Management.Automation.CmdletInfo' 71 | { 72 | New-Object -TypeName JEAnalyzer.CapabilityCommand -Property @{ 73 | Name = $inputItem.Name 74 | CommandType = 'Cmdlet' 75 | } 76 | break 77 | } 78 | #endregion Get-Command data 79 | #region String 80 | 'System.String' 81 | { 82 | if (Test-Path $inputItem) { Import-JeaScriptFile -Path $inputItem } 83 | else { Get-Command -Name $inputItem -ErrorAction SilentlyContinue | ConvertTo-JeaCapability } 84 | break 85 | } 86 | #endregion String 87 | #region Hashtable 88 | 'System.Collections.Hashtable' 89 | { 90 | #region Plain Single-Item hashtable 91 | if ($inputItem.Name) 92 | { 93 | $parameter = @{ 94 | Name = $inputItem.Name 95 | } 96 | if ($inputItem.Parameters) { $parameter['Parameter'] = $inputItem.Parameters } 97 | if ($inputItem.Force) { $parameter['Force'] = $true } 98 | 99 | New-JeaCommand @parameter 100 | } 101 | #endregion Plain Single-Item hashtable 102 | 103 | #region Multiple Command Hashtable 104 | else 105 | { 106 | foreach ($valueItem in $inputItem.Values) 107 | { 108 | $parameter = @{ 109 | Name = $valueItem.Name 110 | } 111 | if ($valueItem.Parameters) { $parameter['Parameter'] = $valueItem.Parameters } 112 | if ($inputItem.Force -or $valueItem.Force) { $parameter['Force'] = $true } 113 | 114 | New-JeaCommand @parameter 115 | } 116 | } 117 | #endregion Multiple Command Hashtable 118 | 119 | break 120 | } 121 | #endregion Hashtable 122 | #region JEAnalyzer: Command Info 123 | 'JEAnalyzer.CommandInfo' 124 | { 125 | $inputItem.CommandObject | ConvertTo-JeaCapability 126 | break 127 | } 128 | #endregion JEAnalyzer: Command Info 129 | default 130 | { 131 | Write-PSFMessage -String 'ConvertTo-Capability.CapabilityNotKnown' -StringValues $inputItem -Level Warning 132 | break 133 | } 134 | } 135 | #endregion Decide based on input type 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /JEAnalyzer/functions/construct/Import-JeaScriptFile.ps1: -------------------------------------------------------------------------------- 1 | function Import-JeaScriptFile 2 | { 3 | <# 4 | .SYNOPSIS 5 | Loads scriptfiles as JEA Capability. 6 | 7 | .DESCRIPTION 8 | Loads scriptfiles as JEA Capability. 9 | This will ... 10 | - convert the specified script into a function, 11 | - register that function as a capability and 12 | - add the function as a public function to the module. 13 | 14 | .PARAMETER Path 15 | The path to the file(s). 16 | Folder items will be skipped. 17 | 18 | .PARAMETER FunctionName 19 | The name to apply to the function. 20 | Overrides the default function name finding. 21 | 22 | .PARAMETER Role 23 | The role to add the capability to. 24 | Specifying a role will suppress the object return. 25 | 26 | .PARAMETER Encoding 27 | The encoding in which to read the files. 28 | 29 | .PARAMETER EnableException 30 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 31 | This is less user friendly, but allows catching exceptions in calling scripts. 32 | 33 | .EXAMPLE 34 | PS C:\> Import-JeaScriptFile -Path '.\script.ps1' 35 | 36 | Creates a script capability from the .\script.ps1 file. 37 | The function added will be named 'Invoke-Script' 38 | 39 | .EXAMPLE 40 | PS C:\> Import-JeaScriptFile -Path .\script.ps1 -FunctionName 'Get-ServerHealth' 41 | 42 | Creates a script capability from the .\script.ps1 file. 43 | The function added will be named 'Get-ServerHealth' 44 | 45 | .EXAMPLE 46 | PS C:\> Get-ChildItem C:\JEA\Role1\*.ps1 | Import-JeaScriptFile -Role $role 47 | 48 | Reads all scriptfiles in C:\JEA\Role1, converts them into functions, names them and adds them to the role stored in $role. 49 | #> 50 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '')] 51 | [CmdletBinding()] 52 | param ( 53 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 54 | [Alias('FullName')] 55 | [string[]] 56 | $Path, 57 | 58 | [Parameter(ValueFromPipelineByPropertyName = $true)] 59 | [string] 60 | $FunctionName, 61 | 62 | [JEAnalyzer.Role] 63 | $Role, 64 | 65 | [PSFEncoding] 66 | $Encoding = (Get-PSFConfigValue -FullName PSFramework.Text.Encoding.DefaultRead), 67 | 68 | [switch] 69 | $EnableException 70 | ) 71 | 72 | begin 73 | { 74 | #region Utility Functions 75 | function Test-Function 76 | { 77 | [CmdletBinding()] 78 | param ( 79 | [string] 80 | $Path 81 | ) 82 | $tokens = $null 83 | $errors = $null 84 | $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors) 85 | if ($errors) 86 | { 87 | return [pscustomobject]@{ 88 | IsFunction = $false 89 | ErrorType = 'ParseError' 90 | Errors = $errors 91 | } 92 | } 93 | elseif ($ast.EndBlock.Statements.Count -ne 1) 94 | { 95 | return $false 96 | } 97 | elseif ($ast.EndBlock.Statements[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]) 98 | { 99 | return [pscustomobject]@{ 100 | IsFunction = $true 101 | Name = $Ast.EndBlock.Statements[0].Name 102 | } 103 | } 104 | return $false 105 | } 106 | #endregion Utility Functions 107 | } 108 | process 109 | { 110 | foreach ($file in (Resolve-PSFPath -Path $Path -Provider FileSystem)) 111 | { 112 | Write-PSFMessage -String 'Import-JeaScriptFile.ProcessingInput' -StringValues $file 113 | $fileItem = Get-Item -LiteralPath $Path 114 | if ($fileItem.PSIsContainer) { continue } 115 | 116 | $testResult = Test-Function -Path $file 117 | #region Case: Script File 118 | if (-not $testResult) 119 | { 120 | $functionName = 'Invoke-{0}' -f $host.CurrentCulture.TextInfo.ToTitleCase(($fileItem.BaseName -replace '\[\]\s','_')) 121 | if ($fileItem.BaseName -like '*-*') 122 | { 123 | $verb = $fileItem.BaseName -split '-', 2 124 | if (Get-Verb -verb $verb) { $functionName = $host.CurrentCulture.TextInfo.ToTitleCase(($fileItem.BaseName -replace '\[\]\s', '_')) } 125 | } 126 | if ($Name) { $functionName = $Name } 127 | 128 | $functionString = @' 129 | function {0} 130 | {{ 131 | {1} 132 | }} 133 | '@ -f $functionName, ([System.IO.File]::ReadAllText($file, $Encoding)) 134 | 135 | Invoke-Expression $functionString 136 | $functionInfo = Get-Command -Name $functionName 137 | $capability = New-Object -TypeName 'JEAnalyzer.CapabilityScript' 138 | $capability.Content = $functionInfo 139 | $capability.Name = $functionInfo.Name 140 | if ($Role) { $Role.CommandCapability[$capability.Name] = $capability } 141 | else { $capability } 142 | } 143 | #endregion Case: Script File 144 | 145 | #region Case: Parse Error 146 | elseif ($testResult.ErrorType -eq 'ParseError') 147 | { 148 | Stop-PSFFunction -String 'Import-JeaScriptFile.ParsingError' -StringValues $file -Continue -EnableException $EnableException 149 | } 150 | #endregion Case: Parse Error 151 | 152 | #region Case: Function File 153 | elseif ($testResult.IsFunction) 154 | { 155 | . $file 156 | $functionInfo = Get-Command -Name $testResult.Name 157 | $capability = New-Object -TypeName 'JEAnalyzer.CapabilityScript' 158 | $capability.Content = $functionInfo 159 | $capability.Name = $functionInfo.Name 160 | if ($Role) { $Role.CommandCapability[$capability.Name] = $capability } 161 | else { $capability } 162 | } 163 | #endregion Case: Function File 164 | 165 | #region Case: Unknown State (Should never happen) 166 | else 167 | { 168 | Stop-PSFFunction -String 'Import-JeaScriptFile.UnknownError' -StringValues $file -Continue -EnableException $EnableException 169 | } 170 | #endregion Case: Unknown State (Should never happen) 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /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/functions/construct/New-JeaModule.ps1: -------------------------------------------------------------------------------- 1 | function New-JeaModule 2 | { 3 | <# 4 | .SYNOPSIS 5 | Creates a new JEA module object. 6 | 7 | .DESCRIPTION 8 | Used to create a JEA module object. 9 | This is the container used to add roles and resources that will later be used to generate a full JEA Module. 10 | 11 | Modules are created with an empty default role. Unless adding additional roles, all changes will be applied against the default role. 12 | To create a new role, use the New-JeaRole command. 13 | 14 | Use Export-JeaModule to convert this object into the full module. 15 | 16 | .PARAMETER Name 17 | The name of the JEA Module. 18 | Cannot coexist with other modules of the same name, the latest version will superseed older versions. 19 | 20 | .PARAMETER Identity 21 | Users or groups with permission to connect to an endpoint and receive the default role. 22 | If left empty, only remote management users will be able to connect to this endpoint. 23 | Either use AD Objects (such as the output of Get-ADGroup) or offer netbios-domain-qualified names as string. 24 | 25 | .PARAMETER ServiceAccount 26 | The group Managed Service Account under which the JEA endpoint is being executed. 27 | If this is not specified, the JEA endpoint will run under a virtual local admin account. 28 | 29 | .PARAMETER Description 30 | A description for the module to be created. 31 | 32 | .PARAMETER Author 33 | The author that created the JEA Module. 34 | Controlled using the 'JEAnalyzer.Author' configuration setting. 35 | 36 | .PARAMETER Company 37 | The company the JEA Module was created for. 38 | Controlled using the 'JEAnalyzer.Company' configuration setting. 39 | 40 | .PARAMETER Version 41 | The version of the JEA Module. 42 | A higher version will superseed all older versions of the same name. 43 | 44 | .PARAMETER PreImport 45 | Scripts to execute during JEA module import, before loading functions. 46 | Offer either: 47 | - The path to the file to add 48 | - A hashtable with two keys: Name & Text 49 | 50 | .PARAMETER PostImport 51 | Scripts to execute during JEA module import, after loading functions. 52 | Offer either: 53 | - The path to the file to add 54 | - A hashtable with two keys: Name & Text 55 | 56 | .PARAMETER RequiredModules 57 | Any dependencies the module has. 58 | Note: Specify this in the same manner you would in a module manifest. 59 | Note2: Do not use this for modules you cannot publish in a repository if you want to distribute this JEA module in such. 60 | For example, taking a dependency on the Active Directory module would be disadvised. 61 | In this cases, use the ModulesToImport instead. 62 | 63 | .PARAMETER ModulesToImport 64 | Any modules to also import when importing the JEA module. 65 | For modules that are distributed via package management you should instead use the RequiredModules parameter. 66 | For modules that are not - such as built-in windows modules - this is the place to put them. 67 | In a JEA endpoint, automatic module import is disabled, all modules must either be a dependency or loaded explicitly. 68 | 69 | .EXAMPLE 70 | PS C:\> New-JeaModule -Name 'JEA_ADUser' -Description 'Grants access to the Get-ADUser command' 71 | 72 | Creates a JEA module object with the name JEA_ADUser. 73 | #> 74 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] 75 | [CmdletBinding()] 76 | param ( 77 | [Parameter(Mandatory = $true)] 78 | [string] 79 | $Name, 80 | 81 | [string] 82 | $Identity, 83 | 84 | [PsfValidateScript('JEAnalyzer.Validate.ServiceAccount', ErrorString = 'JEAnalyzer.Validate.ServiceAccount')] 85 | [string] 86 | $ServiceAccount, 87 | 88 | [string] 89 | $Description, 90 | 91 | [string] 92 | $Author = (Get-PSFConfigValue -FullName 'JEAnalyzer.Author'), 93 | 94 | [string] 95 | $Company = (Get-PSFConfigValue -FullName 'JEAnalyzer.Company'), 96 | 97 | [version] 98 | $Version = '1.0.0', 99 | 100 | [JEAnalyzer.ScriptFile[]] 101 | $PreImport, 102 | 103 | [JEAnalyzer.ScriptFile[]] 104 | $PostImport, 105 | 106 | [object] 107 | $RequiredModules, 108 | 109 | [string[]] 110 | $ModulesToImport 111 | ) 112 | 113 | process 114 | { 115 | Write-PSFMessage -String 'New-JeaModule.Creating' -StringValues $Name, $Version 116 | $module = New-Object -TypeName JEAnalyzer.Module -Property @{ 117 | Name = $Name 118 | Description = $Description 119 | Version = $Version 120 | Author = $Author 121 | Company = $Company 122 | } 123 | if ($Identity) { $module.Roles[$Name] = New-JeaRole -Name $Name -Identity $Identity } 124 | if ($ServiceAccount) { $module.ServiceAccount = $ServiceAccount -replace '\$$' } 125 | if ($RequiredModules) { $module.RequiredModules = $RequiredModules } 126 | if ($ModulesToImport) { $module.ModulesToImport = $ModulesToImport } 127 | foreach ($scriptFile in $PreImport) { $module.PreimportScripts[$scriptFile.Name] = $scriptFile } 128 | foreach ($scriptFile in $PostImport) { $module.PostimportScripts[$scriptFile.Name] = $scriptFile } 129 | 130 | $module 131 | } 132 | } -------------------------------------------------------------------------------- /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/functions/deploy/Get-JeaEndpoint.ps1: -------------------------------------------------------------------------------- 1 | function Get-JeaEndpoint { 2 | <# 3 | .SYNOPSIS 4 | Retrieve JEA Endpoints and their capabilities from target computers. 5 | 6 | .DESCRIPTION 7 | Retrieve JEA Endpoints and their capabilities from target computers. 8 | Resolves all roles / identity mappings, all capabilities and the remoting configuration. 9 | Check the "Roles" property for the actual capabilities exposed by the endpoint. 10 | 11 | .PARAMETER Name 12 | Name of the JEA endpoint to filter by. 13 | Defaults to '*' 14 | 15 | .PARAMETER ComputerName 16 | Computer to retrieve JEA endpoints from. 17 | Defaults to: $env:COMPUTERNAME 18 | 19 | .PARAMETER Credential 20 | Credentials to use for the remoting connection. 21 | 22 | .EXAMPLE 23 | PS C:\> Get-JeaEndpoint 24 | 25 | Searches the current computer for JEA endpoints 26 | 27 | .EXAMPLE 28 | PS C:\> Get-JeaEndpoint -ComputerName server1,server2,server3 -Name JEA_ServiceManager 29 | 30 | Retrieves the deployed JEA endpoints named JEA_ServiceManager. 31 | This includes the version they are deployed at if they were originally deployed through JEAnalyzer. 32 | #> 33 | [CmdletBinding()] 34 | param ( 35 | [string] 36 | $Name = '*', 37 | 38 | [Parameter(ValueFromPipeline = $true)] 39 | [PSFComputer[]] 40 | $ComputerName = $env:COMPUTERNAME, 41 | 42 | [PSCredential] 43 | $Credential 44 | ) 45 | begin { 46 | #region Scriptblock 47 | $scriptblock = { 48 | param ( 49 | $Name 50 | ) 51 | 52 | #region Functions 53 | function Convert-JeaSessionConfiguration { 54 | [CmdletBinding()] 55 | param ( 56 | $Configuration 57 | ) 58 | 59 | $fromJEAnalyzer = $false 60 | $version = 'unknown' 61 | $description = $Configuration.Description 62 | $pattern = '^\[{0} \d+\.\d+\.\d+\]' -f ([regex]::Escape($Configuration.Name)) 63 | if ($Configuration.Description -match $pattern) { 64 | $fromJEAnalyzer = $true 65 | $version = ($description -replace '^\[.+? (\d+\.\d+\.\d+)\].{0,}$', '$1') -as [version] 66 | $description = ($description -replace '^.+?\]').Trim() 67 | } 68 | $mode = 'gMSA' 69 | if ($Configuration.RunAsVirtualAccount) { 70 | $mode = 'Virtual Admin' 71 | if ($Configuration.RunAsVirtualAccountGroups) { 72 | $mode = 'Virtual Admin (Constrained)' 73 | } 74 | } 75 | 76 | [PSCustomObject]@{ 77 | PSTypeName = 'JEAnalyzer.Jea.Endpoint' 78 | ComputerName = $env:COMPUTERNAME 79 | Name = $Configuration.Name 80 | Mode = $mode 81 | Author = $Configuration.Author 82 | Description = $description 83 | Version = $version 84 | Permissions = $Configuration.Permission 85 | Roles = $null 86 | FromJEAnalyzer = $fromJEAnalyzer 87 | PSVersion = $Configuration.PSVersion 88 | RunAsUser = $Configuration.RunAsUser 89 | RunAsVirtualAccount = $Configuration.RunAsVirtualAccount 90 | RunAsVirtualAccountGroups = $Configuration.RunAsVirtualAccountGroups 91 | } 92 | } 93 | 94 | function Convert-JeaSessionConfiguration { 95 | [CmdletBinding()] 96 | param ( 97 | $Configuration 98 | ) 99 | 100 | $fromJEAnalyzer = $false 101 | $version = 'unknown' 102 | $description = $Configuration.Description 103 | $pattern = '^\[{0} \d+\.\d+\.\d+\]' -f ([regex]::Escape($Configuration.Name)) 104 | if ($Configuration.Description -match $pattern) { 105 | $fromJEAnalyzer = $true 106 | $version = ($description -replace '^\[.+? (\d+\.\d+\.\d+)\].{0,}$', '$1') -as [version] 107 | $description = ($description -replace '^.+?\]').Trim() 108 | } 109 | $mode = 'gMSA' 110 | if ($Configuration.RunAsVirtualAccount) { 111 | $mode = 'Virtual Admin' 112 | if ($Configuration.RunAsVirtualAccountGroups) { 113 | $mode = 'Virtual Admin (Constrained)' 114 | } 115 | } 116 | 117 | [PSCustomObject]@{ 118 | PSTypeName = 'JEAnalyzer.Jea.Endpoint' 119 | ComputerName = $env:COMPUTERNAME 120 | Name = $Configuration.Name 121 | Mode = $mode 122 | Enabled = $Configuration.Enabled 123 | Author = $Configuration.Author 124 | Description = $description 125 | Version = $version 126 | Permissions = $Configuration.Permission 127 | Roles = $null 128 | FromJEAnalyzer = $fromJEAnalyzer 129 | PSVersion = $Configuration.PSVersion 130 | RunAsUser = $Configuration.RunAsUser 131 | RunAsVirtualAccount = $Configuration.RunAsVirtualAccount 132 | RunAsVirtualAccountGroups = $Configuration.RunAsVirtualAccountGroups 133 | } 134 | } 135 | 136 | function Get-JeaRoleDefinition { 137 | [CmdletBinding()] 138 | param ( 139 | $Configuration, 140 | 141 | [string] 142 | $Identity, 143 | 144 | [Hashtable] 145 | $Definition 146 | ) 147 | 148 | foreach ($capabilityFile in $Definition.RoleCapabilityFiles) { 149 | $fail = $null 150 | $content = $null 151 | try { $content = Import-PowerShellDataFile -Path $capabilityFile -ErrorAction Stop } 152 | catch { 153 | $fail = $_ 154 | } 155 | $name = (Split-Path -Path $capabilityFile -Leaf) -replace '\.psrc$' 156 | [PSCustomObject]@{ 157 | PSTypeName = 'JEAnalyzer.Jea.RoleCapability' 158 | ComputerName = $ENV:COMPUTERNAME 159 | JeaEndpoint = $Configuration.Name 160 | Identity = $Identity 161 | Type = 'ByFile' 162 | Name = $name 163 | Path = $capabilityFile 164 | Error = $fail 165 | ModulesToImport = $content.ModulesToImport 166 | VisibleCmdlets = $($content.VisibleCmdlets) 167 | VisibleAliases = $($content.VisibleAliases) 168 | VisibleFunctions = $($content.VisibleFunctions) 169 | VisibleExternalCommands = $($content.VisibleExternalCommands) 170 | VisibleProviders = $($content.VisibleProviders) 171 | AliasDefinitions = $($content.AliasDefinitions) 172 | FunctionDefinitions = $($content.FunctionDefinitions) 173 | } 174 | } 175 | 176 | foreach ($capability in $Definition.RoleCapabilities) { 177 | $file = $null 178 | $fail = $null 179 | $content = $null 180 | $file = Get-Item -Path "$env:ProgramFiles\WindowsPowerShell\Modules\*\RoleCapability\$capability.psrc" -ErrorAction Ignore | Select-Object -First 1 181 | if (-not $file) { $fail = "Role Capability File not found!" } 182 | else { 183 | try { $content = Import-PowerShellDataFile -Path $file.FullName -ErrorAction Stop } 184 | catch { $fail = $_ } 185 | } 186 | 187 | [PSCustomObject]@{ 188 | PSTypeName = 'JEAnalyzer.Jea.RoleCapability' 189 | ComputerName = $ENV:COMPUTERNAME 190 | JeaEndpoint = $Configuration.Name 191 | Identity = $Identity 192 | Type = 'ByName' 193 | Name = $capability 194 | Path = $file.FullName 195 | Error = $fail 196 | ModulesToImport = $content.ModulesToImport 197 | VisibleCmdlets = $content.VisibleCmdlets 198 | VisibleAliases = $content.VisibleAliases 199 | VisibleFunctions = $content.VisibleFunctions 200 | VisibleExternalCommands = $content.VisibleExternalCommands 201 | VisibleProviders = $content.VisibleProviders 202 | AliasDefinitions = $content.AliasDefinitions 203 | FunctionDefinitions = $content.FunctionDefinitions 204 | } 205 | } 206 | } 207 | #endregion Functions 208 | 209 | $jeaConfigurations = Get-PSSessionConfiguration | Where-Object SessionType -EQ RestrictedRemoteServer 210 | foreach ($jeaConfiguration in $jeaConfigurations) { 211 | if ($jeaConfiguration.Name -notlike $Name) { continue } 212 | $coreData = Convert-JeaSessionConfiguration -Configuration $jeaConfiguration 213 | $coreData.Roles = foreach ($pair in $jeaConfiguration.RoleDefinitions.GetEnumerator()) { 214 | Get-JeaRoleDefinition -Identity $pair.Key -Definition $pair.Value -Configuration $jeaConfiguration 215 | } 216 | $coreData 217 | } 218 | } 219 | #endregion Scriptblock 220 | } 221 | process { 222 | Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $Name | Remove-SerializationLabel | ForEach-Object { 223 | $_.Roles = $_.Roles | Remove-SerializationLabel 224 | $_ 225 | } 226 | } 227 | } -------------------------------------------------------------------------------- /JEAnalyzer/functions/deploy/Install-JeaModule.ps1: -------------------------------------------------------------------------------- 1 | function Install-JeaModule 2 | { 3 | <# 4 | .SYNOPSIS 5 | Installs a JEA module on a target endpoint. 6 | 7 | .DESCRIPTION 8 | Installs a JEA module on a target endpoint. 9 | 10 | .PARAMETER ComputerName 11 | The computers to install the module on 12 | 13 | .PARAMETER Credential 14 | The credentials to use for remoting 15 | 16 | .PARAMETER Module 17 | The module object(s) to export and install 18 | Generate a JEA module object using New-JeaModule 19 | 20 | .PARAMETER Basic 21 | Whether the JEA module should be deployed as a basic/compatibility version. 22 | In that mode, it will not generate a version folder and target role capabilities by name rather than path. 23 | This is compatible with older operating systems but prevents simple deployment via package management. 24 | 25 | .PARAMETER EnableException 26 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 27 | This is less user friendly, but allows catching exceptions in calling scripts. 28 | 29 | .PARAMETER Confirm 30 | If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. 31 | 32 | .PARAMETER WhatIf 33 | If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. 34 | 35 | .EXAMPLE 36 | PS C:\> Install-JeaModule -ComputerName dc1.contoso.com,dc2.contoso.com -Module $Module 37 | 38 | Installs the JEA module in $Module on dc1.contoso.com and dc2.contoso.com 39 | #> 40 | [CmdletBinding(SupportsShouldProcess = $true)] 41 | param ( 42 | [Parameter(Mandatory = $true, Position = 0)] 43 | [PSFComputer[]] 44 | $ComputerName, 45 | 46 | [PSCredential] 47 | $Credential, 48 | 49 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 50 | [JEAnalyzer.Module[]] 51 | $Module, 52 | 53 | [switch] 54 | $Basic, 55 | 56 | [switch] 57 | $EnableException 58 | ) 59 | 60 | begin 61 | { 62 | $workingDirectory = New-Item -Path (Get-PSFPath -Name Temp) -Name "JEA_$(Get-Random)" -ItemType Directory -Force 63 | $credParam = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential 64 | } 65 | process 66 | { 67 | foreach ($moduleObject in $Module) 68 | { 69 | if (-not (Test-PSFShouldProcess -ActionString 'Install-JeaModule.Install' -ActionStringValues $moduleObject.Name -Target ($ComputerName -join ", ") -PSCmdlet $PSCmdlet)) { continue } 70 | 71 | Write-PSFMessage -String 'Install-JeaModule.Exporting.Module' -StringValues $moduleObject.Name 72 | Export-JeaModule -Path $workingDirectory.FullName -Module $moduleObject -Basic:$Basic 73 | $moduleName = "JEA_$($moduleObject.Name)" 74 | 75 | #region Establish Sessions 76 | Write-PSFMessage -String 'Install-JeaModule.Connecting.Sessions' -StringValues ($ComputerName -join ", ") -Target $ComputerName 77 | $sessions = New-PSSession -ComputerName $ComputerName -ErrorAction SilentlyContinue -ErrorVariable failedServers @credParam 78 | if ($failedServers) 79 | { 80 | if ($EnableException) { Stop-PSFFunction -String 'Install-JeaModule.Connections.Failed' -StringValues ($failedServers.TargetObject -join ", ") -Target $failedServers.TargetObject -EnableException $EnableException } 81 | foreach ($failure in $failedServers) { Write-PSFMessage -Level Warning -String 'Install-JeaModule.Connections.Failed' -StringValues $failure.TargetObject -ErrorRecord $failure -Target $failure.TargetObject } 82 | } 83 | if (-not $sessions) 84 | { 85 | Write-PSFMessage -Level Warning -String 'Install-JeaModule.Connections.NoSessions' 86 | return 87 | } 88 | #endregion Establish Sessions 89 | 90 | foreach ($session in $sessions) 91 | { 92 | Write-PSFMessage -String 'Install-JeaModule.Copying.Module' -StringValues $moduleObject.Name, $session.ComputerName -Target $session.ComputerName 93 | Copy-Item -Path "$($workingDirectory.FullName)\$moduleName" -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Force -ToSession $session 94 | } 95 | 96 | Write-PSFMessage -String 'Install-JeaModule.Installing.Module' -StringValues $moduleObject.Name -Target $sessions 97 | Invoke-Command -Session $sessions -ScriptBlock { 98 | Import-Module $using:moduleName 99 | $null = & (Get-Module $using:moduleName) { Register-JeaEndpoint -WarningAction SilentlyContinue } 100 | } -ErrorAction SilentlyContinue 101 | 102 | $sessions | Remove-PSSession -WhatIf:$false -Confirm:$false -ErrorAction Ignore 103 | } 104 | } 105 | end 106 | { 107 | Remove-Item -Path $workingDirectory.FullName -Force -Recurse -ErrorAction SilentlyContinue 108 | } 109 | } -------------------------------------------------------------------------------- /JEAnalyzer/functions/deploy/Uninstall-JeaModule.ps1: -------------------------------------------------------------------------------- 1 | function Uninstall-JeaModule { 2 | <# 3 | .SYNOPSIS 4 | Removes a JEA endpoint from the target computer, optionally including the implementing code. 5 | 6 | .DESCRIPTION 7 | Removes a JEA endpoint from the target computer, optionally including the implementing code. 8 | 9 | .PARAMETER ComputerName 10 | The computer to execute against. 11 | Defaults to: $env:COMPUTERNAME 12 | 13 | .PARAMETER Credential 14 | The credentials to use for the operation 15 | 16 | .PARAMETER Name 17 | The name(s) of the JEA endpoints to remove. 18 | Must be the exact, case insensitive name, wildcards not supported. 19 | 20 | .PARAMETER RemoveCode 21 | Whether to also remove the code implementing the JEA endpoint. 22 | When this is set, the command will check all Role Capability files registered, 23 | and if they are inside of a module under programfiles, the entire module will be removed. 24 | 25 | .PARAMETER Force 26 | Whether to skip the endpoint type check. 27 | By default, only full JEA session configurations are being processed. 28 | 29 | .EXAMPLE 30 | PS C:\> Get-JeaEndpoint | Uninstall-JeaModule -RemoveCode 31 | 32 | Removes all JEA endpoints from the current machine, including all their implementing code. 33 | #> 34 | [CmdletBinding()] 35 | param ( 36 | [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 37 | [PSFComputer[]] 38 | $ComputerName = $env:COMPUTERNAME, 39 | 40 | [PSCredential] 41 | $Credential, 42 | 43 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 44 | [string[]] 45 | $Name, 46 | 47 | [switch] 48 | $RemoveCode, 49 | 50 | [switch] 51 | $Force 52 | ) 53 | begin { 54 | #region Scriptblock 55 | $scriptblock = { 56 | param ( 57 | $Data 58 | ) 59 | 60 | foreach ($name in $Data.Name) { 61 | $configuration = Get-PSSessionConfiguration | Where-Object { 62 | $name -eq $_.Name -and 63 | ( 64 | $Data.Force -or 65 | $_.SessionType -EQ 'RestrictedRemoteServer' 66 | ) 67 | } 68 | if (-not $configuration) { continue } 69 | 70 | Unregister-PSSessionConfiguration -Name $configuration.Name 71 | 72 | if (-not $Data.RemoveCode) { continue } 73 | 74 | $moduleRoot = "$env:ProgramFiles\WindowsPowerShell\Modules" 75 | foreach ($filePath in $configuration.RoleDefinitions.Values.RoleCapabilityFiles) { 76 | if ($filePath -notlike "$moduleRoot\*") { continue } 77 | 78 | $moduleName = ($filePath.SubString($moduleRoot.Length).Trim("\/") -split '\\|/')[0] 79 | $modulePath = Join-Path -Path $moduleRoot -ChildPath $moduleName 80 | 81 | Remove-Item -LiteralPath $modulePath -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue 82 | } 83 | 84 | foreach ($capability in $configuration.RoleDefinitions.Values.RoleCapabilities) { 85 | $file = $null 86 | $filePath = $null 87 | $file = Get-Item -Path "$env:ProgramFiles\WindowsPowerShell\Modules\*\RoleCapability\$capability.psrc" -ErrorAction Ignore | Select-Object -First 1 88 | $filePath = $file.FullName 89 | if ($filePath -notlike "$moduleRoot\*") { continue } 90 | 91 | $moduleName = ($filePath.SubString($moduleRoot.Length).Trim("\/") -split '\\|/')[0] 92 | $modulePath = Join-Path -Path $moduleRoot -ChildPath $moduleName 93 | 94 | Remove-Item -LiteralPath $modulePath -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue 95 | } 96 | } 97 | } 98 | #endregion Scriptblock 99 | } 100 | process { 101 | $data = @{ 102 | Name = $Name 103 | RemoveCode = $RemoveCode 104 | Force = $Force 105 | } 106 | Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $data 107 | } 108 | } -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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/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/functions/write/Export-JeaModule.ps1: -------------------------------------------------------------------------------- 1 | function Export-JeaModule { 2 | <# 3 | .SYNOPSIS 4 | Exports a JEA module object into a PowerShell Module. 5 | 6 | .DESCRIPTION 7 | Exports a JEA module object into a PowerShell Module. 8 | This will create a full PowerShell Module, including: 9 | - Role Definitions for all Roles 10 | - Command: Register-JeaEndpoint_ to register the session configuration. 11 | - Any additional commands and scripts required/contained by the Roles 12 | Create a JEA Module object using New-JeaModule 13 | Create roles by using New-JeaRole. 14 | 15 | .PARAMETER Path 16 | The folder where to place the module. 17 | 18 | .PARAMETER Module 19 | The module object to export. 20 | 21 | .PARAMETER Basic 22 | Whether the JEA module should be deployed as a basic/compatibility version. 23 | In that mode, it will not generate a version folder and target role capabilities by name rather than path. 24 | This is compatible with older operating systems but prevents simple deployment via package management. 25 | 26 | .PARAMETER NoConnectionCode 27 | By default, when exporting the module to file, it also includes a file describing how to connect to the JEA endpoint. 28 | This switch disables that. 29 | 30 | .PARAMETER AsBootstrap 31 | Rather than exporting the JEA module as a module folder structure, it is exported as a single scriptfile that will set itself up on execution. 32 | This is intended to ease JEA endpoint deployment via Intune or other orchestration platforms. 33 | 34 | .PARAMETER IncludeDependency 35 | This parameter is only used when combined with -AsBootstrap 36 | All required modules will also be embedded into the bootstrap script. 37 | Note: These modules are retrieved from the default repository! 38 | Update the "JEAnalyzer.DefaultRepository" setting to select an internal repository. 39 | 40 | .EXAMPLE 41 | PS C:\> $module | Export-JeaModule -Path 'C:\temp' 42 | 43 | Exports the JEA Module stored in $module to the designated path. 44 | 45 | .EXAMPLE 46 | PS C:\> $module | Export-JeaModule -Path 'C:\temp' -AsBootstrap 47 | 48 | Exports the JEA Module stored in $module to the designated path as a selfcontained installation-script. 49 | #> 50 | [CmdletBinding()] 51 | param ( 52 | [Parameter(Mandatory = $true, Position = 0)] 53 | [PsfValidateScript('JEAnalyzer.ValidatePath.Directory', ErrorString = 'Validate.FileSystem.Directory.Fail')] 54 | [string] 55 | $Path, 56 | 57 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 58 | [JEAnalyzer.Module[]] 59 | $Module, 60 | 61 | [switch] 62 | $Basic, 63 | 64 | [switch] 65 | $NoConnectionCode, 66 | 67 | [switch] 68 | $AsBootstrap, 69 | 70 | [switch] 71 | $IncludeDependency 72 | ) 73 | 74 | begin { 75 | #region Utility Functions 76 | function Write-Function { 77 | <# 78 | .SYNOPSIS 79 | Creates a function file with UTF8Bom encoding. 80 | 81 | .DESCRIPTION 82 | Creates a function file with UTF8Bom encoding. 83 | 84 | .PARAMETER Function 85 | The function object to write. 86 | 87 | .PARAMETER Path 88 | The path to writer it to 89 | 90 | .EXAMPLE 91 | PS C:\> Write-Function -Function (Get-Command mkdir) -Path C:\temp\mkdir.ps1 92 | 93 | Writes the function definition for mkdir (including function statement) to the specified path. 94 | #> 95 | [CmdletBinding()] 96 | param ( 97 | [System.Management.Automation.FunctionInfo] 98 | $Function, 99 | 100 | [string] 101 | $Path 102 | ) 103 | 104 | $functionString = @' 105 | function {0} 106 | {{ 107 | {1} 108 | }} 109 | '@ -f $Function.Name, $Function.Definition.Trim("`n`r") 110 | $encoding = New-Object System.Text.UTF8Encoding($true) 111 | Write-PSFMessage -String 'Export-JeaModule.File.Create' -StringValues $Path -FunctionName Export-JeaModule 112 | [System.IO.File]::WriteAllText($Path, $functionString, $encoding) 113 | } 114 | 115 | function Write-File { 116 | [CmdletBinding()] 117 | param ( 118 | [string[]] 119 | $Text, 120 | 121 | [string] 122 | $Path 123 | ) 124 | $encoding = New-Object System.Text.UTF8Encoding($true) 125 | Write-PSFMessage -String 'Export-JeaModule.File.Create' -StringValues $Path -FunctionName Export-JeaModule 126 | [System.IO.File]::WriteAllText($Path, ($Text -join "`n"), $encoding) 127 | } 128 | #endregion Utility Functions 129 | 130 | # Will succeed, as the validation scriptblock checks this first 131 | $resolvedPath = Resolve-PSFPath -Path $Path -Provider FileSystem -SingleItem 132 | } 133 | process { 134 | foreach ($moduleObject in $Module) { 135 | $moduleName = $moduleObject.Name -replace '\s', '_' 136 | if ($moduleName -notlike "JEA_*") { $moduleName = "JEA_{0}" -f $moduleName } 137 | 138 | #region Bootstrap 139 | if ($AsBootstrap) { 140 | $tempRoot = New-PSFTempDirectory -Name bootstrap -ModuleName JEAnalyzer 141 | $modulesPath = New-Item -Path $tempRoot -Name Modules -ItemType Directory -Force 142 | Export-JeaModule -Module $moduleObject -Path $modulesPath.FullName -Basic:$Basic -NoConnectionCode 143 | if ($IncludeDependency) { 144 | $saveDefaults = @{ 145 | Repository = Get-PSFConfigValue -FullName 'JEAnalyzer.DefaultRepository' 146 | Path = $modulesPath.FullName 147 | } 148 | foreach ($requiredModule in $moduleObject.RequiredModules) { 149 | $call = @{ Name = $requiredModule } 150 | if ($requiredModule.ModuleName) { $call.Name = $requiredModule.ModuleName } 151 | if ($requiredModule.RequiredVersion) { $call.RequiredVersion = $requiredModule.RequiredVersion } 152 | if ($requiredModule.ModuleVersion) {$call.MinimumVersion = $requiredModule.ModuleVersion } 153 | Save-Module @saveDefaults @call 154 | } 155 | } 156 | $runCode = [System.IO.File]::ReadAllText("$script:ModuleRoot\internal\resources\run.ps1") 157 | $runCode = $runCode -replace '%name%', $moduleName 158 | $runPath = Join-Path -Path $tempRoot -ChildPath run.ps1 159 | $encoding = [System.Text.UTF8Encoding]::new($true) 160 | [System.IO.File]::WriteAllText($runPath, $runCode, $encoding) 161 | New-BootstrapScript -Path $tempRoot -OutPath (Join-Path -Path $Path -ChildPath "$moduleName.ps1") 162 | Remove-PSFTempItem -Name bootstrap -ModuleName JEAnalyzer 163 | 164 | continue 165 | } 166 | #endregion Bootstrap 167 | 168 | #region Create Module folder 169 | if (Test-Path -Path (Join-Path $resolvedPath $moduleName)) { 170 | $moduleBase = Get-Item -Path (Join-Path $resolvedPath $moduleName) 171 | Write-PSFMessage -String 'Export-JeaModule.Folder.ModuleBaseExists' -StringValues $moduleBase.FullName 172 | } 173 | else { 174 | $moduleBase = New-Item -Path $resolvedPath -Name $moduleName -ItemType Directory -Force 175 | Write-PSFMessage -String 'Export-JeaModule.Folder.ModuleBaseNew' -StringValues $moduleBase.FullName 176 | } 177 | if ($Basic) { 178 | $rootFolder = $moduleBase 179 | } 180 | else { 181 | Write-PSFMessage -String 'Export-JeaModule.Folder.VersionRoot' -StringValues $moduleBase.FullName, $moduleObject.Version 182 | $rootFolder = New-Item -Path $moduleBase.FullName -Name $moduleObject.Version -ItemType Directory -Force 183 | } 184 | 185 | # Other folders for the scaffold 186 | $folders = @( 187 | 'functions' 188 | 'internal\functions' 189 | 'internal\scriptsPre' 190 | 'internal\scriptsPost' 191 | 'internal\scriptsRole' 192 | ) 193 | foreach ($folder in $folders) { 194 | Write-PSFMessage -String 'Export-JeaModule.Folder.Content' -StringValues $folder 195 | $folderItem = New-Item -Path (Join-Path -Path $rootFolder.FullName -ChildPath $folder) -ItemType Directory -Force 196 | '# ' | Set-Content -Path "$($folderItem.FullName)\readme.md" 197 | } 198 | #endregion Create Module folder 199 | 200 | #region Create Role Capabilities 201 | Write-PSFMessage -String 'Export-JeaModule.Folder.RoleCapailities' -StringValues $rootFolder.FullName 202 | $roleCapabilityFolder = New-Item -Path $rootFolder.FullName -Name 'RoleCapabilities' -Force -ItemType Directory 203 | foreach ($role in $moduleObject.Roles.Values) { 204 | $RoleCapParams = @{ 205 | Path = ('{0}\{1}.psrc' -f $roleCapabilityFolder.FullName, $role.Name) 206 | Author = $moduleObject.Author 207 | CompanyName = $moduleObject.Company 208 | VisibleCmdlets = $role.VisibleCmdlets() 209 | VisibleFunctions = $role.VisibleFunctions($moduleName) 210 | ModulesToImport = $moduleName 211 | } 212 | Write-PSFMessage -String 'Export-JeaModule.Role.NewRole' -StringValues $role.Name, $role.CommandCapability.Count 213 | New-PSRoleCapabilityFile @RoleCapParams 214 | #region Logging Visible Commands 215 | foreach ($cmdlet in $role.VisibleCmdlets()) { 216 | $commandName = $cmdlet.Name 217 | $parameters = @() 218 | foreach ($parameter in $cmdlet.Parameters) { 219 | $string = $parameter.Name 220 | if ($parameter.ValidateSet) { $string += (' | {0}' -f ($parameter.ValidateSet -join ",")) } 221 | if ($parameter.ValidatePattern) { $string += (' | {0}' -f $parameter.ValidatePattern) } 222 | $parameters += '({0})' -f $string 223 | } 224 | $parameterText = ' : {0}' -f ($parameters -join ",") 225 | if (-not $parameters) { $parameterText = '' } 226 | Write-PSFMessage -String 'Export-JeaModule.Role.VisibleCmdlet' -StringValues $role.Name, $commandName, $parameterText 227 | } 228 | foreach ($cmdlet in $role.VisibleFunctions($moduleName)) { 229 | $commandName = $cmdlet.Name 230 | $parameters = @() 231 | foreach ($parameter in $cmdlet.Parameters) { 232 | $string = $parameter.Name 233 | if ($parameter.ValidateSet) { $string += (' | {0}' -f ($parameter.ValidateSet -join ",")) } 234 | if ($parameter.ValidatePattern) { $string += (' | {0}' -f $parameter.ValidatePattern) } 235 | $parameters += '({0})' -f $string 236 | } 237 | $parameterText = ' : {0}' -f ($parameters -join ",") 238 | if (-not $parameters) { $parameterText = '' } 239 | Write-PSFMessage -String 'Export-JeaModule.Role.VisibleFunction' -StringValues $role.Name, $commandName, $parameterText 240 | } 241 | #endregion Logging Visible Commands 242 | 243 | # Transfer all function definitions stored in the role. 244 | $role.CopyFunctionDefinitions($moduleObject) 245 | } 246 | #endregion Create Role Capabilities 247 | 248 | #region Create Private Functions 249 | $privateFunctionPath = Join-Path -Path $rootFolder.FullName -ChildPath 'internal\functions' 250 | foreach ($privateFunction in $moduleObject.PrivateFunctions.Values) { 251 | $outputPath = Join-Path -Path $privateFunctionPath -ChildPath "$($privateFunction.Name).ps1" 252 | Write-Function -Function $privateFunction -Path $outputPath 253 | } 254 | #endregion Create Private Functions 255 | 256 | #region Create Public Functions 257 | $publicFunctionPath = Join-Path -Path $rootFolder.FullName -ChildPath 'functions' 258 | foreach ($publicFunction in $moduleObject.PublicFunctions.Values) { 259 | $outputPath = Join-Path -Path $publicFunctionPath -ChildPath "$($publicFunction.Name).ps1" 260 | Write-Function -Function $publicFunction -Path $outputPath 261 | } 262 | #endregion Create Public Functions 263 | 264 | #region Create Scriptblocks 265 | foreach ($scriptFile in $moduleObject.PreimportScripts.Values) { 266 | Write-File -Text $scriptFile.Text -Path "$($rootFolder.FullName)\internal\scriptsPre\$($scriptFile.Name).ps1" 267 | } 268 | foreach ($scriptFile in $moduleObject.PostimportScripts.Values) { 269 | Write-File -Text $scriptFile.Text -Path "$($rootFolder.FullName)\internal\scriptsPost\$($scriptFile.Name).ps1" 270 | } 271 | if ($moduleObject.ModulesToImport) { 272 | $lines = foreach ($moduleToImport in $moduleObject.ModulesToImport) { 273 | "Import-Module '{0}' -Scope Global -Force" -f $moduleToImport 274 | } 275 | Write-File -Text $lines -Path "$($rootFolder.FullName)\internal\scriptsPre\__ImportModules.ps1" 276 | } 277 | #endregion Create Scriptblocks 278 | 279 | #region Create Common Resources 280 | # Register-JeaEndpoint 281 | $encoding = New-Object System.Text.UTF8Encoding($true) 282 | $functionText = [System.IO.File]::ReadAllText("$script:ModuleRoot\internal\resources\Register-JeaEndpointPublic.ps1", $encoding) 283 | $functionText = $functionText -replace 'Register-JeaEndpointPublic', "Register-JeaEndpoint_$($moduleName)" 284 | Write-File -Text $functionText -Path "$($rootFolder.FullName)\functions\Register-JeaEndpoint_$($moduleName).ps1" 285 | $functionText2 = [System.IO.File]::ReadAllText("$script:ModuleRoot\internal\resources\Register-JeaEndpoint.ps1", $encoding) 286 | Write-File -Text $functionText2 -Path "$($rootFolder.FullName)\internal\functions\Register-JeaEndpoint.ps1" 287 | 288 | # PSM1 289 | Copy-Item -Path "$script:ModuleRoot\internal\resources\jeamodule.psm1" -Destination "$($rootFolder.FullName)\$($moduleName).psm1" 290 | 291 | # PSSession Configuration 292 | $grouped = $moduleObject.Roles.Values | ForEach-Object { 293 | foreach ($identity in $_.Identity) { 294 | [pscustomobject]@{ 295 | Identity = $identity 296 | Role = $_ 297 | } 298 | } 299 | } | Group-Object Identity 300 | $roleDefinitions = @{ } 301 | foreach ($groupItem in $grouped) { 302 | if ($Basic) { 303 | $roleDefinitions[$groupItem.Name] = @{ 304 | RoleCapabilities = $groupItem.Group.Role.Name 305 | } 306 | } 307 | else { 308 | $roleDefinitions[$groupItem.Name] = @{ 309 | RoleCapabilityFiles = ($groupItem.Group.Role.Name | ForEach-Object { "C:\Program Files\WindowsPowerShell\Modules\{0}\{1}\RoleCapabilities\{2}.psrc" -f $moduleName, $Module.Version, $_ }) 310 | } 311 | } 312 | } 313 | $paramNewPSSessionConfigurationFile = @{ 314 | SessionType = 'RestrictedRemoteServer' 315 | Path = "$($rootFolder.FullName)\sessionconfiguration.pssc" 316 | RunAsVirtualAccount = $true 317 | RoleDefinitions = $roleDefinitions 318 | Author = $moduleObject.Author 319 | Description = "[{0} {1}] {2}" -f $moduleName, $moduleObject.Version, $moduleObject.Description 320 | CompanyName = $moduleObject.Company 321 | } 322 | if ($moduleObject.ServiceAccount) { 323 | $paramNewPSSessionConfigurationFile.Remove('RunAsVirtualAccount') 324 | $paramNewPSSessionConfigurationFile.GroupManagedServiceAccount = $moduleObject.ServiceAccount 325 | } 326 | Write-PSFMessage -String 'Export-JeaModule.File.Create' -StringValues "$($rootFolder.FullName)\sessionconfiguration.pssc" 327 | New-PSSessionConfigurationFile @paramNewPSSessionConfigurationFile 328 | 329 | # Create Manifest 330 | $paramNewModuleManifest = @{ 331 | FunctionsToExport = (Get-ChildItem -Path "$($rootFolder.FullName)\functions" -Filter '*.ps1').BaseName 332 | CmdletsToExport = @() 333 | AliasesToExport = @() 334 | VariablesToExport = @() 335 | Path = "$($rootFolder.FullName)\$($moduleName).psd1" 336 | Author = $moduleObject.Author 337 | Description = $moduleObject.Description 338 | CompanyName = $moduleObject.Company 339 | RootModule = "$($moduleName).psm1" 340 | ModuleVersion = $moduleObject.Version 341 | Tags = 'JEA', 'JEAnalyzer', 'JEA_Module' 342 | } 343 | if ($moduleObject.RequiredModules) { $paramNewModuleManifest.RequiredModules = $moduleObject.RequiredModules } 344 | Write-PSFMessage -String 'Export-JeaModule.File.Create' -StringValues "$($rootFolder.FullName)\$($moduleName).psd1" 345 | New-ModuleManifest @paramNewModuleManifest 346 | #endregion Create Common Resources 347 | 348 | #region Generate Connection Script 349 | if ($NoConnectionCode) { continue } 350 | $connectionSegments = @() 351 | foreach ($role in $moduleObject.Roles.Values) { 352 | $connectionSegments += @' 353 | # Connect to JEA Endpoint for Role {0} 354 | $session = New-PSSession -ComputerName '' -ConfigurationName '{1}' 355 | Import-PSSession -AllowClobber -Session $session -DisableNameChecking -CommandName '{2}' 356 | Invoke-Command -Session $session -Scriptblock {{ {3} }} 357 | '@ -f $role.Name, $moduleName, ($role.CommandCapability.Keys -join "', '"), ($role.CommandCapability.Keys | Select-Object -First 1) 358 | } 359 | 360 | $finalConnectionText = @' 361 | <# 362 | These are the connection scriptblocks for the {0} JEA Module. 363 | For each role there is an entry with all that is needed to connect and consume it. 364 | Just Copy&Paste the section you need, add it to the top of your script and insert the computername. 365 | You will always need to create the session, but whether to Import it or use Invoke-Command is up to you. 366 | Either option will work, importing it is usually more convenient but will overwrite local copies. 367 | Invoke-Command is the better option if you want to connect to multiple such sessions or still need access to the local copies. 368 | 369 | Note: If a user has access to multiple roles, you still only need one session, but: 370 | - On Invoke-Command you have immediately access to ALL commands allowed in any role the user is in. 371 | - On Import-PSSession, you need to explicitly state all the commands you want. 372 | #> 373 | 374 | {1} 375 | '@ -f $moduleName, ($connectionSegments -join "`n`n`n") 376 | Write-File -Text $finalConnectionText -Path (Join-Path -Path $resolvedPath -ChildPath "connect_$($moduleName).ps1") 377 | #endregion Generate Connection Script 378 | } 379 | } 380 | } -------------------------------------------------------------------------------- /JEAnalyzer/functions/write/Export-JeaRoleCapFile.ps1: -------------------------------------------------------------------------------- 1 | function Export-JeaRoleCapFile 2 | { 3 | <# 4 | .SYNOPSIS 5 | Converts a list of commands into a JEA Role Capability File. 6 | 7 | .DESCRIPTION 8 | Converts a list of commands into a JEA Role Capability File. 9 | 10 | Accepts a list of input types, both from the output of other commands in the module and other calls legitimately pointing at a command. 11 | Then builds a Role Capability File that can be used to create a JEA Endpoint permitting use of the listed commands. 12 | 13 | .PARAMETER Path 14 | The path where to create the output file. 15 | If only a folder is specified, a 'configuration.psrc' will be created in that folder. 16 | If a filename is specified, it will use the name, adding the '.psrc' extension if necessary. 17 | The parent folder must exist, the file needs not exist (and will be overwritten if it does). 18 | 19 | .PARAMETER InputObject 20 | The commands to add to the list of allowed commands. 21 | 22 | .PARAMETER Author 23 | The author that created the RCF. 24 | Controlled using the 'JEAnalyzer.Author' configuration setting. 25 | 26 | .PARAMETER Company 27 | The company the RCF was created for. 28 | Controlled using the 'JEAnalyzer.Company' configuration setting. 29 | 30 | .PARAMETER EnableException 31 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 32 | This is less user friendly, but allows catching exceptions in calling scripts. 33 | 34 | .EXAMPLE 35 | PS C:\> Get-Content commands.txt | Export-JeaRoleCapFile -Path '.\mytask.psrc' 36 | 37 | Creates a Jea Role Capability File permitting the use of all commands allowed in commands.txt. 38 | #> 39 | [CmdletBinding()] 40 | param ( 41 | [Parameter(Mandatory = $true)] 42 | [string] 43 | $Path, 44 | 45 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 46 | [Alias('CommandName','Name','Command')] 47 | [object[]] 48 | $InputObject, 49 | 50 | [string] 51 | $Author = (Get-PSFConfigValue -FullName 'JEAnalyzer.Author'), 52 | 53 | [string] 54 | $Company = (Get-PSFConfigValue -FullName 'JEAnalyzer.Company'), 55 | 56 | [switch] 57 | $EnableException 58 | ) 59 | 60 | begin 61 | { 62 | #region Resolves the path 63 | try { $resolvedPath = Resolve-PSFPath -Path $Path -NewChild -Provider FileSystem -SingleItem -ErrorAction Stop } 64 | catch 65 | { 66 | Stop-PSFFunction -Message 'Failed to resolve output path' -ErrorRecord $_ -EnableException $EnableException 67 | return 68 | } 69 | if (Test-Path $resolvedPath) 70 | { 71 | $item = Get-Item -Path $resolvedPath 72 | if ($item.PSIsContainer) { $resolvedPath = Join-Path -Path $resolvedPath -ChildPath 'configuration.psrc' } 73 | } 74 | if ($resolvedPath -notlike '*.psrc') { $resolvedPath += '.psrc' } 75 | #endregion Resolves the path 76 | 77 | $commands = @() 78 | } 79 | process 80 | { 81 | if (Test-PSFFunctionInterrupt) { return } 82 | 83 | #region Add commands to list as they are received 84 | foreach ($item in $InputObject) 85 | { 86 | # Plain Names 87 | if ($item -is [string]) 88 | { 89 | Write-PSFMessage -Level Verbose -Message "Adding command: $item" -Target $item 90 | $commands += $item 91 | continue 92 | } 93 | # Cmdlet objects 94 | if ($item -is [System.Management.Automation.CmdletInfo]) 95 | { 96 | Write-PSFMessage -Level Verbose -Message "Adding command: $item" -Target $item 97 | $commands += $item.Name 98 | continue 99 | } 100 | # Function objects 101 | if ($item -is [System.Management.Automation.FunctionInfo]) 102 | { 103 | Write-PSFMessage -Level Verbose -Message "Adding command: $item" -Target $item 104 | $commands += $item.Name 105 | continue 106 | } 107 | # Alias objects 108 | if ($item -is [System.Management.Automation.AliasInfo]) 109 | { 110 | Write-PSFMessage -Level Verbose -Message "Adding command: $($item.ResolvedCommand.Name)" -Target $item 111 | $commands += $item.ResolvedCommand.Name 112 | continue 113 | } 114 | # Analyzer Objects 115 | if ($item.CommandName -is [string]) 116 | { 117 | Write-PSFMessage -Level Verbose -Message "Adding command: $($item.CommandName)" -Target $item 118 | $commands += $item.CommandName 119 | continue 120 | } 121 | Stop-PSFFunction -Message "Failed to interpret as command: $item" -Target $item -Continue -EnableException $EnableException 122 | } 123 | #endregion Add commands to list as they are received 124 | } 125 | end 126 | { 127 | if (Test-PSFFunctionInterrupt) { return } 128 | 129 | #region Realize RCF 130 | if ($commands) 131 | { 132 | Write-PSFMessage -Level Verbose -Message "Creating Jea Role Capability File with $($commands.Count) commands permitted." 133 | $RoleCapParams = @{ 134 | Path = $resolvedPath 135 | Author = $Author 136 | CompanyName = $Company 137 | VisibleCmdlets = ([string[]]($commands | Select-Object -Unique)) 138 | } 139 | New-PSRoleCapabilityFile @RoleCapParams 140 | } 141 | else 142 | { 143 | Write-PSFMessage -Level Warning -Message 'No commands specified!' 144 | } 145 | #endregion Realize RCF 146 | } 147 | } -------------------------------------------------------------------------------- /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' -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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/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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /JEAnalyzer/internal/scriptblock/validateServiceAccount.ps1: -------------------------------------------------------------------------------- 1 | Set-PSFScriptblock -Name 'JEAnalyzer.Validate.ServiceAccount' -Scriptblock { 2 | $_ -match '^[^\\]+\\[^\\]+$' 3 | } -Global -------------------------------------------------------------------------------- /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 | "@ -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /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/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/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/tepp/assignment.ps1: -------------------------------------------------------------------------------- 1 | Register-PSFTeppArgumentCompleter -Command Import-JeaScriptFile -Parameter Encoding -Name psframework-encoding -------------------------------------------------------------------------------- /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/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/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/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/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 | } -------------------------------------------------------------------------------- /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/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/tests/general/Help.Tests.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | The original test this is based upon was written by June Blender. 4 | After several rounds of modifications it stands now as it is, but the honor remains hers. 5 | 6 | Thank you June, for all you have done! 7 | 8 | .DESCRIPTION 9 | This test evaluates the help for all commands in a module. 10 | 11 | .PARAMETER SkipTest 12 | Disables this test. 13 | 14 | .PARAMETER CommandPath 15 | List of paths under which the script files are stored. 16 | This test assumes that all functions have their own file that is named after themselves. 17 | These paths are used to search for commands that should exist and be tested. 18 | Will search recursively and accepts wildcards, make sure only functions are found 19 | 20 | .PARAMETER ModuleName 21 | Name of the module to be tested. 22 | The module must already be imported 23 | 24 | .PARAMETER ExceptionsFile 25 | File in which exceptions and adjustments are configured. 26 | In it there should be two arrays and a hashtable defined: 27 | $global:FunctionHelpTestExceptions 28 | $global:HelpTestEnumeratedArrays 29 | $global:HelpTestSkipParameterType 30 | These can be used to tweak the tests slightly in cases of need. 31 | See the example file for explanations on each of these usage and effect. 32 | #> 33 | [CmdletBinding()] 34 | Param ( 35 | [switch] 36 | $SkipTest, 37 | 38 | [string[]] 39 | $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions"), 40 | 41 | [string] 42 | $ModuleName = "JEAnalyzer", 43 | 44 | [string] 45 | $ExceptionsFile = "$global:testroot\general\Help.Exceptions.ps1" 46 | ) 47 | if ($SkipTest) { return } 48 | . $ExceptionsFile 49 | 50 | $includedNames = (Get-ChildItem $CommandPath -Recurse -File | Where-Object Name -like "*.ps1").BaseName 51 | $commandTypes = @('Cmdlet', 'Function') 52 | if ($PSVersionTable.PSEdition -eq 'Desktop' ) { $commandTypes += 'Workflow' } 53 | $commands = Get-Command -Module (Get-Module $ModuleName) -CommandType $commandTypes | Where-Object Name -In $includedNames 54 | 55 | ## When testing help, remember that help is cached at the beginning of each session. 56 | ## To test, restart session. 57 | 58 | 59 | foreach ($command in $commands) { 60 | $commandName = $command.Name 61 | 62 | # Skip all functions that are on the exclusions list 63 | if ($global:FunctionHelpTestExceptions -contains $commandName) { continue } 64 | 65 | # The module-qualified command fails on Microsoft.PowerShell.Archive cmdlets 66 | $Help = Get-Help $commandName -ErrorAction SilentlyContinue 67 | 68 | Describe "Test help for $commandName" { 69 | 70 | # If help is not found, synopsis in auto-generated help is the syntax diagram 71 | It "should not be auto-generated" -TestCases @{ Help = $Help } { 72 | $Help.Synopsis | Should -Not -BeLike '*`[``]*' 73 | } 74 | 75 | # Should be a description for every function 76 | It "gets description for $commandName" -TestCases @{ Help = $Help } { 77 | $Help.Description | Should -Not -BeNullOrEmpty 78 | } 79 | 80 | # Should be at least one example 81 | It "gets example code from $commandName" -TestCases @{ Help = $Help } { 82 | ($Help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty 83 | } 84 | 85 | # Should be at least one example description 86 | It "gets example help from $commandName" -TestCases @{ Help = $Help } { 87 | ($Help.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty 88 | } 89 | 90 | Context "Test parameter help for $commandName" { 91 | 92 | $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable' 93 | 94 | $parameters = $command.ParameterSets.Parameters | Sort-Object -Property Name -Unique | Where-Object Name -notin $common 95 | $parameterNames = $parameters.Name 96 | $HelpParameterNames = $Help.Parameters.Parameter.Name | Sort-Object -Unique 97 | foreach ($parameter in $parameters) { 98 | $parameterName = $parameter.Name 99 | $parameterHelp = $Help.parameters.parameter | Where-Object Name -EQ $parameterName 100 | 101 | # Should be a description for every parameter 102 | It "gets help for parameter: $parameterName : in $commandName" -TestCases @{ parameterHelp = $parameterHelp } { 103 | $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty 104 | } 105 | 106 | $codeMandatory = $parameter.IsMandatory.toString() 107 | It "help for $parameterName parameter in $commandName has correct Mandatory value" -TestCases @{ parameterHelp = $parameterHelp; codeMandatory = $codeMandatory } { 108 | $parameterHelp.Required | Should -Be $codeMandatory 109 | } 110 | 111 | if ($HelpTestSkipParameterType[$commandName] -contains $parameterName) { continue } 112 | 113 | $codeType = $parameter.ParameterType.Name 114 | 115 | if ($parameter.ParameterType.IsEnum) { 116 | # Enumerations often have issues with the typename not being reliably available 117 | $names = $parameter.ParameterType::GetNames($parameter.ParameterType) 118 | # Parameter type in Help should match code 119 | It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { 120 | $parameterHelp.parameterValueGroup.parameterValue | Should -be $names 121 | } 122 | } 123 | elseif ($parameter.ParameterType.FullName -in $HelpTestEnumeratedArrays) { 124 | # Enumerations often have issues with the typename not being reliably available 125 | $names = [Enum]::GetNames($parameter.ParameterType.DeclaredMembers[0].ReturnType) 126 | It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { 127 | $parameterHelp.parameterValueGroup.parameterValue | Should -be $names 128 | } 129 | } 130 | else { 131 | # To avoid calling Trim method on a null object. 132 | $helpType = if ($parameterHelp.parameterValue) { $parameterHelp.parameterValue.Trim() } 133 | # Parameter type in Help should match code 134 | It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ helpType = $helpType; codeType = $codeType } { 135 | $helpType | Should -be $codeType 136 | } 137 | } 138 | } 139 | foreach ($helpParm in $HelpParameterNames) { 140 | # Shouldn't find extra parameters in help. 141 | It "finds help parameter in code: $helpParm" -TestCases @{ helpParm = $helpParm; parameterNames = $parameterNames } { 142 | $helpParm -in $parameterNames | Should -Be $true 143 | } 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /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/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/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/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/tests/pester.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | $TestGeneral = $true, 3 | 4 | $TestFunctions = $true, 5 | 6 | [ValidateSet('None', 'Normal', 'Detailed', 'Diagnostic')] 7 | [Alias('Show')] 8 | $Output = "None", 9 | 10 | $Include = "*", 11 | 12 | $Exclude = "" 13 | ) 14 | 15 | Write-PSFMessage -Level Important -Message "Starting Tests" 16 | 17 | Write-PSFMessage -Level Important -Message "Importing Module" 18 | 19 | $global:testroot = $PSScriptRoot 20 | $global:__pester_data = @{ } 21 | 22 | Remove-Module JEAnalyzer -ErrorAction Ignore 23 | Import-Module "$PSScriptRoot\..\JEAnalyzer.psd1" 24 | Import-Module "$PSScriptRoot\..\JEAnalyzer.psm1" -Force 25 | 26 | # Need to import explicitly so we can use the configuration class 27 | Import-Module Pester 28 | 29 | Write-PSFMessage -Level Important -Message "Creating test result folder" 30 | $null = New-Item -Path "$PSScriptRoot\..\.." -Name TestResults -ItemType Directory -Force 31 | 32 | $totalFailed = 0 33 | $totalRun = 0 34 | 35 | $testresults = @() 36 | $config = [PesterConfiguration]::Default 37 | $config.TestResult.Enabled = $true 38 | 39 | #region Run General Tests 40 | if ($TestGeneral) 41 | { 42 | Write-PSFMessage -Level Important -Message "Modules imported, proceeding with general tests" 43 | foreach ($file in (Get-ChildItem "$PSScriptRoot\general" | Where-Object Name -like "*.Tests.ps1")) 44 | { 45 | if ($file.Name -notlike $Include) { continue } 46 | if ($file.Name -like $Exclude) { continue } 47 | 48 | Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" 49 | $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" 50 | $config.Run.Path = $file.FullName 51 | $config.Run.PassThru = $true 52 | $config.Output.Verbosity = $Output 53 | $results = Invoke-Pester -Configuration $config 54 | foreach ($result in $results) 55 | { 56 | $totalRun += $result.TotalCount 57 | $totalFailed += $result.FailedCount 58 | $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { 59 | $testresults += [pscustomobject]@{ 60 | Block = $_.Block 61 | Name = "It $($_.Name)" 62 | Result = $_.Result 63 | Message = $_.ErrorRecord.DisplayErrorMessage 64 | } 65 | } 66 | } 67 | } 68 | } 69 | #endregion Run General Tests 70 | 71 | $global:__pester_data.ScriptAnalyzer | Out-Host 72 | 73 | #region Test Commands 74 | if ($TestFunctions) 75 | { 76 | Write-PSFMessage -Level Important -Message "Proceeding with individual tests" 77 | foreach ($file in (Get-ChildItem "$PSScriptRoot\functions" -Recurse -File | Where-Object Name -like "*Tests.ps1")) 78 | { 79 | if ($file.Name -notlike $Include) { continue } 80 | if ($file.Name -like $Exclude) { continue } 81 | 82 | Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" 83 | $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" 84 | $config.Run.Path = $file.FullName 85 | $config.Run.PassThru = $true 86 | $config.Output.Verbosity = $Output 87 | $results = Invoke-Pester -Configuration $config 88 | foreach ($result in $results) 89 | { 90 | $totalRun += $result.TotalCount 91 | $totalFailed += $result.FailedCount 92 | $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { 93 | $testresults += [pscustomobject]@{ 94 | Block = $_.Block 95 | Name = "It $($_.Name)" 96 | Result = $_.Result 97 | Message = $_.ErrorRecord.DisplayErrorMessage 98 | } 99 | } 100 | } 101 | } 102 | } 103 | #endregion Test Commands 104 | 105 | $testresults | Sort-Object Describe, Context, Name, Result, Message | Format-List 106 | 107 | if ($totalFailed -eq 0) { Write-PSFMessage -Level Critical -Message "All $totalRun tests executed without a single failure!" } 108 | else { Write-PSFMessage -Level Critical -Message "$totalFailed tests out of $totalRun tests failed!" } 109 | 110 | if ($totalFailed -gt 0) 111 | { 112 | throw "$totalFailed / $totalRun tests failed!" 113 | } -------------------------------------------------------------------------------- /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/xml/JEAnalyzer.Format.ps1xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | JEAnalyzer.CommandInfo 7 | 8 | JEAnalyzer.CommandInfo 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | IsDangerous 25 | 26 | 27 | CommandName 28 | 29 | 30 | ModuleName 31 | 32 | 33 | 34 | $_.CommandObject.CommandType 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | JEAnalyzer.Jea.Endpoint 46 | 47 | JEAnalyzer.Jea.Endpoint 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ComputerName 64 | 65 | 66 | Name 67 | 68 | 69 | Mode 70 | 71 | 72 | Enabled 73 | 74 | 75 | Version 76 | 77 | 78 | Description 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | JEAnalyzer.Jea.RoleCapability 89 | 90 | JEAnalyzer.Jea.RoleCapability 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ComputerName 108 | 109 | 110 | JeaEndpoint 111 | 112 | 113 | Identity 114 | 115 | 116 | Name 117 | 118 | 119 | 120 | 121 | @($_.VisibleCmdlets) + @($_.VisibleFunctions) + @($_.VisibleExternalCommands) | Remove-PSFNull 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build/filesAfter.txt: -------------------------------------------------------------------------------- 1 | # List all files that are loaded in the preimport.ps1 2 | # In the order they are loaded during preimport 3 | 4 | internal\configurations\*.ps1 5 | internal\tepp\*.tepp.ps1 6 | internal\scriptblock\*.ps1 7 | internal\tepp\assignment.ps1 8 | internal\scripts\variables.ps1 9 | internal\scripts\license.ps1 -------------------------------------------------------------------------------- /build/filesBefore.txt: -------------------------------------------------------------------------------- 1 | # List all files that are loaded in the postimport.ps1 2 | # In the order they are loaded during postimport 3 | 4 | internal\scripts\strings.ps1 -------------------------------------------------------------------------------- /build/vsts-build.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This script publishes the module to the gallery. 3 | It expects as input an ApiKey authorized to publish the module. 4 | 5 | Insert any build steps you may need to take before publishing it here. 6 | #> 7 | param ( 8 | $ApiKey, 9 | 10 | $WorkingDirectory = $env:SYSTEM_DEFAULTWORKINGDIRECTORY 11 | ) 12 | 13 | #region Handle Working Directory Defaults 14 | if (-not $WorkingDirectory) 15 | { 16 | if ($env:RELEASE_PRIMARYARTIFACTSOURCEALIAS) 17 | { 18 | $WorkingDirectory = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath $env:RELEASE_PRIMARYARTIFACTSOURCEALIAS 19 | } 20 | else { $WorkingDirectory = $env:SYSTEM_DEFAULTWORKINGDIRECTORY } 21 | } 22 | if (-not $WorkingDirectory) { $WorkingDirectory = Split-Path $PSScriptRoot } 23 | #endregion Handle Working Directory Defaults 24 | 25 | # Prepare publish folder 26 | Write-PSFMessage -Level Important -Message "Creating and populating publishing directory" 27 | $publishDir = New-Item -Path $WorkingDirectory -Name publish -ItemType Directory 28 | Copy-Item -Path "$($WorkingDirectory)\JEAnalyzer" -Destination $publishDir.FullName -Recurse -Force 29 | 30 | #region Gather text data to compile 31 | $text = @() 32 | $processed = @() 33 | 34 | # Gather Stuff to run before 35 | foreach ($line in (Get-Content "$($PSScriptRoot)\filesBefore.txt" | Where-Object { $_ -notlike "#*" })) 36 | { 37 | if ([string]::IsNullOrWhiteSpace($line)) { continue } 38 | 39 | $basePath = Join-Path "$($publishDir.FullName)\JEAnalyzer" $line 40 | foreach ($entry in (Resolve-PSFPath -Path $basePath)) 41 | { 42 | $item = Get-Item $entry 43 | if ($item.PSIsContainer) { continue } 44 | if ($item.FullName -in $processed) { continue } 45 | $text += [System.IO.File]::ReadAllText($item.FullName) 46 | $processed += $item.FullName 47 | } 48 | } 49 | 50 | # Gather commands 51 | Get-ChildItem -Path "$($publishDir.FullName)\JEAnalyzer\internal\functions\" -Recurse -File -Filter "*.ps1" | ForEach-Object { 52 | $text += [System.IO.File]::ReadAllText($_.FullName) 53 | } 54 | Get-ChildItem -Path "$($publishDir.FullName)\JEAnalyzer\functions\" -Recurse -File -Filter "*.ps1" | ForEach-Object { 55 | $text += [System.IO.File]::ReadAllText($_.FullName) 56 | } 57 | 58 | # Gather stuff to run afterwards 59 | foreach ($line in (Get-Content "$($PSScriptRoot)\filesAfter.txt" | Where-Object { $_ -notlike "#*" })) 60 | { 61 | if ([string]::IsNullOrWhiteSpace($line)) { continue } 62 | 63 | $basePath = Join-Path "$($publishDir.FullName)\JEAnalyzer" $line 64 | foreach ($entry in (Resolve-PSFPath -Path $basePath)) 65 | { 66 | $item = Get-Item $entry 67 | if ($item.PSIsContainer) { continue } 68 | if ($item.FullName -in $processed) { continue } 69 | $text += [System.IO.File]::ReadAllText($item.FullName) 70 | $processed += $item.FullName 71 | } 72 | } 73 | #endregion Gather text data to compile 74 | 75 | #region Update the psm1 file 76 | $fileData = Get-Content -Path "$($publishDir.FullName)\JEAnalyzer\JEAnalyzer.psm1" -Raw 77 | $fileData = $fileData.Replace('""', '""') 78 | $fileData = $fileData.Replace('""', ($text -join "`n`n")) 79 | [System.IO.File]::WriteAllText("$($publishDir.FullName)\JEAnalyzer\JEAnalyzer.psm1", $fileData, [System.Text.Encoding]::UTF8) 80 | #endregion Update the psm1 file 81 | 82 | # Publish to Gallery 83 | Publish-Module -Path "$($publishDir.FullName)\JEAnalyzer" -NuGetApiKey $ApiKey -Force -------------------------------------------------------------------------------- /build/vsts-prerequisites.ps1: -------------------------------------------------------------------------------- 1 | $modules = @( 2 | 'Pester' 3 | 'PSScriptAnalyzer' 4 | 'PSFramework' 5 | 'PSModuleDevelopment' 6 | ) 7 | 8 | foreach ($module in $modules) { 9 | Write-Host "Installing $module" -ForegroundColor Cyan 10 | Install-Module $module -Force -SkipPublisherCheck 11 | } -------------------------------------------------------------------------------- /build/vsts-validate.ps1: -------------------------------------------------------------------------------- 1 | # Guide for available variables and working with secrets: 2 | # https://docs.microsoft.com/en-us/vsts/build-release/concepts/definitions/build/variables?tabs=powershell 3 | 4 | # Needs to ensure things are Done Right and only legal commits to master get built 5 | Set-PSFConfig -Module 'JEAnalyzer' -Name 'Import.DoDotSource' -Value $true 6 | 7 | # Run internal pester tests 8 | & "$PSScriptRoot\..\JEAnalyzer\tests\pester.ps1" -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 results = new List(); 55 | 56 | foreach (Capability cap in CommandCapability.Values) 57 | if ((cap.Type == CapabilityType.Command) && (((CapabilityCommand)cap).CommandType == System.Management.Automation.CommandTypes.Cmdlet)) 58 | results.Add(ConvertHashtable(cap.ToHashtable(""))); 59 | 60 | return results.ToArray(); 61 | } 62 | 63 | /// 64 | /// The list of Functions that are part of the role. 65 | /// 66 | /// 67 | public object[] VisibleFunctions(string ModuleName) 68 | { 69 | List results = new List(); 70 | 71 | foreach (Capability cap in CommandCapability.Values) 72 | if (((cap.Type == CapabilityType.Command) && (((CapabilityCommand)cap).CommandType == System.Management.Automation.CommandTypes.Function) || (cap.Type == CapabilityType.Script))) 73 | results.Add(ConvertHashtable(cap.ToHashtable(ModuleName))); 74 | 75 | return results.ToArray(); 76 | } 77 | 78 | /// 79 | /// Transcribes all function definitions to the specified module object. 80 | /// 81 | /// The object to receive the function definitions 82 | public void CopyFunctionDefinitions(Module ModuleObject) 83 | { 84 | foreach (Capability cap in CommandCapability.Values.Where(o => o.Type == CapabilityType.Script)) 85 | ModuleObject.PublicFunctions[((CapabilityScript)cap).Content.Name] = ((CapabilityScript)cap).Content; 86 | } 87 | 88 | private object ConvertHashtable(Hashtable Table) 89 | { 90 | if (!Table.ContainsKey("Parameters")) 91 | return Table["Name"]; 92 | return Table; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------