PowerPlatform-EnterprisePolicies
microsoft/PowerPlatform-EnterprisePolicies/CLAUDE.md
PowerShell module and scripts for managing Power Platform Enterprise Policies as Azure resources. Supports: - Customer Managed Key (CMK) policies for encryption - Subnet Injection policies for virtual network delegation - Diagnostic tools for VNET functionality troubleshooting Supports multiple Azure environments: AzureCloud, AzureUSGovernment (DoD, USGovHigh), AzureChinaCloud. Testing requirement: Tests must pass on both PowerShell (Core) and Windows PowerShell. The test command only runs in the current environment, so you must invoke it separately in each: CRITICAL: Always verify that Build…
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
PowerShell module and scripts for managing Power Platform Enterprise Policies as Azure resources. Supports:
- **Customer Managed Key (CMK)** policies for encryption
- **Subnet Injection** policies for virtual network delegation
- Diagnostic tools for VNET functionality troubleshooting
Supports multiple Azure environments: AzureCloud, AzureUSGovernment (DoD, USGovHigh), AzureChinaCloud.
## Build Commands
```powershell
# Restore NuGet packages (required before building/testing)
dotnet restore
# Build the module to Release directory
Build/build.ps1 -Tasks @("Build")
# Run tests (in current PowerShell environment)
Build/build.ps1 -Tasks @("Test")
# Build and test together
Build/build.ps1 -Tasks @("Build", "Test")
# Generate markdown documentation (updates docs/ folder)
Build/build.ps1 -Tasks @("BuildHelp")
```
**Testing requirement:** Tests must pass on both PowerShell (Core) and Windows PowerShell. The test command only runs in the current environment, so you must invoke it separately in each:
```powershell
# Run in Windows PowerShell
powershell -Command "Build/build.ps1 -Tasks @('Test')"
# Run in PowerShell Core
pwsh -Command "Build/build.ps1 -Tasks @('Test')"
```
**CRITICAL:** Always verify that `Build Succeeded!` appears in the output after running any build task. If the build fails or errors occur, do not proceed - investigate and fix the issue first. Build failures can leave files in an incomplete state (e.g., `BuildHelp` may not append the Types section to docs if it fails mid-task).
**Important:** The `docs/` folder contains autogenerated documentation. After any changes to module scripts, run `.\Build\build.ps1 -Tasks @("BuildHelp")` to regenerate the docs.
**NEVER manually edit docs for public functions** - only edit the comment-based help (`.SYNOPSIS`, `.DESCRIPTION`, `.OUTPUTS`, `.EXAMPLE`) in the script file itself, then run BuildHelp. Manual edits will be overwritten.
**Exception:** Types in `Private/Types.psm1` (classes and enums) cannot be autogenerated and require manual documentation updates in `docs/`.
**Note:** Non-Microsoft contributors must modify `Nuget.config` to point to the public NuGet repository.
## Running Tests
Tests use Pester loaded from NuGet packages (version defined in `Directory.Packages.props`). The test framework validates:
- Module structure and manifest
- Public/Private functions
- File headers (Microsoft sample code disclaimer required)
```powershell
# Load test environment in VSCode (Run and Debug > "Load Modules")
# Or manually:
. Source/Tests/Shared.ps1
# Tests are in Source/Tests/*.Tests.ps1
# Mock Azure module: Source/Tests/FakeAzModule/FakeAZ.psd1
```
### Fake Az Module for Testing
The tests use a fake Az module (`Source/Tests/FakeAzModule/`) instead of the real Az module. When adding new cmdlets that call Az cmdlets:
1. Check if the Az cmdlet is already in `Fake-AzModuleFunctions.psm1`
2. If not, add a stub function with the required parameters:
```powershell
function Remove-AzResource {
param(
[string]$ResourceId,
[switch]$Force
)
}
```
3. In tests, mock the function with `-ModuleName "Microsoft.PowerPlatform.EnterprisePolicies"`
## Architecture
### Module Structure
```
Source/Microsoft.PowerPlatform.EnterprisePolicies/
├── Public/
│ └── SubnetInjection/
│ ├── New-SubnetInjectionEnterprisePolicy.ps1 # Create subnet injection policy
│ ├── Get-SubnetInjectionEnterprisePolicy.ps1 # Retrieve subnet injection policies
│ ├── Remove-SubnetInjectionEnterprisePolicy.ps1 # Remove subnet injection policy
│ ├── New-VnetForSubnetDelegation.ps1 # VNet setup cmdlet
│ └── Diagnostics/ # Diagnostic cmdlets
│ ├── Get-EnvironmentUsage.ps1
│ ├── Get-EnvironmentHistoricalUsage.ps1
│ ├── Get-EnvironmentRegion.ps1
│ ├── Test-AccountPermissions.ps1
│ ├── Test-DnsResolution.ps1
│ └── Test-NetworkConnectivity.ps1
└── Private/ # Internal implementation
├── Types.psm1 # Enums and data classes
├── AuthenticationOperations.ps1 # Azure authentication, MSAL client caching
├── EnvironmentOperations.ps1 # BAP environment operations
├── RESTHelpers.ps1 # HTTP client with retry logic, BAP API endpoints
├── AzHelper.ps1 # Azure resource operations
├── CacheMethods.ps1 # Response caching
└── VnetValidations.ps1 # Network validation
```
### Legacy Scripts (DO NOT follow these patterns)
Everything under `Source/` outside the module and tests is legacy code being replaced:
- `Source/Cmk/` - Legacy CMK scripts
- `Source/SubnetInjection/` - Legacy subnet injection scripts
- `Source/Common/` - Legacy shared helpers
**Do not use these as reference for new code.** Follow patterns in the module only.
`Source/Tests/` is NOT legacy - it contains the Pester tests for the module.
### Key Types (Private/Types.psm1)
- `BAPEndpoint` enum: tip1, tip2, prod, usgovhigh, dod, china
- `AzureEnvironment` enum: AzureCloud, AzureChinaCloud, AzureUSGovernment
- `PolicyType` enum: Encryption, NetworkInjection, Identity
- Data classes: `NetworkUsage`, `VnetInformation`, `EnvironmentNetworkUsageDocument`
### REST API Pattern
The module uses `RESTHelpers.ps1` for all BAP (Business Application Platform) API calls:
- Singleton `HttpClient` with User-Agent header
- Retry logic with `Retry-After` header support (429/503 responses)
- Environment-specific endpoints via `Get-BAPEndpointUrl`
### Existing Utility Functions
Before creating new helper functions, check if one already exists:
- **`ConvertFrom-JsonToClass`** (RESTHelpers.ps1): Converts JSON to typed classes. Use this instead of creating custom converters.
- **`Get-BAPEndpointUrl`** (RESTHelpers.ps1): Gets BAP API endpoint URLs for a given endpoint
- **`Get-BAPResourceUrl`** (RESTHelpers.ps1): Gets BAP resource/audience URLs for token acquisition
- **`Connect-Azure`** (AuthenticationOperations.ps1): Handles Azure authentication with endpoint mapping
### Authentication Flow
`AuthenticationOperations.ps1` handles Azure authentication:
- Reuses existing `AzContext` when available
- Maps `BAPEndpoint` to `AzureEnvironment` for login
- Supports tenant-specific authentication with `-TenantId`
## Code Conventions
### Required File Header
All `.ps1` files in the module (`Source/Microsoft.PowerPlatform.EnterprisePolicies/`) must start with this Microsoft sample code disclaimer:
```powershell
<#
SAMPLE CODE NOTICE
THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED,
OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY.
THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER.
NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO.
#>
```
**Exception:** Test files (`Source/Tests/*.Tests.ps1`) do not require this header.
### Creating New PowerShell Files (CRLF Line Endings)
**CRITICAL:** The Write tool creates files with LF line endings, but the header disclaimer test expects CRLF (`\r\n`). After creating any new `.ps1` file in the module, you **MUST** fix the line endings by running:
```powershell
Set-Content -Path 'path/to/file.ps1' -Value (Get-Content 'path/to/file.ps1')
```
This reads the file and writes it back with Windows CRLF line endings. Without this step, the "Contains header disclaimer file" test will fail.
**Example workflow for creating a new cmdlet:**
1. Use the Write tool to create the file with the header and code
2. Immediately run the `Set-Content` command to fix line endings
3. Then run tests
### Common Parameters for Public Cmdlets
Public cmdlets that call Azure/BAP APIs should include these common parameters:
- `TenantId` - Optional Azure AD tenant ID
- `Endpoint` - BAP endpoint (default: `[BAPEndpoint]::Prod`)
- `ForceAuth` - Switch to force re-authentication instead of reusing existing session
### General Conventions
- Public functions go in `Public/` subdirectories, private in `Private/`
- Each public/private function should have a corresponding test file
- Functions use advanced parameter validation with `CmdletBinding`
- Use `$ErrorActionPreference = "Stop"` at the start of functions
- In `HelpMessage` attributes, do not enumerate specific enum values (e.g., don't say "AzureCloud, AzureUSGovernment, AzureChinaCloud") - enum values may change over time
### PowerShell Best Practices
- **Avoid code duplication**: Consolidate similar functions into one using parameter sets
- **Use parameter sets**: When a function has mutually exclusive behaviors, use `ParameterSetName` to define them
- **Use splatting**: Build `$params` hashtable and call cmdlets with `@params` instead of long parameter lists
- **Use enums**: When a parameter accepts a fixed set of values, use the defined enums (`PolicyType`, `BAPEndpoint`, etc.) instead of strings
- **Always use named parameters**: When calling functions, use `-ParameterName $value` syntax, not positional arguments
- **Validate parameters**: Use `[ValidateNotNullOrEmpty()]` for mandatory string parameters; use `[string]::IsNullOrWhiteSpace()` for optional parameters when building splatted calls
- **Single authentication call**: Call `Connect-Azure` once at the start of a function, not in each branch
### Testing Conventions
- Write concise, meaningful tests focused on behavior and error handling
- Avoid testing implementation details (e.g., "Should call X with correct parameters") - focus on outcomes
- Avoid redundant tests that exercise the same code path with different inputs
- When using `ConvertTo-SecureString -AsPlainText` in tests, add the suppression attribute at the top of the file:
```powershell
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="Unit test code")]
param()
```
- **Testing internal types**: Custom types defined in `Private/Types.psm1` aren't directly accessible in tests. Use `InModuleScope` to access them:
```powershell
It 'Should validate resource ID' {
InModuleScope "Microsoft.PowerPlatform.EnterprisePolicies" {
$attribute = [ValidateAzureResourceIdAttribute]::new("Microsoft.PowerPlatform/enterprisePolicies")
{ $attribute.Validate($resourceId, $null) } | Should -Not -Throw
}
}
```
## Dependencies
Required PowerShell modules (installed via `InstallPowerAppsCmdlets.ps1`):
- Az.Accounts, Az.Network, Az.Resources, Az.PowerPlatform
- Pester (via NuGet for testing, version in `Directory.Packages.props`)
## Git Workflow
- **Never push directly to main** - the repository requires pull requests
- **Always create a feature branch** with the naming pattern: `user-alias/relevant-branch-name`
- Example: `osfaixat/update-environmentregion-api`
- Create a pull request from your feature branch to main
## CI/CD
Azure Pipelines with security scanning (CodeQL, CredScan, BinSkim, PoliCheck). Version managed by GitVersion with semantic versioning.
### Commit Message Versioning
Use these in commit messages to control version bumps:
- **`+semver:major`** (suffix) - Breaking API changes or milestone accomplishments (use sparingly)
- **`+semver:minor`** (suffix) - New public cmdlets or significant behavior changes via private functions
- **`+semver:patch`** (suffix) - Small bug fixes (default if no suffix)
- **`[skip ci]`** (prefix) - Changes that don't affect the module (CLAUDE.md, README.md, docs, legacy scripts)
Examples:
```
Add Get-SubnetInjectionEnterprisePolicy cmdlet +semver:minor
Fix null reference in error handling +semver:patch
[skip ci] Update CLAUDE.md with coding guidelines
```
## PR Checklist
Before submitting a PR, ensure:
1. **If module code changed** (`Source/Microsoft.PowerPlatform.EnterprisePolicies/`):
- Tests pass in both PowerShell Core (`pwsh`) and Windows PowerShell (`powershell`)
- Run `Build/build.ps1 -Tasks @("BuildHelp")` to regenerate docs
2. Update `README.md` if adding new user-facing features or parameters
3. **If modifying `Types.psm1`**:
- Create/update manual documentation in `docs/en-US/Microsoft.PowerPlatform.EnterprisePolicies/`
- Update `Build/build.settings.ps1` PostBuildHelp task to include references to new types
- Add new types to the `$ExportableTypes` array in Types.psm1
**Note:** Tests are not required for changes to legacy scripts, README.md, CLAUDE.md, or other non-module files.
## Adding New Types
When adding new classes or enums to `Private/Types.psm1`:
1. **Keep types minimal**: Only include properties that are actually needed. Don't add every possible property from an API response - include only what consumers will use.
2. **Add to $ExportableTypes**: New types must be added to the `$ExportableTypes` array at the bottom of Types.psm1 to be exported.
3. **Create manual documentation**: Types cannot be auto-documented. Create a markdown file in `docs/en-US/Microsoft.PowerPlatform.EnterprisePolicies/` following the manual type documentation format (see below).
4. **Update build.settings.ps1**: Add entries to the `$markdownToAppend` variable in the `PostBuildHelp` task to link to your documentation file.
### Custom Validation Attributes
When creating custom `ValidateArgumentsAttribute` classes (like `ValidateAzureResourceIdAttribute`), the type accelerator registration in Types.psm1 automatically registers both:
- The full name (e.g., `ValidateAzureResourceIdAttribute`)
- The short name without "Attribute" suffix (e.g., `ValidateAzureResourceId`)
This allows using `[ValidateAzureResourceId("...")]` syntax in parameter declarations. The registration logic handles this automatically for any type ending in "Attribute".
### Manual Type Documentation Format
**CRITICAL**: Manual type docs use a different format than autogenerated cmdlet docs. Using the wrong format will break the build.
**Key metadata differences:**
- **Autogenerated cmdlet docs**: `document type: cmdlet`, no `autogenerated` field
- **Manual type docs**: `document type: class` (or `enum`), must include `autogenerated: false`
**Required structure for classes:**
```markdown
---
document type: class
external help file: Microsoft.PowerPlatform.EnterprisePolicies-Help.xml
HelpUri: ''
Locale: en-US
Module Name: Microsoft.PowerPlatform.EnterprisePolicies
ms.date: MM/DD/YYYY
PlatyPS schema version: 2024-05-01
title: ClassName
autogenerated: false
---
# ClassName
## DESCRIPTION
The `ClassName` class represents...
## PROPERTIES
### PropertyName
Type: `System.String`
Description: Description of the property.
## METHODS
This class does not define any methods.
## EXAMPLES
```powershell
$obj = [ClassName]::new()
$obj.PropertyName = "value"
```
```
**Important notes:**
- Use `# ClassName` as the main heading (not `## ClassName`)
- Use `### PropertyName` for each property with `Type:` and `Description:` lines below
- Examples must show **PowerShell class initialization**, not JSON
- Multiple related classes can be in one file, separated by `---` horizontal rules
- See `docs/en-US/Microsoft.PowerPlatform.EnterprisePolicies/NetworkUsage.md` for a reference example
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
No one has posted yet. Be the first.

