├── .github ├── settings.yml └── workflows │ └── push.yml ├── .gitignore ├── LICENSE ├── README.md ├── example.png ├── logo.png └── occam ├── Assets ├── favicon.ico └── logo.png ├── Internal ├── Build-MsolProxy.ps1 ├── Build-RuleSet.ps1 ├── Invoke-TenantAudit.ps1 ├── Invoke-TenantListGUI.ps1 └── Write-PSObject.ps1 ├── Public └── Invoke-Occam.ps1 ├── Rules ├── Find-ExplicitAuthPolicyUsers.Rule.ps1 ├── Test-BasicAuthPolicies.Rule.ps1 ├── Test-PopImap.Rule.ps1 └── Test-UnifiedAuditLogging.Rule.ps1 └── occam.psd1 /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # These settings are synced to GitHub by https://probot.github.io/apps/settings/ 2 | # pliancy/.github:.github/settings.yaml 3 | 4 | repository: 5 | # See https://developer.github.com/v3/repos/#edit for all available settings. 6 | 7 | # The name of the repository. Changing this will rename the repository 8 | name: occam 9 | 10 | # Either `true` to make the repository private, or `false` to make it public. 11 | private: false 12 | 13 | # A short description of the repository that will show up on GitHub 14 | description: O365 Configuration Compliance Audit Manager 15 | 16 | # A URL with more information about the repository 17 | homepage: https://github.com/pliancy/occam 18 | 19 | # A comma-separated list of topics to set on the repository 20 | topics: O365, Best Practice, Analyzer, OCCAM, CSP 21 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Pester Tests 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - main 7 | 8 | jobs: 9 | pester-tests: 10 | # The type of runner that the job will run on 11 | runs-on: windows-latest 12 | 13 | # Steps represent a sequence of tasks that will be executed as part of the job 14 | steps: 15 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 16 | - uses: actions/checkout@v2 17 | #Get Pwsh Cache 18 | - name: Get Pwsh Cache Directory 19 | id: pwsh-cache 20 | uses: actions/cache@v2 21 | with: 22 | path: '~\Documents\PowerShell\Modules' 23 | key: ${{ runner.os }}-PS-PSScriptAnalyzer-GitHubActions-pwsh 24 | # Install Pwsh module if not cached 25 | - name: Install Modules for pwsh 26 | if: steps.pwsh-cache.outputs.cache-hit != 'true' 27 | shell: pwsh 28 | run: | 29 | Set-PSRepository PSGallery -InstallationPolicy Trusted 30 | Install-Module GitHubActions, PSScriptAnalyzer -ErrorAction Stop 31 | 32 | # https://github.com/marketplace/actions/pester-tests-report 33 | - uses: zyborg/pester-tests-report@v1 34 | name: Run and Create Pester Tests Report with Powershell Core 35 | with: 36 | report_name: pester_tests_report 37 | report_title: Pester Tests Report 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | # Runs a single command using the runners shell 41 | - name: Run Pester Tests with Standard Powershell 42 | shell: powershell 43 | run: | 44 | Set-PSRepository PSGallery -InstallationPolicy Trusted 45 | Install-Module PSScriptAnalyzer -ErrorAction Stop 46 | Import-Module -name PSScriptAnalyzer 47 | $results = Invoke-Pester -PassThru 48 | if($results.Result.ToLower() -ne "passed"){Write-Error "Standard PowerShell Pester Test: Failed"}else {Write-Host "Standard Powershell Pester Test: Passed"} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | Office365**/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pliancy 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCCAM - O365 Configuration Compliance Audit Manager 2 | 3 | ![O365 Configuration Compliance Audit Manager](logo.png) 4 | 5 | ---- 6 | 7 | ![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/occam?color=%237f4bae) ![GitHub](https://img.shields.io/github/license/pliancy/occam) 8 | 9 | OCCAM is an open-source toolkit for testing Office365 tenants against a set of security and compliance best practices. It is built for CSPs managing multiple tenants, though a future version will allow for use without CSP membership. 10 | 11 | ## Install 12 | 13 | OCCAM can be installed via the PowerShell Gallery: 14 | 15 | ```ps1 16 | Install-Module -Name occam 17 | ``` 18 | 19 | ## Usage 20 | 21 | :warning: Currently the O365 commands this module relies on are only supported on Windows 22 | 23 | Import the module and run the `Invoke-Occam` command to begin an Occam audit. 24 | 25 | ```ps1 26 | Import-Module occam 27 | Invoke-Occam -UserPrincipalName person@example.com 28 | ``` 29 | 30 | If you do not use the `-UserPrincipalName` or `-UPN` parameter, you will be prompted to enter your CSP-level User Principal Name. A modern auth workflow will be launched to authenticate with your CSP credentials. At that point, you will be prompted to select what tenents you wish to audit. Progress bars will indicate status, and final output is similar to the following example: 31 | 32 | ![Example Output Screenshot](example.png) 33 | 34 | The output will also be saved to a CSV with an execution timestamp. 35 | 36 | ## Default Rules 37 | 38 | A set of pre-made best practices have been bundled with this module. They include: 39 | 40 | 1. `Find-ExplicitAuthPolicyUsers` - Finds any users not using the organization's implicit authentication policy and (if any) exports the list as CSV 41 | 2. `Test-BasicAuthPolicies` - Checks to ensure that authentication policies block basic authentication mechanisms 42 | 3. `Test-PopImap` - Checks for any users that have POP or IMAP enabled and exports a CSV list of them 43 | 4. `Test-UnifiedAuditLogging` - Checks that Unified Audit Logging is enabled on the tenant 44 | 45 | Additional rules can be added by creating a file ending in `.Rule.ps1` in the directory from which you invoke OCCAM. Rulesets are dynamically evaluated at run-time. 46 | 47 | If you wish to ignore all default rules entirely, you can use the `-NoDefaultRules` switch: 48 | 49 | ```ps1 50 | Invoke-Occam -NoDefaultRules 51 | ``` 52 | 53 | ## Writing Custom Rules 54 | 55 | A Rule is any PowerShell script that returns a hashtable of boolean pass/fail values. Albeit simple, Rules are flexible and powerful - anything you can write in PowerShell can be packaged as a Rule and evaluated against every Office365 tenant you manage. 56 | 57 | Any files ending in `.Rule.ps1` in the working directory are discovered and parsed automatically. The name of a `.Rule.ps1` file is expected to have the same name as the function contained within them (e.g., `Test-Something.Rule.ps1` is espected to have a function named `Test-Something` inside). 58 | 59 | If a name conflict is found between a custom Rule and a default Rule, the custom Rule takes precedence. 60 | 61 | ### Rule Execution Environment 62 | 63 | Rules are ran in an environment that has the [MSOnline](https://docs.microsoft.com/en-us/powershell/module/msonline/) and [ExchangeOnlineManagement](https://docs.microsoft.com/en-us/powershell/exchange/exchange-online-powershell-v2) modules pre-loaded and authenticated to the given tenant the Rule is being evaluated against. All cmdlets and functions in those modules are available for immediate use. 64 | 65 | There is no need for MSOnline commands to use the `-TenantId` parameter, as this value is dynamically injected with the ID of the tenant being audited. This means that you can call `Get-MsolUsers` or related functions and it will automatically return a collection scoped to the desired tenant! 66 | 67 | ### Environment Variables 68 | 69 | OCCAM exposes custom environment variables that are available for use in your custom Rules. They are in the same form as the built-in PowerShell `env:` drive, and can be accessed accordingly: 70 | 71 | ```ps1 72 | Write-Host $OCCAM:TenantName 73 | ``` 74 | 75 | The following OCCAM environment variables are avalable for use: 76 | 77 | | Variable | Description | Example | 78 | |--------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------------------| 79 | | $OCCAM:TenantName | Office 365 Tenant Name | Contoso Corp | 80 | | $OCCAM:TenantId | Tenant ID (GUID format) | `b3d628ab-3271-4cc5-bd84-ce69d0946ec6` | 81 | | $OCCAM:TenantDomain | Tenant's Primary Domain | contoso.onmicrosoft.com | 82 | | $OCCAM:RuleName | Name of the rule currently being evaluated | Test-UnifiedAuditLogging | 83 | | $OCCAM:OutputDir | Output directory scoped to current tenant and rule | `Office365 Security Audit - 2021-01-13_15_18_06/Contoso Corp/Test-UnifiedAuditLogging` | 84 | | $OCCAM:AuthenticatedUser | User Principal Name of the account used for Exchange Online | steve@example.com | 85 | 86 | ### Rule Output 87 | 88 | Rules are expected to return a hashtable of key/value pairs corresponding to the test case(s) the Rule evaluates. Each value is expected to be a boolean, as Rules are meant to evaluate to a simple Pass/Fail criteria. 89 | 90 | ```ps1 91 | @{ 92 | ImapDisabled = $false 93 | PopDisabled = $true 94 | } 95 | ``` 96 | 97 | ### Exporting as CSV 98 | 99 | If more robust information is needed (e.g., a list of authentication policies with Basic Auth enabled), it is suggested to export that information as a CSV. Any files that a rule generates should be created by using the `$OCCAM:OutputDir` relative path that is provided. The `$OCCAM:OutputDir` path is unique for each invocation, tenant, _and_ rule, and it follows the following format: 100 | 101 | ```txt 102 | \\ 103 | ``` 104 | 105 | Your Rule is responsible for creating the directory path using `New-Item`. The following example snippet gets a list of users and exports it as CSV. Assuming a customer name of `Contoso Corp` and a rule name of `Test-MyCustomRule`, the following code would generate a CSV file at `./Office365 Security Audit - 2021-01-13_15_18_06/Contoso Corp/Test-MyCustomRule/users.csv`. 106 | 107 | ```ps1 108 | $Users = Get-MsolUsers 109 | New-Item -ItemType Directory -Force -Path $OCCAM:OutputDir | Out-Null 110 | $Users | ConvertTo-Csv -NoTypeInformation | Out-File ('{0}/users.csv' -f $OCCAM:OutputDir) -Force 111 | ``` 112 | 113 | ### Rule Metadata 114 | 115 | OCCAM uses PowerShell's built-in Help syntax to dynamically gain metadata about each Rule. The `.SYNOPSIS` help value is presented to the user during runtime in a PowerShell progress bar. This helps provide the user with an indication of what action is being performed, which is important for longer-running Rules. Any string is supported, but it is best to keep the string short and descriptive. 116 | 117 | The `.OUTPUTS` help value is expected to contain a list of keys matching exactly the keys present in the hashtable the Rule returns. Each output key is to be separated by a new line. 118 | 119 | The following is an example of the `Test-PopImap` Rule that is pre-packaged with OCCAM. It contains a synopsis, two outputs, and makes use of an ExchangeOnlineManagement cmdlet: 120 | 121 | ```ps1 122 | <# 123 | .SYNOPSIS 124 | Test that POP and IMAP are disabled on all mailbox plans 125 | 126 | .OUTPUTS 127 | ImapDisabled 128 | PopDisabled 129 | #> 130 | function Test-PopImap { 131 | $MailboxPlans = Get-CasMailboxPlan 132 | $output = @{ 133 | ImapDisabled = !(@($MailboxPlans.ImapEnabled) -contains $true) 134 | PopDisabled = !(@($MailboxPlans.PopEnabled) -contains $true) 135 | } 136 | return $output 137 | } 138 | ``` 139 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pliancy/occam/522025dfcef18a183733a6a0e0214bf3aca23a50/example.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pliancy/occam/522025dfcef18a183733a6a0e0214bf3aca23a50/logo.png -------------------------------------------------------------------------------- /occam/Assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pliancy/occam/522025dfcef18a183733a6a0e0214bf3aca23a50/occam/Assets/favicon.ico -------------------------------------------------------------------------------- /occam/Assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pliancy/occam/522025dfcef18a183733a6a0e0214bf3aca23a50/occam/Assets/logo.png -------------------------------------------------------------------------------- /occam/Internal/Build-MsolProxy.ps1: -------------------------------------------------------------------------------- 1 | function Build-MsolProxy { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory = $true)] 5 | [System.Guid]$TenantId 6 | ) 7 | 8 | $commands = Get-Command -Module MSOnline 9 | 10 | $functionsToExport = @() 11 | 12 | foreach ($command in $commands) { 13 | $metadata = New-Object System.Management.Automation.CommandMetaData $command 14 | 15 | # remove TenantId from command metadata 16 | $hasTenantIdParam = $metadata.Parameters.ContainsKey("TenantId") 17 | if ($hasTenantIdParam) { 18 | $metadata.Parameters.Remove("TenantId") | Out-Null 19 | } 20 | 21 | # create a proxy function that wraps the initial MSOnline cmdlet 22 | $proxy = [System.Management.Automation.ProxyCommand]::Create($metadata) 23 | 24 | # string-replace the PSBoundParameters splat operation to insert the -TenantId parameter 25 | # into the underlying command being called/wrapped 26 | if ($hasTenantIdParam) { 27 | $proxy = $proxy -replace '@PSBoundParameters', ('@PSBoundParameters -TenantId {0}' -f $TenantId) 28 | } 29 | 30 | # Pack the internals as a function 31 | $proxyAsFunction = "function $($command.Name) { `n $proxy `n }" 32 | 33 | # Append the full proxy function as a string onto an array 34 | $functionsToExport += $proxyAsFunction 35 | } 36 | 37 | # Concatenate all functions into one large string with new lines separating each 38 | $ScriptString = ($functionsToExport -join("`n")) 39 | 40 | # Convert the string to a scriptblock 41 | $ScriptBlock = [Scriptblock]::Create($ScriptString) 42 | 43 | # Load the proxy functions as a dynamic module into memory, and pipe to 44 | # the Import-Module command so we can clean it up with Remove-Module later 45 | New-Module -Name "MSOL_$TenantId" -ScriptBlock $ScriptBlock | Import-Module 46 | 47 | return "MSOL_$TenantId" 48 | } 49 | -------------------------------------------------------------------------------- /occam/Internal/Build-RuleSet.ps1: -------------------------------------------------------------------------------- 1 | function Build-RuleSet { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter()] 5 | [Switch]$NoDefaultRules = $false 6 | ) 7 | $ModuleBase = $MyInvocation.MyCommand.Module.ModuleBase 8 | # $ModuleBase = ".\occam" 9 | $DefaultRulePath = "$ModuleBase\Rules" 10 | 11 | # Populate rule name list by grabbing any files in the Rules directory 12 | $Rules = @() 13 | 14 | # Discover additional rules in execution path 15 | $Rules += Get-ChildItem -Filter *.Rule.ps1 -Recurse 16 | 17 | # Get default (prepackaged) rules 18 | if (!$NoDefaultRules) { 19 | $Rules += Get-ChildItem -Path "$DefaultRulePath" -Filter *.Rule.ps1 20 | } 21 | 22 | $Rules = $Rules | Sort-Object -Property Name -Unique | ForEach-Object {@{ Name = ($_.Name.split(".") | Select-Object -First 1); Path = $_.VersionInfo.FileName }} 23 | 24 | $FormattedRuleSet = @() 25 | 26 | $i = 0 27 | foreach($Rule in $Rules) { 28 | Write-Progress -Activity "Building Rule Set" -PercentComplete ($i / $Rules.count * 100) -CurrentOperation "Importing Rule $($Rule.Name)" 29 | # Load the rule as a module 30 | Import-Module $Rule.Path -Force 31 | 32 | $RuleHelp = Get-Help $Rule.Name 33 | $FormattedRuleSet += @{ 34 | Name = $Rule.Name; 35 | OutputKeys = $RuleHelp.returnvalues.returnValue.type.name.Split([Environment]::NewLine); 36 | Synopsis = $RuleHelp.SYNOPSIS; 37 | Path = $Rule.Path 38 | } 39 | $i++ 40 | } 41 | 42 | Write-Progress -Activity "Building Rule Set" -PercentComplete 100 -CurrentOperation ("Successfully Imported {0} Rules" -f $Rules.count) 43 | 44 | return $FormattedRuleSet 45 | } 46 | -------------------------------------------------------------------------------- /occam/Internal/Invoke-TenantAudit.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-TenantAudit { 2 | param ( 3 | [Object]$tenant, 4 | [String]$UPN, 5 | [Object]$RuleSet, 6 | [String]$ReportPath 7 | ) 8 | $totalSteps = $RuleSet.length + 5 9 | $i = 0 10 | 11 | $FormattedTenant = @{ 12 | Name = $tenant.Name 13 | } 14 | 15 | Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete ($i / $totalSteps * 100) -CurrentOperation "Connecting to Exchange Online" 16 | # Connect to client's Exchange Online using EXO V2 17 | try { 18 | Connect-ExchangeOnline -UserPrincipalName $UPN -DelegatedOrganization $tenant.Domain -ShowBanner:$false -ShowProgress:$false 19 | # Connect-IPPSSession -UserPrincipalName $UPN -DelegatedOrganization $tenant.Domain -ShowBanner:$false -ShowProgress:$false 20 | } 21 | catch { 22 | Write-Warning ("Failed to connect to Tenant {0}, skipping audit" -f $tenant.Name) 23 | Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete 100 24 | return New-Object PSObject -Property $FormattedTenant 25 | } 26 | 27 | # Clean up MSOnline proxy module 28 | $i++; Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete ($i / $totalSteps * 100) -CurrentOperation "Dynamically Generating MSOnline Proxy Module" 29 | $MsolProxyModule = Build-MsolProxy -TenantId $tenant.id 30 | 31 | # Generate PS Drive 32 | $i++; Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete ($i / $totalSteps * 100) -CurrentOperation "Creating Runtime Environment Variables" 33 | New-PSDrive -Name "OCCAM" -PSProvider Environment -Root . | Out-Null 34 | $OCCAM:TenantName = $tenant.Name 35 | $OCCAM:TenantId = $tenant.id 36 | $OCCAM:TenantDomain = $tenant.Domain 37 | $OCCAM:AuthenticatedUser = $UPN 38 | 39 | foreach ($Rule in $RuleSet) { 40 | # Set Rule-specific environment variables 41 | $OCCAM:OutputDir = "$ReportPath/$($tenant.Name)/$($Rule.Name)" 42 | $OCCAM:RuleName = $Rule.Name 43 | $i++; Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete ($i / $totalSteps * 100) -CurrentOperation $Rule.Synopsis 44 | try { 45 | Import-Module $Rule.Path -Force 46 | $output = Invoke-Expression $Rule.Name 47 | $FormattedTenant += $output 48 | } 49 | catch { 50 | Write-Warning ("Unable to run Rule {0} on Tenant {1}" -f $Rule.Name, $tenant.name) 51 | } 52 | } 53 | 54 | # Clean up PS Drive 55 | $i++; Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete ($i / $totalSteps * 100) -CurrentOperation "Cleaning up Runtime Environment Variables" 56 | Remove-PSDrive -Name "OCCAM" -Force 57 | 58 | # Clean up MSOnline proxy module 59 | $i++; Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete ($i / $totalSteps * 100) -CurrentOperation "Removing MSOnline Proxy Module" 60 | Remove-Module -Name $MsolProxyModule -Force 61 | 62 | # Disconnect from Exchange Online 63 | $i++; Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete ($i / $totalSteps * 100) -CurrentOperation "Disconnecting from Exchange Online" 64 | Disconnect-ExchangeOnline -Confirm:$false *> $null 65 | 66 | Write-Progress -Activity ("Auditing {0}" -f $tenant.Name) -ParentId 1 -PercentComplete 100 67 | return New-Object PSObject -Property $FormattedTenant 68 | } -------------------------------------------------------------------------------- /occam/Internal/Invoke-TenantListGUI.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-TenantListGui { 2 | [CmdletBinding()] 3 | Param 4 | ( 5 | [Parameter( 6 | Mandatory = $True, 7 | Position = 0, 8 | ValueFromPipeline = $True, 9 | ValueFromPipelineByPropertyName = $True 10 | )][Array]$tenants 11 | ) 12 | 13 | $ModuleBase = $MyInvocation.MyCommand.Module.ModuleBase 14 | 15 | $colors = @{ 16 | white = "#ffffff"; 17 | purple = "#7f4bae"; 18 | navy = "#171837" 19 | } 20 | 21 | <# This form was created using POSHGUI.com a free online gui designer for PowerShell 22 | .NAME 23 | o365auditutil 24 | #> 25 | Add-Type -AssemblyName System.Windows.Forms 26 | [System.Windows.Forms.Application]::EnableVisualStyles() 27 | $Form = New-Object system.Windows.Forms.Form 28 | $Form.Icon = [System.Drawing.Icon]::new("$ModuleBase/Assets/favicon.ico") 29 | $Form.ClientSize = '500,315' 30 | $Form.text = "Office365 Configuration Compliance Audit Manager" 31 | $Form.BackColor = $colors.navy 32 | $Form.TopMost = $false 33 | 34 | $Label = New-Object system.Windows.Forms.Label 35 | $Label.text = "Select Tenant(s):" 36 | $Label.AutoSize = $true 37 | $Label.width = 25 38 | $Label.height = 10 39 | $Label.location = New-Object System.Drawing.Point(68, 27) 40 | $Label.Font = 'Microsoft Sans Serif,10,style=Bold' 41 | $Label.ForeColor = $colors.white 42 | 43 | $ListBox = New-Object system.Windows.Forms.ListBox 44 | $ListBox.BackColor = $colors.navy 45 | $ListBox.ForeColor = $colors.white 46 | $ListBox.text = "listBox" 47 | $ListBox.BorderStyle = 1 48 | $ListBox.width = 354 49 | $ListBox.height = 171 50 | $ListBox.location = New-Object System.Drawing.Point(68, 55) 51 | $ListBox.SelectionMode = 'MultiExtended' 52 | foreach ($tenant in ($tenants.Name | Sort-Object)) { 53 | [void] $ListBox.Items.Add($tenant) 54 | } 55 | $OK = New-Object system.Windows.Forms.Button 56 | $OK.BackColor = $colors.purple 57 | $OK.text = "Next" 58 | $OK.width = 96 59 | $OK.height = 30 60 | $OK.location = New-Object System.Drawing.Point(325, 255) 61 | $OK.Font = 'Microsoft Sans Serif,10,style=Bold' 62 | $OK.ForeColor = $colors.white 63 | $OK.DialogResult = [System.Windows.Forms.DialogResult]::OK 64 | $form.AcceptButton = $OK 65 | $form.Controls.Add($OK) 66 | $logobox = New-Object system.Windows.Forms.PictureBox 67 | $logobox.width = 200 68 | $logobox.height = 30 69 | $logobox.location = New-Object System.Drawing.Point(68, 255) 70 | $logobox.Load("$ModuleBase/Assets/logo.png") 71 | $logobox.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::zoom 72 | $Form.Controls.AddRange(@($Label, $ListBox, $OK, $logobox)) 73 | $form.Controls.Add($ListBox) 74 | $form.Topmost = $true 75 | $result = $form.ShowDialog() 76 | if ($result -eq [System.Windows.Forms.DialogResult]::OK) { 77 | return $ListBox.SelectedItems 78 | } 79 | } -------------------------------------------------------------------------------- /occam/Internal/Write-PSObject.ps1: -------------------------------------------------------------------------------- 1 | ## Code Type: Function 2 | ## Decription: Write the output of PSObjects with formatted defined colors with two ways. 3 | ## The first way is to format the whole table to write the Odd lines with a specific fore/back colors, and the even with deferent ones. 4 | ## The second way is to write a specific column's value or the whole line based on a condition or more for the output values. 5 | ## Author: Ahmad Gad 6 | ## Contact Email: ahmad.gad@jemmpress.com 7 | ## WebSite: http://ahmad.jempress.com 8 | ## Date Format: DD/MM/YYYY 9 | ## Created On: 01/12/2016 10 | ## Updated On: 10/10/2019 11 | ## PSVersion Supported: 2.0+ 12 | ## PowerShell Core Supported: Yes 13 | 14 | Function Write-PSObject 15 | { 16 | <# 17 | .SYNOPSIS 18 | Display the input PSObject(s)/Object(s) as formatted table with defined fore/back colors for headers row and/or body rows and/or columns values based on specified conditions or criteria. 19 | .DESCRIPTION 20 | Display the input PSObject(s)/Object(s) with formatted defined colors with three ways which could be combined together. 21 | The first way is to format the whole table to write the Odd lines with a specific fore/back colors, and the even with deferent ones. 22 | The second way is to write a specific column's value or the whole line based on a condition or more for the output values. 23 | The third way is to write a separator line between each two lines which could be blank line, or any other values with our without specified fore/back color. 24 | .EXAMPLE 25 | #In this example, the output lines will be colored with "Yellow" forecolor and "Blue" backcolor if any value inside that line exactly equals to "False" for any property. 26 | Write-PSObject -PSObject $psObjects -MatchMethod Exact -Column "*" -Value $False -RowForeColor Yellow -RowBackColor Blue; 27 | .EXAMPLE 28 | #In this example, the output lines will be colored with "Red" forecolor if any value inside that line exactly equals to "False" for any property. 29 | Write-PSObject -PSObject $psObjects -MatchMethod Exact -Column "*" -Value $False -RowForeColor Red; 30 | .PARAMETER Object 31 | Alias: O, I 32 | Data Type: Object 33 | Mandatory: True 34 | Description: The input PSOject(s) to display with formatted colors. 35 | Example(s): N/A 36 | Default Value: N/A 37 | Notes: N/A. 38 | .PARAMETER MatchMethod 39 | Alias: MM, M 40 | Data Type: String[] 41 | Mandatory: False 42 | Description: The array of values validations way which must be provided with one of the three valid set "Exact", "Match" or "Query". 43 | Example(s): N/A 44 | Default Value: N/A 45 | Notes: If this parameter not provided, the conditional formatting will not be functional and all the tailed parameters will be ignored. 46 | This must be tailed with the following parameters to be functional: 47 | "Column", "Value" and one or more of the following parameters: 48 | "ValueForeColor", "ValueBackColor", "RowForeColor" and/or "RowBackColor". 49 | .PARAMETER FormatTableColor 50 | Alias: Format-TableC, Format-Table, F 51 | Data Type: Switch 52 | Mandatory: False 53 | Description: If specified, lines will be formatted with specified fore/back colors based on the sequence. 54 | User can define specific fore or/and back color for odd lines in sequence, and different fore or/and back colors for even lines. 55 | Example(s): N/A 56 | Default Value: N/A 57 | Notes: User needs to use one or more of the following parameters with this switch in order to make it functional: 58 | "OddLineForeColor", "OddLineBackColor", "EvenLineForeColor" and/or "EvenLineBackColor" 59 | .PARAMETER Column 60 | Alias: C, Col 61 | Data Type: String[] 62 | Mandatory: False 63 | Description: The list of the names of columns/properties which hold the value(s) to be validated to apply the table formatting. 64 | Example(s): N/A 65 | Default Value: * 66 | Notes: This parameter can provided as one string or array of them which must match the same count of the "Value" parameter or the script will be terminated (If the "$IgnoreErrors" switch is provided, the function will not be terminated, but the formatting will not be functional). 67 | If the parameter "MatchMethod" is not provided, this column and all the other relative ones will be ignored and the whole conditional formatting will not be functional. 68 | The "*" is referring to all the columns/properties within the table/PSObject(s). 69 | If the "*" is provided as a name of column/property, that means the same condition defined by the "MatchMethod" and "Value" parameters will be applied on all the columns/properties. 70 | For example, you can just mention "*" as column/property name to color all the "False" values for any column/property with forecolor "Red" and/or backcolor "Black". 71 | .PARAMETER Value 72 | Alias: V 73 | Data Type: String[] 74 | Mandatory: False 75 | Description: The value validation way which must be matching with one of the following three valid set provided by the parameter "MatchMethod" (all case insensitive): 76 | 1. Exact ---» The provided value must match exactly with the inputs. 77 | Ex1: -Value "True" 78 | Ex2: -Value $True 79 | Ex3: -Value 4 80 | Ex4: -Value "Ahmad", 4, "True", $False 81 | 2. Match ---» The provided value must match with part of the inputs. 82 | Ex1: "Ahmad" 83 | Ex2: -Value "Ah" 84 | Ex3: -Value "ma" 85 | Ex4: -Value "ad" 86 | Ex5: -Value "X", "C123", "A" 87 | 3. Query ---» The provided value must match with the provided query which could contains various conditions on the same column. 88 | Ex1: -Value "'Up Time' -Like '00 Days*'" 89 | Ex2: -Value "'CPU' -gt 90 -and CPU -le 95'" 90 | Ex3: -Value "'Name' -Match 'C3' -and 'Name' -Notmatch 'C34'" 91 | Ex4: -Value "'Name' -Match 'C3' -and 'Name' -Notmatch 'C34'", "'Up Time' -Like '00 Days*'" 92 | Ex5: -Value "'Name' -Match 'Ahmad' -and 'Address' -Notmatch 'Central Park'", "'Up Time' -Like '00 Days*'" 93 | Please note that, column/property name must be put as it is between two single quotations, and the whole condition/query between two double quotations. 94 | Example(s): N/A 95 | Default Value: N/A 96 | Notes: This must be tailed with the following parameters to be functional: 97 | "Column", "Value" and one or more of the following parameters: 98 | "ValueForeColor", "ValueBackColor", "RowForeColor" and/or "RowBackColor". 99 | This parameter can provided as one string value or array of them which must match the same count of the "Column" parameter or the script will be terminated (If the "$IgnoreErrors" switch is provided, the function will not be terminated, but the formatting will not be functional). 100 | If the parameter "MatchMethod" is not provided, this column and all the other relative ones will be ignored and the whole conditional formatting will not be functional. 101 | .PARAMETER FormatTableColor 102 | Alias: Format-TableC, Format-Table, F 103 | Data Type: Switch 104 | Mandatory: False 105 | Description: If specified, lines will be formatted with specified fore/back colors based on the sequence. 106 | User can define specific fore or/and back color for odd lines in sequence, and different fore or/and back colors for even lines. 107 | Example(s): N/A 108 | Default Value: N/A 109 | Notes: User needs to use one ore more of the following parameters with this switch in order to make it functional: 110 | "OddLineForeColor", "OddLineBackColor", "EvenLineForeColor" and/or "EvenLineBackColor" 111 | .PARAMETER ValueForeColor 112 | Alias: VFC 113 | Data Type: ConsoleColor[] 114 | Mandatory: False 115 | Description: Used to define the forecolor of the values that match the specified condition. 116 | Example(s): Red, Black, Blue, White, etc. 117 | Default Value: N/A 118 | Notes: This parameter is function only when the following parameters are provided "MatchMethod" “Column” and “Value”. 119 | .PARAMETER ValueBackColor 120 | Alias: VBC 121 | Data Type: ConsoleColor[] 122 | Mandatory: False 123 | Description: Used to define the backcolor of the values that match the specified condition. 124 | Example(s): Red, Black, Blue, White, etc. 125 | Default Value: N/A 126 | Notes: This parameter is function only when the following parameters are provided "MatchMethod" “Column” and “Value”. 127 | .PARAMETER RowForeColor 128 | Alias: RFC 129 | Data Type: ConsoleColor 130 | Mandatory: False 131 | Description: Used to define the forecolor of the whole line/row that when of its values match the specified condition(s). 132 | With another meaning, if no values inside that row/line (single PSObject) matches any provided condition, this parameter will be ignored. 133 | Example(s): Red, Black, Blue, White, etc. 134 | Default Value: Null 135 | Notes: This parameter is function only when the "MatchMethod" parameter is provided. 136 | If the "ValueForeColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 137 | The "FlagsForeColor" of the flagged columns would override it. 138 | .PARAMETER RowBackColor 139 | Alias: RBC 140 | Data Type: ConsoleColor 141 | Mandatory: False 142 | Description: Used to define the backcolor of the whole line/row that any of its values match the specified condition(s). 143 | With another meaning, if no values inside that row/line (single PSObject) matches any provided condition, this parameter will be ignored. 144 | Example(s): Red, Black, Blue, White, etc. 145 | Default Value: Null 146 | Notes: This parameter is function only when the "MatchMethod" parameter is provided. 147 | If the "ValueBackColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 148 | The "FlagsBackColor" of the flagged columns would override it. 149 | .PARAMETER OddLineForeColor 150 | Alias: OLFC 151 | Data Type: ConsoleColor 152 | Mandatory: False 153 | Description: Used to define the forecolor of the whole line/row that with an odd sequence of the whole rows starting from the first row of values. 154 | Example, you can provide that parameter with the forecolor of the rows number 1, 3, 5, 7, etc. 155 | Example(s): Red, Black, Blue, White, etc. 156 | Default Value: The default forecolor of the host. 157 | Notes: This parameter is function only when the "FormatTableColor" parameter is provided. 158 | It can work with or without the other relative odd/even fore/back colors. 159 | If the "ValueForeColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 160 | If the "RowForeColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 161 | The "FlagsForeColor" of the flagged columns would override it. 162 | .PARAMETER OddLineBackColor 163 | Alias: OLBC 164 | Data Type: ConsoleColor 165 | Mandatory: False 166 | Description: Used to define the backcolor of the whole line/row that with an odd sequence of the whole rows starting from the first row of values. 167 | Example, you can provide that parameter with the backcolor of the rows number 1, 3, 5, 7, etc. 168 | Example(s): Red, Black, Blue, White, etc. 169 | Default Value: The default backcolor of the host. 170 | Notes: This parameter is function only when the "FormatTableColor" parameter is provided. 171 | It can work with or without the other relative odd/even fore/back colors. 172 | If the "ValueBackColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 173 | If the "RowBackColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 174 | The "FlagsBackColor" of the flagged columns would override it. 175 | .PARAMETER EvenLineForeColor 176 | Alias: ELFC 177 | Data Type: ConsoleColor 178 | Mandatory: False 179 | Description: Used to define the forecolor of the whole line/row that with an even sequence of the whole rows starting from the second row of values. 180 | Example, you can provide that parameter with the forecolor of the rows number 2, 4, 6, 8, etc. 181 | Example(s): Red, Black, Blue, White, etc. 182 | Default Value: The default forecolor of the host. 183 | Notes: This parameter is function only when the "FormatTableColor" parameter is provided. 184 | It can work with or without the other relative odd/even fore/back colors. 185 | If the "ValueForeColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 186 | If the "RowForeColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 187 | The "FlagsForeColor" of the flagged columns would override it. 188 | .PARAMETER EvenLineBackColor 189 | Alias: ELBC 190 | Data Type: ConsoleColor 191 | Mandatory: False 192 | Description: Used to define the backcolor of the whole line/row that with an odd sequence of the whole rows starting from the second row of values. 193 | Example, you can provide that parameter with the backcolor of the rows number 2, 4, 6, 8, etc. 194 | Example(s): Red, Black, Blue, White, etc. 195 | Default Value: The default backcolor of the host. 196 | Notes: This parameter is function only when the "FormatTableColor" parameter is provided. 197 | It can work with or without the other relative odd/even fore/back colors. 198 | If the "ValueBackColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 199 | If the "RowBackColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 200 | The "FlagsBackColor" of the flagged columns would override it. 201 | .PARAMETER HeadersForeColor 202 | Alias: HFC 203 | Data Type: ConsoleColor 204 | Mandatory: False 205 | Description: Used to define the forecolor of the whole two headers lines/rows. 206 | The first header row/line which describes the names of the columns/properties. 207 | While, the second header row/line is the underlines dashes characters which separate the header names than the rows' values. 208 | Example, When get the results of the function "Get-ChildItem -Path C:\ | Format-Table -Al;", the output would be something like the following: 209 | Mode LastWriteTime Length Name # First header line. 210 | ---- ------------- ------ ---- # Second header line. 211 | Example(s): Red, Black, Blue, White, etc. 212 | Default Value: The default forecolor of the host. 213 | Notes: This parameter is not dependent on any other parameters or conditions. 214 | .PARAMETER HeadersBackColor 215 | Alias: HBC 216 | Data Type: ConsoleColor 217 | Mandatory: False 218 | Description: Used to define the backcolor of the whole two headers lines/rows. 219 | The first header row/line which describes the names of the columns/properties. 220 | While, the second header row/line is the underlines dashes characters which separate the header names than the rows' values. 221 | Example, When get the results of the function "Get-ChildItem -Path C:\ | Format-Table -Al;", the output would be something like the following: 222 | Mode LastWriteTime Length Name # First header line. 223 | ---- ------------- ------ ---- # Second header line. 224 | Example(s): Red, Black, Blue, White, etc. 225 | Default Value: The default backcolor of the host. 226 | Notes: This parameter is not dependent on any other parameters or conditions. 227 | .PARAMETER BodyForeColor 228 | Alias: BFC 229 | Data Type: ConsoleColor 230 | Mandatory: False 231 | Description: Used to define the forecolor of the whole lines/rows values. 232 | Example(s): Red, Black, Blue, White, etc. 233 | Default Value: The default forecolor of the host. 234 | Notes: This parameter is not dependent on any other parameters or conditions. 235 | If the "ValueForeColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 236 | If the "RowForeColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 237 | If the "OddLineForeColor" and /or "EvenLineForeColor" parameter(s) are provided, they would override it. 238 | The "FlagsForeColor" of the flagged columns would override it. 239 | .PARAMETER BodyBackColor 240 | Alias: BBC 241 | Data Type: ConsoleColor 242 | Mandatory: False 243 | Description: Used to define the backcolor of the whole lines/rows values. 244 | Example(s): Red, Black, Blue, White, etc. 245 | Default Value: The default backcolor of the host. 246 | Notes: This parameter is not dependent on any other parameters or conditions. 247 | If the "ValueBackColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 248 | If the "RowBackColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 249 | If the "OddLineBackColor" and /or "EvenLineBackColor" parameter(s) are provided, they would override it. 250 | The "FlagsBackColor" of the flagged columns would override it. 251 | .PARAMETER ColoredColumns 252 | Alias: CC 253 | Data Type: String[] 254 | Mandatory: False 255 | Description: Define the columns/properties to have their values colored without conditions. 256 | Example(s): "CPU", "Memory", "SN" 257 | Default Value: N/A 258 | Notes: This parameter is not dependent on any other parameters or conditions. 259 | .PARAMETER ColumnForeColor 260 | Alias: CFC 261 | Data Type: ConsoleColor[] 262 | Mandatory: False 263 | Description: Used to define the forecolor of values the specified "ColoredColumns". 264 | Example(s): Red, Black, Blue, White, etc. 265 | Default Value: N/A 266 | Notes: This parameter is function only when the "ColoredColumns" parameter is provided. 267 | If the "ValueForeColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 268 | If the "RowForeColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 269 | If the "OddLineForeColor" and /or "EvenLineForeColor" parameter(s) are provided, they would override it. 270 | The "FlagsForeColor" of the flagged columns would override it. 271 | .PARAMETER ColumnBackColor 272 | Alias: CBC 273 | Data Type: ConsoleColor[] 274 | Mandatory: False 275 | Description: Used to define the backcolor of the whole lines/rows values. 276 | Example(s): Red, Black, Blue, White, etc. 277 | Default Value: N/A 278 | Notes: This parameter is function only when the "ColoredColumns" parameter is provided. 279 | If the "ValueBackColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 280 | If the "RowBackColor" is provided, it could override the color of the whole line/row, if they match with specific provided condition(s). 281 | If the "OddLineBackColor" and /or "EvenLineBackColor" parameter(s) are provided, they would override it. 282 | The "FlagsBackColor" of the flagged columns would override it. 283 | .PARAMETER FlagColumns 284 | Alias: FC 285 | Data Type: String[] 286 | Mandatory: False 287 | Description: Define the columns/properties to have their values colored when any of the specified values match the specified condition(s). 288 | Example(s): "CPU", "Memory", "SN" 289 | Default Value: N/A 290 | Notes: This parameter is function only when the following parameters are provided "MatchMethod" “Column” and “Value”. 291 | .PARAMETER FlagsForeColor 292 | Alias: FFC 293 | Data Type: ConsoleColor[] 294 | Mandatory: False 295 | Description: Used to define the forecolor of the flagged columns/properties. 296 | Example(s): Red, Black, Blue, White, etc. 297 | Default Value: N/A 298 | Notes: This parameter is function only when the ""MatchMethod" “Column”, “Value” and "FlagColumns" parameters are provided. 299 | If the "ValueForeColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 300 | This would override the colors specified by the "RowBackColor", "OddLineFBackColor" and/or the "EvenLineBackColor" parameters. 301 | 302 | .PARAMETER FlagsBackColor 303 | Alias: FBC 304 | Data Type: ConsoleColor[] 305 | Mandatory: False 306 | Description: Used to define the backcolor of the whole lines/rows values. 307 | Example(s): Red, Black, Blue, White, etc. 308 | Default Value: N/A 309 | Notes: This parameter is function only when the ""MatchMethod" “Column”, “Value” and "FlagColumns" parameters are provided. 310 | If the "ValueBackColor" is provided, it could override the colors of the matched properties values, if they match with specific provided condition(s). 311 | This would override the colors specified by the "RowForeColor", "OddLineForeColor" and/or the "EvenLineForeColor" parameters. 312 | .PARAMETER InjectRowsSeparator 313 | Alias: IRS 314 | Data Type: Switch 315 | Mandatory: False 316 | Description: If specified, new lines would be injected between body rows/lines. 317 | Example(s): N/A 318 | Default Value: N/A 319 | Notes: By default the new line separator would be just new line with null data as the default value of the "RowsSeparator" parameter is $null. 320 | .PARAMETER RowsSeparator 321 | Alias: RS 322 | Data Type: String 323 | Mandatory: False 324 | Description: Define the shape of the separator line/row between body rows/lines. 325 | The value could be one character such as "-", "==", etc, or mixed ones and the line would be trimmed by the maximum length of the body rows. 326 | Example(s): "-", ".", "=", "|", "#", " ", etc. 327 | Default Value: $null 328 | Notes: This parameter is function only when the "InjectRowsSeparator" parameter is provided. 329 | If not specified, the new line separator would be just new line with null data. 330 | .PARAMETER RowsSeparatorForeColor 331 | Alias: RSFC 332 | Data Type: ConsoleColor 333 | Mandatory: False 334 | Description: Used to define the forecolor of the separator rows/lines. 335 | Example(s): Red, Black, Blue, White, etc. 336 | Default Value: The default forecolor of the host. 337 | Notes: This parameter is function only when the "InjectRowsSeparator" and "RowsSeparator" parameters are provided. 338 | .PARAMETER RowsSeparatorBackColor 339 | Alias: RSBC 340 | Data Type: ConsoleColor 341 | Mandatory: False 342 | Description: Used to define the backcolor of the separator rows/lines. 343 | Example(s): Red, Black, Blue, White, etc. 344 | Default Value: The default backcolor of the host. 345 | Notes: This parameter is function only when the "InjectRowsSeparator" and "RowsSeparator" parameters are provided. 346 | .PARAMETER RemoveHeadersSeparator 347 | Alias: RHS 348 | Data Type: Switch 349 | Mandatory: False 350 | Description: Neglect displaying the second header line "----" (the separator line between headers (columns/properties) names and the body rows/values.). 351 | With another meaning, the values rows/lines would be printed directly aFormat-Tableer the columns/properties names line/row. 352 | Example(s): N/A 353 | Default Value: N/A 354 | Notes: This parameter will not be functional if "BodyOnly" parameter is specified. 355 | .PARAMETER HeadersSeparator 356 | Alias: HS 357 | Data Type: String 358 | Mandatory: False 359 | Description: Define the shape of the separator header separator line "----" between the columns names and body rows. 360 | The value could be one character such as ".", "==", etc, or mixed ones and the line would be trimmed by the maximum length of the body rows. 361 | Example(s): ".", "=", "|", "#", " ", etc. 362 | Default Value: "-" 363 | Notes: This parameter will not be functional if any of the two parameters "BodyOnly" or "RemoveHeadersSeparator" is specified. 364 | .PARAMETER HeadersSeparatorForeColor 365 | Alias: HSFC 366 | Data Type: ConsoleColor 367 | Mandatory: False 368 | Description: Used to define the forecolor of the separator header separator line "----" between the columns names and body rows. 369 | Example(s): Red, Black, Blue, White, etc. 370 | Default Value: The default forecolor of the host. 371 | Notes: This parameter will not be functional if any of the two parameters "BodyOnly" or "RemoveHeadersSeparator" is specified. 372 | .PARAMETER HeadersSeparatorBackColor 373 | Alias: HSBC 374 | Data Type: ConsoleColor 375 | Mandatory: False 376 | Description: Used to define the backcolor of the separator header separator line "----" between the columns names and body rows. 377 | Example(s): Red, Black, Blue, White, etc. 378 | Default Value: The default backcolor of the host. 379 | Notes: This parameter will not be functional if any of the two parameters "BodyOnly" or "RemoveHeadersSeparator" is specified. 380 | .PARAMETER BodyOnly 381 | Alias: BO 382 | Data Type: Switch 383 | Mandatory: False 384 | Description: If specified, only the body rows (values lines) will be displayed, and, the two headers lines will not be displayed. 385 | Example(s): N/A 386 | Default Value: N/A 387 | Notes: This parameter is not dependent on any other parameters or conditions. 388 | .PARAMETER HeadersOnly 389 | Alias: HO 390 | Data Type: Switch 391 | Mandatory: False 392 | Description: If specified, only the two headers lines will be displayed, and, the body rows (values lines) will not be displayed. 393 | Example(s): N/A 394 | Default Value: N/A 395 | Notes: This parameter is not dependent on any other parameters or conditions. 396 | .PARAMETER IgnoreErrors 397 | Alias: IE 398 | Data Type: Switch 399 | Mandatory: False 400 | Description: It would try to suppress and error/exception could be raised due to missing or non matched parameters and continue displaying the rows. 401 | Example(s): N/A 402 | Default Value: N/A 403 | Notes: This parameter is not dependent on any other parameters or conditions. 404 | If one of the provided conditions which is provided by the combination of the properties "MatchMethod", "Column", "Value" and/or "FlagColumns" and/or their relative properties was not well provided, or mismatching, the "MatchMethod" property would be ignored and the conditional formatting will not be functional. 405 | .PARAMETER HostWindowWidth 406 | Alias: HWW 407 | Data Type: UInt16 408 | Mandatory: False 409 | Description: Resize the host PowerShell window with a new specified width before presenting the data. 410 | Example(s): N/A 411 | Default Value: N/A 412 | Notes: This parameter is not dependent on any other parameters or conditions. 413 | It would also resize the buffer width size with the same specified value if the current value is less than the new specified window width. 414 | .PARAMETER HostWindowHeight 415 | Alias: HWH 416 | Data Type: UInt16 417 | Mandatory: False 418 | Description: Resize the host PowerShell window with a new specified height before presenting the data. 419 | Example(s): N/A 420 | Default Value: N/A 421 | Notes: This parameter is not dependent on any other parameters or conditions. 422 | It would also resize the buffer height size with the same specified height if the current value is less than the new specified window height. 423 | .PARAMETER HostWindowForeColor 424 | Alias: HWFC 425 | Data Type: ConsoleColor 426 | Mandatory: False 427 | Description: Override the current forecolor of the host PowerShell with a new specified one before presenting the data. 428 | Example(s): N/A 429 | Default Value: N/A 430 | Notes: This parameter is not dependent on any other parameters or conditions. 431 | .PARAMETER HostWindowBackColor 432 | Alias: HWBC 433 | Data Type: ConsoleColor 434 | Mandatory: False 435 | Description: Override the current background color of the host PowerShell with a new specified one before presenting the data. 436 | Example(s): N/A 437 | Default Value: N/A 438 | Notes: This parameter is not dependent on any other parameters or conditions. 439 | #> 440 | 441 | [CmdletBinding()] 442 | [OutputType("Void")] 443 | Param 444 | ( 445 | [Parameter(Mandatory=$True, Position= 0, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)][Alias("O", "I")][Object]$Object, 446 | [Parameter(Mandatory=$False, Position= 1)][Alias("MM", "M")] [String[]][ValidateSet("Exact","Match","Query")]$MatchMethod, 447 | [Parameter(Mandatory=$False, Position= 2)][Alias("Format-TableC", "Format-Table", "F")] [Switch]$FormatTableColor, 448 | [Parameter(Mandatory=$False, Position= 3)][Alias("C")] [String[]]$Column = "*", 449 | [Parameter(Mandatory=$False, Position= 4)][Alias("V")] [String[]]$Value, 450 | [Parameter(Mandatory=$False, Position= 5)][Alias("VFC")][ConsoleColor[]]$ValueForeColor, 451 | [Parameter(Mandatory=$False, Position= 6)][Alias("VBC")][ConsoleColor[]]$ValueBackColor, 452 | [Parameter(Mandatory=$False, Position= 7)][Alias("RFC")][ConsoleColor]$RowForeColor, 453 | [Parameter(Mandatory=$False, Position= 8)][Alias("RBC")] [ConsoleColor]$RowBackColor, 454 | [Parameter(Mandatory=$False, Position= 9)][Alias("OLFC")] [ConsoleColor]$OddRowForeColor = (Get-Host).UI.RawUI.ForegroundColor, 455 | [Parameter(Mandatory=$False, Position=10)][Alias("OLBC")] [ConsoleColor]$OddRowBackColor = (Get-Host).UI.RawUI.BackgroundColor, 456 | [Parameter(Mandatory=$False, Position=11)][Alias("ELFC")] [ConsoleColor]$EvenRowForeColor = (Get-Host).UI.RawUI.ForegroundColor, 457 | [Parameter(Mandatory=$False, Position=12)][Alias("ELBC")] [ConsoleColor]$EvenRowBackColor = (Get-Host).UI.RawUI.BackgroundColor, 458 | [Parameter(Mandatory=$False, Position=13)][Alias("HFC")] [ConsoleColor]$HeadersForeColor = (Get-Host).UI.RawUI.ForegroundColor, 459 | [Parameter(Mandatory=$False, Position=14)][Alias("HBC")] [ConsoleColor]$HeadersBackColor = (Get-Host).UI.RawUI.BackgroundColor, 460 | [Parameter(Mandatory=$False, Position=15)][Alias("BFC")] [ConsoleColor]$BodyForeColor = (Get-Host).UI.RawUI.ForegroundColor, 461 | [Parameter(Mandatory=$False, Position=16)][Alias("BBC")] [ConsoleColor]$BodyBackColor = (Get-Host).UI.RawUI.BackgroundColor, 462 | [Parameter(Mandatory=$False, Position=17)][Alias("CC")] [String[]]$ColoredColumns, 463 | [Parameter(Mandatory=$False, Position=18)][Alias("CFC")] [ConsoleColor[]]$ColumnForeColor, 464 | [Parameter(Mandatory=$False, Position=19)][Alias("CBC")] [ConsoleColor[]]$ColumnBackColor, 465 | [Parameter(Mandatory=$False, Position=20)][Alias("FC")] [String[]]$FlagColumns, 466 | [Parameter(Mandatory=$False, Position=21)][Alias("FFC")] [ConsoleColor[]]$FlagsForeColor, 467 | [Parameter(Mandatory=$False, Position=22)][Alias("FBC")] [ConsoleColor[]]$FlagsBackColor, 468 | [Parameter(Mandatory=$False, Position=23)][Alias("IRS")] [Switch]$InjectRowsSeparator, 469 | [Parameter(Mandatory=$False, Position=24)][Alias("RS")] [String]$RowsSeparator = $null, 470 | [Parameter(Mandatory=$False, Position=25)][Alias("RSFC")] [ConsoleColor]$RowsSeparatorForeColor = (Get-Host).UI.RawUI.ForegroundColor, 471 | [Parameter(Mandatory=$False, Position=26)][Alias("RSBC")] [ConsoleColor]$RowsSeparatorBackColor = (Get-Host).UI.RawUI.BackgroundColor, 472 | [Parameter(Mandatory=$False, Position=27)][Alias("RHS")] [Switch]$RemoveHeadersSeparator, 473 | [Parameter(Mandatory=$False, Position=28)][Alias("HS")] [String]$HeadersSeparator = "-", 474 | [Parameter(Mandatory=$False, Position=29)][Alias("HSFC")] [ConsoleColor]$HeadersSeparatorForeColor = (Get-Host).UI.RawUI.ForegroundColor, 475 | [Parameter(Mandatory=$False, Position=30)][Alias("HSBC")] [ConsoleColor]$HeadersSeparatorBackColor = (Get-Host).UI.RawUI.BackgroundColor, 476 | [Parameter(Mandatory=$False, Position=31)][Alias("BO")] [Switch]$BodyOnly, 477 | [Parameter(Mandatory=$False, Position=32)][Alias("HO")] [Switch]$HeadersOnly, 478 | [Parameter(Mandatory=$False, Position=33)][Alias("IE")] [Switch]$IgnoreErrors, 479 | [Parameter(Mandatory=$False, Position=34)][Alias("HWW")] [UInt16]$HostWindowWidth, 480 | [Parameter(Mandatory=$False, Position=35)][Alias("HWH")] [UInt16]$HostWindowHeight, 481 | [Parameter(Mandatory=$False, Position=36)][Alias("HWFC")] [ConsoleColor]$HostWindowForeColor, 482 | [Parameter(Mandatory=$False, Position=37)][Alias("HWBC")] [ConsoleColor]$HostWindowBackColor 483 | ) 484 | 485 | Function Write-Line 486 | { 487 | [CmdletBinding()] 488 | [OutputType("Void")] 489 | Param 490 | ( 491 | [Parameter(Mandatory=$True, Position= 0, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)][Alias("O", "I")][object]$Object, 492 | [Parameter(Mandatory=$False, Position= 1)][Alias("F", "FC")] [ConsoleColor]$ForegroundColor = (Get-Host).UI.RawUI.ForegroundColor, 493 | [Parameter(Mandatory=$False, Position= 2)][Alias("B", "BC")] [ConsoleColor]$BackgroundColor = (Get-Host).UI.RawUI.BackgroundColor, 494 | [Parameter(Mandatory=$False, Position= 3)][Alias("NNL")] [Switch]$NoNewline 495 | ) 496 | 497 | If (([int]$ForegroundColor) -eq -1) 498 | { 499 | $ForegroundColor = [ConsoleColor]::White; 500 | } 501 | 502 | If (([int]$BackgroundColor) -eq -1) 503 | { 504 | Write-Host -NoNewline:$NoNewline -ForegroundColor $ForegroundColor -Object $Object; 505 | } 506 | Else 507 | { 508 | Write-Host -NoNewline:$NoNewline -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor -Object $Object; 509 | } 510 | } 511 | 512 | If(($HostWindowWidth -And $HostWindowWidth -ne 0) -Or ($HostWindowHeight -And $HostWindowHeight -ne 0) -OR $HostWindowForeColor -ne $null -Or $HostWindowBackColor -ne $null) 513 | { 514 | Try 515 | { 516 | $psHost = Get-Host; 517 | $psWindow = $psHost.UI.RawUI; 518 | 519 | If($HostWindowForeColor -ne $null -Or $HostWindowBackColor -ne $null) 520 | { 521 | If ($HostWindowBackColor -ne $null) 522 | { 523 | $psWindow.BackgroundColor = $HostWindowBackColor; 524 | $RowBackColor = $HostWindowBackColor; 525 | $OddRowBackColor = $HostWindowBackColor; 526 | $EvenRowBackColor = $HostWindowBackColor; 527 | $HeadersBackColor = $HostWindowBackColor; 528 | $BodyBackColor = $HostWindowBackColor; 529 | $ColumnBackColor = $HostWindowBackColor; 530 | $FlagsBackColor = $HostWindowBackColor; 531 | $RowsSeparatorBackColor = $HostWindowBackColor; 532 | $HeadersSeparatorBackColor = $HostWindowBackColor; 533 | } 534 | 535 | If ($HostWindowForeColor -ne $null) 536 | { 537 | $psWindow.ForegroundColor = $HostWindowForeColor; 538 | 539 | $RowForeColor = $HostWindowForeColor; 540 | $OddRowForeColor = $HostWindowForeColor; 541 | $EvenRowForeColor = $HostWindowForeColor; 542 | $HeadersForeColor = $HostWindowForeColor; 543 | $BodyForeColor = $HostWindowForeColor; 544 | $ColumnForeColor = $HostWindowForeColor; 545 | $FlagsForeColor = $HostWindowForeColor; 546 | $RowsSeparatorForeColor = $HostWindowForeColor; 547 | $HeadersSeparatorForeColor = $HostWindowForeColor; 548 | } 549 | } 550 | 551 | $newBufferSize = $psWindow.BufferSize; 552 | 553 | If ($HostWindowHeight -ne $null -And $HostWindowHeight -ne 0) 554 | { 555 | If ($newBufferSize.Height -lt $HostWindowHeight) 556 | { 557 | $newBufferSize.Height = $HostWindowHeight; 558 | } 559 | } 560 | 561 | If ($HostWindowWidth -ne $null -And $HostWindowWidth -ne 0) 562 | { 563 | If ($newBufferSize.Width -lt $HostWindowWidth) 564 | { 565 | $newBufferSize.Width = $HostWindowWidth; 566 | } 567 | } 568 | 569 | $psWindow.BufferSize = $newBufferSize; 570 | 571 | $newSize = $psWindow.WindowSize; 572 | 573 | If ($HostWindowHeight -ne $null -And $HostWindowHeight -ne 0) 574 | { 575 | $newSize.Height = $HostWindowHeight; 576 | } 577 | 578 | If ($HostWindowWidth -ne $null -And $HostWindowWidth -ne 0) 579 | { 580 | $newSize.Width = $HostWindowWidth; 581 | } 582 | 583 | If(($HostWindowWidth -ne $null -And $HostWindowWidth -ne 0) -Or ($HostWindowHeight -ne $null -And $HostWindowHeight -ne 0)) 584 | { 585 | $psWindow.WindowSize = $newSize; 586 | } 587 | } 588 | Catch{} 589 | } 590 | 591 | $lines = ($input | Format-Table -AutoSize | Out-String).Split("`r`n") | Where-Object {$_.Trim() -ne ""}; 592 | If(!($lines)) 593 | { 594 | $lines = ($Object | Format-Table -AutoSize | Out-String).Split("`r`n") | Where-Object {$_.Trim() -ne ""}; 595 | } 596 | else 597 | { 598 | $Object = $input; 599 | } 600 | 601 | If(!($lines) -Or $lines.Length -eq 0) 602 | { 603 | Return; 604 | } 605 | 606 | $maxLength = $lines | ForEach-Object {$_.Length} | Sort-Object -Descending | Select-Object -First 1; 607 | 608 | If($MatchMethod) 609 | { 610 | If(($Column.Count -ne $Value.Count)) 611 | { 612 | If ($IgnoreErrors) 613 | { 614 | $MatchMethod = $False; 615 | } 616 | else 617 | { 618 | Write-Host "The count of the input columns seems not matching with the count of the input values or values' forecolors."; 619 | Return; 620 | } 621 | } 622 | 623 | If ($MatchMethod.Count -lt $Column.Count) 624 | { 625 | $MatchMethod = @($MatchMethod[0]) * $Column.Count; 626 | } 627 | } 628 | else 629 | { 630 | $Column = @(); 631 | $FlagColumns = @(); 632 | } 633 | 634 | #region Set Default Colors 635 | #region Defaults 636 | 637 | #endregion Defaults 638 | #region Match Method 639 | If ($RowForeColor -eq -1) 640 | { 641 | $RowForeColor = $BodyForeColor; 642 | } 643 | 644 | If ($RowBackColor -eq -1) 645 | { 646 | $RowBackColor = $BodyBackColor; 647 | } 648 | #endregion Match Method 649 | 650 | #region Flag Columns 651 | If($FlagColumns.Count -gt 0) 652 | { 653 | If ($FlagColumns.Count -gt $FlagsForeColor.Count -and $FlagsForeColor.Count -ne 0) 654 | { 655 | $FlagsForeColor = @($FlagsForeColor[0]) * $FlagColumns.Count; 656 | } 657 | 658 | If ($FlagColumns.Count -gt $FlagsBackColor.Count -and $FlagsBackColor.Count -ne 0) 659 | { 660 | $FlagsBackColor = @($FlagsBackColor[0]) * $FlagColumns.Count; 661 | } 662 | } 663 | 664 | If($ColoredColumns.Count -gt 0) 665 | { 666 | If ($null -ne $ColumnForeColor -and $ColoredColumns.Count -gt $ColumnForeColor.Count -and $ColumnForeColor.Count -ne 0) 667 | { 668 | $ColumnForeColor = @($ColumnForeColor[0]) * $ColoredColumns.Count; 669 | } 670 | 671 | If ($null -ne $ColumnBackColor -and $ColoredColumns.Count -gt $ColumnBackColor.Count -and $ColumnBackColor.Count -ne 0) 672 | { 673 | $ColumnBackColor = @($ColumnBackColor[0]) * $ColoredColumns.Count; 674 | } 675 | } 676 | #endregion Flag Columns 677 | #endregion Set Default Colors 678 | 679 | $l = 0; 680 | Foreach($line in $lines) 681 | { 682 | If($maxLength -gt $line.Length) 683 | { 684 | $line += " " * ($maxLength - $line.Length) 685 | } 686 | 687 | $l++; 688 | 689 | If($l -le 2) 690 | { 691 | If($l -eq 2 -And ($MatchMethod -Or $ColoredColumns)) 692 | { 693 | $headerLine = $line; 694 | $header = $lines[0]; 695 | [String[]]$headerLines = $headerLine -split " " | Where-Object {$_.Trim() -ne ""} | Foreach-Object {$_.Trim("`t").Trim();}; 696 | $colCount = $headerLines.Count; 697 | $columns = @($null) * $colCount; 698 | 699 | [Int[]] $headersPos = @(0) * $colCount; 700 | [Int[]] $headersLen = @(0) * $colCount; 701 | 702 | $pos = 0; 703 | $i = 0; 704 | $headersPos[$i] = 0; 705 | 706 | $columns[$i] = $header.Substring($pos, $headerLines[$i].Length); 707 | $col = $Columns[$i]; 708 | $headersLen[$i] = $object | Select-Object $col, @{Name="Len";Expression={$_.$col.ToString().Length}} | Sort-Object Len -Descending | Select-Object Len -First 1 -ExpandProperty Len; 709 | If ($headerLines[$i].Length -gt $headersLen[$i]) 710 | { 711 | $headersLen[$i] = $headerLines[$i].Length; 712 | } 713 | 714 | While($pos -ne -1) 715 | { 716 | $i++; 717 | $pos = $headerLine.IndexOf(" -", $pos + 1, [System.StringComparison]::InvariantCultureIgnoreCase); 718 | If($pos -eq -1) 719 | { 720 | Break; 721 | } 722 | 723 | $columns[$i] = $header.Substring($pos + 1, $headerLines[$i].Length); 724 | $col = $Columns[$i]; 725 | $colLen = $object | Select-Object $col, @{Name="Len";Expression={$_.$col.ToString().Length}} | Sort-Object Len -Descending | Select-Object Len -First 1 -ExpandProperty Len; 726 | If ($col.Length -gt $colLen) 727 | { 728 | $colLen = $col.Length; 729 | } 730 | 731 | $headersLen[$i] = $colLen; 732 | $headersPos[$i] = $headersPos[$i - 1] + $headersLen[$i - 1] + 1; 733 | } 734 | } 735 | 736 | If ($l -eq 2 -And $RemoveHeadersSeparator) 737 | { 738 | Continue; 739 | } 740 | 741 | If(!($BodyOnly)) 742 | { 743 | $hfc = $HeadersForeColor; 744 | $hbc = $HeadersBackColor; 745 | 746 | If($l -eq 2) 747 | { 748 | If ($HeadersSeparator -ne "-") 749 | { 750 | If(!($HeadersSeparator)) 751 | { 752 | $line = $null; 753 | } 754 | else 755 | { 756 | $line = $line.Replace("-", $HeadersSeparator); 757 | $line = $line.Substring(0, $lines[0].Length); 758 | } 759 | 760 | $hfc = $HeadersSeparatorForeColor; 761 | $hbc = $HeadersSeparatorBackColor; 762 | } 763 | } 764 | 765 | Write-Line -Object $line -ForegroundColor $hfc -BackgroundColor $hbc; 766 | } 767 | 768 | If($l -eq 2) 769 | { 770 | If($HeadersOnly) 771 | { 772 | Return; 773 | } 774 | } 775 | 776 | Continue; 777 | } 778 | elseIf($l -gt 2 -And ($MatchMethod -Or $ColoredColumns)) 779 | { 780 | $oLine = $object[$l -3]; 781 | $values = @($null) * $colCount; 782 | 783 | For($i = 0; $i -lt $colCount; $i++) 784 | { 785 | $col = $Columns[$i]; 786 | $values[$i] = $oLine | Select-Object -ExpandProperty $col; 787 | } 788 | } 789 | 790 | If($FormatTableColor) 791 | { 792 | If($l % 2 -eq 0) 793 | { 794 | $BodyForeColor = $EvenRowForeColor; 795 | $BodyBackColor = $EvenRowBackColor; 796 | } 797 | else 798 | { 799 | $BodyForeColor = $OddRowForeColor; 800 | $BodyBackColor = $OddRowBackColor; 801 | } 802 | } 803 | 804 | $fc = @($BodyForeColor) * $colCount; 805 | $bc = @($BodyBackColor) * $colCount; 806 | 807 | If ($ColoredColumns) 808 | { 809 | For($j = 0; $j -lt $columns.Count; $j++) 810 | { 811 | If ($ColoredColumns -contains $columns[$j]) 812 | { 813 | $fColIndex = [System.Array]::IndexOf(($ColoredColumns | ForEach-Object {$_.ToLower()}), $columns[$j].ToLower()); 814 | 815 | If($fColIndex -lt $ColumnForeColor.Count) 816 | { 817 | $fc[$j] = $ColumnForeColor[$fColIndex]; 818 | } 819 | 820 | If($fColIndex -lt $ColumnBackColor.Count) 821 | { 822 | $bc[$j] = $ColumnBackColor[$fColIndex]; 823 | } 824 | } 825 | } 826 | } 827 | 828 | If($MatchMethod -Or $ColoredColumns) 829 | { 830 | $matchCond = $false; 831 | $matchCondGroup = @($false) * $Column.Count; 832 | $matchColumnFlag = @($false) * $columns.Count; 833 | For($i = 0; $i -lt $columns.Count; $i++) 834 | { 835 | For($j = 0; $j -lt $Column.Count; $j++) 836 | { 837 | $colVal = $null; 838 | $col = $Column[$j]; 839 | $val = $Value[$j]; 840 | 841 | If ($col -eq $columns[$i] -Or $col -eq "*") 842 | { 843 | $colVal = $values[$i]; 844 | $query = $null; 845 | $r = $null; 846 | Switch ($MatchMethod[$j]) 847 | { 848 | "Exact" {$query = """$colVal"" -eq ""$val"""}; 849 | "Match" {$query = """$colVal"" -match ""$val"""}; 850 | "Query" 851 | { 852 | For($c = 0; $c -lt $columns.Count; $c++) 853 | { 854 | $colC = $Columns[$c]; 855 | $valC = $values[$c]; 856 | [double]$valCNum = New-Object System.Double; 857 | $isNum = [double]::TryParse($valC, [ref] $valCNum); 858 | if($isNum) 859 | { 860 | $val = $val -replace "'$colC'" , "$valC"; 861 | } 862 | else 863 | { 864 | $val = $val -replace "'$colC'" , "'$valC'"; 865 | } 866 | $query = $val; 867 | } 868 | }; 869 | } 870 | 871 | $r = Invoke-Expression $query; 872 | If ($r) 873 | { 874 | $matchCond = $true; 875 | If($null -ne $ValueForeColor -and $ValueForeColor.Count -gt $j) 876 | { 877 | $fColor = $ValueForeColor[$j] 878 | } 879 | elseIf($RowForeColor) 880 | { 881 | $fColor = $RowForeColor; 882 | } 883 | else 884 | { 885 | $fColor = $BodyForeColor; 886 | } 887 | 888 | If($null -ne $ValueBackColor -and $ValueBackColor.Count -gt $j) 889 | { 890 | $bColor = $ValueBackColor[$j] 891 | } 892 | elseIf($RowBackColor) 893 | { 894 | $bColor = $RowBackColor; 895 | } 896 | else 897 | { 898 | $bColor = $BodyBackColor; 899 | } 900 | 901 | $fc[$i] = $fColor; 902 | $bc[$i] = $bColor; 903 | 904 | $matchCondGroup[$j] = $True; 905 | $matchColumnFlag[$i] = $True; 906 | If($null -ne $FlagColumns -and $FlagColumns.Count -gt $j -and $null -ne $FlagColumns[$j] -and $FlagColumns[$j].Trim() -ne "") 907 | { 908 | [String[]]$fColumnsSplit = @(); 909 | $fColumnsSplit = $FlagColumns[$j] -split "," | Where-Object {$_.Trim() -ne ""} | Foreach-Object {$_.Trim("").Trim("'");}; 910 | 911 | Foreach($fcs in $fColumnsSplit) 912 | { 913 | $fColIndex = [System.Array]::IndexOf(($columns | Foreach-Object {$_.ToLower()}), $fcs.ToLower()); 914 | If($fColIndex -ne -1) 915 | { 916 | If($j -lt $FlagsForeColor.Count) 917 | { 918 | $matchColumnFlag[$fColIndex] = $True; 919 | $fc[$fColIndex] = $FlagsForeColor[$j]; 920 | } 921 | 922 | If($j -lt $FlagsBackColor.Count) 923 | { 924 | $matchColumnFlag[$fColIndex] = $True; 925 | $bc[$fColIndex] = $FlagsBackColor[$j]; 926 | } 927 | } 928 | } 929 | } 930 | } 931 | } 932 | } 933 | } 934 | 935 | For($j = 0; $j -lt $columns.Count; $j++) 936 | { 937 | $foreColor = $fc[$j]; 938 | $backColor = $bc[$j]; 939 | 940 | If ($matchCond) 941 | { 942 | If (!($matchColumnFlag[$j])) 943 | { 944 | If ($null -ne $RowForeColor) 945 | { 946 | $foreColor = $RowForeColor; 947 | } 948 | 949 | If ($null -ne $RowBackColor) 950 | { 951 | $backColor = $RowBackColor; 952 | } 953 | } 954 | } 955 | 956 | If ($j -eq 0) 957 | { 958 | $vPos = $headersPos[$j]; 959 | $vLen = $headersLen[$j]; 960 | } 961 | else 962 | { 963 | If ($null -ne $RowBackColor -and $matchCond) 964 | { 965 | Write-Line " " -NoNewline -BackgroundColor $RowBackColor; 966 | } 967 | else 968 | { 969 | Write-Line " " -NoNewline -BackgroundColor $BodyBackColor; 970 | } 971 | 972 | $vPos = $headersPos[$j]; 973 | $vLen = $headersLen[$j]; 974 | } 975 | 976 | $valueText = $line.SubString($vPos, $vLen); 977 | Write-Line -Object $valueText -NoNewline -ForegroundColor $foreColor -BackgroundColor $backColor; 978 | If ($j -eq $columns.Count - 1) 979 | { 980 | Write-Host; 981 | } 982 | } 983 | } 984 | ElseIf(!($HeadersOnly)) 985 | { 986 | Write-Line -ForegroundColor $BodyForeColor -BackgroundColor $BodyBackColor $line; 987 | } 988 | 989 | If ($l -ne ($lines.Length)) 990 | { 991 | If ($InjectRowsSeparator) 992 | { 993 | If (!$RowsSeparator) 994 | { 995 | $RowsSeparator = " "; 996 | } 997 | 998 | $RowsSeparatorLine = $RowsSeparator * $line.Length; 999 | $RowsSeparatorLine = $RowsSeparatorLine.Substring(0, $line.Length); 1000 | 1001 | Write-Line -Object $RowsSeparatorLine -ForegroundColor $RowsSeparatorForeColor -BackgroundColor $RowsSeparatorBackColor; 1002 | } 1003 | } 1004 | } 1005 | } -------------------------------------------------------------------------------- /occam/Public/Invoke-Occam.ps1: -------------------------------------------------------------------------------- 1 | Function Invoke-Occam { 2 | [CmdletBinding()] 3 | param ( 4 | [Alias("UPN")] 5 | [Parameter(Mandatory = $false)] 6 | [String]$UserPrincipalName = '', 7 | 8 | [Parameter()] 9 | [Switch]$NoDefaultRules = $false 10 | ) 11 | Begin { 12 | # Load Rules 13 | $RuleSet = Build-RuleSet -NoDefaultRules:$NoDefaultRules 14 | 15 | # Create a folder to hold all results in, and add a timestamp for uniqueness 16 | $ReportPath = New-Item -Name ('Office365 Security Audit - {0}' -f (get-date -f yyyy-MM-dd_HH_mm_ss)) -ItemType "directory" 17 | $ReportPath = $ReportPath.Name 18 | } 19 | Process { 20 | if ([string]::IsNullOrEmpty($UserPrincipalName)) { 21 | $UserPrincipalName = Read-Host -Prompt "Please enter your CSP email" 22 | } 23 | 24 | try { 25 | Connect-MsolService 26 | } 27 | catch { 28 | Write-Error "Failure to connect to MSOnline Service" 29 | exit 30 | } 31 | 32 | # Get all tenants and domains 33 | $customers = Get-MsolPartnerContract -All 34 | $tenants = @() 35 | $i = 0 36 | foreach ($customer in $customers) { 37 | $i++ 38 | Write-Progress -Activity "Getting Tenants" -PercentComplete ($i / $customers.count * 100) -CurrentOperation $domain 39 | $tenant = @{ 40 | 'id' = $customer.TenantId 41 | 'Name' = $customer.Name 42 | 'Domain' = $customer.DefaultDomainName 43 | } 44 | $tenants += $tenant 45 | } 46 | Write-Progress -Activity "Getting Tenants" -PercentComplete 100 47 | 48 | $selectedTenants = Invoke-TenantListGUI -Tenants $tenants 49 | 50 | $i = 0 51 | $formattedTenants = @() 52 | foreach ($selectedTenant in $selectedTenants) { 53 | $tenant = $tenants | Where-Object {$_.Name -eq $selectedTenant} 54 | Write-Progress -Activity "Auditing Tenants" -Id 1 -PercentComplete ($i / $selectedTenants.count * 100) 55 | $formattedTenants += Invoke-TenantAudit -tenant $tenant -UPN $UserPrincipalName -RuleSet $RuleSet -ReportPath $ReportPath 56 | $i++ 57 | 58 | } 59 | Write-Progress -Activity "Auditing Tenants" -Id 1 -PercentComplete 100 60 | 61 | # Strip empty values out of the array so that only valid keys are sent to the Select-Object cmdlet 62 | $RuleOutputKeys = $RuleSet.OutputKeys | Where-Object { $_ } 63 | # Prepend "Name" so that it shows up first 64 | $Properties = (,"Name" + $RuleOutputKeys) 65 | $formattedTenants = $formattedTenants | Select-Object -Property $Properties 66 | $formattedTenants | Write-PSObject -MatchMethod Exact -Column *, * -Value $false, $true -ValueForeColor Red, Green 67 | $formattedTenants | ConvertTo-Csv -NoTypeInformation | Out-File ('./{0}/results.csv' -f $ReportPath) 68 | } 69 | End {} 70 | } 71 | -------------------------------------------------------------------------------- /occam/Rules/Find-ExplicitAuthPolicyUsers.Rule.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Report users assigned an explicit authentication policy 4 | 5 | .OUTPUTS 6 | #> 7 | function Find-ExplicitAuthPolicyUsers { 8 | param () 9 | Begin { 10 | $Users = @(Get-User -ResultSize Unlimited) 11 | 12 | $Properties = @( 13 | "UserPrincipalName", 14 | "DisplayName", 15 | "AuthenticationPolicy", 16 | "AccountDisabled", 17 | "Guid", 18 | "SID" 19 | ) 20 | } 21 | Process { 22 | 23 | # Find users without the default authentication policy 24 | $NonDefaultAuthPolicyUsers = $Users | Where-Object { [string]::IsNullOrEmpty($_.AuthenticationPolicy) } 25 | 26 | # Filter out only select properties 27 | $NonDefaultAuthPolicyUsers = $NonDefaultAuthPolicyUsers | Select-Object -Property $Properties 28 | 29 | if ($NonDefaultAuthPolicyUsers.Count) { 30 | # Create an output directory and export as CSV 31 | New-Item -ItemType Directory -Force -Path $OCCAM:OutputDir | Out-Null 32 | $NonDefaultAuthPolicyUsers | ConvertTo-Csv -NoTypeInformation | Out-File ('{0}/users.csv' -f $OCCAM:OutputDir) -Force 33 | } 34 | 35 | 36 | $output = @{} 37 | 38 | return $output 39 | } 40 | End { 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /occam/Rules/Test-BasicAuthPolicies.Rule.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Verify that authentication policies block basic auth 4 | 5 | .OUTPUTS 6 | BlockBasicAuthDefaultPolicy 7 | BlockBasicAuthAllPolicies 8 | #> 9 | function Test-BasicAuthPolicies { 10 | param () 11 | Begin { 12 | $AuthPolicies = @(Get-AuthenticationPolicy) 13 | $DefaultPolicy = (Get-OrganizationConfig).DefaultAuthenticationPolicy 14 | $BlockBasicAuthAllPolicies = $false 15 | $BlockBasicAuthDefaultPolicy = $false 16 | 17 | $BasicAuthMembers = @( 18 | "AllowBasicAuthActiveSync", 19 | "AllowBasicAuthAutodiscover", 20 | "AllowBasicAuthImap", 21 | "AllowBasicAuthMapi", 22 | "AllowBasicAuthOfflineAddressBook", 23 | "AllowBasicAuthOutlookService", 24 | "AllowBasicAuthPop", 25 | "AllowBasicAuthPowershell", 26 | "AllowBasicAuthReportingWebServices", 27 | "AllowBasicAuthRest", 28 | "AllowBasicAuthRpc", 29 | "AllowBasicAuthSmtp", 30 | "AllowBasicAuthWebServices" 31 | ) 32 | } 33 | Process { 34 | if ($AuthPolicies.Count -ne 0) { 35 | $BlockBasicAuthAllPolicies = $true 36 | 37 | foreach ($Policy in $AuthPolicies) { 38 | # Let's check for basic auth being enabled in any category listed in the $BasicAuthMembers array 39 | $MemberValueArray = @() 40 | foreach ($Member in $BasicAuthMembers) { 41 | $MemberValueArray += $Policy.$Member 42 | } 43 | 44 | # if any form of basic auth is allowed, let's mark the policy as insecure 45 | $BlockAllBasicAuth = !([bool]($MemberValueArray -contains $true)) 46 | 47 | # if this is the default policy, let's report that the default policy specifically is insecure 48 | if ($Policy.Name -eq $DefaultPolicy) { 49 | $BlockBasicAuthDefaultPolicy = $BlockAllBasicAuth 50 | } 51 | 52 | if ($BlockAllBasicAuth -eq $false) { 53 | $BlockBasicAuthAllPolicies = $false 54 | } 55 | } 56 | } 57 | 58 | # Export policy list 59 | # TODO 60 | 61 | $output = @{ 62 | BlockBasicAuthDefaultPolicy = $BlockBasicAuthDefaultPolicy 63 | BlockBasicAuthAllPolicies = $BlockBasicAuthAllPolicies 64 | } 65 | 66 | return $output 67 | } 68 | End { 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /occam/Rules/Test-PopImap.Rule.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Test that POP and IMAP are disabled on all mailbox plans 4 | 5 | .OUTPUTS 6 | ImapDisabled 7 | PopDisabled 8 | #> 9 | function Test-PopImap { 10 | param () 11 | Begin { 12 | $MailboxPlans = Get-CasMailboxPlan 13 | } 14 | Process { 15 | $output = @{ 16 | ImapDisabled = !(@($MailboxPlans.ImapEnabled) -contains $true) 17 | PopDisabled = !(@($MailboxPlans.PopEnabled) -contains $true) 18 | } 19 | 20 | return $output 21 | } 22 | End { 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /occam/Rules/Test-UnifiedAuditLogging.Rule.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Verify that Unified Audit Logging is enabled 4 | 5 | .OUTPUTS 6 | UnifiedAuditLogging 7 | #> 8 | function Test-UnifiedAuditLogging { 9 | param () 10 | Begin { 11 | $AuditConfig = Get-AdminAuditLogConfig 12 | } 13 | Process { 14 | $output = @{ 15 | UnifiedAuditLogging = [bool]$AuditConfig.UnifiedAuditLogIngestionEnabled 16 | } 17 | 18 | return $output 19 | } 20 | End { 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /occam/occam.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'occam' 3 | # 4 | # Generated by: Caleb Albers 5 | # 6 | # Generated on: 1/8/2021 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest 12 | # RootModule = '' 13 | 14 | # Version number of this module. 15 | # Follows https://semver.org Semantic Versioning 2.0.0 16 | # Given a version number MAJOR.MINOR.PATCH, increment the: 17 | # -- MAJOR version when you make incompatible API changes, 18 | # -- MINOR version when you add functionality in a backwards-compatible manner, and 19 | # -- PATCH version when you make backwards-compatible bug fixes. 20 | 21 | ModuleVersion = '1.1.0' 22 | 23 | # ID used to uniquely identify this module 24 | GUID = '2b29a79c-8399-4ad2-b6b9-a826020da7d9' 25 | 26 | # Author of this module 27 | Author = 'Caleb Albers' 28 | 29 | # Company or vendor of this module 30 | CompanyName = 'Pliancy' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'This module provides a rule engine and test suite for O365 tenant compliance.' 34 | 35 | # Copyright information of this module 36 | Copyright = 'https://github.com/pliancy/occam/blob/master/LICENSE' 37 | 38 | # Minimum version of the Windows PowerShell engine required by this module 39 | #PowerShellVersion = '' 40 | 41 | # Name of the Windows PowerShell host required by this module 42 | # PowerShellHostName = '' 43 | 44 | # Minimum version of the Windows PowerShell host required by this module 45 | # PowerShellHostVersion = '' 46 | 47 | # Minimum version of the .NET Framework required by this module 48 | # DotNetFrameworkVersion = '' 49 | 50 | # Minimum version of the common language runtime (CLR) required by this module 51 | # CLRVersion = '' 52 | 53 | # Processor architecture (None, X86, Amd64) required by this module 54 | # ProcessorArchitecture = '' 55 | 56 | # Modules that must be imported into the global environment prior to importing this module 57 | RequiredModules = @('ExchangeOnlineManagement', 'MSOnline') 58 | 59 | # Assemblies that must be loaded prior to importing this module 60 | # RequiredAssemblies = @() 61 | 62 | # Script files (.ps1) that are run in the caller's environment prior to importing this module 63 | # ScriptsToProcess = @() 64 | 65 | # Type files (.ps1xml) to be loaded when importing this module 66 | # TypesToProcess = @() 67 | 68 | # Format files (.ps1xml) to be loaded when importing this module 69 | # FormatsToProcess = @() 70 | 71 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 72 | NestedModules = 'Internal/Build-MsolProxy.ps1', 73 | 'Internal/Build-RuleSet.ps1', 74 | 'Internal/Invoke-TenantAudit.ps1', 75 | 'Internal/Invoke-TenantListGUI.ps1', 76 | 'Internal/Write-PSObject.ps1', 77 | 'Public/Invoke-Occam.ps1' 78 | 79 | # Functions to export from this module 80 | FunctionsToExport = 'Invoke-Occam' 81 | 82 | #FunctionsToExport = '*' 83 | 84 | # Cmdlets to export from this module 85 | CmdletsToExport = @() 86 | 87 | # Variables to export from this module 88 | VariablesToExport = '*' 89 | 90 | # Aliases to export from this module 91 | AliasesToExport = '*' 92 | 93 | # List of all modules packaged with this module 94 | # ModuleList = @() 95 | 96 | # List of all files packaged with this module 97 | # FileList = @() 98 | 99 | # Private data to pass to the module specified in RootModule/ModuleToProcess 100 | # PrivateData = '' 101 | 102 | # HelpInfo URI of this module 103 | HelpInfoURI = 'https://github.com/pliancy/occam' 104 | 105 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 106 | # DefaultCommandPrefix = '' 107 | 108 | } 109 | --------------------------------------------------------------------------------