From 27bfb41e471bcf9a750606270367464b7c7fbc30 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 17:02:28 +0100 Subject: [PATCH 01/76] remove ready for sampler --- .gitignore | 22 - CONTRIBUTING.md | 45 - FabricTools/FabricTools.psd1 | 209 - FabricTools/FabricTools.psm1 | 92 - FabricTools/private/Test-TokenExpired.ps1 | 66 - FabricTools/private/Write-Message.ps1 | 84 - .../Capacity/Get-AllFabricCapacities.ps1 | 75 - .../public/Capacity/Get-FabricCapacity.ps1 | 39 - .../Get-FabricCapacityRefreshables.ps1 | 36 - .../Capacity/Get-FabricCapacitySkus.ps1 | 37 - .../Capacity/Get-FabricCapacityState.ps1 | 48 - .../Get-FabricCapacityTenantOverrides.ps1 | 33 - .../Capacity/Get-FabricCapacityWorkload.ps1 | 42 - .../public/Capacity/Resume-FabricCapacity.ps1 | 51 - .../Capacity/Suspend-FabricCapacity.ps1 | 52 - .../public/Confirm-FabricAuthToken.ps1 | 50 - FabricTools/public/Connect-FabricAccount.ps1 | 56 - .../Eventhouse/Get-FabricEventhouse.ps1 | 213 - .../Eventhouse/New-FabricEventhouse.ps1 | 104 - .../Eventhouse/Remove-FabricEventhouse.ps1 | 106 - .../Eventhouse/Set-FabricEventhouse.ps1 | 130 - .../Eventstream/Get-FabricEventstream.ps1 | 133 - .../Eventstream/New-FabricEventstream.ps1 | 87 - .../Eventstream/Remove-FabricEventstream.ps1 | 105 - .../Eventstream/Set-FabricEventstream.ps1 | 113 - .../public/Get-AllFabricDatasetRefreshes.ps1 | 57 - .../public/Get-FabricAPIClusterURI.ps1 | 43 - FabricTools/public/Get-FabricAuthToken.ps1 | 39 - FabricTools/public/Get-FabricConnection.ps1 | 49 - .../public/Get-FabricDatasetRefreshes.ps1 | 69 - FabricTools/public/Get-FabricDebugInfo.ps1 | 46 - .../public/Get-FabricUsageMetricsQuery.ps1 | 84 - FabricTools/public/Get-SHA256.ps1 | 36 - .../public/Invoke-FabricAPIRequest.ps1 | 175 - .../public/Invoke-FabricDatasetRefresh.ps1 | 47 - FabricTools/public/Item/Export-FabricItem.ps1 | 116 - FabricTools/public/Item/Get-FabricItem.ps1 | 67 - FabricTools/public/Item/Import-FabricItem.ps1 | 239 - FabricTools/public/Item/Remove-FabricItem.ps1 | 67 - .../KQL Dashboard/Get-FabricKQLDashboard.ps1 | 112 - .../Get-FabricKQLDashboardDefinition.ps1 | 119 - .../KQL Dashboard/New-FabricKQLDashboard.ps1 | 92 - .../KQL Database/Get-FabricKQLDatabase.ps1 | 228 - .../KQL Database/New-FabricKQLDatabase.ps1 | 106 - .../KQL Database/Remove-FabricKQLDatabase.ps1 | 77 - .../KQL Database/Set-FabricKQLDatabase.ps1 | 114 - .../KQL Queryset/Get-FabricKQLQueryset.ps1 | 130 - .../KQL Queryset/Invoke-FabricKQLCommand.ps1 | 230 - .../KQL Queryset/Remove-FabricKQLQueryset.ps1 | 77 - .../KQL Queryset/Set-FabricKQLQueryset.ps1 | 111 - .../public/Lakehouse/New-FabricLakehouse.ps1 | 113 - .../SQL Database/Get-FabricSQLDatabase.ps1 | 78 - .../SQL Database/New-FabricSQLDatabase.ps1 | 89 - .../SQL Database/Remove-FabricSQLDatabase.ps1 | 72 - FabricTools/public/Set-FabricAuthToken.ps1 | 104 - .../Tenant/Get-FabricTenantSettings.ps1 | 27 - .../Utils/Get-FabricLongRunningOperation.ps1 | 88 - .../Get-FabricLongRunningOperationResult.ps1 | 67 - .../public/Utils/Set-FabricApiHeaders.ps1 | 116 - .../public/Utils/Test-FabricApiResponse.ps1 | 53 - .../Add-FabricWorkspaceRoleAssignment.ps1 | 94 - .../public/Workspace/Get-FabricWorkspace.ps1 | 45 - .../public/Workspace/Get-FabricWorkspace2.ps1 | 190 - .../Get-FabricWorkspaceDatasetRefreshes.ps1 | 53 - .../Get-FabricWorkspaceRoleAssignment.ps1 | 56 - .../Get-FabricWorkspaceUsageMetricsData.ps1 | 60 - .../Workspace/Get-FabricWorkspaceUsers.ps1 | 57 - .../public/Workspace/New-FabricWorkspace.ps1 | 80 - .../public/Workspace/New-FabricWorkspace2.ps1 | 89 - .../New-FabricWorkspaceUsageMetricsReport.ps1 | 48 - .../Register-FabricWorkspaceToCapacity.ps1 | 71 - .../Workspace/Remove-FabricWorkspace.ps1 | 37 - .../Unregister-FabricWorkspaceToCapacity.ps1 | 58 - .../tiago/Private/Get-FileDefinitionParts.ps1 | 57 - .../tiago/Private/Test-TokenExpired.ps1 | 57 - FabricTools/tiago/Private/Write-Message.ps1 | 84 - .../Public/Capacity/Get-FabricCapacity.ps1 | 92 - .../Public/Copy Job/Get-FabricCopyJob.ps1 | 100 - .../Copy Job/Get-FabricCopyJobDefinition.ps1 | 75 - .../Public/Copy Job/New-FabricCopyJob.ps1 | 145 - .../Public/Copy Job/Remove-FabricCopyJob.ps1 | 61 - .../Public/Copy Job/Update-FabricCopyJob.ps1 | 90 - .../Update-FabricCopyJobDefinition.ps1 | 134 - .../Public/Dashboard/Get-FabricDashboard.ps1 | 54 - .../Data Pipeline/Get-FabricDataPipeline.ps1 | 99 - .../Data Pipeline/New-FabricDataPipeline.ps1 | 84 - .../Remove-FabricDataPipeline.ps1 | 60 - .../Update-FabricDataPipeline.ps1 | 90 - .../Public/Datamart/Get-FabricDatamart.ps1 | 80 - ...Assign-FabricDomainWorkspaceByCapacity.ps1 | 113 - .../Assign-FabricDomainWorkspaceById.ps1 | 83 - ...ssign-FabricDomainWorkspaceByPrincipal.ps1 | 119 - ...gn-FabricDomainWorkspaceRoleAssignment.ps1 | 102 - .../tiago/Public/Domain/Get-FabricDomain.ps1 | 121 - .../Domain/Get-FabricDomainWorkspace.ps1 | 80 - .../tiago/Public/Domain/New-FabricDomain.ps1 | 128 - .../Public/Domain/Remove-FabricDomain.ps1 | 68 - .../Domain/Unassign-FabricDomainWorkspace.ps1 | 96 - ...gn-FabricDomainWorkspaceRoleAssignment.ps1 | 103 - .../Public/Domain/Update-FabricDomain.ps1 | 112 - .../Environment/Get-FabricEnvironment.ps1 | 157 - .../Get-FabricEnvironmentLibrary.ps1 | 76 - .../Get-FabricEnvironmentSparkCompute.ps1 | 76 - .../Get-FabricEnvironmentStagingLibrary.ps1 | 76 - ...t-FabricEnvironmentStagingSparkCompute.ps1 | 78 - .../Environment/New-FabricEnvironment.ps1 | 123 - .../Environment/Publish-FabricEnvironment.ps1 | 104 - .../Environment/Remove-FabricEnvironment.ps1 | 74 - ...Remove-FabricEnvironmentStagingLibrary.ps1 | 85 - .../Stop-FabricEnvironmentPublish.ps1 | 76 - .../Environment/Update-FabricEnvironment.ps1 | 108 - ...e-FabricEnvironmentStagingSparkCompute.ps1 | 177 - ...Upload-FabricEnvironmentStagingLibrary.ps1 | 79 - .../Eventhouse/Get-FabricEventhouse.ps1 | 156 - .../Get-FabricEventhouseDefinition.ps1 | 124 - .../Eventhouse/New-FabricEventhouse.ps1 | 193 - .../Eventhouse/Remove-FabricEventhouse.ps1 | 73 - .../Eventhouse/Update-FabricEventhouse.ps1 | 105 - .../Update-FabricEventhouseDefinition.ps1 | 168 - .../Eventstream/Get-FabricEventstream.ps1 | 159 - .../Get-FabricEventstreamDefinition.ps1 | 121 - .../Eventstream/New-FabricEventstream.ps1 | 188 - .../Eventstream/Remove-FabricEventstream.ps1 | 73 - .../Eventstream/Update-FabricEventstream.ps1 | 107 - .../Update-FabricEventstreamDefinition.ps1 | 180 - .../Get-FabricExternalDataShares.ps1 | 50 - .../Revoke-FabricExternalDataShares.ps1 | 61 - .../KQL Dashboard/Get-FabricKQLDashboard.ps1 | 155 - .../Get-FabricKQLDashboardDefinition.ps1 | 120 - .../KQL Dashboard/New-FabricKQLDashboard.ps1 | 188 - .../Remove-FabricKQLDashboard.ps1 | 73 - .../Update-FabricKQLDashboard.ps1 | 107 - .../Update-FabricKQLDashboardDefinition.ps1 | 166 - .../KQL Database/Get-FabricKQLDatabase.ps1 | 153 - .../Get-FabricKQLDatabaseDefinition.ps1 | 129 - .../KQL Database/New-FabricKQLDatabase.ps1 | 288 - .../KQL Database/Remove-FabricKQLDatabase.ps1 | 74 - .../KQL Database/Update-FabricKQLDatabase.ps1 | 108 - .../Update-FabricKQLDatabaseDefinition.ps1 | 202 - .../KQL Queryset/Get-FabricKQLQueryset.ps1 | 154 - .../Get-FabricKQLQuerysetDefinition.ps1 | 120 - .../KQL Queryset/New-FabricKQLQueryset.ps1 | 188 - .../KQL Queryset/Remove-FabricKQLQueryset.ps1 | 73 - .../KQL Queryset/Update-FabricKQLQueryset.ps1 | 107 - .../Update-FabricKQLQuerysetDefinition.ps1 | 166 - .../Public/Lakehouse/Get-FabricLakehouse.ps1 | 154 - .../Lakehouse/Get-FabricLakehouseTable.ps1 | 104 - .../Lakehouse/Load-FabricLakehouseTable.ps1 | 139 - .../Public/Lakehouse/New-FabricLakehouse.ps1 | 136 - .../Lakehouse/Remove-FabricLakehouse.ps1 | 73 - .../Start-FabricLakehouseTableMaintenance.ps1 | 160 - .../Lakehouse/Update-FabricLakehouse.ps1 | 108 - .../ML Experiment/Get-FabricMLExperiment.ps1 | 156 - .../ML Experiment/New-FabricMLExperiment.ps1 | 130 - .../Remove-FabricMLExperiment.ps1 | 72 - .../Update-FabricMLExperiment.ps1 | 105 - .../Public/ML Model/Get-FabricMLModel.ps1 | 156 - .../Public/ML Model/New-FabricMLModel.ps1 | 130 - .../Public/ML Model/Remove-FabricMLModel.ps1 | 72 - .../Public/ML Model/Update-FabricMLModel.ps1 | 93 - .../Get-FabricMirroredDatabase.ps1 | 157 - .../Get-FabricMirroredDatabaseDefinition.ps1 | 109 - .../Get-FabricMirroredDatabaseStatus.ps1 | 52 - .../Get-FabricMirroredDatabaseTableStatus.ps1 | 97 - .../New-FabricMirroredDatabase.ps1 | 186 - .../Remove-FabricMirroredDatabase.ps1 | 72 - .../Start-FabricMirroredDatabaseMirroring.ps1 | 51 - .../Stop-FabricMirroredDatabaseMirroring.ps1 | 53 - .../Update-FabricMirroredDatabase.ps1 | 107 - ...pdate-FabricMirroredDatabaseDefinition.ps1 | 168 - .../Get-FabricMirroredWarehouse.ps1 | 157 - .../Public/Notebook/Get-FabricNotebook.ps1 | 155 - .../Notebook/Get-FabricNotebookDefinition.ps1 | 131 - .../Public/Notebook/New-FabricNotebook.ps1 | 193 - .../Public/Notebook/New-FabricNotebookNEW.ps1 | 158 - .../Public/Notebook/Remove-FabricNotebook.ps1 | 73 - .../Public/Notebook/Update-FabricNotebook.ps1 | 107 - .../Update-FabricNotebookDefinition.ps1 | 179 - .../Get-FabricPaginatedReport.ps1 | 155 - .../Update-FabricPaginatedReport.ps1 | 105 - .../tiago/Public/Reflex/Get-FabricReflex.ps1 | 156 - .../Reflex/Get-FabricReflexDefinition.ps1 | 124 - .../tiago/Public/Reflex/New-FabricReflex.ps1 | 193 - .../Public/Reflex/Remove-FabricReflex.ps1 | 73 - .../Public/Reflex/Update-FabricReflex.ps1 | 105 - .../Reflex/Update-FabricReflexDefinition.ps1 | 168 - .../tiago/Public/Report/Get-FabricReport.ps1 | 156 - .../Report/Get-FabricReportDefinition.ps1 | 124 - .../tiago/Public/Report/New-FabricReport.ps1 | 150 - .../Public/Report/Remove-FabricReport.ps1 | 73 - .../Public/Report/Update-FabricReport.ps1 | 105 - .../Report/Update-FabricReportDefinition.ps1 | 145 - .../SQL Endpoints/Get-FabricSQLEndpoint.ps1 | 154 - .../Get-FabricSemanticModel.ps1 | 156 - .../Get-FabricSemanticModelDefinition.ps1 | 125 - .../New-FabricSemanticModel.ps1 | 145 - .../Remove-FabricSemanticModel.ps1 | 73 - .../Update-FabricSemanticModel.ps1 | 105 - .../Update-FabricSemanticModelDefinition.ps1 | 135 - .../Get-FabricSparkJobDefinition.ps1 | 156 - ...Get-FabricSparkJobDefinitionDefinition.ps1 | 124 - .../New-FabricSparkJobDefinition.ps1 | 194 - .../Remove-FabricSparkJobDefinition.ps1 | 72 - ...Start-FabricSparkJobDefinitionOnDemand.ps1 | 118 - .../Update-FabricSparkJobDefinition.ps1 | 104 - ...ate-FabricSparkJobDefinitionDefinition.ps1 | 168 - .../Spark/Get-FabricSparkCustomPool.ps1 | 161 - .../Public/Spark/Get-FabricSparkSettings.ps1 | 119 - .../Spark/New-FabricSparkCustomPool.ps1 | 190 - .../Spark/Remove-FabricSparkCustomPool.ps1 | 72 - .../Spark/Update-FabricSparkCustomPool.ps1 | 164 - .../Spark/Update-FabricSparkSettings.ps1 | 189 - ...t-FabricCapacityTenantSettingOverrides.ps1 | 72 - ...Get-FabricDomainTenantSettingOverrides.ps1 | 55 - .../Public/Tenant/Get-FabricTenantSetting.ps1 | 78 - ...-FabricWorkspaceTenantSettingOverrides.ps1 | 54 - ...e-FabricCapacityTenantSettingOverrides.ps1 | 60 - ...e-FabricCapacityTenantSettingOverrides.ps1 | 136 - .../Tenant/Update-FabricTenantSetting.ps1 | 164 - .../Get-FabricUserListAccessEntities.ps1 | 68 - .../tiago/Public/Utils/Convert-FromBase64.ps1 | 55 - .../tiago/Public/Utils/Convert-ToBase64.ps1 | 60 - .../Utils/Get-FabricLongRunningOperation.ps1 | 88 - .../Get-FabricLongRunningOperationResult.ps1 | 67 - .../Public/Utils/Invoke-FabricAPIRequest.ps1 | 133 - .../Public/Utils/Set-FabricApiHeaders.ps1 | 116 - .../Public/Warehouse/Get-FabricWarehouse.ps1 | 101 - .../Public/Warehouse/New-FabricWarehouse.ps1 | 83 - .../Warehouse/Remove-FabricWarehouse.ps1 | 61 - .../Warehouse/Update-FabricWarehouse.ps1 | 91 - .../Workspace/Add-FabricWorkspaceIdentity.ps1 | 101 - .../Add-FabricWorkspaceRoleAssignment.ps1 | 112 - .../Assign-FabricWorkspaceCapacity.ps1 | 83 - .../Public/Workspace/Get-FabricWorkspace.ps1 | 149 - .../Get-FabricWorkspaceRoleAssignment.ps1 | 153 - .../Public/Workspace/New-FabricWorkspace.ps1 | 122 - .../Workspace/Remove-FabricWorkspace.ps1 | 68 - .../Remove-FabricWorkspaceIdentity.ps1 | 94 - .../Remove-FabricWorkspaceRoleAssignment.ps1 | 74 - .../Unassign-FabricWorkspaceCapacity.ps1 | 67 - .../Workspace/Update-FabricWorkspace.ps1 | 103 - .../Update-FabricWorkspaceRoleAssignment.ps1 | 104 - LICENSE | 21 - README.md | 106 - ReleaseNotes.md | 54 - .../Sales.Dataset/.pbi/editorSettings.json | 7 - .../Sales.Dataset/definition.pbidataset | 6 - .../Sales.Dataset/diagramLayout.json | 144 - .../SamplePBIP/Sales.Dataset/item.config.json | 4 - .../Sales.Dataset/item.metadata.json | 4 - Test/SamplePBIP/Sales.Dataset/model.bim | 15833 ---------------- .../Light4437032645752863.json | 499 - ...7-1a23-4b5f-bd8b-8dc472366284171093267.jpg | Bin 366046 -> 0 bytes .../SharedResources/BaseThemes/CY23SU04.json | 674 - Test/SamplePBIP/Sales.Report/definition.pbir | 9 - Test/SamplePBIP/Sales.Report/item.config.json | 4 - .../Sales.Report/item.metadata.json | 4 - Test/SamplePBIP/Sales.Report/report.json | 517 - Test/SamplePBIP/Sales.pbip | 13 - Test/Test-DeployPBIP-Overrides.ps1 | 72 - Test/Test-DeployPBIP.ps1 | 45 - Test/Test-Export.ps1 | 11 - Test/Test-ExportForBPA.ps1 | 99 - Test/Test-ExportImport.ps1 | 16 - Test/Test-Import.ps1 | 9 - Test/Test-InvokeFabricRequest.ps1 | 11 - Test/Test-ThemeSwap.ps1 | 44 - Test/sample-resources/logo1.jpg | Bin 366046 -> 0 bytes Test/sample-resources/logo2.jpg | Bin 31754 -> 0 bytes .../Add-FabricWorkspaceRoleAssignment.md | 147 - documentation/Confirm-FabricAuthToken.md | 54 - documentation/Connect-FabricAccount.md | 72 - documentation/Export-FabricItem.md | 128 - documentation/FabricTools.md | 193 - documentation/Get-AllFabricCapacities.md | 75 - .../Get-AllFabricDatasetRefreshes.md | 35 - documentation/Get-FabricAPIclusterURI.md | 35 - documentation/Get-FabricAuthToken.md | 56 - documentation/Get-FabricCapacity.md | 68 - .../Get-FabricCapacityRefreshables.md | 68 - documentation/Get-FabricCapacitySkus.md | 96 - documentation/Get-FabricCapacityState.md | 99 - .../Get-FabricCapacityTenantOverrides.md | 35 - documentation/Get-FabricCapacityWorkload.md | 68 - documentation/Get-FabricDatasetRefreshes.md | 88 - documentation/Get-FabricDebugInfo.md | 58 - documentation/Get-FabricEventhouse.md | 148 - documentation/Get-FabricEventstream.md | 135 - documentation/Get-FabricItem.md | 121 - documentation/Get-FabricKQLDashboard.md | 111 - .../Get-FabricKQLDashboardDefinition.md | 140 - documentation/Get-FabricKQLDatabase.md | 133 - documentation/Get-FabricKQLQueryset.md | 136 - documentation/Get-FabricSQLDatabase.md | 128 - documentation/Get-FabricTenantSettings.md | 32 - documentation/Get-FabricUsageMetricsQuery.md | 114 - documentation/Get-FabricWorkspace.md | 70 - documentation/Get-FabricWorkspace2.md | 198 - .../Get-FabricWorkspaceDatasetRefreshes.md | 69 - .../Get-FabricWorkspaceRoleAssignment.md | 75 - .../Get-FabricWorkspaceUsageMetricsData.md | 84 - documentation/Get-FabricWorkspaceUsers.md | 89 - documentation/Get-Sha256.md | 50 - documentation/Import-FabricItem.md | 116 - documentation/Invoke-FabricAPIRequest.md | 158 - documentation/Invoke-FabricDatasetRefresh.md | 70 - documentation/Invoke-FabricKQLCommand.md | 129 - documentation/New-FabricEventhouse.md | 152 - documentation/New-FabricEventstream.md | 140 - documentation/New-FabricKQLDashboard.md | 138 - documentation/New-FabricKQLDatabase.md | 160 - documentation/New-FabricLakehouse.md | 149 - documentation/New-FabricWorkspace.md | 121 - documentation/New-FabricWorkspace2.md | 136 - .../New-FabricWorkspaceUsageMetricsReport.md | 100 - .../Register-FabricWorkspaceToCapacity.md | 137 - documentation/Remove-FabricEventhouse.md | 154 - documentation/Remove-FabricEventstream.md | 148 - documentation/Remove-FabricItem.md | 135 - documentation/Remove-FabricKQLDatabase.md | 122 - documentation/Remove-FabricKQLQueryset.md | 125 - documentation/Remove-FabricSQLDatabase.md | 92 - documentation/Remove-FabricWorkspace.md | 100 - documentation/Resume-FabricCapacity.md | 130 - documentation/Set-FabricAuthToken.md | 184 - documentation/Set-FabricEventhouse.md | 179 - documentation/Set-FabricEventstream.md | 160 - documentation/Set-FabricKQLDatabase.md | 165 - documentation/Set-FabricKQLQueryset.md | 166 - documentation/Suspend-FabricCapacity.md | 130 - .../Unregister-FabricWorkspaceToCapacity.md | 132 - helper/Get-RootPath.ps1 | 12 - helper/createdocumentation.ps1 | 69 - helper/createmodule.ps1 | 49 - helper/startscriptanalyzer.ps1 | 17 - images/FabricToolsLogo.ico | Bin 195868 -> 0 bytes images/FabricToolsLogo.png | Bin 35558 -> 0 bytes images/FabricToolsLogo.svg | 4 - images/Fabtools.ico | Bin 90225 -> 0 bytes images/Fabtools.png | Bin 543892 -> 0 bytes images/Fabtools50.png | Bin 180186 -> 0 bytes 341 files changed, 51122 deletions(-) delete mode 100644 .gitignore delete mode 100644 CONTRIBUTING.md delete mode 100644 FabricTools/FabricTools.psd1 delete mode 100644 FabricTools/FabricTools.psm1 delete mode 100644 FabricTools/private/Test-TokenExpired.ps1 delete mode 100644 FabricTools/private/Write-Message.ps1 delete mode 100644 FabricTools/public/Capacity/Get-AllFabricCapacities.ps1 delete mode 100644 FabricTools/public/Capacity/Get-FabricCapacity.ps1 delete mode 100644 FabricTools/public/Capacity/Get-FabricCapacityRefreshables.ps1 delete mode 100644 FabricTools/public/Capacity/Get-FabricCapacitySkus.ps1 delete mode 100644 FabricTools/public/Capacity/Get-FabricCapacityState.ps1 delete mode 100644 FabricTools/public/Capacity/Get-FabricCapacityTenantOverrides.ps1 delete mode 100644 FabricTools/public/Capacity/Get-FabricCapacityWorkload.ps1 delete mode 100644 FabricTools/public/Capacity/Resume-FabricCapacity.ps1 delete mode 100644 FabricTools/public/Capacity/Suspend-FabricCapacity.ps1 delete mode 100644 FabricTools/public/Confirm-FabricAuthToken.ps1 delete mode 100644 FabricTools/public/Connect-FabricAccount.ps1 delete mode 100644 FabricTools/public/Eventhouse/Get-FabricEventhouse.ps1 delete mode 100644 FabricTools/public/Eventhouse/New-FabricEventhouse.ps1 delete mode 100644 FabricTools/public/Eventhouse/Remove-FabricEventhouse.ps1 delete mode 100644 FabricTools/public/Eventhouse/Set-FabricEventhouse.ps1 delete mode 100644 FabricTools/public/Eventstream/Get-FabricEventstream.ps1 delete mode 100644 FabricTools/public/Eventstream/New-FabricEventstream.ps1 delete mode 100644 FabricTools/public/Eventstream/Remove-FabricEventstream.ps1 delete mode 100644 FabricTools/public/Eventstream/Set-FabricEventstream.ps1 delete mode 100644 FabricTools/public/Get-AllFabricDatasetRefreshes.ps1 delete mode 100644 FabricTools/public/Get-FabricAPIClusterURI.ps1 delete mode 100644 FabricTools/public/Get-FabricAuthToken.ps1 delete mode 100644 FabricTools/public/Get-FabricConnection.ps1 delete mode 100644 FabricTools/public/Get-FabricDatasetRefreshes.ps1 delete mode 100644 FabricTools/public/Get-FabricDebugInfo.ps1 delete mode 100644 FabricTools/public/Get-FabricUsageMetricsQuery.ps1 delete mode 100644 FabricTools/public/Get-SHA256.ps1 delete mode 100644 FabricTools/public/Invoke-FabricAPIRequest.ps1 delete mode 100644 FabricTools/public/Invoke-FabricDatasetRefresh.ps1 delete mode 100644 FabricTools/public/Item/Export-FabricItem.ps1 delete mode 100644 FabricTools/public/Item/Get-FabricItem.ps1 delete mode 100644 FabricTools/public/Item/Import-FabricItem.ps1 delete mode 100644 FabricTools/public/Item/Remove-FabricItem.ps1 delete mode 100644 FabricTools/public/KQL Dashboard/Get-FabricKQLDashboard.ps1 delete mode 100644 FabricTools/public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 delete mode 100644 FabricTools/public/KQL Dashboard/New-FabricKQLDashboard.ps1 delete mode 100644 FabricTools/public/KQL Database/Get-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/public/KQL Database/New-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/public/KQL Database/Remove-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/public/KQL Database/Set-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/public/KQL Queryset/Get-FabricKQLQueryset.ps1 delete mode 100644 FabricTools/public/KQL Queryset/Invoke-FabricKQLCommand.ps1 delete mode 100644 FabricTools/public/KQL Queryset/Remove-FabricKQLQueryset.ps1 delete mode 100644 FabricTools/public/KQL Queryset/Set-FabricKQLQueryset.ps1 delete mode 100644 FabricTools/public/Lakehouse/New-FabricLakehouse.ps1 delete mode 100644 FabricTools/public/SQL Database/Get-FabricSQLDatabase.ps1 delete mode 100644 FabricTools/public/SQL Database/New-FabricSQLDatabase.ps1 delete mode 100644 FabricTools/public/SQL Database/Remove-FabricSQLDatabase.ps1 delete mode 100644 FabricTools/public/Set-FabricAuthToken.ps1 delete mode 100644 FabricTools/public/Tenant/Get-FabricTenantSettings.ps1 delete mode 100644 FabricTools/public/Utils/Get-FabricLongRunningOperation.ps1 delete mode 100644 FabricTools/public/Utils/Get-FabricLongRunningOperationResult.ps1 delete mode 100644 FabricTools/public/Utils/Set-FabricApiHeaders.ps1 delete mode 100644 FabricTools/public/Utils/Test-FabricApiResponse.ps1 delete mode 100644 FabricTools/public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 FabricTools/public/Workspace/Get-FabricWorkspace.ps1 delete mode 100644 FabricTools/public/Workspace/Get-FabricWorkspace2.ps1 delete mode 100644 FabricTools/public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 delete mode 100644 FabricTools/public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 FabricTools/public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 delete mode 100644 FabricTools/public/Workspace/Get-FabricWorkspaceUsers.ps1 delete mode 100644 FabricTools/public/Workspace/New-FabricWorkspace.ps1 delete mode 100644 FabricTools/public/Workspace/New-FabricWorkspace2.ps1 delete mode 100644 FabricTools/public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 delete mode 100644 FabricTools/public/Workspace/Register-FabricWorkspaceToCapacity.ps1 delete mode 100644 FabricTools/public/Workspace/Remove-FabricWorkspace.ps1 delete mode 100644 FabricTools/public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 delete mode 100644 FabricTools/tiago/Private/Get-FileDefinitionParts.ps1 delete mode 100644 FabricTools/tiago/Private/Test-TokenExpired.ps1 delete mode 100644 FabricTools/tiago/Private/Write-Message.ps1 delete mode 100644 FabricTools/tiago/Public/Capacity/Get-FabricCapacity.ps1 delete mode 100644 FabricTools/tiago/Public/Copy Job/Get-FabricCopyJob.ps1 delete mode 100644 FabricTools/tiago/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Copy Job/New-FabricCopyJob.ps1 delete mode 100644 FabricTools/tiago/Public/Copy Job/Remove-FabricCopyJob.ps1 delete mode 100644 FabricTools/tiago/Public/Copy Job/Update-FabricCopyJob.ps1 delete mode 100644 FabricTools/tiago/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Dashboard/Get-FabricDashboard.ps1 delete mode 100644 FabricTools/tiago/Public/Data Pipeline/Get-FabricDataPipeline.ps1 delete mode 100644 FabricTools/tiago/Public/Data Pipeline/New-FabricDataPipeline.ps1 delete mode 100644 FabricTools/tiago/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 delete mode 100644 FabricTools/tiago/Public/Data Pipeline/Update-FabricDataPipeline.ps1 delete mode 100644 FabricTools/tiago/Public/Datamart/Get-FabricDatamart.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Get-FabricDomain.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Get-FabricDomainWorkspace.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/New-FabricDomain.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Remove-FabricDomain.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspace.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 delete mode 100644 FabricTools/tiago/Public/Domain/Update-FabricDomain.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Get-FabricEnvironment.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Get-FabricEnvironmentLibrary.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/New-FabricEnvironment.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Publish-FabricEnvironment.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Remove-FabricEnvironment.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Stop-FabricEnvironmentPublish.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Update-FabricEnvironment.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 delete mode 100644 FabricTools/tiago/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 delete mode 100644 FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouse.ps1 delete mode 100644 FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Eventhouse/New-FabricEventhouse.ps1 delete mode 100644 FabricTools/tiago/Public/Eventhouse/Remove-FabricEventhouse.ps1 delete mode 100644 FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouse.ps1 delete mode 100644 FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Eventstream/Get-FabricEventstream.ps1 delete mode 100644 FabricTools/tiago/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Eventstream/New-FabricEventstream.ps1 delete mode 100644 FabricTools/tiago/Public/Eventstream/Remove-FabricEventstream.ps1 delete mode 100644 FabricTools/tiago/Public/Eventstream/Update-FabricEventstream.ps1 delete mode 100644 FabricTools/tiago/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/External Data Share/Get-FabricExternalDataShares.ps1 delete mode 100644 FabricTools/tiago/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Database/New-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Database/Remove-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Queryset/New-FabricKQLQueryset.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 delete mode 100644 FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouseTable.ps1 delete mode 100644 FabricTools/tiago/Public/Lakehouse/Load-FabricLakehouseTable.ps1 delete mode 100644 FabricTools/tiago/Public/Lakehouse/New-FabricLakehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Lakehouse/Remove-FabricLakehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 delete mode 100644 FabricTools/tiago/Public/Lakehouse/Update-FabricLakehouse.ps1 delete mode 100644 FabricTools/tiago/Public/ML Experiment/Get-FabricMLExperiment.ps1 delete mode 100644 FabricTools/tiago/Public/ML Experiment/New-FabricMLExperiment.ps1 delete mode 100644 FabricTools/tiago/Public/ML Experiment/Remove-FabricMLExperiment.ps1 delete mode 100644 FabricTools/tiago/Public/ML Experiment/Update-FabricMLExperiment.ps1 delete mode 100644 FabricTools/tiago/Public/ML Model/Get-FabricMLModel.ps1 delete mode 100644 FabricTools/tiago/Public/ML Model/New-FabricMLModel.ps1 delete mode 100644 FabricTools/tiago/Public/ML Model/Remove-FabricMLModel.ps1 delete mode 100644 FabricTools/tiago/Public/ML Model/Update-FabricMLModel.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Notebook/Get-FabricNotebook.ps1 delete mode 100644 FabricTools/tiago/Public/Notebook/Get-FabricNotebookDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Notebook/New-FabricNotebook.ps1 delete mode 100644 FabricTools/tiago/Public/Notebook/New-FabricNotebookNEW.ps1 delete mode 100644 FabricTools/tiago/Public/Notebook/Remove-FabricNotebook.ps1 delete mode 100644 FabricTools/tiago/Public/Notebook/Update-FabricNotebook.ps1 delete mode 100644 FabricTools/tiago/Public/Notebook/Update-FabricNotebookDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 delete mode 100644 FabricTools/tiago/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 delete mode 100644 FabricTools/tiago/Public/Reflex/Get-FabricReflex.ps1 delete mode 100644 FabricTools/tiago/Public/Reflex/Get-FabricReflexDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Reflex/New-FabricReflex.ps1 delete mode 100644 FabricTools/tiago/Public/Reflex/Remove-FabricReflex.ps1 delete mode 100644 FabricTools/tiago/Public/Reflex/Update-FabricReflex.ps1 delete mode 100644 FabricTools/tiago/Public/Reflex/Update-FabricReflexDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Report/Get-FabricReport.ps1 delete mode 100644 FabricTools/tiago/Public/Report/Get-FabricReportDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Report/New-FabricReport.ps1 delete mode 100644 FabricTools/tiago/Public/Report/Remove-FabricReport.ps1 delete mode 100644 FabricTools/tiago/Public/Report/Update-FabricReport.ps1 delete mode 100644 FabricTools/tiago/Public/Report/Update-FabricReportDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 delete mode 100644 FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModel.ps1 delete mode 100644 FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Semantic Model/New-FabricSemanticModel.ps1 delete mode 100644 FabricTools/tiago/Public/Semantic Model/Remove-FabricSemanticModel.ps1 delete mode 100644 FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModel.ps1 delete mode 100644 FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 delete mode 100644 FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 delete mode 100644 FabricTools/tiago/Public/Spark/Get-FabricSparkCustomPool.ps1 delete mode 100644 FabricTools/tiago/Public/Spark/Get-FabricSparkSettings.ps1 delete mode 100644 FabricTools/tiago/Public/Spark/New-FabricSparkCustomPool.ps1 delete mode 100644 FabricTools/tiago/Public/Spark/Remove-FabricSparkCustomPool.ps1 delete mode 100644 FabricTools/tiago/Public/Spark/Update-FabricSparkCustomPool.ps1 delete mode 100644 FabricTools/tiago/Public/Spark/Update-FabricSparkSettings.ps1 delete mode 100644 FabricTools/tiago/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 delete mode 100644 FabricTools/tiago/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 delete mode 100644 FabricTools/tiago/Public/Tenant/Get-FabricTenantSetting.ps1 delete mode 100644 FabricTools/tiago/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 delete mode 100644 FabricTools/tiago/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 delete mode 100644 FabricTools/tiago/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 delete mode 100644 FabricTools/tiago/Public/Tenant/Update-FabricTenantSetting.ps1 delete mode 100644 FabricTools/tiago/Public/Users/Get-FabricUserListAccessEntities.ps1 delete mode 100644 FabricTools/tiago/Public/Utils/Convert-FromBase64.ps1 delete mode 100644 FabricTools/tiago/Public/Utils/Convert-ToBase64.ps1 delete mode 100644 FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperation.ps1 delete mode 100644 FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperationResult.ps1 delete mode 100644 FabricTools/tiago/Public/Utils/Invoke-FabricAPIRequest.ps1 delete mode 100644 FabricTools/tiago/Public/Utils/Set-FabricApiHeaders.ps1 delete mode 100644 FabricTools/tiago/Public/Warehouse/Get-FabricWarehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Warehouse/New-FabricWarehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Warehouse/Remove-FabricWarehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Warehouse/Update-FabricWarehouse.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Get-FabricWorkspace.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/New-FabricWorkspace.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Remove-FabricWorkspace.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Update-FabricWorkspace.ps1 delete mode 100644 FabricTools/tiago/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 ReleaseNotes.md delete mode 100644 Test/SamplePBIP/Sales.Dataset/.pbi/editorSettings.json delete mode 100644 Test/SamplePBIP/Sales.Dataset/definition.pbidataset delete mode 100644 Test/SamplePBIP/Sales.Dataset/diagramLayout.json delete mode 100644 Test/SamplePBIP/Sales.Dataset/item.config.json delete mode 100644 Test/SamplePBIP/Sales.Dataset/item.metadata.json delete mode 100644 Test/SamplePBIP/Sales.Dataset/model.bim delete mode 100644 Test/SamplePBIP/Sales.Report/StaticResources/RegisteredResources/Light4437032645752863.json delete mode 100644 Test/SamplePBIP/Sales.Report/StaticResources/RegisteredResources/_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg delete mode 100644 Test/SamplePBIP/Sales.Report/StaticResources/SharedResources/BaseThemes/CY23SU04.json delete mode 100644 Test/SamplePBIP/Sales.Report/definition.pbir delete mode 100644 Test/SamplePBIP/Sales.Report/item.config.json delete mode 100644 Test/SamplePBIP/Sales.Report/item.metadata.json delete mode 100644 Test/SamplePBIP/Sales.Report/report.json delete mode 100644 Test/SamplePBIP/Sales.pbip delete mode 100644 Test/Test-DeployPBIP-Overrides.ps1 delete mode 100644 Test/Test-DeployPBIP.ps1 delete mode 100644 Test/Test-Export.ps1 delete mode 100644 Test/Test-ExportForBPA.ps1 delete mode 100644 Test/Test-ExportImport.ps1 delete mode 100644 Test/Test-Import.ps1 delete mode 100644 Test/Test-InvokeFabricRequest.ps1 delete mode 100644 Test/Test-ThemeSwap.ps1 delete mode 100644 Test/sample-resources/logo1.jpg delete mode 100644 Test/sample-resources/logo2.jpg delete mode 100644 documentation/Add-FabricWorkspaceRoleAssignment.md delete mode 100644 documentation/Confirm-FabricAuthToken.md delete mode 100644 documentation/Connect-FabricAccount.md delete mode 100644 documentation/Export-FabricItem.md delete mode 100644 documentation/FabricTools.md delete mode 100644 documentation/Get-AllFabricCapacities.md delete mode 100644 documentation/Get-AllFabricDatasetRefreshes.md delete mode 100644 documentation/Get-FabricAPIclusterURI.md delete mode 100644 documentation/Get-FabricAuthToken.md delete mode 100644 documentation/Get-FabricCapacity.md delete mode 100644 documentation/Get-FabricCapacityRefreshables.md delete mode 100644 documentation/Get-FabricCapacitySkus.md delete mode 100644 documentation/Get-FabricCapacityState.md delete mode 100644 documentation/Get-FabricCapacityTenantOverrides.md delete mode 100644 documentation/Get-FabricCapacityWorkload.md delete mode 100644 documentation/Get-FabricDatasetRefreshes.md delete mode 100644 documentation/Get-FabricDebugInfo.md delete mode 100644 documentation/Get-FabricEventhouse.md delete mode 100644 documentation/Get-FabricEventstream.md delete mode 100644 documentation/Get-FabricItem.md delete mode 100644 documentation/Get-FabricKQLDashboard.md delete mode 100644 documentation/Get-FabricKQLDashboardDefinition.md delete mode 100644 documentation/Get-FabricKQLDatabase.md delete mode 100644 documentation/Get-FabricKQLQueryset.md delete mode 100644 documentation/Get-FabricSQLDatabase.md delete mode 100644 documentation/Get-FabricTenantSettings.md delete mode 100644 documentation/Get-FabricUsageMetricsQuery.md delete mode 100644 documentation/Get-FabricWorkspace.md delete mode 100644 documentation/Get-FabricWorkspace2.md delete mode 100644 documentation/Get-FabricWorkspaceDatasetRefreshes.md delete mode 100644 documentation/Get-FabricWorkspaceRoleAssignment.md delete mode 100644 documentation/Get-FabricWorkspaceUsageMetricsData.md delete mode 100644 documentation/Get-FabricWorkspaceUsers.md delete mode 100644 documentation/Get-Sha256.md delete mode 100644 documentation/Import-FabricItem.md delete mode 100644 documentation/Invoke-FabricAPIRequest.md delete mode 100644 documentation/Invoke-FabricDatasetRefresh.md delete mode 100644 documentation/Invoke-FabricKQLCommand.md delete mode 100644 documentation/New-FabricEventhouse.md delete mode 100644 documentation/New-FabricEventstream.md delete mode 100644 documentation/New-FabricKQLDashboard.md delete mode 100644 documentation/New-FabricKQLDatabase.md delete mode 100644 documentation/New-FabricLakehouse.md delete mode 100644 documentation/New-FabricWorkspace.md delete mode 100644 documentation/New-FabricWorkspace2.md delete mode 100644 documentation/New-FabricWorkspaceUsageMetricsReport.md delete mode 100644 documentation/Register-FabricWorkspaceToCapacity.md delete mode 100644 documentation/Remove-FabricEventhouse.md delete mode 100644 documentation/Remove-FabricEventstream.md delete mode 100644 documentation/Remove-FabricItem.md delete mode 100644 documentation/Remove-FabricKQLDatabase.md delete mode 100644 documentation/Remove-FabricKQLQueryset.md delete mode 100644 documentation/Remove-FabricSQLDatabase.md delete mode 100644 documentation/Remove-FabricWorkspace.md delete mode 100644 documentation/Resume-FabricCapacity.md delete mode 100644 documentation/Set-FabricAuthToken.md delete mode 100644 documentation/Set-FabricEventhouse.md delete mode 100644 documentation/Set-FabricEventstream.md delete mode 100644 documentation/Set-FabricKQLDatabase.md delete mode 100644 documentation/Set-FabricKQLQueryset.md delete mode 100644 documentation/Suspend-FabricCapacity.md delete mode 100644 documentation/Unregister-FabricWorkspaceToCapacity.md delete mode 100644 helper/Get-RootPath.ps1 delete mode 100644 helper/createdocumentation.ps1 delete mode 100644 helper/createmodule.ps1 delete mode 100644 helper/startscriptanalyzer.ps1 delete mode 100644 images/FabricToolsLogo.ico delete mode 100644 images/FabricToolsLogo.png delete mode 100644 images/FabricToolsLogo.svg delete mode 100644 images/Fabtools.ico delete mode 100644 images/Fabtools.png delete mode 100644 images/Fabtools50.png diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 67e2e3a2..00000000 --- a/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -**/TEST-Results.xml - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# Build results -Output/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Visual Studio code coverage results -*.coverage -*.coveragexml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 856ac897..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,45 +0,0 @@ -# Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery and unwelcome sexual attention or advances -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or electronic address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) - -For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) diff --git a/FabricTools/FabricTools.psd1 b/FabricTools/FabricTools.psd1 deleted file mode 100644 index ad2b2c2f..00000000 --- a/FabricTools/FabricTools.psd1 +++ /dev/null @@ -1,209 +0,0 @@ -# -# Module manifest for module 'FabricTools' -# -# Generated by: Ioana Bouariu -# -# Generated on: 05.11.2023 -# - -@{ - - # Script module or binary module file associated with this manifest. - RootModule = 'FabricTools.psm1' - - # Version number of this module. - ModuleVersion = '0.20.0' - - # Supported PSEditions - # CompatiblePSEditions = @() - - # ID used to uniquely identify this module - GUID = 'f2a0f9e6-fab6-41fc-9e1c-0c94ff38f794' - - # Author of this module - Author = 'The FabricTools Team' - - # Company or vendor of this module - CompanyName = 'fabrictools.io' - - # Copyright statement for this module - Copyright = 'Copyright (c) 2025 by FabricTools, licensed under MIT' - - # Description of the functionality provided by this module - Description = 'A module to be able to do more with Microsoft Fabric. - It lets you pause and resume Fabric capacities. - Adds functionallity previously only available with the REST API as PowerShell functions. - There are also functions to make it easier to monitor usage metrics and refreshes. - It also adds Fabric-friendly aliases for PowerBI functions to make it easier to use the module.' - - # Minimum version of the PowerShell engine required by this module - PowerShellVersion = '5.1' - - # Name of the PowerShell host required by this module - # PowerShellHostName = '' - - # Minimum version of the PowerShell host required by this module - # PowerShellHostVersion = '' - - # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # DotNetFrameworkVersion = '' - - # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # ClrVersion = '' - - # Processor architecture (None, X86, Amd64) required by this module - # ProcessorArchitecture = '' - - # Modules that must be imported into the global environment prior to importing this module - RequiredModules = @('Az.Accounts', 'Az.Resources','MicrosoftPowerBIMgmt') - - # Assemblies that must be loaded prior to importing this module - #RequiredAssemblies = @('Microsoft.Azure.PowerShell.Authentication') - - # Script files (.ps1) that are run in the caller's environment prior to importing this module. - # ScriptsToProcess = @() - - # Type files (.ps1xml) to be loaded when importing this module - # TypesToProcess = @() - - # Format files (.ps1xml) to be loaded when importing this module - # FormatsToProcess = @() - - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - # NestedModules = '' - - # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @( - "Export-FabricItem", - "Get-AllFabricCapacities", - "Get-AllFabricDatasetRefreshes", - "Get-FabricAPIclusterURI", - "Get-FabricAuthToken", - "Get-FabricCapacity", - "Get-FabricCapacityRefreshables", - "Get-FabricCapacityState", - "Get-FabricCapacityTenantOverrides", - "Get-FabricCapacityWorkload", - "Get-FabricDatasetRefreshes", - "Get-FabricItem", - "Get-FabricTenantSettings", - "Get-FabricUsagemetricsQuery", - "Get-FabricWorkspace", - "Get-FabricWorkspaceDatasetRefreshes", - "Get-FabricWorkspaceUsageMetricsData", - "Get-FabricWorkspaceUsers", - "Get-SHA256", - "Import-FabricItem", - "Invoke-FabricAPIRequest", - "Invoke-FabricDatasetRefresh", - "New-FabricWorkspace", - "New-FabricWorkspaceUsageMetricsReport", - "Register-FabricWorkspaceToCapacity", - "Remove-FabricItem", - "Remove-FabricWorkspace", - "Resume-FabricCapacity", - "Set-FabricAuthToken", - "Suspend-FabricCapacity", - "Unregister-FabricWorkspaceToCapacity", - - 'Connect-FabricAccount', - 'Get-FabricEventhouse', - 'Get-FabricEventstream', - 'Get-FabricKQLDashboard', - 'Get-FabricKQLDatabase', - 'Get-FabricKQLQueryset', - 'Get-FabricWorkspace2', - 'Invoke-FabricKQLCommand', - 'New-FabricEventhouse', - 'New-FabricEventstream', - 'New-FabricKQLDashboard', - 'New-FabricKQLDatabase', - 'New-FabricLakehouse', - 'New-FabricWorkspace2', - 'Remove-FabricEventhouse', - 'Remove-FabricEventstream', - 'Remove-FabricKQLDatabase', - 'Remove-FabricKQLQueryset', - 'Set-FabricEventhouse', - 'Set-FabricEventstream', - 'Set-FabricKQLDatabase', - 'Set-FabricKQLQueryset', - 'Add-FabricWorkspaceRoleAssignment', - 'Get-FabricWorkspaceRoleAssignment', - 'Get-FabricKQLDashboardDefinition', - 'Get-FabricDebugInfo', - - 'Get-FabricSQLDatabase', - 'Remove-FabricSQLDatabase', - 'Get-FabricCapacitySkus', - 'Confirm-FabricAuthToken', - 'Get-FabricConnection', - 'New-FabricSQLDatabase', - 'Test-FabricApiResponse' - ) - - # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = @() - - # Variables to export from this module - VariablesToExport = '*' - - # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. - AliasesToExport = @( - "Export-FabItem", - "Get-FabricDataset", - "Get-FabricReport", - "Login-Fabric", - "Logout-Fabric" - ) - - # DSC resources to export from this module - # DscResourcesToExport = @() - - # List of all modules packaged with this module - # ModuleList = @() - - # List of all files packaged with this module - # FileList = @() - - # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @("microsoftfabric", "powerbi", "developermode", "pbip", "FabricTools", "Fabric") - - # A URL to the license for this module. - LicenseUri = 'https://www.github.com/dataplat/FabricTools/LICENSE.md' - - # A URL to the main website for this project. - ProjectUri = 'https://www.github.com/dataplat/FabricTools' - - # A URL to an icon representing this module. - IconUri = 'https://raw.githubusercontent.com/dataplat/FabricTools/main/images/FabricToolsLogo.ico' - - # ReleaseNotes of this module - ReleaseNotes = 'https://github.com/dataplat/FabricTools/blob/main/ReleaseNotes.md' - - # Prerelease string of this module - # Prerelease = '' - - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false - - # External dependent modules of this module - # ExternalModuleDependencies = @() - - } # End of PSData hashtable - - } # End of PrivateData hashtable - - # HelpInfo URI of this module - HelpInfoURI = 'https://www.github.com/dataplat/FabricTools' - - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' - -} - diff --git a/FabricTools/FabricTools.psm1 b/FabricTools/FabricTools.psm1 deleted file mode 100644 index ae776dfd..00000000 --- a/FabricTools/FabricTools.psm1 +++ /dev/null @@ -1,92 +0,0 @@ -<# -.SYNOPSIS - This script loads necessary modules, sources functions from .ps1 files, and sets aliases for PowerBI functions. - -.DESCRIPTION - The script first tries to load the Az.Accounts and Az.Resources modules. If these modules are not available, it installs and imports them. - It then gets all .ps1 files in the Functions folder, sources each function, and exports it as a module member. - Finally, it sets an alias for the PowerBI login function. - -.EXAMPLE - .\FabricTools.psm1 - - This command runs the script. - -.INPUTS - None. You cannot pipe inputs to this script. - -.OUTPUTS - None. This script does not return any output. - -.NOTES - This script is part of the FabricTools module. -#> - -$script:FabricSession = [ordered]@{ - BaseApiUrl = 'https://api.fabric.microsoft.com/v1' - ResourceUrl = 'https://api.fabric.microsoft.com' - FabricToken = $null - HeaderParams = $null - ContentType = @{'Content-Type' = "application/json"} - KustoURL = "https://api.kusto.windows.net" - AccessToken = $null -} - -$script:AzureSession = [ordered]@{ - BaseApiUrl = "https://management.azure.com" - AccessToken = $null - Token = $null - HeaderParams = $null -} - -$script:PowerBI = [ordered]@{ - BaseApiUrl = "https://api.powerbi.com/v1.0/myorg" -} - -$FabricTools = @{ - FeatureFlags = @{ - AutoRenewExpiredToken = $true - } -} - -$FabricConfig = @{ - BaseUrl = "https://api.fabric.microsoft.com/v1" - ResourceUrl = "https://api.fabric.microsoft.com" - FabricHeaders = @{} - TenantIdGlobal = "" - TokenExpiresOn = "" -} - -Export-ModuleMember -Variable FabricConfig -Export-ModuleMember -Variable FabricTools - -# Get all .ps1 files in the (public) Functions folder -$publicFunctions = Get-ChildItem -Path "$PSScriptRoot\public" -Filter '*.ps1' -Recurse - -# Loop over each function file -foreach ($function in $publicFunctions) { - # Dot-source the function - . $function.fullname - # Export the function as a module member - Export-ModuleMember -Function $function.basename -} - -$privateFunctions = Get-ChildItem -Path "$PSScriptRoot\private" -Filter '*.ps1' -Recurse -$privateFunctions | ForEach-Object { . $_.FullName } - -# Set aliases for PowerBI functions -Set-Alias -Name Login-Fabric -Value Login-PowerBI -Export-ModuleMember -Alias Login-Fabric -Set-Alias -Name Get-FabWorkspace -Value Get-FabricWorkspace -Export-ModuleMember -Alias Get-FabDataset -Set-Alias -Name Get-FabricDataset -Value Get-PowerBIDataset -Export-ModuleMember -Alias Get-FabricDataset -Set-Alias -Name Get-FabReport -Value Get-PowerBIReport -Export-ModuleMember -Alias Get-FabReport -Set-Alias -Name Get-FabricReport -Value Get-PowerBIReport -Export-ModuleMember -Alias Get-FabricReport -Set-Alias -Name Logout-Fabric -Value Logout-PowerBI -Export-ModuleMember -Alias Logout-Fabric - -$moduleName = 'FabricTools' -Write-Host "Module $moduleName imported." diff --git a/FabricTools/private/Test-TokenExpired.ps1 b/FabricTools/private/Test-TokenExpired.ps1 deleted file mode 100644 index 1f3da68f..00000000 --- a/FabricTools/private/Test-TokenExpired.ps1 +++ /dev/null @@ -1,66 +0,0 @@ -<# -.SYNOPSIS -Checks if the Fabric token is expired and logs appropriate messages. - -.DESCRIPTION -The `Test-TokenExpired` function checks the expiration status of the Fabric token stored in the `$FabricConfig.TokenExpiresOn` variable. -If the token is expired, it logs an error message and provides guidance for refreshing the token. -Otherwise, it logs that the token is still valid. - -.PARAMETER FabricConfig -The configuration object containing the token expiration details. - -.EXAMPLE -Test-TokenExpired -FabricConfig $config - -Checks the token expiration status using the provided `$config` object. - -.NOTES -- Ensure the `FabricConfig` object includes a valid `TokenExpiresOn` property of type `DateTimeOffset`. -- Requires the `Write-Message` function for logging. - -.AUTHOR -Tiago Balabuch -#> -function Test-TokenExpired { - [CmdletBinding()] - param () - - Confirm-FabricAuthToken | Out-Null - - Write-Message -Message "Validating token..." -Level Debug - - try { - # Ensure required properties have valid values - if ([string]::IsNullOrWhiteSpace($FabricConfig.TenantIdGlobal) -or - [string]::IsNullOrWhiteSpace($FabricConfig.TokenExpiresOn)) { - Write-Message -Message "Token details are missing. Please run 'Set-FabricApiHeaders' to configure them." -Level Error - throw "MissingTokenDetailsException: Token details are missing." - } - - # Convert the TokenExpiresOn value to a DateTime object - if ($FabricConfig.TokenExpiresOn.GetType() -eq [datetimeoffset]) { - $tokenExpiryDate = $FabricConfig.TokenExpiresOn - } else { - $tokenExpiryDate = [datetimeoffset]::Parse($FabricConfig.TokenExpiresOn) - } - - # Check if the token is expired - if ($tokenExpiryDate -le [datetimeoffset]::Now) { - Write-Message -Message "Your authentication token has expired. Please sign in again to refresh your session." -Level Warning - #throw "TokenExpiredException: Token has expired." - #Set-FabricApiHeaders -tenantId $FabricConfig.TenantIdGlobal - } - - # Log valid token status - Write-Message -Message "Token is still valid. Expiry time: $($tokenExpiryDate.ToString("u"))" -Level Debug - } catch [System.FormatException] { - Write-Message -Message "Invalid 'TokenExpiresOn' format in the FabricConfig object. Ensure it is a valid datetime string." -Level Error - throw "FormatException: Invalid TokenExpiresOn value." - } catch { - # Log unexpected errors with details - Write-Message -Message "An unexpected error occurred: $_" -Level Error - throw $_ - } - Write-Message -Message "Token validation completed." -Level Debug -} diff --git a/FabricTools/private/Write-Message.ps1 b/FabricTools/private/Write-Message.ps1 deleted file mode 100644 index 80da8e1e..00000000 --- a/FabricTools/private/Write-Message.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -<# -.SYNOPSIS -Logs messages with different severity levels to the console and optionally to a file. - -.DESCRIPTION -The `Write-Message` function provides a unified way to log messages with levels such as Info, Error, Alert, Verbose, and Debug. -It supports logging to the console with color-coded messages and optionally writing logs to a file with timestamps. - -.PARAMETER Message -The message to log. Supports pipeline input. - -.PARAMETER Level -Specifies the log level. Supported values are Info, Error, Alert, Verbose, and Debug. - -.PARAMETER LogFile -(Optional) Specifies a file path to write the log messages to. If not provided, messages are only written to the console. - -.EXAMPLE -Write-Message -Message "This is an info message." -Level Info - -Logs an informational message to the console. - -.EXAMPLE -Write-Message -Message "Logging to file." -Level Info -LogFile "C:\Logs\MyLog.txt" - -Logs an informational message to the console and writes it to a file. - -.EXAMPLE -"Pipeline message" | Write-Message -Level Alert - -Logs a message from the pipeline with an Alert level. - -.NOTES -Author: Tiago Balabuch -#> - -function Write-Message { - [CmdletBinding()] - param ( - [Parameter(Mandatory, ValueFromPipeline)] - [string]$Message, - - [Parameter()] - [ValidateSet("Message","Info", "Error", "Warning","Critical", "Verbose", "Debug", IgnoreCase = $true)] - [string]$Level = "Info", - - [Parameter()] - [string]$LogFile - ) - - process { - try { - # Format timestamp - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - - # Construct log message - $logMessage = "[$timestamp] [$Level] $Message" - - # Write log message to console with colors - switch ($Level) { - "Message" { Write-Host $logMessage -ForegroundColor White } - "Info" { Write-Host $logMessage -ForegroundColor Green } - "Error" { Write-Host $logMessage -ForegroundColor Red } - "Warning" { Write-Host $logMessage -ForegroundColor Yellow } - "Critical" { Write-Host $logMessage -ForegroundColor Red } - "Verbose" { Write-Verbose $logMessage } - "Debug" { Write-Debug $logMessage } - - } - - # Optionally write log message to a file - if ($LogFile) { - try { - Add-Content -Path $LogFile -Value $logMessage -Encoding UTF8 - } catch { - # Catch and log any errors when writing to file - Write-Host "[ERROR] Failed to write to log file '$LogFile': $_" -ForegroundColor Red - } - } - } catch { - Write-Host "[ERROR] An unexpected error occurred: $_" -ForegroundColor Red - } - } -} diff --git a/FabricTools/public/Capacity/Get-AllFabricCapacities.ps1 b/FabricTools/public/Capacity/Get-AllFabricCapacities.ps1 deleted file mode 100644 index 1e5434db..00000000 --- a/FabricTools/public/Capacity/Get-AllFabricCapacities.ps1 +++ /dev/null @@ -1,75 +0,0 @@ -<# -.SYNOPSIS - This function retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. - -.DESCRIPTION - The Get-AllFabricCapacities function is used to retrieve all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. It uses the Az module to interact with Azure. - -.PARAMETER subscriptionID - An optional parameter that specifies the subscription ID. If this parameter is not provided, the function will retrieve resources from all subscriptions. - -.EXAMPLE - Get-AllFabricCapacities -subscriptionID "12345678-1234-1234-1234-123456789012" - - This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in the subscription with the ID "12345678-1234-1234-1234-123456789012". - -.EXAMPLE - Get-AllFabricCapacities - - This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in all subscriptions. - -.NOTES - Alias: Get-AllFabCapacities -#> -function Get-AllFabricCapacities { - # Define aliases for the function for flexibility. - - # Define parameters for the function - Param ( - # Optional parameter for subscription ID - [Parameter(Mandatory = $false)] - [string]$subscriptionID - ) - - # Initialize an array to store the results - $res = @() - - Get-FabricAuthToken | Out-Null - - # If a subscription ID is provided - if ($subscriptionID) { - # Set the context to the provided subscription ID - Set-AzContext -SubscriptionId $subscriptionID | Out-Null - - # Get all resource groups in the subscription - $rgs = Get-AzResourceGroup - - # For each resource group, get all resources of type "Microsoft.Fabric/capacities" - foreach ($r in $rgs) { - # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array - $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue - } - } - else { - # If no subscription ID is provided, get all subscriptions - $subscriptions = Get-AzSubscription - - # For each subscription, set the context to the subscription ID - foreach ($sub in $subscriptions) { - # Set the context to the subscription ID - Set-AzContext -SubscriptionId $sub.id | Out-Null - - # Get all resource groups in the subscription - $rgs = Get-AzResourceGroup - - # For each resource group, get all resources of type "Microsoft.Fabric/capacities" - foreach ($r in $rgs) { - # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array - $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue - } - } - } - - # Return the results - return $res -} \ No newline at end of file diff --git a/FabricTools/public/Capacity/Get-FabricCapacity.ps1 b/FabricTools/public/Capacity/Get-FabricCapacity.ps1 deleted file mode 100644 index 78fa93d9..00000000 --- a/FabricTools/public/Capacity/Get-FabricCapacity.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -<# -.SYNOPSIS -Retrieves the fabric capacity information. - -.DESCRIPTION -This function makes a GET request to the Fabric API to retrieve the tenant settings. - -.PARAMETER capacity -Specifies the capacity to retrieve information for. If not provided, all capacities will be retrieved. - -.EXAMPLE -Get-FabricCapacity -capacity "exampleCapacity" -Retrieves the fabric capacity information for the specified capacity. - -Get-FabricCapacity -Retrieves the fabric capacity information for all capacities. - -#> - -function Get-FabricCapacity { - # Define aliases for the function for flexibility. - [Alias("Get-FabCapacity")] - - Param( - [Parameter(Mandatory=$false)] - [string]$capacity - ) - - Confirm-FabricAuthToken | Out-Null - - if ($capacity) { - $result = Invoke-FabricAPIRequest -uri "capacities/$capacity" -Method GET - } else { - $result = Invoke-FabricAPIRequest -uri "capacities" -Method GET - } - - return $result.value - -} \ No newline at end of file diff --git a/FabricTools/public/Capacity/Get-FabricCapacityRefreshables.ps1 b/FabricTools/public/Capacity/Get-FabricCapacityRefreshables.ps1 deleted file mode 100644 index fa21ff63..00000000 --- a/FabricTools/public/Capacity/Get-FabricCapacityRefreshables.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -<# -.SYNOPSIS -Retrieves the top refreshable capacities for the tenant. - -.DESCRIPTION -The Get-FabricCapacityRefreshables function retrieves the top refreshable capacities for the tenant. It supports multiple aliases for flexibility. - -.PARAMETER top -The number of top refreshable capacities to retrieve. This is a mandatory parameter. - -.EXAMPLE -Get-FabricCapacityRefreshables -top 5 - -This example retrieves the top 5 refreshable capacities for the tenant. - -.NOTES -The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the top refreshable capacities. It then returns the 'value' property of the response, which contains the capacities. -#> - -# This function retrieves the top refreshable capacities for the tenant. -function Get-FabricCapacityRefreshables { - # Define aliases for the function for flexibility. - [Alias("Get-FabCapacityRefreshables")] - - # Define a mandatory parameter for the number of top refreshable capacities to retrieve. - Param ( - [Parameter(Mandatory=$false)] - [string]$top = 5 - ) - - Confirm-FabricAuthToken | Out-Null - - # Make a GET request to the PowerBI API to retrieve the top refreshable capacities. - # The function returns the 'value' property of the response. - return (Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/capacities/refreshables?`$top=$top" -Headers $FabricSession.HeaderParams -Method GET).value -} \ No newline at end of file diff --git a/FabricTools/public/Capacity/Get-FabricCapacitySkus.ps1 b/FabricTools/public/Capacity/Get-FabricCapacitySkus.ps1 deleted file mode 100644 index 06e27a5f..00000000 --- a/FabricTools/public/Capacity/Get-FabricCapacitySkus.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -<# -.SYNOPSIS -Retrieves the fabric capacity information. - -.DESCRIPTION -This function makes a GET request to the Fabric API to retrieve the tenant settings. - -.PARAMETER capacity -Specifies the capacity to retrieve information for. If not provided, all capacities will be retrieved. - -.EXAMPLE -Get-FabricCapacitySkus -capacity "exampleCapacity" -Retrieves the fabric capacity information for the specified capacity. - -#> - -function Get-FabricCapacitySkus { - # Define aliases for the function for flexibility. - - Param( - [Parameter(Mandatory = $true)] - [string]$subscriptionID, - [Parameter(Mandatory = $true)] - [string]$ResourceGroupName, - [Parameter(Mandatory=$true)] - [string]$capacity - ) - - Confirm-FabricAuthToken | Out-Null - - #GET https://management.azure.com/subscriptions/548B7FB7-3B2A-4F46-BB02-66473F1FC22C/resourceGroups/TestRG/providers/Microsoft.Fabric/capacities/azsdktest/skus?api-version=2023-11-01 - $uri = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.Fabric/capacities/$capacity/skus?api-version=2023-11-01" - $result = Invoke-RestMethod -Headers $AzureSession.HeaderParams -Uri $uri -Method GET - - return $result.value - -} \ No newline at end of file diff --git a/FabricTools/public/Capacity/Get-FabricCapacityState.ps1 b/FabricTools/public/Capacity/Get-FabricCapacityState.ps1 deleted file mode 100644 index 7b8f5307..00000000 --- a/FabricTools/public/Capacity/Get-FabricCapacityState.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -<# -.SYNOPSIS -Retrieves the state of a specific capacity. - -.DESCRIPTION -The Get-FabricCapacityState function retrieves the state of a specific capacity. It supports multiple aliases for flexibility. - -.PARAMETER subscriptionID -The ID of the subscription. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.PARAMETER resourcegroup -The resource group. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.PARAMETER capacity -The capacity. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.EXAMPLE -Get-FabricCapacityState -subscriptionID "your-subscription-id" -resourcegroupID "your-resource-group" -capacityID "your-capacity" - -This example retrieves the state of a specific capacity given the subscription ID, resource group, and capacity. - -.NOTES -The function checks if the Azure token is null. If it is, it connects to the Azure account and retrieves the token. It then defines the headers for the GET request and the URL for the GET request. Finally, it makes the GET request and returns the response. -#> - -# This function retrieves the state of a specific capacity. -function Get-FabricCapacityState { - # Define aliases for the function for flexibility. - [Alias("Get-FabCapacityState")] - - # Define mandatory parameters for the subscription ID, resource group, and capacity. - Param ( - [Parameter(Mandatory=$true)] - [string]$subscriptionID, - [Parameter(Mandatory=$true)] - [string]$resourcegroup, - [Parameter(Mandatory=$true)] - [string]$capacity - ) - - Confirm-FabricAuthToken | Out-Null - - # Define the URL for the GET request. - $getCapacityState = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$resourcegroup/providers/Microsoft.Fabric/capacities/$capacity/?api-version=2022-07-01-preview" - - # Make the GET request and return the response. - return Invoke-RestMethod -Method GET -Uri $getCapacityState -Headers $script:AzureSession.HeaderParams -ErrorAction Stop -} \ No newline at end of file diff --git a/FabricTools/public/Capacity/Get-FabricCapacityTenantOverrides.ps1 b/FabricTools/public/Capacity/Get-FabricCapacityTenantOverrides.ps1 deleted file mode 100644 index ac6862bd..00000000 --- a/FabricTools/public/Capacity/Get-FabricCapacityTenantOverrides.ps1 +++ /dev/null @@ -1,33 +0,0 @@ -<# -.SYNOPSIS -Retrieves the tenant overrides for all capacities. - -.DESCRIPTION -The Get-FabricCapacityTenantOverrides function retrieves the tenant overrides for all capacities. It supports multiple aliases for flexibility. - -.PARAMETER authToken -The authentication token used to authorize the request. If not provided, the function will retrieve the token using the Get-FabricAuthToken function. - -.EXAMPLE -Get-FabricCapacityTenantOverrides - -This example retrieves the tenant overrides for all capacities. - -.NOTES -The function retrieves the PowerBI access token and makes a GET request to the Fabric API to retrieve the tenant overrides for all capacities. It then returns the response of the GET request. -#> - -# This function retrieves the tenant overrides for all capacities. -function Get-FabricCapacityTenantOverrides { - # Define aliases for the function for flexibility. - [Alias("Get-FabCapacityTenantOverrides")] - - Param ( - ) - - Confirm-FabricAuthToken | Out-Null - - # Make a GET request to the Fabric API to retrieve the tenant overrides for all capacities. - # The function returns the response of the GET request. - return Invoke-RestMethod -uri "$($FabricSession.BaseApiUrl)/admin/capacities/delegatedTenantSettingOverrides" -Headers $FabricSession.HeaderParams -Method GET -} \ No newline at end of file diff --git a/FabricTools/public/Capacity/Get-FabricCapacityWorkload.ps1 b/FabricTools/public/Capacity/Get-FabricCapacityWorkload.ps1 deleted file mode 100644 index f7036ae3..00000000 --- a/FabricTools/public/Capacity/Get-FabricCapacityWorkload.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -<# -.SYNOPSIS -Retrieves the workloads for a specific capacity. - -.DESCRIPTION -The Get-FabricCapacityWorkload function retrieves the workloads for a specific capacity. It supports multiple aliases for flexibility. - -.PARAMETER capacityID -The ID of the capacity. This is a mandatory parameter. - -.PARAMETER authToken -The authentication token to access the PowerBI API. If not provided, the function will retrieve the token using the Get-FabricAuthToken function. - -.EXAMPLE -Get-FabricCapacityWorkload -capacityID "your-capacity-id" -authToken "your-auth-token" - -This example retrieves the workloads for a specific capacity given the capacity ID and authentication token. - -.NOTES -The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the workloads for the specified capacity. It then returns the 'value' property of the response, which contains the workloads. -#> - -# This function retrieves the workloads for a specific capacity. -function Get-FabricCapacityWorkload { - # Define aliases for the function for flexibility. - [Alias("Get-FabCapacityWorkload")] - - # Define a mandatory parameter for the capacity ID. - Param ( - [Parameter(Mandatory=$true)] - [string]$capacityID - ) - - Confirm-FabricAuthToken | Out-Null - - # Make a GET request to the PowerBI API to retrieve the workloads for the specified capacity. - # The function returns the 'value' property of the response. - return (Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/capacities/$capacityID/Workloads" -Headers $FabricSession.HeaderParams -Method GET).value -} - - -#https://learn.microsoft.com/en-us/rest/api/power-bi/capacities/get-workloads \ No newline at end of file diff --git a/FabricTools/public/Capacity/Resume-FabricCapacity.ps1 b/FabricTools/public/Capacity/Resume-FabricCapacity.ps1 deleted file mode 100644 index edf0d2f0..00000000 --- a/FabricTools/public/Capacity/Resume-FabricCapacity.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -<# -.SYNOPSIS -Resumes a capacity. - -.DESCRIPTION -The Resume-FabricCapacity function resumes a capacity. It supports multiple aliases for flexibility. - -.PARAMETER subscriptionID -The the ID of the subscription. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.PARAMETER resourcegroup -The resource group. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.PARAMETER capacity -The capacity. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.EXAMPLE -Resume-FabricCapacity -subscriptionID "your-subscription-id" -resourcegroupID "your-resource-group" -capacityID "your-capacity" - -This example resumes a capacity given the subscription ID, resource group, and capacity. - -.NOTES -The function defines parameters for the subscription ID, resource group, and capacity. If the 'azToken' environment variable is null, it connects to the Azure account and sets the 'azToken' environment variable. It then defines the headers for the request, defines the URI for the request, and makes a GET request to the URI. -#> - -# This function resumes a capacity. -function Resume-FabricCapacity { - # Define aliases for the function for flexibility. - [Alias("Resume-FabCapacity")] - [CmdletBinding(SupportsShouldProcess)] - - # Define parameters for the subscription ID, resource group, and capacity. - Param ( - [Parameter(Mandatory = $true)] - [string]$subscriptionID, - [Parameter(Mandatory = $true)] - [string]$resourcegroup, - [Parameter(Mandatory = $true)] - [string]$capacity - ) - - Confirm-FabricAuthToken | Out-Null - - # Define the URI for the request. - $resumeCapacity = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$resourcegroup/providers/Microsoft.Fabric/capacities/$capacity/resume?api-version=2022-07-01-preview" - - # Make a GET request to the URI and return the response. - if ($PSCmdlet.ShouldProcess("Resume capacity $capacity")) { - return Invoke-RestMethod -Method POST -Uri $resumeCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop - } -} \ No newline at end of file diff --git a/FabricTools/public/Capacity/Suspend-FabricCapacity.ps1 b/FabricTools/public/Capacity/Suspend-FabricCapacity.ps1 deleted file mode 100644 index d7c3fe94..00000000 --- a/FabricTools/public/Capacity/Suspend-FabricCapacity.ps1 +++ /dev/null @@ -1,52 +0,0 @@ -<# -.SYNOPSIS -Suspends a capacity. - -.DESCRIPTION -The Suspend-FabricCapacity function suspends a capacity. It supports multiple aliases for flexibility. - -.PARAMETER subscriptionID -The ID of the subscription. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.PARAMETER resourcegroup -The resource group. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.PARAMETER capacity -The the capacity. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. - -.EXAMPLE -Suspend-FabricCapacity -subscriptionID "your-subscription-id" -resourcegroupID "your-resource-group" -capacityID "your-capacity" - -This example suspends a capacity given the subscription ID, resource group, and capacity. - -.NOTES -The function defines parameters for the subscription ID, resource group, and capacity. If the 'azToken' environment variable is null, it connects to the Azure account and sets the 'azToken' environment variable. It then defines the headers for the request, defines the URI for the request, and makes a GET request to the URI. -#> - -# This function suspends a capacity. -function Suspend-FabricCapacity { - # Define aliases for the function for flexibility. - [Alias("Suspend-PowerBICapacity", "Suspend-FabCapacity")] - [CmdletBinding(SupportsShouldProcess)] - - # Define parameters for the subscription ID, resource group, and capacity. - Param ( - [Parameter(Mandatory = $true)] - [string]$subscriptionID, - [Parameter(Mandatory = $true)] - [string]$resourcegroup, - [Parameter(Mandatory = $true)] - [string]$capacity - ) - - Confirm-FabricAuthToken | Out-Null - - # Define the URI for the request. - $suspendCapacity = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$resourcegroup/providers/Microsoft.Fabric/capacities/$capacity/suspend?api-version=2023-11-01" - - # Make a GET request to the URI and return the response. - if ($PSCmdlet.ShouldProcess("Suspend capacity $capacity")) { - return Invoke-RestMethod -Method POST -Uri $suspendCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop - } - -} \ No newline at end of file diff --git a/FabricTools/public/Confirm-FabricAuthToken.ps1 b/FabricTools/public/Confirm-FabricAuthToken.ps1 deleted file mode 100644 index 14c7933b..00000000 --- a/FabricTools/public/Confirm-FabricAuthToken.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -<# -.SYNOPSIS - Check whether the Fabric API authentication token is set and not expired and reset it if necessary. - -.DESCRIPTION - The Confirm-FabricAuthToken function retrieves the Fabric API authentication token. If the token is not already set, it calls the Set-FabricAuthToken function to set it. It then outputs the token. - -.EXAMPLE - Confirm-FabricAuthToken - - This command retrieves the Fabric API authentication token. - -.INPUTS - None. You cannot pipe inputs to this function. - -.OUTPUTS - Returns object as Get-FabricDebugInfo function - -.NOTES - -#> - -function Confirm-FabricAuthToken { - [CmdletBinding()] - param ( ) - - Write-Verbose "Check if session is established and token not expired." - - # Check if the Fabric token is already set - if (!$FabricSession.FabricToken -or !$AzureSession.AccessToken) { - Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken" - Set-FabricAuthToken | Out-Null - } - - $now = (Get-Date) - $s = Get-FabricDebugInfo - if ($FabricSession.AccessToken.ExpiresOn -lt $now ) { - Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#1" - Set-FabricAuthToken -reset | Out-Null - } - - if ($s.AzureSession.AccessToken.ExpiresOn -lt $now ) { - Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#2" - Set-FabricAuthToken -reset | Out-Null - } - - $s = Get-FabricDebugInfo - return $s - -} \ No newline at end of file diff --git a/FabricTools/public/Connect-FabricAccount.ps1 b/FabricTools/public/Connect-FabricAccount.ps1 deleted file mode 100644 index a96a1cec..00000000 --- a/FabricTools/public/Connect-FabricAccount.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -function Connect-FabricAccount { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Connects to the Fabric WebAPI. - -.DESCRIPTION - Connects to the Fabric WebAPI by using the cmdlet Connect-AzAccount. - This function retrieves the authentication token for the Fabric API and sets up the headers for API calls. - -.PARAMETER TenantId - The TenantId of the Azure Active Directory tenant you want to connect to - and in which your Fabric Capacity is. - -.EXAMPLE - Connect-FabricAccount ` - -TenantID '12345678-1234-1234-1234-123456789012' - -.NOTES - - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output - -.LINK - Connect-AzAccount https://learn.microsoft.com/de-de/powershell/module/az.accounts/connect-azaccount?view=azps-12.4.0 - -#> - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$TenantId - ) - -begin { -} - -process { - Write-Verbose "Connect to Azure Account" - Connect-AzAccount -TenantId $TenantId | Out-Null - - Write-Verbose "Get authentication token" - $FabricSession.FabricToken = (Get-AzAccessToken -ResourceUrl $FabricSession.BaseApiUrl).Token - Write-Verbose "Token: $($FabricSession.FabricToken)" - - Write-Verbose "Setup headers for API calls" - $FabricSession.HeaderParams = @{'Authorization'="Bearer {0}" -f $FabricSession.FabricToken} - Write-Verbose "HeaderParams: $($FabricSession.HeaderParams)" -} - -end { -} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventhouse/Get-FabricEventhouse.ps1 b/FabricTools/public/Eventhouse/Get-FabricEventhouse.ps1 deleted file mode 100644 index 4d3bc871..00000000 --- a/FabricTools/public/Eventhouse/Get-FabricEventhouse.ps1 +++ /dev/null @@ -1,213 +0,0 @@ -function Get-FabricEventhouse { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric Eventhouses - -.DESCRIPTION - Retrieves Fabric Eventhouses. Without the EventhouseName or EventhouseID parameter, all Eventhouses are returned. - If you want to retrieve a specific Eventhouse, you can use the EventhouseName or EventhouseID parameter. These - parameters cannot be used together. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventhouses should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseName - The name of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseID. - -.PARAMETER EventhouseId - The Id of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseName. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' - - This example will give you all Eventhouses in the Workspace. - -.EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' - - This example will give you all Information about the Eventhouse with the name 'MyEventhouse'. - -.EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' - - This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. - - .EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -Verbose - - This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. - It will also give you verbose output which is useful for debugging. - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/list-eventhouses?tabs=HTTP - -.NOTES - TODO: Add functionality to list all Eventhouses in the subscription. To do so fetch all workspaces - and then all eventhouses in each workspace. - - Revsion History: - - - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName - - 2024-11-16 - FGE: Added Verbose Output - - 2024-11-27 - FGE: Added more Verbose Output -#> - -# - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Name","DisplayName")] - [string]$EventhouseName, - - [Alias("Id")] - [string]$EventhouseId - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - # You can either use Name or WorkspaceID - Write-Verbose "Checking if EventhouseName and EventhouseID are used together. This is not allowed" - if ($PSBoundParameters.ContainsKey("EventhouseName") -and $PSBoundParameters.ContainsKey("EventhouseID")) { - throw "Parameters EventhouseName and EventhouseID cannot be used together" - } - - # Create Eventhouse API - $eventhouseAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses" - Write-Verbose "Creating the URL for the Eventhouse API: $eventhouseAPI" - - $eventhouseAPIEventhouseId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" - Write-Verbose "Creating the URL for the Eventhouse API when the Id is used: $eventhouseAPIEventhouseId" - -} - -process { - - if ($PSBoundParameters.ContainsKey("EventhouseId")) { - Write-Verbose "Calling Eventhouse API with EventhouseId" - Write-Verbose "----------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $eventhouseAPIEventhouseId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventhouseAPIEventhouseId ` - -ContentType "application/json" - - Write-Verbose "Adding the member queryServiceUri: $($response.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $response.properties.queryServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding the member ingestionServiceUri: $($response.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $response.properties.ingestionServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding the member databasesItemIds: $($response.properties.databasesItemIds)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databasesItemIds' ` - -Value $response.properties.databasesItemIds ` - -InputObject $response ` - -Force - - Write-Verbose "Adding the member minimumConsumptionUnits: $($response.properties.minimumConsumptionUnits)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'minimumConsumptionUnits' ` - -Value $response.properties.minimumConsumptionUnits ` - -InputObject $response ` - -Force - - $response - } - else { - Write-Verbose "Calling Eventhouse API without EventhouseId" - Write-Verbose "-------------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $eventhouseAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventhouseAPI ` - -ContentType "application/json" - - foreach ($eventhouse in $response.value) { - Write-Verbose "Adding the member queryServiceUri: $($eventhouse.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $eventhouse.properties.queryServiceUri ` - -InputObject $eventhouse ` - -Force - - Write-Verbose "Adding the member ingestionServiceUri: $($eventhouse.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $eventhouse.properties.ingestionServiceUri ` - -InputObject $eventhouse ` - -Force - - Write-Verbose "Adding the member databasesItemIds: $($eventhouse.properties.databasesItemIds)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databasesItemIds' ` - -Value $eventhouse.properties.databasesItemIds ` - -InputObject $eventhouse ` - -Force - - Write-Verbose "Adding the member minimumConsumptionUnits: $($eventhouse.properties.minimumConsumptionUnits)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'minimumConsumptionUnits' ` - -Value $eventhouse.properties.minimumConsumptionUnits ` - -InputObject $eventhouse ` - -Force - } - - if ($PSBoundParameters.ContainsKey("EventhouseName")) { - Write-Verbose "Filtering the Eventhouse by EventhouseName: $EventhouseName" - $response.value | ` - Where-Object { $_.displayName -eq $EventhouseName } - } - else { - Write-Verbose "Returning all Eventhouses" - $response.value - } - } - -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventhouse/New-FabricEventhouse.ps1 b/FabricTools/public/Eventhouse/New-FabricEventhouse.ps1 deleted file mode 100644 index 103be974..00000000 --- a/FabricTools/public/Eventhouse/New-FabricEventhouse.ps1 +++ /dev/null @@ -1,104 +0,0 @@ -function New-FabricEventhouse { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Creates a new Fabric Eventhouse - -.DESCRIPTION - Creates a new Fabric Eventhouse - -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the Eventhouse should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseName - The name of the Eventhouse to create. - -.PARAMETER EventhouseDescription - The description of the Eventhouse to create. - -.EXAMPLE - New-FabricEventhouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' ` - -EventhouseDescription 'This is my Eventhouse' - - This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. - -.EXAMPLE - New-FabricEventhouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' ` - -EventhouseDescription 'This is my Eventhouse' ` - -Verbose - - This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. - It will also give you verbose output which is useful for debugging. - -.NOTES - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName - - 2024-11-27 - FGE: Added Verbose Output - - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] - [string]$EventhouseName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$EventhouseDescription - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - # Create body of request - $body = @{ - 'displayName' = $EventhouseName - 'description' = $EventhouseDescription - } | ConvertTo-Json ` - -Depth 1 - - # Create Eventhouse API URL - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses" - } - -process { - - Write-Verbose "Calling Eventhouse API" - Write-Verbose "----------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($EventhouseName)) { - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $eventhouseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventhouse/Remove-FabricEventhouse.ps1 b/FabricTools/public/Eventhouse/Remove-FabricEventhouse.ps1 deleted file mode 100644 index cc4274e5..00000000 --- a/FabricTools/public/Eventhouse/Remove-FabricEventhouse.ps1 +++ /dev/null @@ -1,106 +0,0 @@ -function Remove-FabricEventhouse { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Removes an existing Fabric Eventhouse - -.DESCRIPTION - Removes an existing Fabric Eventhouse - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventhouse should be deleted. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseId - The Id of the Eventhouse to delete. The value for EventhouseId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. EventhouseId and EventhouseName cannot be used together. - -.PARAMETER EventhouseName - The name of the Eventhouse to delete. EventhouseId and EventhouseName cannot be used together. - -.EXAMPLE - Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' - - This example will delete the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' from - the Workspace with the Id '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' - - This example will delete the Eventhouse with the name 'MyEventhouse' from the Workspace with the - Id '12345678-1234-1234-1234-123456789012'. -.NOTES - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName - - 2024-11-27 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/delete-eventhouse?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Id")] - [string]$EventhouseId, - - [Alias("Name", "DisplayName")] - [string]$EventhouseName - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Check if EventhouseName and EventhouseID are used together. This is not allowed" - if ($PSBoundParameters.ContainsKey("EventhouseName") -and $PSBoundParameters.ContainsKey("EventhouseID")) { - throw "Parameters EventhouseName and EventhouseID cannot be used together" - } - - if ($PSBoundParameters.ContainsKey("EventhouseName")) { - Write-Verbose "Eventhouse Name $EventhouseName is used. Get Eventhouse ID from Eventhouse Name" - $eh = Get-FabricEventhouse ` - -WorkspaceId $WorkspaceId ` - -EventhouseName $EventhouseName - - $EventhouseId = $eh.id - Write-Verbose "Eventhouse ID is $EventhouseId" - } - - # Create Eventhouse API URL - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" -} - -process { - - Write-Verbose "Calling Eventhouse API with EventhouseId" - Write-Verbose "----------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($EventhouseName)) { - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $eventhouseApiUrl ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventhouse/Set-FabricEventhouse.ps1 b/FabricTools/public/Eventhouse/Set-FabricEventhouse.ps1 deleted file mode 100644 index 701b19f4..00000000 --- a/FabricTools/public/Eventhouse/Set-FabricEventhouse.ps1 +++ /dev/null @@ -1,130 +0,0 @@ -function Set-FabricEventhouse { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Updates Properties of an existing Fabric Eventhouse - -.DESCRIPTION - Updates Properties of an existing Fabric Eventhouse - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventhouse should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseId - The Id of the Eventhouse to update. The value for EventhouseId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseNewName - The new name of the Eventhouse. - -.PARAMETER EventhouseDescription - The new description of the Eventhouse. - -.EXAMPLE - Set-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -EventhouseNewName 'MyNewEventhouse' ` - -EventhouseDescription 'This is my new Eventhouse' - - This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' - in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to - have the name 'MyNewEventhouse' and the description - 'This is my new Eventhouse'. - -.EXAMPLE - Set-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -EventhouseNewName 'MyNewEventhouse' ` - -EventhouseDescription 'This is my new Eventhouse' ` - -Verbose - - This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' - in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to - have the name 'MyNewEventhouse' and the description 'This is my new Eventhouse'. - It will also give you verbose output which is useful for debugging. - -.NOTES - TODO: Add functionality to update Eventhouse properties using EventhouseName instead of EventhouseId - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added NewDisplaName as Alias for EventhouseName - - 2024-11-27 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$EventhouseId, - - [Alias("NewName", "NewDisplayName")] - [string]$EventhouseNewName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$EventhouseDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - # Create body of request - $body = @{} - - if ($PSBoundParameters.ContainsKey("EventhouseNewName")) { - Write-Verbose "New name found for Eventhouse. New name is: $EventhouseNewName" - $body["displayName"] = $EventhouseNewName - } - - if ($PSBoundParameters.ContainsKey("EventhouseDescription")) { - Write-Verbose "Description found for Eventhouse. Description is: $EventhouseDescription" - $body["description"] = $EventhouseDescription - } - - $body = $body ` - | ConvertTo-Json ` - -Depth 1 - - # Create Eventhouse API URL - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" -} - -process { - - Write-Verbose "Calling Eventhouse API with EventhouseId" - Write-Verbose "----------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($EventhouseId)) { - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $eventhouseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventstream/Get-FabricEventstream.ps1 b/FabricTools/public/Eventstream/Get-FabricEventstream.ps1 deleted file mode 100644 index 3189c52a..00000000 --- a/FabricTools/public/Eventstream/Get-FabricEventstream.ps1 +++ /dev/null @@ -1,133 +0,0 @@ -function Get-FabricEventstream { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric Eventstreams - -.DESCRIPTION - Retrieves Fabric Eventstreams. Without the EventstreamName or EventstreamID parameter, all Eventstreams are returned. - If you want to retrieve a specific Eventstream, you can use the EventstreamName or EventstreamID parameter. These - parameters cannot be used together. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventstreams should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventstreamName - The name of the Eventstream to retrieve. This parameter cannot be used together with EventstreamID. - -.PARAMETER EventstreamId - The Id of the Eventstream to retrieve. This parameter cannot be used together with EventstreamName. The value for EventstreamId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' - - This example will give you all Eventstreams in the Workspace. - -.EXAMPLE - Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamName 'MyEventstream' - - This example will give you all Information about the Eventstream with the name 'MyEventstream'. - -.EXAMPLE - Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' - - This example will give you all Information about the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/get-eventstream?tabs=HTTP - -.NOTES - TODO: Add functionality to list all Eventhouses. To do so fetch all workspaces and - then all eventhouses in each workspace. - - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName - - 2024-11-27 - FGE: Added Verbose Output -#> - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Name", "DisplayName")] - [string]$EventstreamName, - - [Alias("Id")] - [string]$EventstreamId - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use Name or WorkspaceID not both. If both are used throw error" - if ($PSBoundParameters.ContainsKey("EventstreamName") -and $PSBoundParameters.ContainsKey("EventstreamID")) { - throw "Parameters EventstreamName and EventstreamID cannot be used together" - } - - # Create Eventhouse API - $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams" - - $eventstreamAPIEventstreamIdUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams/$EventstreamId" - -} - -process { - - if ($PSBoundParameters.ContainsKey("EventstreamId")) { - Write-Verbose "Calling Eventstream API with EventstreamId" - Write-Verbose "------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $eventstreamAPIEventstreamIdUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventstreamAPIEventstreamIdUrl ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling Eventstream API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $eventstreamApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventstreamApiUrl ` - -ContentType "application/json" - - if ($PSBoundParameters.ContainsKey("EventstreamName")) { - Write-Verbose "Filtering Eventstream with name $EventstreamName" - $response.value | ` - Where-Object { $_.displayName -eq $EventstreamName } - } - else { - Write-Verbose "Returning all Eventstreams" - $response.value - } - } - -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventstream/New-FabricEventstream.ps1 b/FabricTools/public/Eventstream/New-FabricEventstream.ps1 deleted file mode 100644 index 35f5f662..00000000 --- a/FabricTools/public/Eventstream/New-FabricEventstream.ps1 +++ /dev/null @@ -1,87 +0,0 @@ -function New-FabricEventstream { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Creates a new Fabric Eventstream - -.DESCRIPTION - Creates a new Fabric Eventstream - -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the Eventstream should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventstreamName - The name of the Eventstream to create. - -.PARAMETER EventstreamDescription - The description of the Eventstream to create. - -.EXAMPLE - New-FabricEventstream - -WorkspaceID '12345678-1234-1234-1234-123456789012' - -EventstreamName 'MyEventstream' - -EventstreamDescription 'This is my Eventstream' - - This example will create a new Eventstream with the name 'MyEventstream' and the description 'This is my Eventstream'. - -.NOTES - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName - - 2024-11-30 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/create-eventstream?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name","DisplayName")] - [string]$EventstreamName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$EventstreamDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{ - 'displayName' = $EventstreamName - 'description' = $EventstreamDescription - } | ConvertTo-Json ` - -Depth 1 - - # Create Eventhouse API URL - $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams" - } - -process { - - # Call Eventstream API - if($PSCmdlet.ShouldProcess($EventstreamName)) { - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $eventstreamApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventstream/Remove-FabricEventstream.ps1 b/FabricTools/public/Eventstream/Remove-FabricEventstream.ps1 deleted file mode 100644 index 25d85024..00000000 --- a/FabricTools/public/Eventstream/Remove-FabricEventstream.ps1 +++ /dev/null @@ -1,105 +0,0 @@ -function Remove-FabricEventstream { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Removes an existing Fabric Eventstream - -.DESCRIPTION - Removes an existing Fabric Eventstream - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventstream should be deleted. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventstreamId - The Id of the Eventstream to delete. The value for Eventstream is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Remove-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' - - This example will delete the Eventstream with the Id '12345678-1234-1234-1234-123456789012' from - the Workspace. - -.EXAMPLE - Remove-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamName 'MyEventstream' - - This example will delete the Eventstream with the name 'MyEventstream' from the Workspace. - -.NOTES - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName - - 2024-12-08 - FGE: Added Verbose Output -#> - - - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Id")] - [string]$EventstreamId, - - [Alias("Name","DisplayName")] - [string]$EventstreamName - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use Name or WorkspaceID not both. If both are used throw error" - if ($PSBoundParameters.ContainsKey("EventstreamId") -and $PSBoundParameters.ContainsKey("EventstreamName")) { - throw "Parameters EventstreamId and EventstreamName cannot be used together" - } - - if ($PSBoundParameters.ContainsKey("EventstreamName")) { - Write-Verbose "The name $EventstreamName was provided. Fetching EventstreamId." - - $eh = Get-FabricEventstream ` - -WorkspaceId $WorkspaceId ` - -EventstreamName $EventstreamName - - $EventstreamId = $eh.id - Write-Verbose "EventstreamId: $EventstreamId" - } - - $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams/$EventstreamId" - -} - -process { - - # Call Eventstream API - if($PSCmdlet.ShouldProcess($EventstreamName)) { - Write-Verbose "Calling Eventstream API with EventstreamId" - Write-Verbose "------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $eventstreamApiUrl" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $eventstreamApiUrl ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Eventstream/Set-FabricEventstream.ps1 b/FabricTools/public/Eventstream/Set-FabricEventstream.ps1 deleted file mode 100644 index c15a8805..00000000 --- a/FabricTools/public/Eventstream/Set-FabricEventstream.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -function Set-FabricEventstream { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Updates Properties of an existing Fabric Eventstream - -.DESCRIPTION - Updates Properties of an existing Fabric Eventstream - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventstream should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventstreamId - The Id of the Eventstream to update. The value for EventstreamId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventstreamNewName - The new name of the Eventstream. - -.PARAMETER EventstreamDescription - The new description of the Eventstream. - -.EXAMPLE - Set-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' ` - -EventstreamNewName 'MyNewEventstream' ` - -EventstreamDescription 'This is my new Eventstream' - - This example will update the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. - -.NOTES - TODO: Add functionality to update Eventstream properties using EventstreamName instead of EventstreamId - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamNewName - - 2024-12-08 - FGE: Added Verbose Output - Added Aliases for EventstreamNewName and EventstreamDescription - Corrected typo in EventstreamNewName Variable -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$EventstreamId, - - [Alias("NewName","NewDisplayName")] - [string]$EventstreamNewName, - - [ValidateLength(0, 256)] - [Alias("Description","NewDescription", "EventstreamNewDescription")] - [string]$EventstreamDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{} - - if ($PSBoundParameters.ContainsKey("EventstreamNewName")) { - Write-Verbose "Setting EventstreamNewName: $EventstreamNewName" - $body["displayName"] = $EventstreamNewName - } - - if ($PSBoundParameters.ContainsKey("EventstreamDescription")) { - Write-Verbose "Setting EventstreamDescription: $EventstreamDescription" - $body["description"] = $EventstreamDescription - } - - $body = $body ` - | ConvertTo-Json ` - -Depth 1 - - # Create Eventstream API URL - $EventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/Eventstreams/$EventstreamId" - } - -process { - - if($PSCmdlet.ShouldProcess($EventhouseName)) { - Write-Verbose "Calling Eventstream API with EventstreamId" - Write-Verbose "------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $EventstreamApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $EventstreamApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Get-AllFabricDatasetRefreshes.ps1 b/FabricTools/public/Get-AllFabricDatasetRefreshes.ps1 deleted file mode 100644 index 94dab947..00000000 --- a/FabricTools/public/Get-AllFabricDatasetRefreshes.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -<# -.SYNOPSIS -Retrieves all refreshes for all datasets in all PowerBI workspaces. - -.DESCRIPTION -The Get-AllFabricDatasetRefreshes function retrieves all refreshes for all datasets in all PowerBI workspaces. It supports multiple aliases for flexibility. - -.EXAMPLE -Get-AllFabricDatasetRefreshes - -This example retrieves all refreshes for all datasets in all PowerBI workspaces. - -.NOTES -The function makes a GET request to the PowerBI API to retrieve the refreshes. It loops through each workspace and each dataset in each workspace. If a dataset is refreshable, it retrieves the refreshes for the dataset and selects the most recent one. It then creates a PSCustomObject with information about the refresh and adds it to an array of refreshes. Finally, it returns the array of refreshes. -#> - -# This function retrieves all refreshes for all datasets in all PowerBI workspaces. -function Get-AllFabricDatasetRefreshes { - # Define aliases for the function for flexibility. - - Confirm-FabricAuthToken | Out-Null - - # Retrieve all PowerBI workspaces. - $wsps = Get-FabricWorkspace - # Initialize an array to store the refreshes. - $refs = @() - # Loop through each workspace. - foreach ($w in $wsps) { - # Get a list of all the datasets in the workspace. - $d = Get-FabricDataset -workspaceid $w.Id -ErrorAction SilentlyContinue - # Loop through each dataset. - foreach ($di in $d) { - # Check if the dataset is refreshable. - if ($di.isrefreshable ) { - # Get a list of all the refreshes for the dataset. - $results = (Invoke-PowerBIRestMethod -Method get -Url ("datasets/" + $di.id + "/Refreshes") | ConvertFrom-Json) - # Select the most recent refresh. - $results.value[0] - # Create a PSCustomObject with the information about the refresh. - $refresh = [PSCustomObject] @{ - Clock = Get-Date # Current date and time. - Workspace = $w.name # Name of the workspace. - Dataset = $di.Name # Name of the dataset. - refreshtype = $results.value[0].refreshType # Type of the refresh. - startTime = $results.value[0].startTime # Start time of the refresh. - endTime = $results.value[0].endTime # End time of the refresh. - status = $results.value[0].status # Status of the refresh. - ErrorMessage = $results.value[0].serviceExceptionJson # Error message of the refresh, if any. - } - # Add the refresh to the array of refreshes. - $refs += $refresh - } - } - } - # Return the array of refreshes. - return $refs -} \ No newline at end of file diff --git a/FabricTools/public/Get-FabricAPIClusterURI.ps1 b/FabricTools/public/Get-FabricAPIClusterURI.ps1 deleted file mode 100644 index 73cdf5f2..00000000 --- a/FabricTools/public/Get-FabricAPIClusterURI.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -<# -.SYNOPSIS -Retrieves the cluster URI for the tenant. - -.DESCRIPTION -The Get-FabricAPIclusterURI function retrieves the cluster URI for the tenant. It supports multiple aliases for flexibility. - -.EXAMPLE -Get-FabricAPIclusterURI - -This example retrieves the cluster URI for the tenant. - -.NOTES -The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the datasets. It then extracts the '@odata.context' property from the response, splits it on the '/' character, and selects the third element. This element is used to construct the cluster URI, which is then returned by the function. - -#> - -#This function retrieves the cluster URI for the tenant. - - -function Get-FabricAPIclusterURI { - # Define aliases for the function for flexibility. - [Alias("Get-FabAPIClusterURI")] - Param ( - ) - - Confirm-FabricAuthToken | Out-Null - - # Make a GET request to the PowerBI API to retrieve the datasets. - $reply = Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/datasets" -Headers $FabricSession.HeaderParams -Method GET - - # Extract the '@odata.context' property from the response. - $unaltered = $reply.'@odata.context' - - # Split the '@odata.context' property on the '/' character and select the third element. - $stripped = $unaltered.split('/')[2] - - # Construct the cluster URI. - $clusterURI = "https://$stripped/beta/myorg/groups" - - # Return the cluster URI. - return $clusterURI -} \ No newline at end of file diff --git a/FabricTools/public/Get-FabricAuthToken.ps1 b/FabricTools/public/Get-FabricAuthToken.ps1 deleted file mode 100644 index 55707a69..00000000 --- a/FabricTools/public/Get-FabricAuthToken.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -<# -.SYNOPSIS - Retrieves the Fabric API authentication token. - -.DESCRIPTION - The Get-FabricAuthToken function retrieves the Fabric API authentication token. If the token is not already set, it calls the Set-FabricAuthToken function to set it. It then outputs the token. - -.EXAMPLE - Get-FabricAuthToken - - This command retrieves the Fabric API authentication token. - -.INPUTS - None. You cannot pipe inputs to this function. - -.OUTPUTS - String. This function returns the Fabric API authentication token. - -.NOTES - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip -#> - -function Get-FabricAuthToken { - [Alias("Get-FabAuthToken")] - [CmdletBinding()] - param - ( - ) - - # Check if the Fabric token is already set - if (!$FabricSession.FabricToken) { - # If not, set the Fabric token - Set-FabricAuthToken - } - - # Output the Fabric token - return $FabricSession.FabricToken -} \ No newline at end of file diff --git a/FabricTools/public/Get-FabricConnection.ps1 b/FabricTools/public/Get-FabricConnection.ps1 deleted file mode 100644 index b621a862..00000000 --- a/FabricTools/public/Get-FabricConnection.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -<# -.SYNOPSIS -Retrieves Fabric connections. - -.DESCRIPTION -The Get-FabricConnection function retrieves Fabric connections. It can retrieve all connections or the specified one only. - -.PARAMETER connectionId -The ID of the connection to retrieve. - -.EXAMPLE -Get-FabricConnection - -This example retrieves all connections from Fabric - -.EXAMPLE -Get-FabricConnection -connectionId "12345" - -This example retrieves specific connection from Fabric with ID "12345". - -.NOTES -https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/get-connection?tabs=HTTP -https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/list-connections?tabs=HTTP -#> - - -Function Get-FabricConnection { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $false)] - [string]$connectionId - ) - - begin { - Confirm-FabricAuthToken | Out-Null - } - - process { - if ($connectionId) { - $result = Invoke-FabricAPIRequest -Uri "/connections/$($connectionId)" -Method GET - } - else { - $result = Invoke-FabricAPIRequest -Uri "/connections" -Method GET - } - - return $result.value - } -} diff --git a/FabricTools/public/Get-FabricDatasetRefreshes.ps1 b/FabricTools/public/Get-FabricDatasetRefreshes.ps1 deleted file mode 100644 index 7f979aae..00000000 --- a/FabricTools/public/Get-FabricDatasetRefreshes.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -<# -.SYNOPSIS - Retrieves the refresh history of a specified dataset in a PowerBI workspace. - -.DESCRIPTION - The Get-FabricDatasetRefreshes function uses the PowerBI cmdlets to retrieve the refresh history of a specified dataset in a workspace. It uses the dataset ID and workspace ID to get the dataset and checks if it is refreshable. If it is, the function retrieves the refresh history. - -.PARAMETER DatasetID - The ID of the dataset. This is a mandatory parameter. - -.PARAMETER workspaceId - The ID of the workspace. This is a mandatory parameter. - -.EXAMPLE - Get-FabricDatasetRefreshes -DatasetID "12345678-90ab-cdef-1234-567890abcdef" -workspaceId "abcdef12-3456-7890-abcd-ef1234567890" - - This command retrieves the refresh history of the specified dataset in the specified workspace. - -.INPUTS - String. You can pipe two strings that contain the dataset ID and workspace ID to Get-FabricDatasetRefreshes. - -.OUTPUTS - Object. Get-FabricDatasetRefreshes returns an object that contains the refresh history. - -.NOTES - Alias: Get-PowerBIDatasetRefreshes, Get-FabricDatasetRefreshes -#> -function Get-FabricDatasetRefreshes { - # Define aliases for the function for flexibility. - [Alias("Get-FabDatasetRefreshes")] - - # Define a mandatory parameter for the dataset ID. - Param ( - [Parameter(Mandatory=$true)] - [string]$DatasetID, - [Parameter(Mandatory=$true)] - [string]$workspaceId - ) - - # Get the dataset information. - $di = get-powerbidataset -id $DatasetID -WorkspaceId $workspaceId - - # Check if the dataset is refreshable. - if ($di.isrefreshable -eq "True") { - # Get a list of all the refreshes for the dataset. - $results = (Invoke-PowerBIRestMethod -Method get -Url ("datasets/" + $di.id + "/Refreshes") | ConvertFrom-Json) - - # Create a PSCustomObject with the information about the refresh. - $refresh = [PSCustomObject]@{ - Clock = Get-Date - Workspace = $w.name - Dataset = $di.Name - refreshtype = $results.value[0].refreshType - startTime = $results.value[0].startTime - endTime = $results.value[0].endTime - status = $results.value[0].status - ErrorMessage = $results.value[0].serviceExceptionJson - } - - # Return the PSCustomObject. - return $refresh - } else { - # If the dataset is not refreshable, return null. - Write-Warning "Dataset is not refreshable." - return $null - } - - -} \ No newline at end of file diff --git a/FabricTools/public/Get-FabricDebugInfo.ps1 b/FabricTools/public/Get-FabricDebugInfo.ps1 deleted file mode 100644 index 1c7704a0..00000000 --- a/FabricTools/public/Get-FabricDebugInfo.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -function Get-FabricDebugInfo { - #Requires -Version 7.1 - -<# -.SYNOPSIS - Shows internal debug information about the current session. - -.DESCRIPTION - Shows internal debug information about the current session. It is useful for troubleshooting purposes. - It will show you the current session object. This includes the bearer token. This can be useful - for connecting to the REST API directly via Postman. - -.Example - Get-FabricDebugInfo - - This example shows the current session object. - -.NOTES - - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output - -#> - -[CmdletBinding()] - param ( - - ) - -begin {} - -process { - Write-Verbose "Show current session object" - - return @{ - FabricSession = $script:FabricSession - AzureSession = $script:AzureSession - FabricConfig = $script:FabricConfig - } - -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Get-FabricUsageMetricsQuery.ps1 b/FabricTools/public/Get-FabricUsageMetricsQuery.ps1 deleted file mode 100644 index 14a57128..00000000 --- a/FabricTools/public/Get-FabricUsageMetricsQuery.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -<# -.SYNOPSIS -Retrieves usage metrics for a specific dataset. - -.DESCRIPTION -The Get-FabricUsageMetricsQuery function retrieves usage metrics for a specific dataset. It supports multiple aliases for flexibility. - -.PARAMETER DatasetID -The ID of the dataset. This is a mandatory parameter. - -.PARAMETER groupId -The ID of the group. This is a mandatory parameter. - -.PARAMETER reportname -The name of the report. This is a mandatory parameter. - -.PARAMETER token -The access token. This is a mandatory parameter. - -.PARAMETER ImpersonatedUser -The name of the impersonated user. This is an optional parameter. - -.PARAMETER authToken -The authentication token. This is an optional parameter. - -.EXAMPLE -Get-FabricUsageMetricsQuery -DatasetID "your-dataset-id" -groupId "your-group-id" -reportname "your-report-name" -token "your-token" - -This example retrieves the usage metrics for a specific dataset given the dataset ID, group ID, report name, and token. - -.NOTES -The function defines the headers and body for a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. It then makes the POST request and returns the response. -#> - -# This function retrieves usage metrics for a specific dataset. -function Get-FabricUsageMetricsQuery { - # Define aliases for the function for flexibility. - [Alias("Get-FabUsageMetricsQuery")] - - # Define parameters for the dataset ID, group ID, report name, token, and impersonated user. - param ( - [Parameter(Mandatory = $true)] - [string]$DatasetID, - [Parameter(Mandatory = $true)] - [string]$groupId, - [Parameter(Mandatory = $true)] - $reportname, - [Parameter(Mandatory = $false)] - [string]$ImpersonatedUser = "" - ) - - # Confirm the authentication token. - Confirm-FabricAuthToken | Out-Null - - # Define the body of the POST request. - if ($ImpersonatedUser -ne "") { - $reportbody = '{ - "queries": [ - { - "query": "EVALUATE VALUES(' + $reportname + ')" - } - ], - "serializerSettings": { - "includeNulls": true - }, - "impersonatedUserName": "'+ $ImpersonatedUser + '" - }' - } - else { - $reportbody = '{ - "queries": [ - { - "query": "EVALUATE VALUES(' + $reportname + ')" - } - ], - "serializerSettings": { - "includeNulls": true - } - }' - } - # Make a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. - # The function returns the response of the POST request. - return Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/groups/$groupId/datasets/$DatasetID/executeQueries" -Headers $FabricSession.HeaderParams -Body $reportbody -Method Post -} \ No newline at end of file diff --git a/FabricTools/public/Get-SHA256.ps1 b/FabricTools/public/Get-SHA256.ps1 deleted file mode 100644 index 905b291d..00000000 --- a/FabricTools/public/Get-SHA256.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -<# -.SYNOPSIS -Calculates the SHA256 hash of a string. - -.DESCRIPTION -The Get-Sha256 function calculates the SHA256 hash of a string. - -.PARAMETER string -The string to hash. This is a mandatory parameter. - -.EXAMPLE -Get-Sha256 -string "your-string" - -This example calculates the SHA256 hash of a string. - -.NOTES -The function creates a new SHA256CryptoServiceProvider object, converts the string to a byte array using UTF8 encoding, computes the SHA256 hash of the byte array, converts the hash to a string and removes any hyphens, and returns the resulting hash. -#> - -# This function calculates the SHA256 hash of a string. -function Get-Sha256 ($string) { - # Create a new SHA256CryptoServiceProvider object. - $sha256 = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider - - # Convert the string to a byte array using UTF8 encoding. - $bytes = [System.Text.Encoding]::UTF8.GetBytes($string) - - # Compute the SHA256 hash of the byte array. - $hash = $sha256.ComputeHash($bytes) - - # Convert the hash to a string and remove any hyphens. - $result = [System.BitConverter]::ToString($hash) -replace '-' - - # Return the resulting hash. - return $result -} \ No newline at end of file diff --git a/FabricTools/public/Invoke-FabricAPIRequest.ps1 b/FabricTools/public/Invoke-FabricAPIRequest.ps1 deleted file mode 100644 index d54b46a9..00000000 --- a/FabricTools/public/Invoke-FabricAPIRequest.ps1 +++ /dev/null @@ -1,175 +0,0 @@ - -<# - .SYNOPSIS - Sends an HTTP request to a Fabric API endpoint and retrieves the response. - Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response - - .DESCRIPTION - The Invoke-FabricAPIRequest function is used to send an HTTP request to a Fabric API endpoint and retrieve the response. It handles various aspects such as authentication, 429 throttling, and Long-Running-Operation (LRO) response. - - .PARAMETER authToken - The authentication token to be used for the request. If not provided, it will be obtained using the Get-FabricAuthToken function. - - .PARAMETER uri - The URI of the Fabric API endpoint to send the request to. - - .PARAMETER method - The HTTP method to be used for the request. Valid values are 'Get', 'Post', 'Delete', 'Put', and 'Patch'. The default value is 'Get'. - - .PARAMETER body - The body of the request, if applicable. - - .PARAMETER contentType - The content type of the request. The default value is 'application/json; charset=utf-8'. - - .PARAMETER timeoutSec - The timeout duration for the request in seconds. The default value is 240 seconds. - - .PARAMETER outFile - The file path to save the response content to, if applicable. - - .PARAMETER retryCount - The number of times to retry the request in case of a 429 (Too Many Requests) error. The default value is 0. - - .EXAMPLE - Invoke-FabricAPIRequest -uri "/api/resource" -method "Get" - - This example sends a GET request to the "/api/resource" endpoint of the Fabric API. - - .EXAMPLE - Invoke-FabricAPIRequest -authToken "abc123" -uri "/api/resource" -method "Post" -body $requestBody - - This example sends a POST request to the "/api/resource" endpoint of the Fabric API with a request body. - - .NOTES - This function requires the Get-FabricAuthToken function to be defined in the same script or module. - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip - #> - - -Function Invoke-FabricAPIRequest { - <# - .SYNOPSIS - Sends an HTTP request to a Fabric API endpoint and retrieves the response. - Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response - #> - [CmdletBinding()] - param( - [Parameter(Mandatory = $false)] [string] $authToken, - [Parameter(Mandatory = $true)] [string] $uri, - [Parameter(Mandatory = $false)] [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] [string] $method = "Get", - [Parameter(Mandatory = $false)] $body, - [Parameter(Mandatory = $false)] [string] $contentType = "application/json; charset=utf-8", - [Parameter(Mandatory = $false)] [int] $timeoutSec = 240, - [Parameter(Mandatory = $false)] [int] $retryCount = 0 - ) - - Confirm-FabricAuthToken | Out-Null - $fabricHeaders = $FabricSession.HeaderParams - - try { - - $requestUrl = "$($FabricSession.BaseApiUrl)/$uri" - Write-Verbose "Calling $requestUrl" - $response = Invoke-WebRequest -Headers $fabricHeaders -Method $method -Uri $requestUrl -Body $body -TimeoutSec $timeoutSec - - if ($response.StatusCode -eq 202) { - if ($uri -match "jobType=Pipeline") { - write-output "Waiting for pipeline to complete. Please check the status in the Power BI Service." - } - else { - do { - $asyncUrl = [string]$response.Headers.Location - - Write-Output "Waiting for request to complete. Sleeping..." - Start-Sleep -Seconds 5 - $response2 = Invoke-WebRequest -Headers $fabricHeaders -Method Get -Uri $asyncUrl - $lroStatusContent = $response2.Content | ConvertFrom-Json - } - while ($lroStatusContent.status -ine "succeeded" -and $lroStatusContent.status -ine "failed") - - try { - $response = Invoke-WebRequest -Headers $fabricHeaders -Method Get -Uri "$asyncUrl/result" - } - catch { - write-output "No result URL" - } - } - } - - #if ($response.StatusCode -in @(200,201) -and $response.Content) - if ($response.Content) { - $contentBytes = $response.RawContentStream.ToArray() - - if ($contentBytes[0] -eq 0xef -and $contentBytes[1] -eq 0xbb -and $contentBytes[2] -eq 0xbf) { - $contentText = [System.Text.Encoding]::UTF8.GetString($contentBytes[3..$contentBytes.Length]) - } - else { - $contentText = $response.Content - } - $jsonResult = $contentText | ConvertFrom-Json - Write-Output $jsonResult -NoEnumerate - } - else { - Write-Output $response -NoEnumerate - } - } - catch { - $ex = $_.Exception - $message = $null - - if ($null -ne $ex.Response) { - - $responseStatusCode = [int]$ex.Response.StatusCode - - if ($responseStatusCode -in @(429)) { - if ($ex.Response.Headers.RetryAfter) { - $retryAfterSeconds = $ex.Response.Headers.RetryAfter.Delta.TotalSeconds + 5 - } - - if (!$retryAfterSeconds) { - $retryAfterSeconds = 60 - } - - Write-Output "Exceeded the amount of calls (TooManyRequests - 429), sleeping for $retryAfterSeconds seconds." - - Start-Sleep -Seconds $retryAfterSeconds - - $maxRetries = 3 - - if ($retryCount -le $maxRetries) { - Invoke-FabricAPIRequest -authToken $authToken -uri $uri -method $method -body $body -contentType $contentType -timeoutSec $timeoutSec -retryCount ($retryCount + 1) - } - else { - throw "Exceeded the amount of retries ($maxRetries) after 429 error." - } - - } - else { - $apiErrorObj = $ex.Response.Headers | Where-Object { $_.key -ieq "x-ms-public-api-error-code" } | Select-object -First 1 - - if ($apiErrorObj) { - $apiError = $apiErrorObj.Value[0] - } - - if ($apiError -ieq "ItemHasProtectedLabel") { - Write-Warning "Item has a protected label." - } - else { - throw - } - - # TODO: Investigate why response.Content is empty but powershell can read it on throw - #$errorContent = $ex.Response.Content.ReadAsStringAsync().Result; - #$message = "$($ex.Message) - StatusCode: '$($ex.Response.StatusCode)'; Content: '$errorContent'" - } - } - else { - $message = "$($ex.Message)" - } - if ($message) { - throw $message - } - } -} \ No newline at end of file diff --git a/FabricTools/public/Invoke-FabricDatasetRefresh.ps1 b/FabricTools/public/Invoke-FabricDatasetRefresh.ps1 deleted file mode 100644 index be979475..00000000 --- a/FabricTools/public/Invoke-FabricDatasetRefresh.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -<# -.SYNOPSIS - This function invokes a refresh of a PowerBI dataset - -.DESCRIPTION - The Invoke-FabricDatasetRefresh function is used to refresh a PowerBI dataset. It first checks if the dataset is refreshable. If it is not, it writes an error. If it is, it invokes a PowerBI REST method to refresh the dataset. The URL for the request is constructed using the provided dataset ID. - - -.PARAMETER DatasetID - A mandatory parameter that specifies the dataset ID. - -.EXAMPLE - Invoke-FabricDatasetRefresh -DatasetID "12345678-1234-1234-1234-123456789012" - - This command invokes a refresh of the dataset with the ID "12345678-1234-1234-1234-123456789012" -.NOTES - Alias: Invoke-FabDatasetRetresh -#> -function Invoke-FabricDatasetRefresh { - # Define aliases for the function for flexibility. - [Alias("Invoke-FabDatasetRefresh")] - - # Define parameters for the workspace ID and dataset ID. - param( - # Mandatory parameter for the dataset ID - [Parameter(Mandatory = $true, ParameterSetName = "DatasetId")] - [string]$DatasetID - ) - - Confirm-FabricAuthToken | Out-Null - - # Check if the dataset is refreshable - if ((Get-FabricDataset -DatasetId $DatasetID).isrefreshable -eq $false) { - # If the dataset is not refreshable, write an error - Write-Error "Dataset is not refreshable" - } - else { - # If the dataset is refreshable, invoke a PowerBI REST method to refresh the dataset - # The URL for the request is constructed using the provided workspace ID and dataset ID. - - # Check if the dataset is refreshable - - # If the dataset is refreshable, invoke a PowerBI REST method to refresh the dataset - # The URL for the request is constructed using the provided workspace ID and dataset ID. - return Invoke-RestMethod -Method POST -uri ("$($PowerBI.BaseApiUrl)/datasets/$datasetid/refreshes") -Headers $FabricSession.HeaderParams - } -} \ No newline at end of file diff --git a/FabricTools/public/Item/Export-FabricItem.ps1 b/FabricTools/public/Item/Export-FabricItem.ps1 deleted file mode 100644 index a760653c..00000000 --- a/FabricTools/public/Item/Export-FabricItem.ps1 +++ /dev/null @@ -1,116 +0,0 @@ -<# -.SYNOPSIS -Exports items from a Fabric workspace. Either all items in a workspace or a specific item. - -.DESCRIPTION -The Export-FabricItem function exports items from a Fabric workspace to a specified directory. It can export items of type "Report", "SemanticModel", "SparkJobDefinitionV1" or "Notebook". If a specific item ID is provided, only that item will be exported. Otherwise, all items in the workspace will be exported. - -.PARAMETER path -The path to the directory where the items will be exported. The default value is '.\pbipOutput'. - -.PARAMETER workspaceId -The ID of the Fabric workspace. - -.PARAMETER filter -A script block used to filter the items to be exported. Only items that match the filter will be exported. The default filter includes items of type "Report", "SemanticModel", "SparkJobDefinitionV1" or "Notebook". - -.PARAMETER itemID -The ID of the specific item to export. If provided, only that item will be exported. - -.EXAMPLE -Export-FabricItem -workspaceId "12345678-1234-1234-1234-1234567890AB" -path "C:\ExportedItems" - -This example exports all items from the Fabric workspace with the specified ID to the "C:\ExportedItems" directory. - -.EXAMPLE -Export-FabricItem -workspaceId "12345678-1234-1234-1234-1234567890AB" -itemID "98765432-4321-4321-4321-9876543210BA" -path "C:\ExportedItems" - -This example exports the item with the specified ID from the Fabric workspace with the specified ID to the "C:\ExportedItems" directory. - -.NOTES -This function is based on the Export-FabricItems function written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -#> -Function Export-FabricItem { - [Alias("Export-FabItem")] - [CmdletBinding()] - param - ( - [string]$path = '.\pbipOutput', - [string]$workspaceId = '', - [scriptblock]$filter = { $_.type -in @("Report", "SemanticModel", "Notebook", "SparkJobDefinitionV1") }, - [string]$itemID = '' - ) - if (![string]::IsNullOrEmpty($itemID)) { - # Invoke the Fabric API to get the specific item in the workspace - - $item = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemID/getDefinition" -Method POST - # If a filter is provided - - - $parts = $item.definition.parts - - if (!(test-path $path)) { - New-Item -ItemType Directory -Force -Path $path - } - - - foreach ($part in $parts) { - $filename = $part.path - $payload = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($part.payload)) - $payload | Out-File -FilePath "$path\$filename" - } - - # Display a message indicating the items were exported - write-output "Items exported to $path" - } else { - $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get - - if ($filter) { - $items = $items.value | Where-Object $filter - } - else { - $items = $items.value - } - - Write-Output "Existing items: $($items.Count)" - - foreach ($item in $items) { - $itemId = $item.id - $itemName = $item.displayName - $itemType = $item.type - $itemOutputPath = "$path\$workspaceId\$($itemName).$($itemType)" - - if ($itemType -in @("report", "semanticmodel", "notebook", "SparkJobDefinitionV1", "DataPipeline")) { - write-output "Getting definition of: $itemId / $itemName / $itemType" - - $response = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/getDefinition" -Method Post - - $partCount = $response.definition.parts.Count - - write-output "Parts: $partCount" - if ($partCount -gt 0) { - foreach ($part in $response.definition.parts) { - write-output "Saving part: $($part.path)" - $outputFilePath = "$itemOutputPath\$($part.path.Replace("/", "\"))" - - New-Item -ItemType Directory -Path (Split-Path $outputFilePath -Parent) -ErrorAction SilentlyContinue | Out-Null - $payload = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($part.payload)) - $payload | Out-File -FilePath "$outputFilePath" - - } - - @{ - "type" = $itemType - "displayName" = $itemName - - } | ConvertTo-Json | Out-File "$itemOutputPath\item.metadata.json" - } - } - else { - write-output "Type '$itemType' not available for export." - } - } - } -} \ No newline at end of file diff --git a/FabricTools/public/Item/Get-FabricItem.ps1 b/FabricTools/public/Item/Get-FabricItem.ps1 deleted file mode 100644 index e9261449..00000000 --- a/FabricTools/public/Item/Get-FabricItem.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -<# -.SYNOPSIS -Retrieves fabric items from a workspace. - -.DESCRIPTION -The Get-FabricItem function retrieves fabric items from a specified workspace. It can retrieve all items or filter them by item type. - -.PARAMETER workspaceId -The ID of the workspace from which to retrieve the fabric items. - -.PARAMETER type -(Optional) The type of the fabric items to retrieve. If not specified, all items will be retrieved. - -.EXAMPLE -Get-FabricItem -workspaceId "12345" -type "file" - -This example retrieves all fabric items of type "file" from the workspace with ID "12345". - -.NOTES -This function was originally written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -#> - - -Function Get-FabricItem { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] - [string]$workspaceId, - - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true )] - $Workspace, - - [Parameter(Mandatory = $false)] - [Alias("itemType")] - [string]$type, - - [Parameter(Mandatory = $false)] - [string]$itemID - ) - - begin { - Confirm-FabricAuthToken | Out-Null - } - - process { - if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { - $workspaceID = $Workspace.id - } - if ($itemID) { - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Get - } - else { - if ($type) { - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items?type=$type" -Method Get - } - else { - # Invoke the Fabric API to get the workspaces - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items" -Method Get - } - # Output the result - return $result.value - } - } -} \ No newline at end of file diff --git a/FabricTools/public/Item/Import-FabricItem.ps1 b/FabricTools/public/Item/Import-FabricItem.ps1 deleted file mode 100644 index b885fb21..00000000 --- a/FabricTools/public/Item/Import-FabricItem.ps1 +++ /dev/null @@ -1,239 +0,0 @@ - -<# - .SYNOPSIS - Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source. - - .PARAMETER fileOverrides - This parameter let's you override a PBIP file without altering the local file. - - .PARAMETER path - The path to the PBIP files. Default value is '.\pbipOutput'. - - .PARAMETER workspaceId - The ID of the Fabric workspace. - - .PARAMETER filter - A filter to limit the search for PBIP files to specific folders. - - .EXAMPLE - Import-FabricItems -path 'C:\PBIPFiles' -workspaceId '12345' -filter 'C:\PBIPFiles\Reports' - - This example imports PBIP files from the 'C:\PBIPFiles' folder into the Fabric workspace with ID '12345'. It only searches for PBIP files in the 'C:\PBIPFiles\Reports' folder. - - .NOTES - This function requires the Invoke-FabricAPIRequest function to be available in the current session. - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip - #> - -Function Import-FabricItem { - <# - .SYNOPSIS - Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source. - - .PARAMETER fileOverrides - This parameter let's you override a PBIP file without altering the local file. - #> - [CmdletBinding()] - param - ( - [string] $path = '.\pbipOutput' - , [string] $workspaceId - , [string] $filter = $null - , [hashtable] $fileOverrides - ) - - # Search for folders with .pbir and .pbidataset in it - - Confirm-FabricAuthToken | Out-Null - - $itemsFolders = Get-ChildItem -Path $path -recurse -include *.pbir, *.pbidataset - - if ($filter) { - $itemsFolders = $itemsFolders | where-object { $_.Directory.FullName -like $filter } - } - - # Get existing items of the workspace - - $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get - - write-output "Existing items: $($items.Count)" - - # Datasets first - - $itemsFolders = $itemsFolders | Select-Object @{n = "Order"; e = { if ($_.Name -like "*.pbidataset") { 1 } else { 2 } } }, * | sort-object Order - - $datasetReferences = @{} - - foreach ($itemFolder in $itemsFolders) { - # Get the parent folder - - $itemPath = $itemFolder.Directory.FullName - - write-output "Processing item: '$itemPath'" - - $files = Get-ChildItem -Path $itemPath -Recurse -Attributes !Directory - - # Remove files not required for the API: item.*.json; cache.abf; .pbi folder - - $files = $files | Where-Object { $_.Name -notlike "item.*.json" -and $_.Name -notlike "*.abf" -and $_.Directory.Name -notlike ".pbi" } - - # There must be a item.metadata.json in the item folder containing the item type and displayname, necessary for the item creation - - $itemMetadataStr = Get-Content "$itemPath\item.metadata.json" - if ($fileOverrides) { - $fileOverrideMatch = $fileOverrides.GetEnumerator() | where-object { "$itemPath\item.metadata.json" -ilike $_.Name } | select-object -First 1 - - if ($fileOverrideMatch) { - $itemMetadataStr = $fileOverrideMatch.Value - } - } - $itemMetadata = $itemMetadataStr | ConvertFrom-Json - $itemType = $itemMetadata.type - - if ($itemType -ieq "dataset") { - $itemType = "SemanticModel" - } - - - $displayName = $itemMetadata.displayName - - $itemPathAbs = Resolve-Path $itemPath - - $parts = $files | ForEach-Object { - - #$fileName = $_.Name - $filePath = $_.FullName - if ($fileOverrides) { - $fileOverrideMatch = $fileOverrides.GetEnumerator() | Where-Object { $filePath -ilike $_.Name } | select-object -First 1 - } - if ($fileOverrideMatch) { - $fileContent = $fileOverrideMatch.Value - - # convert to byte array - - if ($fileContent -is [string]) { - $fileContent = [system.Text.Encoding]::UTF8.GetBytes($fileContent) - } - elseif (!($fileContent -is [byte[]])) { - throw "FileOverrides value type must be string or byte[]" - } - } - else { - if ($filePath -like "*.pbir") { - - $pbirJson = Get-Content -Path $filePath | ConvertFrom-Json - - if ($pbirJson.datasetReference.byPath -and $pbirJson.datasetReference.byPath.path) { - - # try to swap byPath to byConnection - - $reportDatasetPath = (Resolve-path (Join-Path $itemPath $pbirJson.datasetReference.byPath.path.Replace("/", "\"))).Path - - $datasetReference = $datasetReferences[$reportDatasetPath] - - if ($datasetReference) { - # $datasetName = $datasetReference.name - $datasetId = $datasetReference.id - - $newPBIR = @{ - "version" = "1.0" - "datasetReference" = @{ - "byConnection" = @{ - "connectionString" = $null - "pbiServiceModelId" = $null - "pbiModelVirtualServerName" = "sobe_wowvirtualserver" - "pbiModelDatabaseName" = "$datasetId" - "name" = "EntityDataSource" - "connectionType" = "pbiServiceXmlaStyleLive" - } - } - } | ConvertTo-Json - - $fileContent = [system.Text.Encoding]::UTF8.GetBytes($newPBIR) - - } - else { - throw "Item API dont support byPath connection, switch the connection in the *.pbir file to 'byConnection'." - } - } else { - $fileContent = Get-Content -Path $filePath -AsByteStream -Raw - } - } - else { - - $fileContent = Get-Content -Path $filePath -AsByteStream -Raw - } - } - - $partPath = $filePath.Replace($itemPathAbs, "").TrimStart("\").Replace("\", "/") - write-host "Processing part: '$partPath'" - $fileEncodedContent = [Convert]::ToBase64String($fileContent) - - Write-Output @{ - Path = $partPath - Payload = $fileEncodedContent - PayloadType = "InlineBase64" - } - } - - $itemId = $null - - # Check if there is already an item with same displayName and type - - $foundItem = $items | Where-Object { $_.type -ieq $itemType -and $_.displayName -ieq $displayName } - - if ($foundItem) { - if ($foundItem.Count -gt 1) { - throw "Found more than one item for displayName '$displayName'" - } - - Write-output "Item '$displayName' of type '$itemType' already exists." -ForegroundColor Yellow - - $itemId = $foundItem.id - } - - if ($null -eq $itemId) { - write-output "Creating a new item" - - # Prepare the request - - $itemRequest = @{ - displayName = $displayName - type = $itemType - definition = @{ - Parts = $parts - } - } | ConvertTo-Json -Depth 3 - - $createItemResult = Invoke-FabricAPIRequest -uri "workspaces/$workspaceId/items" -method Post -body $itemRequest - - $itemId = $createItemResult.id - - write-output "Created a new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green - - Write-Output $itemId - } - else { - write-output "Updating item definition" - - $itemRequest = @{ - definition = @{ - Parts = $parts - } - } | ConvertTo-Json -Depth 3 - Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/updateDefinition" -Method Post -Body $itemRequest - - write-output "Updated new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green - - Write-Output $itemId - } - - # Save dataset references to swap byPath to byConnection - - if ($itemType -ieq "semanticmodel") { - $datasetReferences[$itemPath] = @{"id" = $itemId; "name" = $displayName } - } - } - -} diff --git a/FabricTools/public/Item/Remove-FabricItem.ps1 b/FabricTools/public/Item/Remove-FabricItem.ps1 deleted file mode 100644 index 8659760b..00000000 --- a/FabricTools/public/Item/Remove-FabricItem.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -<# -.SYNOPSIS - Removes selected items from a Fabric workspace. - -.DESCRIPTION - The Remove-FabricItems function removes selected items from a specified Fabric workspace. It uses the workspace ID and an optional filter to select the items to remove. If a filter is provided, only items whose DisplayName matches the filter are removed. - -.PARAMETER WorkspaceID - The ID of the Fabric workspace. This is a mandatory parameter. - -.PARAMETER Filter - An optional filter to select items to remove. If provided, only items whose DisplayName matches the filter are removed. - -.EXAMPLE - Remove-FabricItems -WorkspaceID "12345678-90ab-cdef-1234-567890abcdef" -Filter "*test*" - - This command removes all items from the workspace with the specified ID whose DisplayName includes "test". - -.INPUTS - String. You can pipe two strings that contain the workspace ID and filter to Remove-FabricItems. - -.OUTPUTS - None. This function does not return any output. - -.NOTES - This function was written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip -#> - -Function Remove-FabricItem { - [CmdletBinding(SupportsShouldProcess)] - param - ( - [Parameter(Mandatory = $true)] - [string]$workspaceId, - [Parameter(Mandatory = $false)] - [string]$filter, - [Parameter(Mandatory = $false)] - [string]$itemID - ) - - Confirm-FabricAuthToken | Out-Null - - # Invoke the Fabric API to get the items in the workspace - if ($PSCmdlet.ShouldProcess("Remove items from workspace $workspaceId")) { - if ($itemID) { - Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Delete - } else { - $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get - - # Display the count of existing items - Write-Output "Existing items: $($items.Count)" - - # If a filter is provided - if ($filter) { - # Filter the items whose DisplayName matches the filter - $items = $items | Where-Object { $_.DisplayName -like $filter } - } - - # For each item - foreach ($item in $items) { - # Remove the item - Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$($item.ID)" -Method Delete - } - } - } -} \ No newline at end of file diff --git a/FabricTools/public/KQL Dashboard/Get-FabricKQLDashboard.ps1 b/FabricTools/public/KQL Dashboard/Get-FabricKQLDashboard.ps1 deleted file mode 100644 index 74a10285..00000000 --- a/FabricTools/public/KQL Dashboard/Get-FabricKQLDashboard.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -function Get-FabricKQLDashboard { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric KQLDashboards - -.DESCRIPTION - Retrieves Fabric KQLDashboards. Without the KQLDashboardName or KQLDashboardID parameter, all KQLDashboards are returned. - If you want to retrieve a specific KQLDashboard, you can use the KQLDashboardName or KQLDashboardID parameter. These - parameters cannot be used together. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLDashboards should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDashboardName - The name of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardID. - -.PARAMETER KQLDashboardID - The Id of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardName. The value for KQLDashboardID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricKQLDashboard - -.NOTES - TODO: Add functionality to list all KQLDashboards. To do so fetch all workspaces and - then all KQLDashboards in each workspace. - - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName - - 2024-12-08 - FGE: Added Verbose Output -#> - - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Name","DisplayName")] - [string]$KQLDashboardName, - - [Alias("Id")] - [string]$KQLDashboardId - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbode "You can either use Name or WorkspaceID" - if ($PSBoundParameters.ContainsKey("KQLDashboardName") -and $PSBoundParameters.ContainsKey("KQLDashboardId")) { - throw "Parameters KQLDashboardName and KQLDashboardId cannot be used together" - } - - # Create KQLDashboard API - $KQLDashboardAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards" - - $KQLDashboardAPIKQLDashboardId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards/$KQLDashboardId" - -} - -process { - - if ($PSBoundParameters.ContainsKey("KQLDashboardId")) { - Write-Verbose "Get KQLDashboard with ID $KQLDashboardId" - Write-Verbose "Calling KQLDashboard API with KQLDashboardId" - Write-Verbose "--------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDashboardAPIKQLDashboardId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDashboardAPIKQLDashboardId ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling KQLDashboard API" - Write-Verbose "------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDashboardAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDashboardAPI ` - -ContentType "application/json" - - if ($PSBoundParameters.ContainsKey("KQLDashboardName")) { - Write-Verbose "Filtering KQLDashboards by name. Name: $KQLDashboardName" - $response.value | ` - Where-Object { $_.displayName -eq $KQLDashboardName } - } - else { - Write-Verbose "Returning all KQLDashboards" - $response.value - } - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 b/FabricTools/public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 deleted file mode 100644 index c74147fe..00000000 --- a/FabricTools/public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 +++ /dev/null @@ -1,119 +0,0 @@ -function Get-FabricKQLDashboardDefinition { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric KQLDashboard Definitions for a given KQLDashboard. - -.DESCRIPTION - Retrieves the Definition of the Fabric KQLDashboard that is specified by the KQLDashboardName or KQLDashboardID. - The KQLDashboard Definition contains the parts of the KQLDashboard, which are the visualizations and their configuration. - This is provided as a JSON object. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace in which the KQLDashboard exists. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDashboardName - The name of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardID. - -.PARAMETER KQLDashboardID - The Id of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardName. The value for KQLDashboardID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricKQLDashboardDefinition ` - -WorkspaceId "12345678-1234-1234-1234-123456789012" ` - -KQLDashboardName "MyKQLDashboard" - - This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the - Workspace with the ID "12345678-1234-1234-1234-123456789012". - -.EXAMPLE - $db = Get-FabricKQLDashboardDefinition ` - -WorkspaceId "12345678-1234-1234-1234-123456789012" ` - -KQLDashboardName "MyKQLDashboard" - - $db[0].payload | ` - Set-Content ` - -Path "C:\temp\mydashboard.json" - - This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the - Workspace with the ID "12345678-1234-1234-1234-123456789012". - The definition is saved to a file named "mydashboard.json". - - -.NOTES - - Revision History: - - 2024-11-16 - FGE: First version - - 2024-12-08 - FGE: Added Verbose Output -#> - - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Name","DisplayName")] - [string]$KQLDashboardName, - - [Alias("Id")] - [string]$KQLDashboardId, - - [string]$Format - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use Name or WorkspaceID" - if ($PSBoundParameters.ContainsKey("KQLDashboardName") -and $PSBoundParameters.ContainsKey("KQLDashboardId")) { - throw "Parameters KQLDashboardName and KQLDashboardId cannot be used together" - } - - # Create KQLDashboard API - - $KQLDashboardAPIKQLDashboardId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards/$KQLDashboardId/getDefinition" - -} - -process { - - if ($PSBoundParameters.ContainsKey("KQLDashboardId")) { - Write-Verbose "Get KQLDashboardDefinition with ID $KQLDashboardId" - Write-Verbose "Calling KQLDashboard API with KQLDashboardId" - Write-Verbose "--------------------------------------------" - Write-Verbose "Sending the following values to the KQLDashboard API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $KQLDashboardAPIKQLDashboardId" - Write-Verbose "Body of request: $$null" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $KQLDashboardAPIKQLDashboardId ` - -Body $null ` - -ContentType "application/json" - - $parts = $response.definition.parts - Write-Verbose "Decoding the payload of the parts: $parts" - - foreach ($part in $parts) { - $bytes = [System.Convert]::FromBase64String($part.payload) - Write-Verbose "Returned bytes for part $part.name: $bytes" - $decodedText = [System.Text.Encoding]::UTF8.GetString($bytes) - Write-Verbose "decodedText for part $part.name: $decodedText" - $part.payload = $decodedText - } - - $parts - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Dashboard/New-FabricKQLDashboard.ps1 b/FabricTools/public/KQL Dashboard/New-FabricKQLDashboard.ps1 deleted file mode 100644 index e3e05f2e..00000000 --- a/FabricTools/public/KQL Dashboard/New-FabricKQLDashboard.ps1 +++ /dev/null @@ -1,92 +0,0 @@ -function New-FabricKQLDashboard { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Creates a new Fabric KQLDashboard - -.DESCRIPTION - Creates a new Fabric KQLDashboard - -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the KQLDashboard should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDashboardName - The name of the KQLDashboard to create. - -.PARAMETER KQLDashboardDescription - The description of the KQLDashboard to create. - -.EXAMPLE - New-FabricDashboard ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -KQLDashboardName 'MyKQLDashboard' ` - -KQLDashboardDescription 'This is my KQLDashboard' - - This example will create a new KQLDashboard with the name 'MyKQLDashboard' and the description 'This is my KQLDashboard'. - -.NOTES - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName - - 2024-12-08 - FGE: Added Verbose Output -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] - [string]$KQLDashboardName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$KQLDashboardDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{ - 'displayName' = $KQLDashboardName - 'description' = $KQLDashboardDescription - } | ConvertTo-Json ` - -Depth 1 - - # Create KQLDashboard API URL - $KQLDashboardApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards" - } - -process { - - if($PSCmdlet.ShouldProcess($KQLDashboardName)) { - Write-Verbose "Calling KQLDashboard API" - Write-Verbose "------------------------" - Write-Verbose "Sending the following values to the KQLDashboard API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $KQLDashboardApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $KQLDashboardApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Database/Get-FabricKQLDatabase.ps1 b/FabricTools/public/KQL Database/Get-FabricKQLDatabase.ps1 deleted file mode 100644 index 701cd233..00000000 --- a/FabricTools/public/KQL Database/Get-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,228 +0,0 @@ -function Get-FabricKQLDatabase { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric KQLDatabases - -.DESCRIPTION - Retrieves Fabric KQLDatabases. Without the KQLDatabaseName or KQLDatabaseID parameter, - all KQLDatabases are returned. If you want to retrieve a specific KQLDatabase, you can - use the KQLDatabaseName or KQLDatabaseID parameter. These parameters cannot be used together. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLDatabases should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDatabaseName - The name of the KQLDatabase to retrieve. This parameter cannot be used together with KQLDatabaseID. - -.PARAMETER KQLDatabaseID - The Id of the KQLDatabase to retrieve. This parameter cannot be used together with KQLDatabaseName. - The value for KQLDatabaseID is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' - - This example will retrieve the KQLDatabase with the name 'MyKQLDatabase'. - -.EXAMPLE - Get-FabricKQLDatabase - - This example will retrieve all KQLDatabases in the workspace that is specified - by the WorkspaceId. - -.EXAMPLE - Get-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseId '12345678-1234-1234-1234-123456789012' - - This example will retrieve the KQLDatabase with the ID '12345678-1234-1234-1234-123456789012'. - -.NOTES - TODO: Add functionality to list all KQLDatabases. To do so fetch all workspaces and - then all KQLDatabases in each workspace. - - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName - - 2024-12-08 - FGE: Added Verbose Output - -#> - - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Name","DisplayName")] - [string]$KQLDatabaseName, - - [Alias("Id")] - [string]$KQLDatabaseId - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use KQLDatabaseName or KQLDatabaseID not both. If both are used throw error" - if ($PSBoundParameters.ContainsKey("KQLDatabaseName") -and $PSBoundParameters.ContainsKey("KQLDatabaseId")) { - throw "Parameters KQLDatabaseName and KQLDatabaseId cannot be used together" - } - - # Create KQLDatabase API - $KQLDatabaseAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/kqldatabases" - - $KQLDatabaseAPIKQLDatabaseId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/kqldatabases/$KQLDatabaseId" - -} - -process { - - if ($PSBoundParameters.ContainsKey("KQLDatabaseId")) { - Write-Verbose "Calling KQLDatabase API with KQLDatabaseId : $KQLDatabaseId" - Write-Verbose "-------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLDatabase API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDatabaseAPIKQLDatabaseId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDatabaseAPIKQLDatabaseId ` - -ContentType "application/json" - - Write-Verbose "Adding Members to the Output object for convenience" - Write-Verbose "Adding Member parentEventhouseItemId with value $($response.properties.parentEventhouseItemId)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'parentEventhouseItemId' ` - -Value $response.properties.parentEventhouseItemId ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member queryServiceUri with value $($response.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $response.properties.queryServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member ingestionServiceUri with value $($response.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $response.properties.ingestionServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member databaseType with value $($response.properties.databaseType)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databaseType' ` - -Value $response.properties.databaseType ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member oneLakeStandardStoragePeriod with value $($response.properties.oneLakeStandardStoragePeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeStandardStoragePeriod' ` - -Value $response.properties.oneLakeStandardStoragePeriod ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member oneLakeCachingPeriod with value $($response.properties.oneLakeCachingPeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeCachingPeriod' ` - -Value $response.properties.oneLakeCachingPeriod ` - -InputObject $response ` - -Force - - $response - } - else { - Write-Verbose "Calling KQLDatabase API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the KQLDatabase API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDatabaseAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDatabaseAPI ` - -ContentType "application/json" - - Write-Verbose "Adding Members to the Output object for convenience" - foreach ($kqlDatabase in $response.value) { - Write-Verbose "Adding Member parentEventhouseItemId with value $($response.properties.parentEventhouseItemId)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'parentEventhouseItemId' ` - -Value $response.properties.parentEventhouseItemId ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member queryServiceUri with value $($kqlDatabase.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $kqlDatabase.properties.queryServiceUri ` - -InputObject $kqlDatabase ` - -Force - - Write-Verbose "Adding Member ingestionServiceUri with value $($kqlDatabase.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $kqlDatabase.properties.ingestionServiceUri ` - -InputObject $kqlDatabase ` - -Force - Write-Verbose "Adding Member databaseType with value $($kqlDatabase.properties.databaseType)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databaseType' ` - -Value $kqlDatabase.properties.databaseType ` - -InputObject $kqlDatabase ` - -Force - - Write-Verbose "Adding Member oneLakeStandardStoragePeriod with value $($kqlDatabase.properties.oneLakeStandardStoragePeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeStandardStoragePeriod' ` - -Value $kqlDatabase.properties.oneLakeStandardStoragePeriod ` - -InputObject $kqlDatabase ` - -Force - - Write-Verbose "Adding Member oneLakeCachingPeriod with value $($kqlDatabase.properties.oneLakeCachingPeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeCachingPeriod' ` - -Value $kqlDatabase.properties.oneLakeCachingPeriod ` - -InputObject $kqlDatabase ` - -Force - } - - if ($PSBoundParameters.ContainsKey("KQLDatabaseName")) { - Write-Verbose "Filtering KQLDatabases by name. Name: $KQLDatabaseName" - $response.value | ` - Where-Object { $_.displayName -eq $KQLDatabaseName } - } - else { - Write-Verbose "Returning all KQLDatabases" - $response.value - } - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Database/New-FabricKQLDatabase.ps1 b/FabricTools/public/KQL Database/New-FabricKQLDatabase.ps1 deleted file mode 100644 index e019acd7..00000000 --- a/FabricTools/public/KQL Database/New-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,106 +0,0 @@ -function New-FabricKQLDatabase { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Creates a new Fabric KQLDatabase - -.DESCRIPTION - Creates a new Fabric KQLDatabase. The KQLDatabase is created in the specified Workspace and Eventhouse. - It will be created with the specified name and description. - -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the KQLDatabase should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseID - Id of the Fabric Eventhouse for which the KQLDatabase should be created. The value for EventhouseID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDatabaseName - The name of the KQLDatabase to create. The name must be unique within the eventhouse and is a - mandatory parameter. - -.PARAMETER KQLDatabaseDescription - The description of the KQLDatabase to create. - -.EXAMPLE - New-FabricKQLDatabase ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseID '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' ` - -KQLDatabaseDescription 'This is my KQLDatabase' - - This example will create a new KQLDatabase with the name 'MyKQLDatabase' and the description 'This is my KQLDatabase'. - -.NOTES - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName - - 2024-12-08 - FGE: Added Verbose Output -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [string]$EventhouseID, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] - [string]$KQLDatabaseName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$KQLDatabaseDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body for the request" - $body = @{ - 'displayName' = $KQLDatabaseName - 'description' = $KQLDatabaseDescription - 'creationPayload'= @{ - 'databaseType' = "ReadWrite"; - 'parentEventhouseItemId' = $EventhouseId} - } | ConvertTo-Json ` - -Depth 1 - - # Create KQLDatabase API URL - $KQLDatabaseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases" - } - -process { - - if($PSCmdlet.ShouldProcess($EventhouseName)) { - Write-Verbose "Calling KQLDatabase API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $KQLDatabaseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $KQLDatabaseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end { - -} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Database/Remove-FabricKQLDatabase.ps1 b/FabricTools/public/KQL Database/Remove-FabricKQLDatabase.ps1 deleted file mode 100644 index 411cec73..00000000 --- a/FabricTools/public/KQL Database/Remove-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,77 +0,0 @@ -function Remove-FabricKQLDatabase { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Removes an existing Fabric Eventhouse - -.DESCRIPTION - Removes an existing Fabric Eventhouse. The Eventhouse is removed from the specified Workspace. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace from which the Eventhouse should be removed. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseId - The Id of the Eventhouse to remove. The value for EventhouseId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' - - This example will remove the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. - -.NOTES - TODO: Add functionality to remove Eventhouse by name. - - Revsion History: - - - 2024-12-08 - FGE: Added Verbose Output - -#> - - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$KQLDatabaseId - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create Eventhouse API URL" - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases/$KQLDatabaseId" - -} - -process { - - if($PSCmdlet.ShouldProcess($KQLDatabaseId)) { - Write-Verbose "Calling KQLDatabase API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $eventhouseApiUrl ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Database/Set-FabricKQLDatabase.ps1 b/FabricTools/public/KQL Database/Set-FabricKQLDatabase.ps1 deleted file mode 100644 index ddeade86..00000000 --- a/FabricTools/public/KQL Database/Set-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,114 +0,0 @@ -function Set-FabricKQLDatabase { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Updates Properties of an existing Fabric KQLDatabase - -.DESCRIPTION - Updates Properties of an existing Fabric KQLDatabase. The KQLDatabase is updated - in the specified Workspace. The KQLDatabaseId is used to identify the KQLDatabase - that should be updated. The KQLDatabaseNewName and KQLDatabaseDescription are the - properties that can be updated. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLDatabase should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDatabaseId - The Id of the KQLDatabase to update. The value for KQLDatabaseId is a GUID. - An example of a GUID is '12345678-1234-123-1234-123456789012'. - -.PARAMETER NewKQLDatabaseName - The new name of the KQLDatabase. - -.PARAMETER KQLDatabaseDescription - The new description of the KQLDatabase. The description can be up to 256 characters long. - -.EXAMPLE - Set-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseId '12345678-1234-1234-1234-123456789012' ` - -NewKQLDatabaseNewName 'MyNewKQLDatabase' ` - -KQLDatabaseDescription 'This is my new KQLDatabase' - - This example will update the KQLDatabase with the Id '12345678-1234-1234-1234-123456789012'. - It will update the name to 'MyNewKQLDatabase' and the description to 'This is my new KQLDatabase'. - -.NOTES - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName - - 2024-12-08 - FGE: Added Verbose Output - Renamed Parameter KQLDatabaseName to NewKQLDatabaseNewName - -.LINK - -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$KQLDatabaseId, - - [Alias("NewName", "NewDisplayName")] - [string]$NewKQLDatabaseName, - - [Alias("Description")] - [ValidateLength(0, 256)] - [string]$KQLDatabaseDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{} - - if ($PSBoundParameters.ContainsKey("NewKQLDatabaseName")) { - $body["displayName"] = $NewKQLDatabaseName - } - - if ($PSBoundParameters.ContainsKey("KQLDatabaseDescription")) { - $body["description"] = $KQLDatabaseDescription - } - - $body = $body | ConvertTo-Json -Depth 1 - - # Create KQLDatabase API URL - $KQLDatabaseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases/$KQLDatabaseId" - } - -process { - - if($PSCmdlet.ShouldProcess($KQLDatabaseId)) { - Write-Verbose "Calling KQLDatabase API with KQLDatabaseId $KQLDatabaseId" - Write-Verbose "------------------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLDatabase API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $KQLDatabaseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $KQLDatabaseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Queryset/Get-FabricKQLQueryset.ps1 b/FabricTools/public/KQL Queryset/Get-FabricKQLQueryset.ps1 deleted file mode 100644 index ce7629a4..00000000 --- a/FabricTools/public/KQL Queryset/Get-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,130 +0,0 @@ -function Get-FabricKQLQueryset { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric KQLQuerysets - -.DESCRIPTION - Retrieves Fabric KQLQuerysets. Without the KQLQuerysetName or KQLQuerysetId parameter, - all KQLQuerysets are returned in the given Workspace. If you want to retrieve a specific - KQLQueryset, you can use the KQLQuerysetName or KQLQuerysetId parameter. These parameters - cannot be used together. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLQuerysets should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. - -.PARAMETER KQLQuerysetName - The name of the KQLQueryset to retrieve. This parameter cannot be used together with KQLQuerysetId. - -.PARAMETER KQLQuerysetId - The Id of the KQLQueryset to retrieve. This parameter cannot be used together with KQLQuerysetName. - The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetName 'MyKQLQueryset' - - This example will retrieve the KQLQueryset with the name 'MyKQLQueryset'. - -.EXAMPLE - Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' - - This example will retrieve all KQLQuerysets in the workspace that is specified - by the WorkspaceId. - -.EXAMPLE - Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' - - This example will retrieve the KQLQueryset with the ID '12345678-1234-1234-1234-123456789012'. - -.NOTES - TODO: Add functionality to list all KQLQuerysets. To do so fetch all workspaces and - then all KQLQuerysets in each workspace. - - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLQuerysetName - - 2024-12-22 - FGE: Added Verbose Output -#> - - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Name","DisplayName")] - [string]$KQLQuerysetName, - - [Alias("Id")] - [string]$KQLQuerysetId - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use Name or WorkspaceID" - if ($PSBoundParameters.ContainsKey("KQLQuerysetName") -and $PSBoundParameters.ContainsKey("KQLQuerysetId")) { - throw "Parameters KQLQuerysetName and KQLQuerysetId cannot be used together" - } - - # Create KQLQueryset API - $KQLQuerysetAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets" - - $KQLQuerysetAPIKQLQuerysetId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" - -} - -process { - - if ($PSBoundParameters.ContainsKey("KQLQuerysetId")) { - Write-Verbose "Calling KQLQueryset API with KQLQuerysetId $KQLQuerysetId" - Write-Verbose "------------------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLQuerysetAPIKQLQuerysetId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLQuerysetAPIKQLQuerysetId ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling KQLQueryset API" - Write-Verbose "------------------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLQuerysetAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLQuerysetAPI ` - -ContentType "application/json" - - if ($PSBoundParameters.ContainsKey("KQLQuerysetName")) { - Write-Verbose "Filtering KQLQuerysets by name. Name: $KQLQuerysetName" - $response.value | ` - Where-Object { $_.displayName -eq $KQLQuerysetName } - } - else { - Write-Verbose "Returning all KQLQuerysets" - $response.value - } - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Queryset/Invoke-FabricKQLCommand.ps1 b/FabricTools/public/KQL Queryset/Invoke-FabricKQLCommand.ps1 deleted file mode 100644 index 34720f2c..00000000 --- a/FabricTools/public/KQL Queryset/Invoke-FabricKQLCommand.ps1 +++ /dev/null @@ -1,230 +0,0 @@ -function Invoke-FabricKQLCommand { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Executes a KQL command in a Kusto Database. - -.DESCRIPTION - Executes a KQL command in a Kusto Database. The KQL command is executed in the Kusto Database - that is specified by the KQLDatabaseName or KQLDatabaseId parameter. The KQL command is executed - in the context of the Fabric Real-Time Intelligence session that is established by the - Connect-RTISession cmdlet. The cmdlet distinguishes between management commands and query commands. - Management commands are executed in the management API, while query commands are executed in the query API. - The distinction is made by checking if the KQL command starts with a dot. If it does, it is a management command - else it is a query command. If the KQL command is a management command, it is crucial to have the - .execute database script <| in the beginning, otherwise the Kusto API will not execute the script. This cmdlet - will automatically add the .execute database script <| in the beginning of the KQL command if it is a management command - and if it is not already present. - If the KQL Command is a query command, the result is returned as an array of PowerShell objects by default. If the - parameter -ReturnRawResult is used, the raw result of the KQL query is returned which is a JSON object. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQL command should be executed. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDatabaseName - The name of the KQLDatabase in which the KQL command should be executed. This parameter cannot be used together with KQLDatabaseId. - -.PARAMETER KQLDatabaseId - The Id of the KQLDatabase in which the KQL command should be executed. This parameter cannot be used together with KQLDatabaseName. - The value for KQLDatabaseId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLCommand - The KQL command that should be executed in the Kusto Database. - The KQL command is a string. An example of a string is '.create table MyTable (MyColumn: string)'. - -.PARAMETER ReturnRawResult - When this switch is used, the raw result of the KQL command is returned. By default, the result is returned as - a PowerShell object. - -.EXAMPLE - Invoke-FabricKQLCommand ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' ` - -KQLCommand '.create table MyTable (MyColumn: string)' - - This example will create a table named 'MyTable' with a column named 'MyColumn' in - the KQLDatabase 'MyKQLDatabase'. - -.EXAMPLE - Invoke-FabricKQLCommand ` - -WorkspaceId '2c4ccbb5-9b13-4495-9ab3-ba41152733d9' ` - -KQLDatabaseName 'MyEventhouse2' ` - -KQLCommand 'productcategory - | take 100' - - This example will Execute the Query 'productcategory | take 100' in the KQLDatabase 'MyEventhouse2' - and it will return the result as an array of PowerShell objects. - -.EXAMPLE - Invoke-FabricKQLCommand ` - -WorkspaceId '2c4ccbb5-9b13-4495-9ab3-ba41152733d9' ` - -KQLDatabaseName 'MyEventhouse2' ` - -ReturnRawResult ` - -KQLCommand 'productcategory - | take 100' - - This example will Execute the Query 'productcategory | take 100' in the KQLDatabase 'MyEventhouse2' - and it will return the result as the raw result of the KQL command, which is a JSON object. - -.NOTES - - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output - - 2024-12-27 - FGE: Major Update to support KQL Queries and Management Commands - -#> - -[CmdletBinding()] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [string]$KQLDatabaseName, - - [string]$KQLDatabaseId, - - [Parameter(Mandatory=$true)] - [string]$KQLCommand, - - [switch]$ReturnRawResult - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Check if KQLDatabaseName and KQLDatabaseId are used together" - if ($PSBoundParameters.ContainsKey("KQLDatabaseName") -and $PSBoundParameters.ContainsKey("KQLDatabaseId")) { - throw "Parameters KQLDatabaseName and KQLDatabaseId cannot be used together" - } - - Write-Verbose "Get Kusto Database" - if ($PSBoundParameters.ContainsKey("KQLDatabaseName")) { - Write-Verbose "Getting Kusto Database by Name: $KQLDatabaseName" - $kustDB = Get-FabricKQLDatabase ` - -WorkspaceId $WorkspaceId ` - -KQLDatabaseName $KQLDatabaseName - } - - if ($PSBoundParameters.ContainsKey("KQLDatabaseId")) { - Write-Verbose "Getting Kusto Database by Id: $KQLDatabaseId" - $kustDB = Get-FabricKQLDatabase ` - -WorkspaceId $WorkspaceId ` - -KQLDatabaseId $KQLDatabaseId - } - - Write-Verbose "Check if Kusto Database was found" - if ($null -eq $kustDB) { - throw "Kusto Database not found" - } - - Write-Verbose "Generate the Management API URL" - $mgmtAPI = "$($kustDB.queryServiceUri)/v1/rest/mgmt" - - Write-Verbose "Generate the query API URL" - $queryAPI = "$($kustDB.queryServiceUri)/v1/rest/query" - - - $KQLCommand = $KQLCommand | Out-String - - Write-Verbose "Check if the KQL command starts with a dot so it is a management command. Otherwise it is a query command" - if (-not ($KQLCommand -match "^\.")) { - $isManamgentCommand = $false - Write-Verbose "The command is a query command." - } - else { - $isManamgentCommand = $true - Write-Verbose "The command is a management command. It is crucial to have the .execute database script <| in the beginning, otherwise the Kusto API will not execute the script." - if (-not ($KQLCommand -match "\.execute database script <\|")) { - $KQLCommand = ".execute database script <| $KQLCommand" - } - } -} - -process { - - Write-Verbose "The KQL-Command is: $KQLCommand" - - Write-Verbose "Create body of the request" - $body = @{ - 'csl' = $KQLCommand; - 'db'= $kustDB.displayName - } | ConvertTo-Json -Depth 1 - - - if ($isManamgentCommand) { - Write-Verbose "Calling Management API" - Write-Verbose "----------------------" - Write-Verbose "Sending the following values to the Query API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $mgmtAPI" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - - $result = Invoke-RestMethod ` - -Headers $headerParams ` - -Method POST ` - -Uri $mgmtAPI ` - -Body ($body) ` - -ContentType "application/json; charset=utf-8" - - Write-Verbose "Result of the Management API: $($result | ` - ConvertTo-Json ` - -Depth 10)" - $result - } - else { - Write-Verbose "Calling Query API" - Write-Verbose "-----------------" - Write-Verbose "Sending the following values to the Query API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $queryAPI" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - - $result = Invoke-RestMethod ` - -Headers $headerParams ` - -Method POST ` - -Uri $queryAPI ` - -Body ($body) ` - -ContentType "application/json; charset=utf-8" - Write-Verbose "Result of the Query API: $($result | ` - ConvertTo-Json ` - -Depth 10)" - - - - if ($ReturnRawResult) { - $result - } - else { - $myRecords = @() - - for ($j = 0; $j -lt $Result.tables[0].rows.Count; $j++) { - $myTableRow = [PSCustomObject]@{} - - for ($i = 0; $i -lt $Result.tables[0].rows[0].Count; $i++) { - $myTableRow | ` - Add-Member ` - -MemberType NoteProperty ` - -Name $Result.Tables[0].Columns[$i].ColumnName ` - -Value $Result.Tables[0].rows[$j][$i] - } - $myRecords += $myTableRow - } - - $myRecords - } - - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Queryset/Remove-FabricKQLQueryset.ps1 b/FabricTools/public/KQL Queryset/Remove-FabricKQLQueryset.ps1 deleted file mode 100644 index 68d2b3df..00000000 --- a/FabricTools/public/KQL Queryset/Remove-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,77 +0,0 @@ -function Remove-FabricKQLQueryset { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Removes an existing Fabric KQLQueryset. - -.DESCRIPTION - Removes an existing Fabric KQLQueryset. The Eventhouse is identified by the WorkspaceId and KQLQuerysetId. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLQueryset should be removed. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. - -.PARAMETER KQLQuerysetId - The Id of the KQLQueryset to remove. The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - This parameter is mandatory. - -.EXAMPLE - Remove-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' - -.NOTES - TODO: Add functionality to remove KQLQueryset by name. - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-12-22 - FGE: Added Verbose Output - -#> - - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$KQLQuerysetId - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - # Create KQLQueryset API URL - $querysetApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" -} - -process { - - # Call KQL Queryset API - if($PSCmdlet.ShouldProcess($KQLQuerysetId)) { - Write-Verbose "Calling KQLQueryset API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $querysetApiUrl" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $querysetApiUrl ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/KQL Queryset/Set-FabricKQLQueryset.ps1 b/FabricTools/public/KQL Queryset/Set-FabricKQLQueryset.ps1 deleted file mode 100644 index f571d379..00000000 --- a/FabricTools/public/KQL Queryset/Set-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,111 +0,0 @@ -function Set-FabricKQLQueryset { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Updates Properties of an existing Fabric KQLQueryset - -.DESCRIPTION - Updates Properties of an existing Fabric KQLQueryset. The KQLQueryset is identified by - the WorkspaceId and KQLQuerysetId. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLQueryset should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. - -.PARAMETER KQLQuerysetId - The Id of the KQLQueryset to update. The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - This parameter is mandatory. - -.PARAMETER KQLQuerysetName - The new name of the KQLQueryset. This parameter is optional. - -.PARAMETER KQLQuerysetDescription - The new description of the KQLQueryset. This parameter is optional. - -.EXAMPLE - Set-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetNewName 'MyKQLQueryset' ` - -KQLQuerysetDescription 'This is my KQLQueryset' - - This example will update the KQLQueryset. The KQLQueryset will have the name 'MyKQLQueryset' - and the description 'This is my KQLQueryset'. - -.NOTES - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added NewDisplaName as Alias for KQLQuerysetNewName - - 2024-12-22 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/KQLQueryset/items/create-KQLQueryset?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$KQLQuerysetId, - - [Alias("NewName", "NewDisplayName")] - [string]$KQLQuerysetNewName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$KQLQuerysetDescription - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{} - - if ($PSBoundParameters.ContainsKey("KQLQuerysetName")) { - $body["displayName"] = $KQLQuerysetNName - } - - if ($PSBoundParameters.ContainsKey("KQLQuerysetDescription")) { - $body["description"] = $KQLQuerysetDescription - } - - $body = $body | ConvertTo-Json -Depth 1 - - # Create KQLQueryset API URL - $KQLQuerysetApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" - } - -process { - - # Call KQLQueryset API - if($PSCmdlet.ShouldProcess($KQLQuerysetId)) { - Write-Verbose "Calling KQLQueryset API with KQLQuerysetId $KQLQuerysetId" - Write-Verbose "---------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $KQLQuerysetApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $KQLQuerysetApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Lakehouse/New-FabricLakehouse.ps1 b/FabricTools/public/Lakehouse/New-FabricLakehouse.ps1 deleted file mode 100644 index 37e622fa..00000000 --- a/FabricTools/public/Lakehouse/New-FabricLakehouse.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -function New-FabricLakehouse { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Creates a new Fabric Lakehouse - -.DESCRIPTION - Creates a new Fabric Lakehouse - -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the Lakehouse should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER LakehouseName - The name of the Lakehouse to create. - -.PARAMETER LakehouseDescription - The description of the Lakehouse to create. - -.EXAMPLE - New-FabricLakehouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -LakehouseName 'MyLakehouse' ` - -LakehouseSchemaEnabled $true ` - -LakehouseDescription 'This is my Lakehouse' - - This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my Lakehouse'. - - .EXAMPLE - New-FabricLakehouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -LakehouseName 'MyLakehouse' ` - -LakehouseSchemaEnabled $true ` - -LakehouseDescription 'This is my Lakehouse' - -Verbose - - This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my Lakehouse'. - It will also give you verbose output which is useful for debugging. - -.NOTES - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/lakehouse/items/create-lakehouse?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] - [string]$LakehouseName, - - [Parameter(Mandatory=$true)] - [Alias("LakehouseSchemaEnabled")] - [bool]$SchemaEnabled, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$LakehouseDescription - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - #create payload - $body = [ordered]@{ - 'displayName' = $LakehouseName - 'description' = $LakehouseDescription - } - - #add enableSchema element if $LakehouseSchemaEnabled is true - if ($LakehouseSchemaEnabled) { - $body['creationPayload'] = @{ - 'enableSchemas' = $LakehouseSchemaEnabled - } - } - - #format payload - $body = $body | ConvertTo-Json -Depth 1 - - # Create Eventhouse API URL - $lakehouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/lakehouses" -} - -process { - - Write-Verbose "Calling Lakehouse API" - Write-Verbose "----------------------" - Write-Verbose "Sending the following values to the Lakehouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $lakehouseApiUrl" - Write-Verbose "Body of request: $bodyJson" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($LakehouseName)) { - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $lakehouseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} diff --git a/FabricTools/public/SQL Database/Get-FabricSQLDatabase.ps1 b/FabricTools/public/SQL Database/Get-FabricSQLDatabase.ps1 deleted file mode 100644 index 6ba60a10..00000000 --- a/FabricTools/public/SQL Database/Get-FabricSQLDatabase.ps1 +++ /dev/null @@ -1,78 +0,0 @@ -function Get-FabricSQLDatabase { - -<# -.SYNOPSIS - Retrieves Fabric SQLDatabases - -.DESCRIPTION - Retrieves Fabric SQLDatabases. Without the SQLDatabaseName or SQLDatabaseID parameter, - all SQLDatabases are returned. If you want to retrieve a specific SQLDatabase, you can - use the SQLDatabaseName or SQLDatabaseID parameter. These parameters cannot be used together. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the SQLDatabases should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER SQLDatabaseName - The name of the KQLDatabase to retrieve. This parameter cannot be used together with SQLDatabaseID. - -.PARAMETER SQLDatabaseID - The Id of the SQLDatabase to retrieve. This parameter cannot be used together with SQLDatabaseName. - The value for SQLDatabaseID is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricSQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -SQLDatabaseName 'MySQLDatabase' - - This example will retrieve the SQLDatabase with the name 'MySQLDatabase'. - -.EXAMPLE - Get-FabricSQLDatabase - - This example will retrieve all SQLDatabases in the workspace that is specified - by the WorkspaceId. - -.EXAMPLE - Get-FabricSQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -SQLDatabaseId '12345678-1234-1234-1234-123456789012' - - This example will retrieve the SQLDatabase with the ID '12345678-1234-1234-1234-123456789012'. - -.NOTES - Revision History: - - 2025-03-06 - KNO: Init version of the function -#> - - -[CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Alias("Name","DisplayName")] - [string]$SQLDatabaseName, - - [Alias("Id")] - [string]$SQLDatabaseId - ) - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use SQLDatabaseName or SQLDatabaseID not both. If both are used throw error" - if ($PSBoundParameters.ContainsKey("SQLDatabaseName") -and $PSBoundParameters.ContainsKey("SQLDatabaseId")) { - throw "Parameters SQLDatabaseName and SQLDatabaseId cannot be used together" - } - - # Create SQLDatabase API - $uri = "$($FabricSession.BaseApiUrl)/workspaces/$workspaceId/SqlDatabases" - if ($SQLDatabaseId) { - $uri = "$uri/$SQLDatabaseId" - } - $result = Invoke-RestMethod -Headers $FabricSession.HeaderParams -Uri $uri - ##$databases.Where({$_.displayName -eq $body.displayName}).id - - return $result.value - -} \ No newline at end of file diff --git a/FabricTools/public/SQL Database/New-FabricSQLDatabase.ps1 b/FabricTools/public/SQL Database/New-FabricSQLDatabase.ps1 deleted file mode 100644 index e980f04e..00000000 --- a/FabricTools/public/SQL Database/New-FabricSQLDatabase.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -<# -.SYNOPSIS -Creates a new SQL Database in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new SQL Database -in the specified workspace. It supports optional parameters for SQL Database description -and path definitions for the SQL Database content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the SQL Database will be created. - -.PARAMETER Name -The name of the SQL Database to be created. - -.PARAMETER Description -An optional description for the SQL Database. - - -.EXAMPLE - New-FabricSQLDatabase -WorkspaceId "workspace-12345" -Name "NewDatabase" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Kamil Nowinski - -#> - -function New-FabricSQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$Name, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$Description - ) - - try { - # Step 1: Ensure token validity - Test-TokenExpired - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $Name - } - - if ($Description) { - $body.description = $Description - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - Write-Message "RESPONSE: $response" -Level Debug - Test-FabricApiResponse -response $response -responseHeader $responseHeader -statusCode $statusCode -Name $Name -TypeName 'SQL Database' - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create SQL Database. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/public/SQL Database/Remove-FabricSQLDatabase.ps1 b/FabricTools/public/SQL Database/Remove-FabricSQLDatabase.ps1 deleted file mode 100644 index 799f07d4..00000000 --- a/FabricTools/public/SQL Database/Remove-FabricSQLDatabase.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -<# -.SYNOPSIS -Deletes a SQL Database from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricSQLDatabase` function sends a DELETE request to the Fabric API to remove a specified SQLDatabase from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the SQLDatabase to delete. - -.PARAMETER SQLDatabaseId -(Mandatory) The ID of the SQL Database to be deleted. - -.EXAMPLE -Remove-FabricSQLDatabas -WorkspaceId "12345" -SQLDatabaseId "67890" - -Deletes the SQL Database with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Kamil Nowinski - -#> - -function Remove-FabricSQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SQLDatabaseId - ) - - try { - # Step 1: Ensure token validity - Confirm-FabricAuthToken | Out-Null - Test-TokenExpired - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SQLDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "SQL Database '$SQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete SQL Database '$SQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/public/Set-FabricAuthToken.ps1 b/FabricTools/public/Set-FabricAuthToken.ps1 deleted file mode 100644 index 591f8ec9..00000000 --- a/FabricTools/public/Set-FabricAuthToken.ps1 +++ /dev/null @@ -1,104 +0,0 @@ -<# -.SYNOPSIS - Sets the Fabric authentication token. - -.DESCRIPTION - The Set-FabricAuthToken function sets the Fabric authentication token. It checks if an Azure context is already available. If not, it connects to the Azure account using either a service principal ID and secret, a provided credential, or interactive login. It then gets the Azure context and sets the Fabric authentication token. - -.PARAMETER azContext - The Azure context. If not provided, the function connects to the Azure account and gets the context. - -.PARAMETER servicePrincipalId - The service principal ID. If provided, the function uses this ID and the service principal secret to connect to the Azure account. - -.PARAMETER servicePrincipalSecret - The service principal secret. Used with the service principal ID to connect to the Azure account. - -.PARAMETER tenantId - The tenant ID. Used with the service principal ID and secret or the credential to connect to the Azure account. - -.PARAMETER credential - The credential. If provided, the function uses this credential to connect to the Azure account. - -.EXAMPLE - Set-FabricAuthToken -servicePrincipalId "12345678-90ab-cdef-1234-567890abcdef" -servicePrincipalSecret "secret" -tenantId "12345678-90ab-cdef-1234-567890abcdef" - - This command sets the Fabric authentication token using the provided service principal ID, secret, and tenant ID. - -.INPUTS - String, SecureString, PSCredential. You can pipe a string that contains the service principal ID, a secure string that contains the service principal secret, and a PSCredential object that contains the credential to Set-FabricAuthToken. - -.OUTPUTS - None. This function does not return any output. - -.NOTES - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip -#> - -function Set-FabricAuthToken { - <# - .SYNOPSIS - Set authentication token for the Fabric service - #> - [CmdletBinding(SupportsShouldProcess)] - param - ( - [string] $servicePrincipalId - , [string] $servicePrincipalSecret - , [PSCredential] $credential - , [string] $tenantId - , [switch] $reset - , [string] $apiUrl - ) - - if (!$reset) { - $azContext = Get-AzContext - } - - if ($apiUrl) { - $FabricSession.BaseApiUrl = $apiUrl - } - - if (!$azContext) { - Write-Output "Getting authentication token" - if ($servicePrincipalId) { - $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $servicePrincipalId, ($servicePrincipalSecret | ConvertTo-SecureString -AsPlainText -Force) - - Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential | Out-Null - - Set-AzContext -Tenant $tenantId | Out-Null - } - elseif ($null -ne $credential) { - Connect-AzAccount -Credential $credential -Tenant $tenantId | Out-Null - } - else { - Connect-AzAccount | Out-Null - } - $azContext = Get-AzContext - } - if ($PSCmdlet.ShouldProcess("Setting Fabric authentication token for $($azContext.Account)")) { - Write-Output "Connnected: $($azContext.Account)" - Write-Output "Fabric ResourceUrl: $($FabricSession.ResourceUrl)" - - $FabricSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $FabricSession.ResourceUrl) - $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($FabricSession.AccessToken.Token) - $FabricSession.FabricToken = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) - Write-Verbose "Setup headers for API calls" - $FabricSession.HeaderParams = @{ Authorization = $FabricSession.AccessToken.Type + ' ' + $FabricSession.FabricToken } - - Write-Output "Azure BaseApiUrl: $($FabricSession.ResourceUrl)" - $script:AzureSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $AzureSession.BaseApiUrl) - $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzureSession.AccessToken.Token) - $script:AzureSession.Token = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) - $AzureSession.HeaderParams = @{ Authorization = $AzureSession.AccessToken.Type + ' ' + $AzureSession.Token } - - # Copy session values to exposed $FabricConfig - $FabricConfig.TenantIdGlobal = $FabricSession.AccessToken.TenantId - $FabricConfig.TokenExpiresOn = $FabricSession.AccessToken.ExpiresOn - $FabricConfig.FabricHeaders = $FabricSession.HeaderParams - - return $($FabricSession.FabricToken) - - } -} \ No newline at end of file diff --git a/FabricTools/public/Tenant/Get-FabricTenantSettings.ps1 b/FabricTools/public/Tenant/Get-FabricTenantSettings.ps1 deleted file mode 100644 index 51f88e64..00000000 --- a/FabricTools/public/Tenant/Get-FabricTenantSettings.ps1 +++ /dev/null @@ -1,27 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the tenant settings from the Fabric API. - -.DESCRIPTION -The Get-FabricTenantSettings function makes a GET request to the Fabric API to retrieve the tenant settings. It returns the 'tenantSettings' property of the first item in the response. - -.PARAMETER None -This function does not have any parameters. - -.EXAMPLE -Get-FabricTenantSettings -Retrieves the tenant settings from the Fabric API. - -#> - -function Get-FabricTenantSettings { - [Alias("Get-FabTenantSettings")] - Param () - - Confirm-FabricAuthToken | Out-Null - - $result = Invoke-FabricAPIRequest -uri "admin/tenantsettings" -Method GET - - return $result.tenantSettings -} \ No newline at end of file diff --git a/FabricTools/public/Utils/Get-FabricLongRunningOperation.ps1 b/FabricTools/public/Utils/Get-FabricLongRunningOperation.ps1 deleted file mode 100644 index 4fa2cc07..00000000 --- a/FabricTools/public/Utils/Get-FabricLongRunningOperation.ps1 +++ /dev/null @@ -1,88 +0,0 @@ -<# -.SYNOPSIS -Monitors the status of a long-running operation in Microsoft Fabric. - -.DESCRIPTION -The Get-FabricLongRunningOperation function queries the Microsoft Fabric API to check the status of a -long-running operation. It periodically polls the operation until it reaches a terminal state (Succeeded or Failed). - -.PARAMETER operationId -The unique identifier of the long-running operation to be monitored. - -.PARAMETER retryAfter -The interval (in seconds) to wait between polling the operation status. The default is 5 seconds. - -.EXAMPLE -Get-FabricLongRunningOperation -operationId "12345-abcd-67890-efgh" -retryAfter 10 - -This command polls the status of the operation with the given operationId every 10 seconds until it completes. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - -.AUTHOR -Tiago Balabuch - -#> -function Get-FabricLongRunningOperation { - param ( - [Parameter(Mandatory = $false)] - [string]$operationId, - - [Parameter(Mandatory = $false)] - [string]$location, - - [Parameter(Mandatory = $false)] - [int]$retryAfter = 5 - ) - - # Step 1: Construct the API URL - if ($location) { - # Use the Location header to define the operationUrl - $apiEndpointUrl = $location - } - else { - $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}" -f $operationId - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - try { - do { - - # Step 2: Wait before the next request - if ($retryAfter) { - Start-Sleep -Seconds $retryAfter - } - else { - Start-Sleep -Seconds 5 # Default retry interval if no Retry-After header - } - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -ResponseHeadersVariable responseHeader ` - -StatusCodeVariable statusCode - - # Step 3: Parse the response - $jsonOperation = $response | ConvertTo-Json - $operation = $jsonOperation | ConvertFrom-Json - - # Log status for debugging - Write-Message -Message "Operation Status: $($operation.status)" -Level Debug - - - } while ($operation.status -notin @("Succeeded", "Completed", "Failed")) - - # Step 5: Return the operation result - return $operation - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while checking the operation: $errorDetails" -Level Error - throw - } -} \ No newline at end of file diff --git a/FabricTools/public/Utils/Get-FabricLongRunningOperationResult.ps1 b/FabricTools/public/Utils/Get-FabricLongRunningOperationResult.ps1 deleted file mode 100644 index 00000c6c..00000000 --- a/FabricTools/public/Utils/Get-FabricLongRunningOperationResult.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -<# -.SYNOPSIS -Retrieves the result of a completed long-running operation from the Microsoft Fabric API. - -.DESCRIPTION -The Get-FabricLongRunningOperationResult function queries the Microsoft Fabric API to fetch the result -of a specific long-running operation. This is typically used after confirming the operation has completed successfully. - -.PARAMETER operationId -The unique identifier of the completed long-running operation whose result you want to retrieve. - -.EXAMPLE -Get-FabricLongRunningOperationResult -operationId "12345-abcd-67890-efgh" - -This command fetches the result of the operation with the specified operationId. - -.NOTES -- Ensure the Fabric API headers (e.g., authorization tokens) are defined in $FabricConfig.FabricHeaders. -- This function does not handle polling. Ensure the operation is in a terminal state before calling this function. - -.AUTHOR -Tiago Balabuch - -#> -function Get-FabricLongRunningOperationResult { - param ( - [Parameter(Mandatory = $true)] - [string]$operationId - ) - - # Step 1: Construct the API URL - $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}/result" -f $operationId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - try { - # Step 2: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - - # Step 3: Return the result - Write-Message -Message "Result response code: $statusCode" -Level Debug - Write-Message -Message "Result return: $response" -Level Debug - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Debug - Write-Message -Message "Error: $($response.message)" -Level Debug - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Debug - Write-Message "Error Code: $($response.errorCode)" -Level Debug - } - - return $response - } - catch { - # Step 3: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while returning the operation result: $errorDetails" -Level Error - throw - } -} \ No newline at end of file diff --git a/FabricTools/public/Utils/Set-FabricApiHeaders.ps1 b/FabricTools/public/Utils/Set-FabricApiHeaders.ps1 deleted file mode 100644 index e1c7a43d..00000000 --- a/FabricTools/public/Utils/Set-FabricApiHeaders.ps1 +++ /dev/null @@ -1,116 +0,0 @@ -<# -.SYNOPSIS -Sets the Fabric API headers with a valid token for the specified Azure tenant. - -.DESCRIPTION -The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. -It also updates the token expiration time and global tenant ID. - -.PARAMETER TenantId -The Azure tenant ID for which the access token is requested. - -.PARAMETER AppId -The Azure app ID for which the service principal access token is requested. - -.PARAMETER AppSecret -The Azure App secret for which the service principal access token is requested. - -.EXAMPLE -Set-FabricApiHeaders -TenantId "your-tenant-id" - -Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. - -.EXAMPLE -$tenantId = "999999999-99999-99999-9999-999999999999" -$appId = "888888888-88888-88888-8888-888888888888" -$appSecret = "your-app-secret" -$secureAppSecret = $appSecret | ConvertTo-SecureString -AsPlainText -Force - -Set-FabricApiHeader -TenantId $tenantId -AppId $appId -AppSecret $secureAppSecret -Logs in to Azure with the specified tenant ID, retrieves an access token for the service principal, and configures the Fabric headers. - -.NOTES -- Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). -- Relies on a global `$FabricConfig` object for storing headers and token metadata. - -.AUTHOR -Tiago Balabuch -#> - -function Set-FabricApiHeaders { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$TenantId, - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$AppId, - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Security.SecureString]$AppSecret - ) - - try { - # Step 1: Connect to the Azure account - Write-Message -Message "Logging in to Azure tenant: $TenantId" -Level Info - - # Step 2: Performing validation checks on the parameters passed to a function or script. - # Checks if 'AppId' is provided without 'AppSecret' and vice versa. - if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { - Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error - throw "AppSecret is required when using AppId." - } - if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { - Write-Message -Message "AppId is required when using AppSecret." -Level Error - throw "AppId is required when using AppId." - } - # Step 3: Connect to the Azure account - # Using AppId and AppSecret - if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { - - Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug - Write-Message -Message "Logging in using the AppId: $AppId" -Level Info - $psCredential = [pscredential]::new($AppId, $AppSecret) - Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId - - } - # Using the current user - else { - - Write-Message -Message "Logging in using the current user" -Level Debug - Write-Message -Message "Logging in using the current user" -Level Info - Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null - } - - ## Step 4: Retrieve the access token for the Fabric API - Write-Message -Message "Retrieve the access token for the Fabric API: $TenantId" -Level Debug - $fabricToken = Get-AzAccessToken -AsSecureString -ResourceUrl $FabricConfig.ResourceUrl -ErrorAction Stop -WarningAction SilentlyContinue - - ## Step 5: Extract the plain token from the secure string - Write-Message -Message "Extract the plain token from the secure string" -Level Debug - $plainToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( - [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($fabricToken.Token) - ) - - ## Step 6: Set the headers in the global configuration - Write-Message -Message "Set the headers in the global configuration" -Level Debug - $FabricConfig.FabricHeaders = @{ - 'Content-Type' = 'application/json' - 'Authorization' = "Bearer $plainToken" - } - - ## Step 7: Update token metadata in the global configuration - Write-Message -Message "Update token metadata in the global configuration" -Level Debug - $FabricConfig.TokenExpiresOn = $fabricToken.ExpiresOn - $FabricConfig.TenantIdGlobal = $TenantId - - Write-Message -Message "Fabric token successfully configured." -Level Info - } - catch { - # Step 8: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to set Fabric token: $errorDetails" -Level Error - throw "Unable to configure Fabric token. Ensure tenant and API configurations are correct." - } -} diff --git a/FabricTools/public/Utils/Test-FabricApiResponse.ps1 b/FabricTools/public/Utils/Test-FabricApiResponse.ps1 deleted file mode 100644 index f27f261b..00000000 --- a/FabricTools/public/Utils/Test-FabricApiResponse.ps1 +++ /dev/null @@ -1,53 +0,0 @@ -function Test-FabricApiResponse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - $statusCode, - [Parameter(Mandatory = $false)] - $response, - [Parameter(Mandatory = $false)] - $responseHeader, - [Parameter(Mandatory = $false)] - $Name, - [Parameter(Mandatory = $false)] - $typeName = 'Fabric Item' - ) - - switch ($statusCode) { - 201 { - Write-Message -Message "$typeName '$Name' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "$typeName '$Name' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - -} diff --git a/FabricTools/public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 b/FabricTools/public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index f1de0d1c..00000000 --- a/FabricTools/public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,94 +0,0 @@ -function Add-FabricWorkspaceRoleAssignment { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Adds a role assignment to a user in a workspace. - -.DESCRIPTION - Adds a role assignment to a user in a workspace. The User is identified by the principalId and the role is - identified by the Role parameter. The Workspace is identified by the WorkspaceId. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the role assignment should be added. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. - -.PARAMETER PrincipalId - Id of the principal for which the role assignment should be added. The value for PrincipalId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. At the - moment only principal type 'User' is supported. - -.PARAMETER Role - The role to assign to the principal. The value for Role is a string. An example of a string is 'Admin'. - The values that can be used are 'Admin', 'Contributor', 'Member' and 'Viewer'. - - -.EXAMPLE - Add-RtiWorkspaceRoleAssignment ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -PrincipalId '12345678-1234-1234-1234-123456789012' ` - -Role 'Admin' - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/add-workspace-role-assignment?tabs=HTTP - - -.NOTES - TODO: Add functionallity to add role assignments to groups. - TODO: Add functionallity to add a user by SPN. -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Alias("Id")] - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [string]$principalId, - - [ValidateSet("Admin", "Contributor", "Member" , "Viewer")] - [Parameter(Mandatory=$true)] - [string]$Role - ) - -begin { - - # Check if session is established - if not throw error - if ($null -eq $FabricSession.headerParams) { - throw "No session established to Fabric Real-Time Intelligence. Please run Connect-FabricAccount" - } - - # Create body of request - $body = @{ - 'principal' = @{ - 'id' = $principalId - 'type' = "User" - } - 'role' = $Role - } | ConvertTo-Json ` - -Depth 1 - - # Create Workspace API URL - $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces/$WorkspaceId/roleassignments" -} - -process { - - if($PSCmdlet.ShouldProcess($WorkspaceName)) { - # Call Workspace API - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $WorkspaceApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Get-FabricWorkspace.ps1 b/FabricTools/public/Workspace/Get-FabricWorkspace.ps1 deleted file mode 100644 index 753e808b..00000000 --- a/FabricTools/public/Workspace/Get-FabricWorkspace.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -<# -.SYNOPSIS - Retrieves all Fabric workspaces. - -.DESCRIPTION - The Get-FabricWorkspace function retrieves all Fabric workspaces. It invokes the Fabric API to get the workspaces and outputs the result. - -.EXAMPLE - Get-FabricWorkspace - - This command retrieves all Fabric workspaces. - -.INPUTS - None. You cannot pipe inputs to this function. - -.OUTPUTS - Object. This function returns the Fabric workspaces. - -.NOTES - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip -#> - -Function Get-FabricWorkspace { - [Alias("Get-FabWorkspace")] - [CmdletBinding()] - param - ( - [Parameter(Mandatory=$false)] - [string]$workspaceId - ) - - Confirm-FabricAuthToken | Out-Null - - # Invoke the Fabric API to get the workspaces - if ($workspaceId) { - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)" -Method Get - } else { - $result = Invoke-FabricAPIRequest -Uri "workspaces" -Method Get - } - - - # Output the result - return $result.value -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Get-FabricWorkspace2.ps1 b/FabricTools/public/Workspace/Get-FabricWorkspace2.ps1 deleted file mode 100644 index f6b1b484..00000000 --- a/FabricTools/public/Workspace/Get-FabricWorkspace2.ps1 +++ /dev/null @@ -1,190 +0,0 @@ -function Get-FabricWorkspace2 { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric Workspaces - -.DESCRIPTION - Retrieves Fabric Workspaces. Without the WorkspaceName or WorkspaceID parameter, - all Workspaces are returned. If you want to retrieve a specific Workspace, you can - use the WorkspaceName, an CapacityID, a WorkspaceType, a WorkspaceState or the WorkspaceID - parameter. The WorkspaceId parameter has precedence over all other parameters because it - is most specific. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace to retrieve. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER WorkspaceName - The name of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. - -.PARAMETER WorkspaceCapacityId - The Id of the Capacity to retrieve. This parameter cannot be used together with WorkspaceID. - The value for WorkspaceCapacityId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER WorkspaceType - The type of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. - The value for WorkspaceType is a string. An example of a string is 'Personal'. The values that - can be used are 'Personal', 'Workspace' and 'Adminworkspace'. - -.PARAMETER WorkspaceState - The state of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. - The value for WorkspaceState is a string. An example of a string is 'active'. The values that - can be used are 'active' and 'deleted'. - -.EXAMPLE - Get-FabricWorkspace - - This example will retrieve all Workspaces. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' - - This example will retrieve the Workspace with the ID '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceName 'MyWorkspace' - - This example will retrieve the Workspace with the name 'MyWorkspace'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceCapacityId '12345678-1234-1234-1234-123456789012' - - This example will retrieve the Workspaces with the Capacity ID '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceType 'Personal' - - This example will retrieve the Workspaces with the type 'Personal'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceState 'active' - - This example will retrieve the Workspaces with the state 'active'. - -.NOTES - - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/get-workspace?tabs=HTTP - - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces?tabs=HTTP -#> - -[CmdletBinding()] - param ( - - [Alias("Id")] - [string]$WorkspaceId, - - [Alias("Name")] - [string]$WorkspaceName, - - [Alias("CapacityId")] - [string]$WorkspaceCapacityId, - - [ValidateSet("Personal", "Workspace", "Adminworkspace")] - [Alias("Type")] - [string]$WorkspaceType, - - [ValidateSet("active", "deleted")] - [Alias("State")] - [string]$WorkspaceState - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "WorkspaceID has to be used alone" - if ($PSBoundParameters.ContainsKey("WorkspaceName") -and - ($PSBoundParameters.ContainsKey("WorkspaceID") ` - -or $PSBoundParameters.ContainsKey("WorkspaceCapcityId") ` - -or $PSBoundParameters.ContainsKey("WorkspaceType") ` - -or $PSBoundParameters.ContainsKey("WorkspaceState"))) { - throw "Parameters WorkspaceName, WorkspaceCapacityId, WorkspaceType or WorkspaceState and WorkspaceID cannot be used together!" - } - - # Create Workspace API URL - $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces" - - # Create URL for WebAPI Call if WorkspaceID is provided - if ($PSBoundParameters.ContainsKey("WorkspaceID")) { - $workspaceApiUrlId = $workspaceApiUrl + '/' + $WorkspaceID - } - - Write-Verbose "If there are any parameters, we need to filter the API call, the URL will be constructed here." - $workspaceApiFilter = $workspaceApiUrl - - if ($PSBoundParameters.ContainsKey("WorkspaceName")) { - $workspaceApiFilter = $workspaceApiFilter + "?name=$WorkspaceName" - } - - if ($PSBoundParameters.ContainsKey("WorkspaceCapacityId")) { - $workspaceApiFilter = $workspaceApiFilter + "?capacityId=$WorkspaceCapacityId" - } - - if ($PSBoundParameters.ContainsKey("WorkspaceType")) { - $workspaceApiFilter = $workspaceApiFilter + "?type=$WorkspaceType" - } - - if ($PSBoundParameters.ContainsKey("WorkspaceState")) { - $workspaceApiFilter = $workspaceApiFilter + "?state=$WorkspaceState" - } - Write-Verbose "Workspace API URL: $workspaceApiFilter" - -} - -process { - - Write-Verbose "Providing a WorkspaceID is so specific that this will have precedence over any other parameter" - if ($PSBoundParameters.ContainsKey("WorkspaceID")) { - Write-Verbose "Calling Workspace API with WorkspaceId $WorkspaceId" - Write-Verbose "---------------------------------------------------" - Write-Verbose "Sending the following values to the Workspace API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $workspaceApiUrlId" - Write-Verbose "ContentType: application/json" - # Call Workspace API for WorkspaceID - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $workspaceApiUrlId ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling Workspace API with Filter" - Write-Verbose "---------------------------------" - Write-Verbose "Sending the following values to the Workspace API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $workspaceApiFilter" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $workspaceApiFilter ` - -ContentType "application/json" - - $response.Workspaces - } - -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 b/FabricTools/public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 deleted file mode 100644 index 81188a66..00000000 --- a/FabricTools/public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 +++ /dev/null @@ -1,53 +0,0 @@ -<# -.SYNOPSIS - Retrieves the refresh history of all datasets in a specified PowerBI workspace. - -.DESCRIPTION - The Get-FabricWorkspaceDatasetRefreshes function uses the PowerBI cmdlets to retrieve the refresh history of all datasets in a specified workspace. - It uses the workspace ID to get the workspace and its datasets, and then retrieves the refresh history for each dataset. - -.PARAMETER WorkspaceID - The ID of the PowerBI workspace. This is a mandatory parameter. - -.EXAMPLE - Get-FabricWorkspaceDatasetRefreshes -WorkspaceID "12345678-90ab-cdef-1234-567890abcdef" - - This command retrieves the refresh history of all datasets in the workspace with the specified ID. - -.INPUTS - String. You can pipe a string that contains the workspace ID to Get-FabricWorkspaceDatasetRefreshes. - -.OUTPUTS - Array. Get-FabricWorkspaceDatasetRefreshes returns an array of refresh history objects. - -.NOTES - Alias: Get-PowerBIWorkspaceDatasetRefreshes, Get-FabWorkspaceDatasetRefreshes -#> - -# Define a function to get the refresh history of all datasets in a PowerBI workspace -function Get-FabricWorkspaceDatasetRefreshes { - # Set aliases for the function - [Alias("Get-FabWorkspaceDatasetRefreshes")] - param( - # Define a mandatory parameter for the workspace ID - [Parameter(Mandatory=$true)] - [string]$WorkspaceID - ) - - Confirm-FabricAuthToken | Out-Null - - # Get the workspace using the workspace ID - $wsp = Get-FabricWorkspace -workspaceid $WorkspaceID - # Initialize an array to store the refresh history - $refs = @() - # Get all datasets in the workspace - $datasets = Get-FabricDataset -workspaceid $wsp.Id - - # Loop over each dataset - foreach ($dataset in $datasets) { - # Get the refresh history of the dataset and add it to the array - $refs += Get-FabricDatasetRefreshes -datasetid $dataset.Id -workspaceId $wsp.Id - } - # Return the refresh history array - return $refs -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 b/FabricTools/public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index 119a219d..00000000 --- a/FabricTools/public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -function Get-FabricWorkspaceRoleAssignment { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric Workspace Role Assignments - -.DESCRIPTION - Retrieves Fabric Workspace Role Assignments. Without the WorkspaceName or WorkspaceID parameter, - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Role Assignments should be retrieved. - The value for WorkspaceId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricWorkspaceRoleAssignment ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' - - This example will retrieve all Role Assignments for the Workspace with the ID '12345678-1234-1234-1234-123456789012'. - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/get-workspace-role-assignment?tabs=HTTP - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/list-workspace-role-assignments?tabs=HTTP -#> - -[CmdletBinding()] - param ( - - [Alias("Id")] - [string]$WorkspaceId - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - # Create Workspace API URL - $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces/$WorkspaceId/roleassignments" -} - -process { - # Call Workspace API for WorkspaceID - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $workspaceApiUrl ` - -ContentType "application/json" - - $response.Workspaces - -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 b/FabricTools/public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 deleted file mode 100644 index f94d101a..00000000 --- a/FabricTools/public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -<# -.SYNOPSIS -Retrieves workspace usage metrics data. - -.DESCRIPTION -The Get-FabricWorkspaceUsageMetricsData function retrieves workspace usage metrics. It supports multiple aliases for flexibility. - -.PARAMETER workspaceId -The ID of the workspace. This is a mandatory parameter. - -.PARAMETER username -The username. This is a mandatory parameter. - -.EXAMPLE -Get-FabricWorkspaceUsageMetricsData -workspaceId "your-workspace-id" -username "your-username" - -This example retrieves the workspace usage metrics for a specific workspace given the workspace ID and username. - -.NOTES -The function retrieves the PowerBI access token and creates a new usage metrics report. It then defines the names of the reports to retrieve, initializes an empty hashtable to store the reports, and for each report name, retrieves the report and adds it to the hashtable. It then returns the hashtable of reports. -#> - -# This function retrieves workspace usage metrics. -function Get-FabricWorkspaceUsageMetricsData { - # Define aliases for the function for flexibility. - [Alias("Get-FabWorkspaceUsageMetricsData")] - - # Define parameters for the workspace ID and username. - param( - [Parameter(Mandatory = $true)] - [string]$workspaceId, - [Parameter(Mandatory = $false)] - [string]$username = "" - ) - - # Create a new workspace usage metrics dataset. - $datasetId = New-FabricWorkspaceUsageMetricsReport -workspaceId $workspaceId - - # Define the names of the reports to retrieve. - $reportnames = @("'Workspace views'", "'Report pages'", "Users", "Reports", "'Report views'", "'Report page views'", "'Report load times'") - - # Initialize an empty hashtable to store the reports. - $reports = @{} - - # For each report name, retrieve the report and add it to the hashtable. - if ($username -eq "") { - foreach ($reportname in $reportnames) { - $report = Get-FabricUsagemetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname - $reports += @{ $reportname.replace("'", "") = $report } - } - } - else { - foreach ($reportname in $reportnames) { - $report = Get-FabricUsagemetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname -ImpersonatedUser $username - $reports += @{ $reportname.replace("'", "") = $report } - } - } - # Return the hashtable of reports. - return $reports -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Get-FabricWorkspaceUsers.ps1 b/FabricTools/public/Workspace/Get-FabricWorkspaceUsers.ps1 deleted file mode 100644 index 38426fcd..00000000 --- a/FabricTools/public/Workspace/Get-FabricWorkspaceUsers.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -<# -.SYNOPSIS -Retrieves the users of a workspace. - -.DESCRIPTION -The Get-FabricWorkspaceUsers function retrieves the users of a workspace. It supports multiple aliases for flexibility. - -.PARAMETER WorkspaceId -The ID of the workspace. This is a mandatory parameter for the 'WorkspaceId' parameter set. - -.PARAMETER Workspace -The workspace object. This is a mandatory parameter for the 'WorkspaceObject' parameter set and can be piped into the function. - -.EXAMPLE -Get-FabricWorkspaceUsers -WorkspaceId "your-workspace-id" - -This example retrieves the users of a workspace given the workspace ID. - -.EXAMPLE -$workspace | Get-FabricWorkspaceUsers - -This example retrieves the users of a workspace given a workspace object. - -.NOTES -The function defines parameters for the workspace ID and workspace object. If the parameter set name is 'WorkspaceId', it retrieves the workspace object. It then makes a GET request to the PowerBI API to retrieve the users of the workspace and returns the 'value' property of the response, which contains the users. -#> - -# This function retrieves the users of a workspace. -function Get-FabricWorkspaceUsers { - # Define aliases for the function for flexibility. - [Alias("Get-FabWorkspaceUsers")] - - # Define parameters for the workspace ID and workspace object. - param( - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true )] - $Workspace - ) - - begin { - Confirm-FabricAuthToken | Out-Null - } - - process { - # If the parameter set name is 'WorkspaceId', retrieve the workspace object. - if ($PSCmdlet.ParameterSetName -eq 'WorkspaceId') { - $workspace = Get-PowerBIWorkspace -Id $WorkspaceId - } - - # Make a GET request to the PowerBI API to retrieve the users of the workspace. - # The function returns the 'value' property of the response, which contains the users. - return (Invoke-PowerBIRestMethod -Method get -Url ("groups/$($workspace.Id)/users") | ConvertFrom-Json).value - } - -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/New-FabricWorkspace.ps1 b/FabricTools/public/Workspace/New-FabricWorkspace.ps1 deleted file mode 100644 index 931ca5f6..00000000 --- a/FabricTools/public/Workspace/New-FabricWorkspace.ps1 +++ /dev/null @@ -1,80 +0,0 @@ -<# -.SYNOPSIS - Creates a new Fabric workspace. - -.DESCRIPTION - The New-FabricWorkspace function creates a new Fabric workspace. It uses the provided name to create the workspace. If the workspace already exists and the skipErrorIfExists switch is provided, it does not throw an error. - -.PARAMETER Name - The name of the new Fabric workspace. This is a mandatory parameter. - -.PARAMETER SkipErrorIfExists - A switch that indicates whether to skip the error if the workspace already exists. If provided, the function does not throw an error if the workspace already exists. - -.EXAMPLE - New-FabricWorkspace -Name "NewWorkspace" -SkipErrorIfExists - - This command creates a new Fabric workspace named "NewWorkspace". If the workspace already exists, it does not throw an error. - -.INPUTS - String, Switch. You can pipe a string that contains the name and a switch that indicates whether to skip the error if the workspace already exists to New-FabricWorkspace. - -.OUTPUTS - String. This function returns the ID of the new Fabric workspace. - -.NOTES - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip -#> - -Function New-FabricWorkspace { - <# - .SYNOPSIS - Creates a new Fabric workspace. - #> - [CmdletBinding(SupportsShouldProcess)] - param - ( - [string]$name, - [switch]$skipErrorIfExists - ) - - # Create a request body for the Fabric API - $itemRequest = @{ - displayName = $name - } | ConvertTo-Json - - try { - # Invoke the Fabric API to create the workspace - if ($PSCmdlet.ShouldProcess("workspaces", "create")) { - $createResult = Invoke-FabricAPIRequest -Uri "workspaces" -Method Post -Body $itemRequest - - # Display a message indicating the workspace was created - Write-Output "Workspace created" - - # Output the ID of the new workspace - Write-Output $createResult.id - } - } - catch { - $ex = $_.Exception - - if ($skipErrorIfExists) { - if ($ex.Message -ilike "*409*") { - Write-Output "Workspace already exists" - - $listWorkspaces = Invoke-FabricAPIRequest -Uri "workspaces" -Method Get - - $workspace = $listWorkspaces | Where-Object { $_.displayName -ieq $name } - - if (!$workspace) { - throw "Cannot find workspace '$name'" - } - Write-Output $workspace.id - } - else { - throw - } - } - } -} diff --git a/FabricTools/public/Workspace/New-FabricWorkspace2.ps1 b/FabricTools/public/Workspace/New-FabricWorkspace2.ps1 deleted file mode 100644 index 16369223..00000000 --- a/FabricTools/public/Workspace/New-FabricWorkspace2.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -function New-FabricWorkspace2 { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Creates a new Fabric Workspace - -.DESCRIPTION - Creates a new Fabric Workspace - -.PARAMETER CapacityID - Id of the Fabric Capacity for which the Workspace should be created. The value for CapacityID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER WorkspaceName - The name of the Workspace to create. This parameter is mandatory. - -.PARAMETER WorkspaceDescription - The description of the Workspace to create. - -.EXAMPLE - New-FabricWorkspace ` - -CapacityID '12345678-1234-1234-1234-123456789012' ` - -WorkspaceName 'TestWorkspace' ` - -WorkspaceDescription 'This is a Test Workspace' - - This example will create a new Workspace with the name 'TestWorkspace' and the description 'This is a test workspace'. - -.NOTES - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$CapacityId, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] - [string]$WorkspaceName, - - [ValidateLength(0, 4000)] - [Alias("Description")] - [string]$WorkspaceDescription - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{ - 'displayName' = $WorkspaceName - 'description' = $WorkspaceDescription - 'capacityId' = $CapacityId - } | ConvertTo-Json ` - -Depth 1 - - Write-Verbose "Create Workspace API URL" - $WorkspaceApiUrl = "$($FabricSession.BaseApiUrl)/workspaces" - } - -process { - - if($PSCmdlet.ShouldProcess($WorkspaceName)) { - Write-Verbose "Calling Workspace API" - Write-Verbose "---------------------" - Write-Verbose "Sending the following values to the Workspace API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $WorkspaceApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $WorkspaceApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 b/FabricTools/public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 deleted file mode 100644 index 4f2a0084..00000000 --- a/FabricTools/public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -<# -.SYNOPSIS -Retrieves the workspace usage metrics dataset ID. - -.DESCRIPTION -The New-FabricWorkspaceUsageMetricsReport function retrieves the workspace usage metrics dataset ID. It supports multiple aliases for flexibility. - -.PARAMETER workspaceId -The ID of the workspace. This is a mandatory parameter. - -.EXAMPLE -New-FabricWorkspaceUsageMetricsReport -workspaceId "your-workspace-id" - -This example retrieves the workspace usage metrics dataset ID for a specific workspace given the workspace ID. - -.NOTES -The function retrieves the PowerBI access token and the Fabric API cluster URI. It then makes a GET request to the Fabric API to retrieve the workspace usage metrics dataset ID, parses the response and replaces certain keys to match the expected format, and returns the 'dbName' property of the first model in the response, which is the dataset ID. -#> - -# This function retrieves the workspace usage metrics dataset ID. -function New-FabricWorkspaceUsageMetricsReport { - # Define aliases for the function for flexibility. - [Alias("New-FabWorkspaceUsageMetricsReport")] - [CmdletBinding(SupportsShouldProcess)] - # Define a parameter for the workspace ID. - param( - [Parameter(Mandatory = $true)] - [string]$workspaceId - ) - - Confirm-FabricAuthToken | Out-Null - - # Retrieve the Fabric API cluster URI. - $url = get-FabricAPIclusterURI - - # Make a GET request to the Fabric API to retrieve the workspace usage metrics dataset ID. - if ($PSCmdlet.ShouldProcess("Workspace Usage Metrics Report", "Retrieve")) { - $data = Invoke-WebRequest -Uri "$url/$workspaceId/usageMetricsReportV2?experience=power-bi" -Headers $FabricSession.HeaderParams -ErrorAction SilentlyContinue - # Parse the response and replace certain keys to match the expected format. - $response = $data.Content.ToString().Replace("nextRefreshTime", "NextRefreshTime").Replace("lastRefreshTime", "LastRefreshTime") | ConvertFrom-Json - - # Return the 'dbName' property of the first model in the response, which is the dataset ID. - return $response.models[0].dbName - } - else { - return $null - } -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Register-FabricWorkspaceToCapacity.ps1 b/FabricTools/public/Workspace/Register-FabricWorkspaceToCapacity.ps1 deleted file mode 100644 index e54d8fd4..00000000 --- a/FabricTools/public/Workspace/Register-FabricWorkspaceToCapacity.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -<# -.SYNOPSIS -Sets a PowerBI workspace to a capacity. - -.DESCRIPTION -The Register-FabricWorkspaceToCapacity function Sets a PowerBI workspace to a capacity. It supports multiple aliases for flexibility. - -.PARAMETER WorkspaceId -The ID of the workspace to be Seted. This is a mandatory parameter. - -.PARAMETER Workspace -The workspace object to be Seted. This is a mandatory parameter and can be piped into the function. - -.PARAMETER CapacityId -The ID of the capacity to which the workspace will be Seted. This is a mandatory parameter. - -.EXAMPLE -Register-FabricWorkspaceToCapacity -WorkspaceId "Workspace-GUID" -CapacityId "Capacity-GUID" - -This example Sets the workspace with ID "Workspace-GUID" to the capacity with ID "Capacity-GUID". - -.EXAMPLE -$workspace | Register-FabricWorkspaceToCapacity -CapacityId "Capacity-GUID" - -This example Sets the workspace object stored in the $workspace variable to the capacity with ID "Capacity-GUID". The workspace object is piped into the function. - -.NOTES -The function makes a POST request to the PowerBI API to Set the workspace to the capacity. The PowerBI access token is retrieved using the Get-PowerBIAccessToken function. -#> - - -# This function Sets a PowerBI workspace to a capacity. -# It supports multiple aliases for flexibility. -function Register-FabricWorkspaceToCapacity { - [Alias("Register-FabWorkspaceToCapacity")] - [CmdletBinding(SupportsShouldProcess)] - param( - # WorkspaceId is a mandatory parameter. It represents the ID of the workspace to be Seted. - [Parameter(ParameterSetName = 'WorkspaceId')] - [string]$WorkspaceId, - - # Workspace is a mandatory parameter. It represents the workspace object to be Seted. - # This parameter can be piped into the function. - [Parameter(ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true)] - $Workspace, - - # CapacityId is a mandatory parameter. It represents the ID of the capacity to which the workspace will be Seted. - [Parameter(Mandatory = $true)] - [string]$CapacityId - ) - Process { - # If the parameter set name is 'WorkspaceObject', the workspace ID is extracted from the workspace object. - if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { - $workspaceid = $workspace.id - } - - # The body of the request is created. It contains the capacity ID. - $body = @{ - capacityId = $CapacityId - } - - Confirm-FabricAuthToken | Out-Null - - # The workspace is Seted to the capacity by making a POST request to the PowerBI API. - # The function returns the value property of the response. - if ($PSCmdlet.ShouldProcess("Set workspace $workspaceid to capacity $CapacityId")) { - #return (Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/assignToCapacity" -Method POST -Body $body).value - return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/assignToCapacity" -Body $body - } - } -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Remove-FabricWorkspace.ps1 b/FabricTools/public/Workspace/Remove-FabricWorkspace.ps1 deleted file mode 100644 index aec748af..00000000 --- a/FabricTools/public/Workspace/Remove-FabricWorkspace.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -<# -.SYNOPSIS -Removes a workspace. - -.DESCRIPTION -The Remove-FabricWorkspace function removes a workspace. It supports multiple aliases for flexibility. - -.PARAMETER groupID -The ID of the group (workspace). This is a mandatory parameter. - -.EXAMPLE -Remove-FabricWorkspace -groupID "your-group-id" - -This example removes a workspace given the group ID. - -.NOTES -The function retrieves the PowerBI access token and makes a DELETE request to the PowerBI API to remove the workspace. -#> - -# This function removes a workspace. -function Remove-FabricWorkspace { - [CmdletBinding(SupportsShouldProcess)] - # Define aliases for the function for flexibility. - - # Define a parameter for the group ID. - Param ( - [Parameter(Mandatory = $true)] - [string]$workspaceID - ) - - Confirm-FabricAuthToken | Out-Null - - # Make a DELETE request to the PowerBI API to remove the workspace. - if ($PSCmdlet.ShouldProcess("Remove workspace $workspaceID")) { - return Invoke-FabricAPIRequest -Uri "workspaces/$workspaceID" -Method Delete - } -} \ No newline at end of file diff --git a/FabricTools/public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 b/FabricTools/public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 deleted file mode 100644 index 9b4f0ae4..00000000 --- a/FabricTools/public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -<# -.SYNOPSIS -Unregisters a workspace from a capacity. - -.DESCRIPTION -The Unregister-FabricWorkspaceToCapacity function unregisters a workspace from a capacity in PowerBI. It can be used to remove a workspace from a capacity, allowing it to be assigned to a different capacity or remain unassigned. - -.PARAMETER WorkspaceId -Specifies the ID of the workspace to be unregistered from the capacity. This parameter is mandatory when using the 'WorkspaceId' parameter set. - -.PARAMETER Workspace -Specifies the workspace object to be unregistered from the capacity. This parameter is mandatory when using the 'WorkspaceObject' parameter set. The workspace object can be piped into the function. - -.EXAMPLE -Unregister-FabricWorkspaceToCapacity -WorkspaceId "12345678" -Unregisters the workspace with ID "12345678" from the capacity. - -.EXAMPLE -Get-FabricWorkspace | Unregister-FabricWorkspaceToCapacity -Unregisters the workspace objects piped from Get-FabricWorkspace from the capacity. - -.INPUTS -System.Management.Automation.PSCustomObject - -.OUTPUTS -System.Object - -.NOTES -Author: Your Name -Date: Today's Date -#> - -function Unregister-FabricWorkspaceToCapacity { - [Alias("Unregister-FabWorkspaceToCapacity")] - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true)] - $Workspace - ) - - begin { - Confirm-FabricAuthToken | Out-Null - } - - Process { - if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { - $workspaceid = $workspace.id - } - - if ($PSCmdlet.ShouldProcess("Unassigns workspace $workspaceid from a capacity")) { - return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/unassignFromCapacity" - #return (Invoke-FabricAPIRequest -Uri "workspaces/$workspaceid/unassignFromCapacity" -Method POST).value - } - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Private/Get-FileDefinitionParts.ps1 b/FabricTools/tiago/Private/Get-FileDefinitionParts.ps1 deleted file mode 100644 index 1131e74c..00000000 --- a/FabricTools/tiago/Private/Get-FileDefinitionParts.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -function Get-FileDefinitionParts { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$sourceDirectory - ) - try { - if (-Not (Test-Path $sourceDirectory)) { - Write-Message -Message "The specified source directory does not exist: $sourceDirectory" -Level Error - throw - } - - # Get all files from the directory recursively - Write-Message -Message "Get all files from the directory recursively" -Level Debug - $fileList = Get-ChildItem -Path $sourceDirectory -File -Recurse - - # Initialize the output JSON object - $jsonObject = @{ parts = @() } - - # Loop through the files to create parts dynamically - Write-Message -Message "Loop through the files to create parts dynamically" -Level Debug - foreach ($file in $fileList) { - - $relativePath = $file.FullName.Substring($sourceDirectory.Length + 1) -replace "\\", "/" - Write-Message -Message "File found: $relativePath" -Level Debug - Write-Message -Message "Starting encode to base64" -Level Debug - - $base64Content = Convert-ToBase64 -filePath $file.FullName - Write-Message -Message "Adding part to json object" -Level Debug - - $jsonObject.parts += @{ - path = $relativePath - payload = $base64Content - payloadType = "InlineBase64" - } - } - Write-Message -Message "Loop through the files finished" -Level Debug - - return $jsonObject - Write-Message -Message "Parts returned" -Level Debug - } - - catch { - # Step 4: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while getting file definition parts: $errorDetails" -Level Error - throw "An error occurred while encoding to Base64: $_" - } -} - - -# Example usage -#$sourceDirectory = "C:\temp\API\Notebook" -#Get-FileParts -sourceDirectory $sourceDirectory -#$fileParts = Get-FileParts -sourceDirectory $sourceDirectory -#$fileParts | ConvertTo-Json -Depth 10 diff --git a/FabricTools/tiago/Private/Test-TokenExpired.ps1 b/FabricTools/tiago/Private/Test-TokenExpired.ps1 deleted file mode 100644 index 31ca46e8..00000000 --- a/FabricTools/tiago/Private/Test-TokenExpired.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -<# -.SYNOPSIS -Checks if the Fabric token is expired and logs appropriate messages. - -.DESCRIPTION -The `Test-TokenExpired` function checks the expiration status of the Fabric token stored in the `$FabricConfig.TokenExpiresOn` variable. -If the token is expired, it logs an error message and provides guidance for refreshing the token. -Otherwise, it logs that the token is still valid. - -.PARAMETER FabricConfig -The configuration object containing the token expiration details. - -.EXAMPLE -Test-TokenExpired -FabricConfig $config - -Checks the token expiration status using the provided `$config` object. - -.NOTES -- Ensure the `FabricConfig` object includes a valid `TokenExpiresOn` property of type `DateTimeOffset`. -- Requires the `Write-Message` function for logging. - -.AUTHOR -Tiago Balabuch -#> -function Test-TokenExpired { - [CmdletBinding()] - param () - - try { - # Ensure required properties have valid values - if ([string]::IsNullOrWhiteSpace($FabricConfig.TenantIdGlobal) -or - [string]::IsNullOrWhiteSpace($FabricConfig.TokenExpiresOn)) { - Write-Message -Message "Token details are missing. Please run 'Set-FabricApiHeaders' to configure them." -Level Error - throw "MissingTokenDetailsException: Token details are missing." - } - - # Convert the TokenExpiresOn value to a DateTime object - $tokenExpiryDate = [datetimeoffset]::Parse($FabricConfig.TokenExpiresOn) - - # Check if the token is expired - if ($tokenExpiryDate -le [datetimeoffset]::Now) { - Write-Message -Message "Your authentication token has expired. Please sign in again to refresh your session." -Level Warning - #throw "TokenExpiredException: Token has expired." - #Set-FabricApiHeaders -tenantId $FabricConfig.TenantIdGlobal - } - - # Log valid token status - Write-Message -Message "Token is still valid. Expiry time: $($tokenExpiryDate.ToString("u"))" -Level Debug - } catch [System.FormatException] { - Write-Message -Message "Invalid 'TokenExpiresOn' format in the FabricConfig object. Ensure it is a valid datetime string." -Level Error - throw "FormatException: Invalid TokenExpiresOn value." - } catch { - # Log unexpected errors with details - Write-Message -Message "An unexpected error occurred: $_" -Level Error - throw $_ - } -} diff --git a/FabricTools/tiago/Private/Write-Message.ps1 b/FabricTools/tiago/Private/Write-Message.ps1 deleted file mode 100644 index 80da8e1e..00000000 --- a/FabricTools/tiago/Private/Write-Message.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -<# -.SYNOPSIS -Logs messages with different severity levels to the console and optionally to a file. - -.DESCRIPTION -The `Write-Message` function provides a unified way to log messages with levels such as Info, Error, Alert, Verbose, and Debug. -It supports logging to the console with color-coded messages and optionally writing logs to a file with timestamps. - -.PARAMETER Message -The message to log. Supports pipeline input. - -.PARAMETER Level -Specifies the log level. Supported values are Info, Error, Alert, Verbose, and Debug. - -.PARAMETER LogFile -(Optional) Specifies a file path to write the log messages to. If not provided, messages are only written to the console. - -.EXAMPLE -Write-Message -Message "This is an info message." -Level Info - -Logs an informational message to the console. - -.EXAMPLE -Write-Message -Message "Logging to file." -Level Info -LogFile "C:\Logs\MyLog.txt" - -Logs an informational message to the console and writes it to a file. - -.EXAMPLE -"Pipeline message" | Write-Message -Level Alert - -Logs a message from the pipeline with an Alert level. - -.NOTES -Author: Tiago Balabuch -#> - -function Write-Message { - [CmdletBinding()] - param ( - [Parameter(Mandatory, ValueFromPipeline)] - [string]$Message, - - [Parameter()] - [ValidateSet("Message","Info", "Error", "Warning","Critical", "Verbose", "Debug", IgnoreCase = $true)] - [string]$Level = "Info", - - [Parameter()] - [string]$LogFile - ) - - process { - try { - # Format timestamp - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - - # Construct log message - $logMessage = "[$timestamp] [$Level] $Message" - - # Write log message to console with colors - switch ($Level) { - "Message" { Write-Host $logMessage -ForegroundColor White } - "Info" { Write-Host $logMessage -ForegroundColor Green } - "Error" { Write-Host $logMessage -ForegroundColor Red } - "Warning" { Write-Host $logMessage -ForegroundColor Yellow } - "Critical" { Write-Host $logMessage -ForegroundColor Red } - "Verbose" { Write-Verbose $logMessage } - "Debug" { Write-Debug $logMessage } - - } - - # Optionally write log message to a file - if ($LogFile) { - try { - Add-Content -Path $LogFile -Value $logMessage -Encoding UTF8 - } catch { - # Catch and log any errors when writing to file - Write-Host "[ERROR] Failed to write to log file '$LogFile': $_" -ForegroundColor Red - } - } - } catch { - Write-Host "[ERROR] An unexpected error occurred: $_" -ForegroundColor Red - } - } -} diff --git a/FabricTools/tiago/Public/Capacity/Get-FabricCapacity.ps1 b/FabricTools/tiago/Public/Capacity/Get-FabricCapacity.ps1 deleted file mode 100644 index a36c88ca..00000000 --- a/FabricTools/tiago/Public/Capacity/Get-FabricCapacity.ps1 +++ /dev/null @@ -1,92 +0,0 @@ - -<# -.SYNOPSIS - Retrieves capacity details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves capacity details from a specified workspace using either the provided capacityId or capacityName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER capacityId - The unique identifier of the capacity to retrieve. This parameter is optional. - -.PARAMETER capacityName - The name of the capacity to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricCapacity -capacityId "capacity-12345" - This example retrieves the capacity details for the capacity with ID "capacity-12345". - -.EXAMPLE - Get-FabricCapacity -capacityName "MyCapacity" - This example retrieves the capacity details for the capacity named "MyCapacity". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricCapacity { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$capacityId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$capacityName - ) - try { - # Handle ambiguous input - if ($capacityId -and $capacityName) { - Write-Message -Message "Both 'capacityId' and 'capacityName' were provided. Please specify only one." -Level Error - return $null - } - - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API endpoint URL - $apiEndpointURI = "{0}/capacities" -f $FabricConfig.BaseUrl - - # Invoke the Fabric API to retrieve capacity details - $capacities = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - - # Filter results based on provided parameters - $response = if ($capacityId) { - $capacities | Where-Object { $_.Id -eq $capacityId } - } - elseif ($capacityName) { - $capacities | Where-Object { $_.DisplayName -eq $capacityName } - } - else { - # No filter, return all capacities - Write-Message -Message "No filter specified. Returning all capacities." -Level Debug - return $capacities - } - - # Handle results - if ($response) { - Write-Message -Message "Capacity found matching the specified criteria." -Level Debug - return $response - } - else { - Write-Message -Message "No capacity found matching the specified criteria." -Level Warning - return $null - } - } - catch { - # Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error - return $null - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Copy Job/Get-FabricCopyJob.ps1 b/FabricTools/tiago/Public/Copy Job/Get-FabricCopyJob.ps1 deleted file mode 100644 index 043ff8de..00000000 --- a/FabricTools/tiago/Public/Copy Job/Get-FabricCopyJob.ps1 +++ /dev/null @@ -1,100 +0,0 @@ -<# -.SYNOPSIS - Retrieves CopyJob details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves CopyJob details from a specified workspace using either the provided CopyJobId or CopyJobName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the CopyJob exists. This parameter is mandatory. - -.PARAMETER CopyJobId - The unique identifier of the CopyJob to retrieve. This parameter is optional. - -.PARAMETER CopyJobName - The name of the CopyJob to retrieve. This parameter is optional. - -.EXAMPLE - FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "CopyJob-67890" - This example retrieves the CopyJob details for the CopyJob with ID "CopyJob-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobName "My CopyJob" - This example retrieves the CopyJob details for the CopyJob named "My CopyJob" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function FabricCopyJob { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$CopyJob - ) - - try { - # Handle ambiguous input - if ($CopyJobId -and $CopyJobName) { - Write-Message -Message "Both 'CopyJobId' and 'CopyJobName' were provided. Please specify only one." -Level Error - return $null - } - - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - - # Construct the API endpoint URL - $apiEndpointURI = "{0}/workspaces/{1}/copyJobs" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Invoke the Fabric API to retrieve capacity details - $copyJobs = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - # Filter results based on provided parameters - $response = if ($CopyJobId) { - $copyJobs | Where-Object { $_.Id -eq $CopyJobId } - } - elseif ($CopyJobName) { - $copyJobs | Where-Object { $_.DisplayName -eq $CopyJobName } - } - else { - # Return all CopyJobs if no filter is provided - Write-Message -Message "No filter provided. Returning all CopyJobs." -Level Debug - $copyJobs - } - - # Step 9: Handle results - if ($response) { - Write-Message -Message "CopyJob found matching the specified criteria." -Level Debug - return $response - } - else { - Write-Message -Message "No CopyJob found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve CopyJob. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 b/FabricTools/tiago/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 deleted file mode 100644 index bfc2fcff..00000000 --- a/FabricTools/tiago/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 +++ /dev/null @@ -1,75 +0,0 @@ -<# -.SYNOPSIS -Retrieves the definition of a Copy Job from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the Copy Job's content or metadata from a workspace. -It supports both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the Copy Job definition is to be retrieved. - -.PARAMETER CopyJobId -(Mandatory) The unique identifier of the Copy Job whose definition needs to be retrieved. - -.PARAMETER CopyJobFormat -(Optional) Specifies the format of the Copy Job definition. For example, 'json' or 'xml'. - -.EXAMPLE -Get-FabricCopyJobDefinition -WorkspaceId "12345" -CopyJobId "67890" - -Retrieves the definition of the Copy Job with ID `67890` from the workspace with ID `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. -- Logs detailed information for debugging purposes. - -#> -function Get-FabricCopyJobDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobFormat - ) - - try { - # Step 1: Validate authentication token before proceeding. - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Step 2: Construct the API endpoint URL for retrieving the Copy Job definition. - $apiEndpointUrl = "{0}/workspaces/{1}/copyJobs/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId - - # Step 3: Append the format query parameter if specified by the user. - if ($CopyJobFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $CopyJobFormat - } - Write-Message -Message "Constructed API Endpoint URL: $apiEndpointUrl" -Level Debug - - # Step 4: Execute the API request to retrieve the Copy Job definition. - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointUrl ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post - - # Step 5: Return the API response containing the Copy Job definition. - return $response - } - catch { - # Step 6: Capture and log detailed error information for troubleshooting. - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Copy Job definition. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Copy Job/New-FabricCopyJob.ps1 b/FabricTools/tiago/Public/Copy Job/New-FabricCopyJob.ps1 deleted file mode 100644 index 7ac76e1c..00000000 --- a/FabricTools/tiago/Public/Copy Job/New-FabricCopyJob.ps1 +++ /dev/null @@ -1,145 +0,0 @@ -<# -.SYNOPSIS - Creates a new copy job in a specified Microsoft Fabric workspace. - -.DESCRIPTION - Sends a POST request to the Microsoft Fabric API to create a new copy job in the specified workspace. - Supports optional parameters for description and definition files. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the copy job will be created. Mandatory. - -.PARAMETER CopyJobName - The name of the copy job to be created. Mandatory. - -.PARAMETER CopyJobDescription - Optional description for the copy job. - -.PARAMETER CopyJobPathDefinition - Optional file path to the copy job definition JSON file. - -.PARAMETER CopyJobPathPlatformDefinition - Optional file path to the platform definition file. - -.EXAMPLE - New-FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobName "New Copy Job" -CopyJobDescription "Description of the new copy job" - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function New-FabricCopyJob { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$CopyJobName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $CopyJobName - } - - if ($CopyJobDescription) { - $body.description = $CopyJobDescription - } - - # Step 4: Add copy job definition file content if provided - if ($CopyJobPathDefinition) { - $CopyJobEncodedContent = Convert-ToBase64 -filePath $CopyJobPathDefinition - - if (-not [string]::IsNullOrEmpty($CopyJobEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "copyjob-content.json" - payload = $CopyJobEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Copy Job definition." -Level Error - return $null - } - } - #Step 5: Add platform definition file content if provided - if ($CopyJobPathPlatformDefinition) { - $CopyJobEncodedPlatformContent = Convert-ToBase64 -filePath $CopyJobPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($CopyJobEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $CopyJobEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 6: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson - - Write-Message -Message "Copy Job created successfully!" -Level Info - return $response - - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Copy Job. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Copy Job/Remove-FabricCopyJob.ps1 b/FabricTools/tiago/Public/Copy Job/Remove-FabricCopyJob.ps1 deleted file mode 100644 index d1613966..00000000 --- a/FabricTools/tiago/Public/Copy Job/Remove-FabricCopyJob.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -<# -.SYNOPSIS - Deletes a Copy Job from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function performs a DELETE operation on the Microsoft Fabric API to remove a Copy Job - from the specified workspace using the provided WorkspaceId and CopyJobId parameters. - -.PARAMETER WorkspaceId - The unique identifier of the workspace containing the Copy Job to be deleted. - -.PARAMETER CopyJobId - The unique identifier of the Copy Job to delete. - -.EXAMPLE - Remove-FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "copyjob-67890" - Deletes the Copy Job with ID "copyjob-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires the `$FabricConfig` global configuration, which must include `BaseUrl` and `FabricHeaders`. - - Ensures token validity by invoking `Test-TokenExpired` before making the API request. - - Author: Tiago Balabuch -#> -function Remove-FabricCopyJob { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobId - ) - try { - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API endpoint URI - $apiEndpointURI = "{0}/workspaces/{1}/copyJobs/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Delete - - Write-Message -Message "Copy Job '$CopyJobId' deleted successfully from workspace '$WorkspaceId'." -Level Info - return $response - - } - catch { - # Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Copy Job '$CopyJobId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Copy Job/Update-FabricCopyJob.ps1 b/FabricTools/tiago/Public/Copy Job/Update-FabricCopyJob.ps1 deleted file mode 100644 index 0db436dc..00000000 --- a/FabricTools/tiago/Public/Copy Job/Update-FabricCopyJob.ps1 +++ /dev/null @@ -1,90 +0,0 @@ -<# -.SYNOPSIS - Updates an existing Copy Job in a specified Microsoft Fabric workspace. - -.DESCRIPTION - Sends a PATCH request to the Microsoft Fabric API to update an existing Copy Job - in the specified workspace. Allows updating the Copy Job's name and optionally its description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace containing the Copy Job. This parameter is mandatory. - -.PARAMETER CopyJobId - The unique identifier of the Copy Job to be updated. This parameter is mandatory. - -.PARAMETER CopyJobName - The new name for the Copy Job. This parameter is mandatory. - -.PARAMETER CopyJobDescription - An optional new description for the Copy Job. - -.EXAMPLE - Update-FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "copyjob-67890" -CopyJobName "Updated Copy Job" -CopyJobDescription "Updated description" - Updates the Copy Job with ID "copyjob-67890" in the workspace "workspace-12345" with a new name and description. - -.NOTES - - Requires the `$FabricConfig` global configuration, which includes `BaseUrl` and `FabricHeaders`. - - Ensures token validity by calling `Test-TokenExpired` before making the API request. - - Author: Tiago Balabuch -#> -function Update-FabricCopyJob { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$CopyJobName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobDescription - ) - - try { - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API endpoint URI - $apiEndpointURI = "{0}/workspaces/{1}/copyJobs/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Construct the request body - $body = @{ - displayName = $CopyJobName - } - - if ($CopyJobDescription) { - $body.description = $CopyJobDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Patch ` - -Body $bodyJson - - Write-Message -Message "Copy Job '$CopyJobName' updated successfully!" -Level Info - return $response - } - catch { - # Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 b/FabricTools/tiago/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 deleted file mode 100644 index 4fd57f0f..00000000 --- a/FabricTools/tiago/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 +++ /dev/null @@ -1,134 +0,0 @@ -<# -.SYNOPSIS -Updates the definition of a Copy Job in a Microsoft Fabric workspace. - -.DESCRIPTION -This function updates the content or metadata of a Copy Job within a Microsoft Fabric workspace. -The Copy Job content and platform-specific definitions can be provided as file paths, which will be encoded as Base64 and sent in the request. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace containing the Copy Job. - -.PARAMETER CopyJobId -(Mandatory) The unique identifier of the Copy Job to be updated. - -.PARAMETER CopyJobPathDefinition -(Mandatory) The file path to the Copy Job content definition file. The file content will be encoded as Base64. - -.PARAMETER CopyJobPathPlatformDefinition -(Optional) The file path to the platform-specific definition file for the Copy Job. The file content will be encoded as Base64. - -.EXAMPLE -Update-FabricCopyJobDefinition -WorkspaceId "12345" -CopyJobId "67890" -CopyJobPathDefinition "C:\CopyJobs\CopyJob.ipynb" - -Updates the content of the Copy Job with ID `67890` in the workspace `12345` using the specified Copy Job file. - -.EXAMPLE -Update-FabricCopyJobDefinition -WorkspaceId "12345" -CopyJobId "67890" -CopyJobPathDefinition "C:\CopyJobs\CopyJob.ipynb" -CopyJobPathPlatformDefinition "C:\CopyJobs\Platform.json" - -Updates both the content and platform-specific definition of the Copy Job with ID `67890` in the workspace `12345`. - -.NOTES -- Requires the `$FabricConfig` global configuration, which must include `BaseUrl` and `FabricHeaders`. -- Validates token expiration using `Test-TokenExpired` before making the API request. -- Encodes file content as Base64 before sending it to the Fabric API. -- Logs detailed messages for debugging and error handling. - -Author: Tiago Balabuch -#> - -function Update-FabricCopyJobDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CopyJobPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/copyJobs/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId - - if ($CopyJobPathPlatformDefinition) { - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - if ($CopyJobPathDefinition) { - $CopyJobEncodedContent = Convert-ToBase64 -filePath $CopyJobPathDefinition - - if (-not [string]::IsNullOrEmpty($CopyJobEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "copyjob-content.json" - payload = $CopyJobEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Copy Job definition." -Level Error - return $null - } - } - - if ($CopyJobPathPlatformDefinition) { - $CopyJobEncodedPlatformContent = Convert-ToBase64 -filePath $CopyJobPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($CopyJobEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $CopyJobEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson - - Write-Message -Message "Copy Job updated successfully!" -Level Info - return $response - - - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Dashboard/Get-FabricDashboard.ps1 b/FabricTools/tiago/Public/Dashboard/Get-FabricDashboard.ps1 deleted file mode 100644 index ad267a30..00000000 --- a/FabricTools/tiago/Public/Dashboard/Get-FabricDashboard.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -<# -.SYNOPSIS - Retrieves dashboards from a specified workspace. - -.DESCRIPTION - This function retrieves all dashboards from a specified workspace using the provided WorkspaceId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The ID of the workspace from which to retrieve dashboards. This parameter is mandatory. - -.EXAMPLE - Get-FabricDashboard -WorkspaceId "12345" - This example retrieves all dashboards from the workspace with ID "12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> - -function Get-FabricDashboard { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId - ) - - try { - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API endpoint URL - $apiEndpointURI = "{0}/workspaces/{1}/dashboards" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Invoke the Fabric API to retrieve capacity details - $Dashboards = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - return $Dashboards - - } - catch { - # Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Dashboard. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Data Pipeline/Get-FabricDataPipeline.ps1 b/FabricTools/tiago/Public/Data Pipeline/Get-FabricDataPipeline.ps1 deleted file mode 100644 index 6f944615..00000000 --- a/FabricTools/tiago/Public/Data Pipeline/Get-FabricDataPipeline.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -<# -.SYNOPSIS - Retrieves data pipelines from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves all data pipelines from a specified workspace using either the provided Data PipelineId or Data PipelineName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Data Pipeline exists. This parameter is mandatory. - -.PARAMETER Data PipelineId - The unique identifier of the Data Pipeline to retrieve. This parameter is optional. - -.PARAMETER Data PipelineName - The name of the Data Pipeline to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineId "Data Pipeline-67890" - This example retrieves the Data Pipeline details for the Data Pipeline with ID "Data Pipeline-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineName "My Data Pipeline" - This example retrieves the Data Pipeline details for the Data Pipeline named "My Data Pipeline" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricDataPipeline { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$DataPipelineId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$DataPipelineName - ) - - try { - # Step 1: Handle ambiguous input - if ($DataPipelineId -and $DataPipelineName) { - Write-Message -Message "Both 'DataPipelineId' and 'DataPipelineName' were provided. Please specify only one." -Level Error - return $null - } - - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API endpoint URL - $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Invoke the Fabric API to retrieve capacity details - $DataPipelines = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - # Filter results based on provided parameters - $response = if ($DataPipelineId) { - $DataPipelines | Where-Object { $_.Id -eq $DataPipelineId } - } - elseif ($DataPipelineName) { - $DataPipelines | Where-Object { $_.DisplayName -eq $DataPipelineName } - } - else { - # Return all DataPipelines if no filter is provided - Write-Message -Message "No filter provided. Returning all DataPipelines." -Level Debug - $DataPipelines - } - - # Handle results - if ($response) { - Write-Message -Message "DataPipeline found matching the specified criteria." -Level Debug - return $response - } - else { - Write-Message -Message "No DataPipeline found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve DataPipeline. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Data Pipeline/New-FabricDataPipeline.ps1 b/FabricTools/tiago/Public/Data Pipeline/New-FabricDataPipeline.ps1 deleted file mode 100644 index 5daa2ff3..00000000 --- a/FabricTools/tiago/Public/Data Pipeline/New-FabricDataPipeline.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -<# -.SYNOPSIS - Creates a new DataPipeline in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new DataPipeline - in the specified workspace. It supports optional parameters for DataPipeline description - and path definitions for the DataPipeline content. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the DataPipeline will be created. - -.PARAMETER DataPipelineName - The name of the DataPipeline to be created. - -.PARAMETER DataPipelineDescription - An optional description for the DataPipeline. - -.EXAMPLE - New-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineName "New DataPipeline" - This example creates a new DataPipeline named "New DataPipeline" in the workspace with ID "workspace-12345" and uploads the definition file from the specified path. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> - -function New-FabricDataPipeline { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$DataPipelineName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$DataPipelineDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $DataPipelineName - } - - if ($DataPipelineDescription) { - $body.description = $DataPipelineDescription - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson - - Write-Message -Message "Data Pipeline created successfully!" -Level Info - return $response - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create DataPipeline. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 b/FabricTools/tiago/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 deleted file mode 100644 index f0ef3f1d..00000000 --- a/FabricTools/tiago/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -<# -.SYNOPSIS - Removes a DataPipeline from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove a DataPipeline - from the specified workspace using the provided WorkspaceId and DataPipelineId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the DataPipeline will be removed. - -.PARAMETER DataPipelineId - The unique identifier of the DataPipeline to be removed. - -.EXAMPLE - Remove-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineId "pipeline-67890" - This example removes the DataPipeline with ID "pipeline-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> - -function Remove-FabricDataPipeline { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DataPipelineId - ) - try { - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API URI - $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $DataPipelineId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Delete - Write-Message -Message "DataPipeline '$DataPipelineId' deleted successfully from workspace '$WorkspaceId'." -Level Info - return $response - } - catch { - # Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete DataPipeline '$DataPipelineId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Data Pipeline/Update-FabricDataPipeline.ps1 b/FabricTools/tiago/Public/Data Pipeline/Update-FabricDataPipeline.ps1 deleted file mode 100644 index 557fcf6a..00000000 --- a/FabricTools/tiago/Public/Data Pipeline/Update-FabricDataPipeline.ps1 +++ /dev/null @@ -1,90 +0,0 @@ -<# -.SYNOPSIS - Updates an existing DataPipeline in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing DataPipeline - in the specified workspace. It supports optional parameters for DataPipeline description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the DataPipeline exists. This parameter is optional. - -.PARAMETER DataPipelineId - The unique identifier of the DataPipeline to be updated. This parameter is mandatory. - -.PARAMETER DataPipelineName - The new name of the DataPipeline. This parameter is mandatory. - -.PARAMETER DataPipelineDescription - An optional new description for the DataPipeline. - -.EXAMPLE - Update-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineId "pipeline-67890" -DataPipelineName "Updated DataPipeline" -DataPipelineDescription "Updated description" - This example updates the DataPipeline with ID "pipeline-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Update-FabricDataPipeline { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DataPipelineId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$DataPipelineName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$DataPipelineDescription - ) - - try { - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $DataPipelineId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Construct the request body - $body = @{ - displayName = $DataPipelineName - } - - if ($DataPipelineDescription) { - $body.description = $DataPipelineDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Patch ` - -Body $bodyJson - - Write-Message -Message "DataPipeline '$DataPipelineName' updated successfully!" -Level Info - return $response - } - catch { - # Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update DataPipeline. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Datamart/Get-FabricDatamart.ps1 b/FabricTools/tiago/Public/Datamart/Get-FabricDatamart.ps1 deleted file mode 100644 index 55de12a3..00000000 --- a/FabricTools/tiago/Public/Datamart/Get-FabricDatamart.ps1 +++ /dev/null @@ -1,80 +0,0 @@ -<# -.SYNOPSIS - Retrieves datamarts from a specified workspace. - -.DESCRIPTION - This function retrieves all datamarts from a specified workspace using the provided WorkspaceId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The ID of the workspace from which to retrieve datamarts. This parameter is mandatory. - -.EXAMPLE - Get-FabricDatamart -WorkspaceId "12345" - This example retrieves all datamarts from the workspace with ID "12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricDatamart { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$datamartId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$datamartName - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - - $apiEndpointURI = "{0}/workspaces/{1}/Datamarts" -f $FabricConfig.BaseUrl, $WorkspaceId - - $Datamarts = = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - # Step 9: Filter results based on provided parameters - - $response = if ($datamartId) { - $Datamarts | Where-Object { $_.Id -eq $datamartId } - } - elseif ($datamartName) { - $Datamarts | Where-Object { $_.DisplayName -eq $datamartName } - } - else { - # No filter, return all datamarts - Write-Message -Message "No filter specified. Returning all datamarts." -Level Debug - return $Datamarts - } - - # Step 10: Handle results - if ($response) { - Write-Message -Message "Datamart found matching the specified criteria." -Level Debug - return $response - } - else { - Write-Message -Message "No Datamart found matching the specified criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Datamart. Error: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 b/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 deleted file mode 100644 index 5669c142..00000000 --- a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -<# -.SYNOPSIS -Assigns workspaces to a Fabric domain based on specified capacities. - -.DESCRIPTION -The `Assign-FabricDomainWorkspaceByCapacity` function assigns workspaces to a Fabric domain using a list of capacity IDs by making a POST request to the relevant API endpoint. - -.PARAMETER DomainId -The unique identifier of the Fabric domain to which the workspaces will be assigned. - -.PARAMETER CapacitiesIds -An array of capacity IDs used to assign workspaces to the domain. - -.EXAMPLE -Assign-FabricDomainWorkspaceByCapacity -DomainId "12345" -CapacitiesIds @("capacity1", "capacity2") - -Assigns workspaces to the domain with ID "12345" based on the specified capacities. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Assign-FabricDomainWorkspaceByCapacity { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [array]$CapacitiesIds - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspacesByCapacities" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - capacitiesIds = $CapacitiesIds - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Assigning domain workspaces by capacity completed successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Assigning domain workspaces by capacity is in progress for domain '$DomainId'." -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - return $operationStatus - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Error occurred while assigning workspaces by capacity for domain '$DomainId'. Details: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 b/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 deleted file mode 100644 index 7c20edb6..00000000 --- a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 +++ /dev/null @@ -1,83 +0,0 @@ -<# -.SYNOPSIS -Assigns workspaces to a specified domain in Microsoft Fabric by their IDs. - -.DESCRIPTION -The `Assign-FabricDomainWorkspaceById` function sends a request to assign multiple workspaces to a specified domain using the provided domain ID and an array of workspace IDs. - -.PARAMETER DomainId -The ID of the domain to which workspaces will be assigned. This parameter is mandatory. - -.PARAMETER WorkspaceIds -An array of workspace IDs to be assigned to the domain. This parameter is mandatory. - -.EXAMPLE -Assign-FabricDomainWorkspaceById -DomainId "12345" -WorkspaceIds @("ws1", "ws2", "ws3") - -Assigns the workspaces with IDs "ws1", "ws2", and "ws3" to the domain with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Assign-FabricDomainWorkspaceById { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [array]$WorkspaceIds - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspaces" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - workspacesIds = $WorkspaceIds - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Successfully assigned workspaces to the domain with ID '$DomainId'." -Level Info - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to assign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 b/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 deleted file mode 100644 index 52f868e8..00000000 --- a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 +++ /dev/null @@ -1,119 +0,0 @@ -<# -.SYNOPSIS -Assigns workspaces to a domain based on principal IDs in Microsoft Fabric. - -.DESCRIPTION -The `Assign-FabricDomainWorkspaceByPrincipal` function sends a request to assign workspaces to a specified domain using a JSON object of principal IDs and types. - -.PARAMETER DomainId -The ID of the domain to which workspaces will be assigned. This parameter is mandatory. - -.PARAMETER PrincipalIds -An array representing the principals with their `id` and `type` properties. Must contain a `principals` key with an array of objects. - -.EXAMPLE -$PrincipalIds = @( - @{id = "813abb4a-414c-4ac0-9c2c-bd17036fd58c"; type = "User"}, - @{id = "b5b9495c-685a-447a-b4d3-2d8e963e6288"; type = "User"} - ) - -Assign-FabricDomainWorkspaceByPrincipal -DomainId "12345" -PrincipalIds $principals - -Assigns the workspaces based on the provided principal IDs and types. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Assign-FabricDomainWorkspaceByPrincipal { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - #[hashtable]$PrincipalIds # Must contain a JSON array of principals with 'id' and 'type' properties - [System.Object]$PrincipalIds - ) - - try { - # Step 1: Ensure each principal contains 'id' and 'type' - foreach ($principal in $PrincipalIds) { - if (-not ($principal.ContainsKey('id') -and $principal.ContainsKey('type'))) { - throw "Each principal object must contain 'id' and 'type' properties." - } - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspacesByPrincipals" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Message - - # Step 4: Construct the request body - $body = @{ - principals = $PrincipalIds - } - - # Convert the PrincipalIds to JSON - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 5: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 6: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Assigning domain workspaces by principal completed successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Assigning domain workspaces by principal is in progress for domain '$DomainId'." -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - return $operationStatus - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to assign domain workspaces by principals. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 b/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 deleted file mode 100644 index da306f7d..00000000 --- a/FabricTools/tiago/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,102 +0,0 @@ -<# -.SYNOPSIS -Bulk assigns roles to principals for workspaces in a Fabric domain. - -.DESCRIPTION -The `AssignFabricDomainWorkspaceRoleAssignment` function performs bulk role assignments for principals in a specific Fabric domain. It sends a POST request to the relevant API endpoint. - -.PARAMETER DomainId -The unique identifier of the Fabric domain where roles will be assigned. - -.PARAMETER DomainRole -The role to assign to the principals. Must be one of the following: -- `Admins` -- `Contributors` - -.PARAMETER PrincipalIds -An array of principals to assign roles to. Each principal must include: -- `id`: The identifier of the principal. -- `type`: The type of the principal (e.g., `User`, `Group`). - -.EXAMPLE -AssignFabricDomainWorkspaceRoleAssignment -DomainId "12345" -DomainRole "Admins" -PrincipalIds @(@{id="user1"; type="User"}, @{id="group1"; type="Group"}) - -Assigns the `Admins` role to the specified principals in the domain with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Assign-FabricDomainWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Admins', 'Contributors')] - [string]$DomainRole, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [array]$PrincipalIds # Array with 'id' and 'type' - ) - - try { - # Step 1: Validate PrincipalIds structure - foreach ($principal in $PrincipalIds) { - if (-not ($principal.id -and $principal.type)) { - throw "Invalid principal detected: Each principal must include 'id' and 'type' properties. Found: $principal" - } - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}/roleAssignments/bulkAssign" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Construct the request body - $body = @{ - type = $DomainRole - principals = $PrincipalIds - } - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 5: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 6: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Bulk role assignment for domain '$DomainId' completed successfully!" -Level Info - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Domain/Get-FabricDomain.ps1 b/FabricTools/tiago/Public/Domain/Get-FabricDomain.ps1 deleted file mode 100644 index 31c8c1c7..00000000 --- a/FabricTools/tiago/Public/Domain/Get-FabricDomain.ps1 +++ /dev/null @@ -1,121 +0,0 @@ -<# -.SYNOPSIS -Retrieves domain information from Microsoft Fabric, optionally filtering by domain ID, domain name, or only non-empty domains. - -.DESCRIPTION -The `Get-FabricDomain` function allows retrieval of domains in Microsoft Fabric, with optional filtering by domain ID or name. Additionally, it can filter to return only non-empty domains. - -.PARAMETER DomainId -(Optional) The ID of the domain to retrieve. - -.PARAMETER DomainName -(Optional) The display name of the domain to retrieve. - -.PARAMETER NonEmptyDomainsOnly -(Optional) If set to `$true`, only domains containing workspaces will be returned. - -.EXAMPLE -Get-FabricDomain -DomainId "12345" - -Fetches the domain with ID "12345". - -.EXAMPLE -Get-FabricDomain -DomainName "Finance" - -Fetches the domain with the display name "Finance". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> -function Get-FabricDomain { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$DomainName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$NonEmptyDomainsOnly = $false - ) - - try { - # Step 1: Handle ambiguous input - if ($DomainId -and $DomainName) { - Write-Message -Message "Both 'DomainId' and 'DomainName' were provided. Please specify only one." -Level Error - return @() - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL with filtering logic - $apiEndpointUrl = "{0}/admin/domains" -f $FabricConfig.BaseUrl - if ($NonEmptyDomainsOnly) { - $apiEndpointUrl = "{0}?nonEmptyOnly=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle empty response - if (-not $response) { - Write-Message -Message "No data returned from the API." -Level Warning - return $null - } - - - # Step 7: Filter results based on provided parameters - $domains = if ($DomainId) { - $response.domains | Where-Object { $_.Id -eq $DomainId } - } - elseif ($DomainName) { - $response.domains | Where-Object { $_.DisplayName -eq $DomainName } - } - else { - # Return all domains if no filter is provided - Write-Message -Message "No filter provided. Returning all domains." -Level Debug - return $response.domains - } - - # Step 8: Handle results - if ($domains) { - return $domains - } - else { - Write-Message -Message "No domain found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Domain/Get-FabricDomainWorkspace.ps1 b/FabricTools/tiago/Public/Domain/Get-FabricDomainWorkspace.ps1 deleted file mode 100644 index 4094ab30..00000000 --- a/FabricTools/tiago/Public/Domain/Get-FabricDomainWorkspace.ps1 +++ /dev/null @@ -1,80 +0,0 @@ -<# -.SYNOPSIS -Retrieves the workspaces associated with a specific domain in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricDomainWorkspace` function fetches the workspaces for the given domain ID. - -.PARAMETER DomainId -The ID of the domain for which to retrieve workspaces. - -.EXAMPLE -Get-FabricDomainWorkspace -DomainId "12345" - -Fetches workspaces for the domain with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricDomainWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}/workspaces" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 5: Handle empty response - if (-not $response) { - Write-Message -Message "No data returned from the API." -Level Warning - return $null - } - # Step 6: Handle results - if ($response) { - return $response.value - } - else { - Write-Message -Message "No workspace found for the '$DomainId'." -Level Warning - return $null - } - - } - catch { - # Step 7: Capture and log error details - $errorDetails = Get-ErrorResponse($_.Exception) - Write-Message -Message "Failed to retrieve domain workspaces. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Domain/New-FabricDomain.ps1 b/FabricTools/tiago/Public/Domain/New-FabricDomain.ps1 deleted file mode 100644 index e60c9048..00000000 --- a/FabricTools/tiago/Public/Domain/New-FabricDomain.ps1 +++ /dev/null @@ -1,128 +0,0 @@ -<# -.SYNOPSIS -Creates a new Fabric domain. - -.DESCRIPTION -The `Add-FabricDomain` function creates a new domain in Microsoft Fabric by making a POST request to the relevant API endpoint. - -.PARAMETER DomainName -The name of the domain to be created. Must only contain alphanumeric characters, underscores, and spaces. - -.PARAMETER DomainDescription -A description of the domain to be created. - -.PARAMETER ParentDomainId -(Optional) The ID of the parent domain, if applicable. - -.EXAMPLE -Add-FabricDomain -DomainName "Finance" -DomainDescription "Finance data domain" -ParentDomainId "12345" - -Creates a "Finance" domain under the parent domain with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricDomain { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$DomainName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$DomainDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ParentDomainId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the request body - $apiEndpointUrl = "{0}/admin/domains" -f $FabricConfig.BaseUrl - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Construct the request body - $body = @{ - displayName = $DomainName - } - - if ($DomainDescription) { - $body.description = $DomainDescription - } - - if ($ParentDomainId) { - $body.parentDomainId = $ParentDomainId - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Domain '$DomainName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Domain '$DomainName' creation accepted. Provisioning in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create domain. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Domain/Remove-FabricDomain.ps1 b/FabricTools/tiago/Public/Domain/Remove-FabricDomain.ps1 deleted file mode 100644 index c7103158..00000000 --- a/FabricTools/tiago/Public/Domain/Remove-FabricDomain.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -<# -.SYNOPSIS -Deletes a Fabric domain by its ID. - -.DESCRIPTION -The `Remove-FabricDomain` function removes a specified domain from Microsoft Fabric by making a DELETE request to the relevant API endpoint. - -.PARAMETER DomainId -The unique identifier of the domain to be deleted. - -.EXAMPLE -Remove-FabricDomain -DomainId "12345" - -Deletes the domain with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricDomain { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Domain '$DomainId' deleted successfully!" -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete domain '$DomainId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspace.ps1 b/FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspace.ps1 deleted file mode 100644 index d86afbdc..00000000 --- a/FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspace.ps1 +++ /dev/null @@ -1,96 +0,0 @@ - -<# -.SYNOPSIS -Unassign workspaces from a specified Fabric domain. - -.DESCRIPTION -The `Unassign -FabricDomainWorkspace` function allows you to Unassign specific workspaces from a given Fabric domain or unassign all workspaces if no workspace IDs are specified. -It makes a POST request to the relevant API endpoint for this operation. - -.PARAMETER DomainId -The unique identifier of the Fabric domain. - -.PARAMETER WorkspaceIds -(Optional) An array of workspace IDs to unassign. If not provided, all workspaces will be unassigned. - -.EXAMPLE -Unassign-FabricDomainWorkspace -DomainId "12345" - -Unassigns all workspaces from the domain with ID "12345". - -.EXAMPLE -Unassign-FabricDomainWorkspace -DomainId "12345" -WorkspaceIds @("workspace1", "workspace2") - -Unassigns the specified workspaces from the domain with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - - -Author: Tiago Balabuch - -#> -function Unassign-FabricDomainWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [array]$WorkspaceIds - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - # Determine the API endpoint URL based on the presence of WorkspaceIds - $endpointSuffix = if ($WorkspaceIds) { "unassignWorkspaces" } else { "unassignAllWorkspaces" } - - $apiEndpointUrl = "{0}/admin/domains/{1}/{2}" -f $FabricConfig.BaseUrl, $DomainId, $endpointSuffix - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - - # Step 3: Construct the request body (if needed) - $bodyJson = if ($WorkspaceIds) { - $body = @{ workspacesIds = $WorkspaceIds } - $body | ConvertTo-Json -Depth 2 - } - else { - $null - } - - Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request to unassign specific workspaces - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Successfully unassigned workspaces to the domain with ID '$DomainId'." -Level Info - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to unassign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 b/FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 deleted file mode 100644 index c818fbb7..00000000 --- a/FabricTools/tiago/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -<# -.SYNOPSIS -Bulk unUnassign roles to principals for workspaces in a Fabric domain. - -.DESCRIPTION -The `AssignFabricDomainWorkspaceRoleAssignment` function performs bulk role assignments for principals in a specific Fabric domain. It sends a POST request to the relevant API endpoint. - -.PARAMETER DomainId -The unique identifier of the Fabric domain where roles will be assigned. - -.PARAMETER DomainRole -The role to assign to the principals. Must be one of the following: -- `Admins` -- `Contributors` - -.PARAMETER PrincipalIds -An array of principals to assign roles to. Each principal must include: -- `id`: The identifier of the principal. -- `type`: The type of the principal (e.g., `User`, `Group`). - -.EXAMPLE -AssignFabricDomainWorkspaceRoleAssignment -DomainId "12345" -DomainRole "Admins" -PrincipalIds @(@{id="user1"; type="User"}, @{id="group1"; type="Group"}) - -Unassign the `Admins` role to the specified principals in the domain with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Unassign-FabricDomainWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Admins', 'Contributors')] - [string]$DomainRole, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [array]$PrincipalIds # Array with 'id' and 'type' - ) - - try { - # Step 1: Validate PrincipalIds structure - foreach ($principal in $PrincipalIds) { - if (-not ($principal.id -and $principal.type)) { - throw "Invalid principal detected: Each principal must include 'id' and 'type' properties. Found: $principal" - } - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}/roleAssignments/bulkUnassign" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Construct the request body - $body = @{ - type = $DomainRole - principals = $PrincipalIds - } - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 5: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 6: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Bulk role unassignment for domain '$DomainId' completed successfully!" -Level Info - - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Domain/Update-FabricDomain.ps1 b/FabricTools/tiago/Public/Domain/Update-FabricDomain.ps1 deleted file mode 100644 index 0ffad315..00000000 --- a/FabricTools/tiago/Public/Domain/Update-FabricDomain.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -<# -.SYNOPSIS -Updates a Fabric domain by its ID. - -.DESCRIPTION -The `Update-FabricDomain` function modifies a specified domain in Microsoft Fabric using the provided parameters. - -.PARAMETER DomainId -The unique identifier of the domain to be updated. - -.PARAMETER DomainName -The new name for the domain. Must be alphanumeric. - -.PARAMETER DomainDescription -(Optional) A new description for the domain. - -.PARAMETER DomainContributorsScope -(Optional) The contributors' scope for the domain. Accepted values: 'AdminsOnly', 'AllTenant', 'SpecificUsersAndGroups'. - -.EXAMPLE -Update-FabricDomain -DomainId "12345" -DomainName "NewDomain" -DomainDescription "Updated description" -DomainContributorsScope "AdminsOnly" - -Updates the domain with ID "12345" with a new name, description, and contributors' scope. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricDomain { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DomainId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$DomainName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$DomainDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('AdminsOnly', 'AllTenant', 'SpecificUsersAndGroups')] - [string]$DomainContributorsScope - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/admin/domains/{1}" -f $FabricConfig.BaseUrl, $DomainId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $DomainName - } - - if ($DomainDescription) { - $body.description = $DomainDescription - } - - if ($DomainContributorsScope) { - $body.contributorsScope = $DomainContributorsScope - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Domain '$DomainName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update domain '$DomainId'. Error: $errorDetails" -Level Error - } -} - \ No newline at end of file diff --git a/FabricTools/tiago/Public/Environment/Get-FabricEnvironment.ps1 b/FabricTools/tiago/Public/Environment/Get-FabricEnvironment.ps1 deleted file mode 100644 index 48e497c8..00000000 --- a/FabricTools/tiago/Public/Environment/Get-FabricEnvironment.ps1 +++ /dev/null @@ -1,157 +0,0 @@ -<# -.SYNOPSIS -Retrieves an environment or a list of environments from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricEnvironment` function sends a GET request to the Fabric API to retrieve environment details for a given workspace. It can filter the results by `EnvironmentName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query environments. - -.PARAMETER EnvironmentName -(Optional) The name of the specific environment to retrieve. - -.EXAMPLE -Get-FabricEnvironment -WorkspaceId "12345" -EnvironmentName "Development" - -Retrieves the "Development" environment from workspace "12345". - -.EXAMPLE -Get-FabricEnvironment -WorkspaceId "12345" - -Retrieves all environments in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Returns the matching environment details or all environments if no filter is provided. - -Author: Tiago Balabuch - -#> - -function Get-FabricEnvironment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EnvironmentName - ) - - try { - # Step 1: Handle ambiguous input - if ($EnvironmentId -and $EnvironmentName) { - Write-Message -Message "Both 'EnvironmentId' and 'EnvironmentName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $environments = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - $baseApiEndpointUrl = "{0}/workspaces/{1}/environments" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Step 4: Loop to retrieve data with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $environments += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $environment = if ($EnvironmentId) { - $environments | Where-Object { $_.Id -eq $EnvironmentId } - } - elseif ($EnvironmentName) { - $environments | Where-Object { $_.DisplayName -eq $EnvironmentName } - } - else { - # Return all workspaces if no filter is provided - Write-Message -Message "No filter provided. Returning all environments." -Level Debug - $environments - } - - # Step 9: Handle results - if ($environment) { - Write-Message -Message "Environment found in the Workspace '$WorkspaceId'." -Level Debug - return $environment - } - else { - Write-Message -Message "No environment found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentLibrary.ps1 b/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentLibrary.ps1 deleted file mode 100644 index 75a650ab..00000000 --- a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentLibrary.ps1 +++ /dev/null @@ -1,76 +0,0 @@ -<# -.SYNOPSIS -Retrieves the list of libraries associated with a specific environment in a Microsoft Fabric workspace. - -.DESCRIPTION -The Get-FabricEnvironmentLibrary function fetches library information for a given workspace and environment -using the Microsoft Fabric API. It ensures the authentication token is valid and validates the response -to handle errors gracefully. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace where the environment is located. - -.PARAMETER EnvironmentId -The unique identifier of the environment whose libraries are being queried. - -.EXAMPLE -Get-FabricEnvironmentLibrary -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" - -Retrieves the libraries associated with the specified environment in the given workspace. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. -- Uses `Test-TokenExpired` to validate the token before making API calls. - -Author: Tiago Balabuch -#> -function Get-FabricEnvironmentLibrary { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/libraries" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 5: Handle results - return $response - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve environment libraries. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 b/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 deleted file mode 100644 index 65cf74de..00000000 --- a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 +++ /dev/null @@ -1,76 +0,0 @@ -<# -.SYNOPSIS -Retrieves the Spark compute details for a specific environment in a Microsoft Fabric workspace. - -.DESCRIPTION -The Get-FabricEnvironmentSparkCompute function communicates with the Microsoft Fabric API to fetch information -about Spark compute resources associated with a specified environment. It ensures that the API token is valid -and gracefully handles errors during the API call. - -.PARAMETER WorkspaceId -The unique identifier of the workspace containing the target environment. - -.PARAMETER EnvironmentId -The unique identifier of the environment whose Spark compute details are being retrieved. - -.EXAMPLE -Get-FabricEnvironmentSparkCompute -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" - -Retrieves Spark compute details for the specified environment in the given workspace. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. -- Uses `Test-TokenExpired` to validate the token before making API calls. - -Author: Tiago Balabuch -#> -function Get-FabricEnvironmentSparkCompute { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/sparkcompute" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 5: Handle results - return $response - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve environment Spark compute. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 b/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 deleted file mode 100644 index 0a8a5505..00000000 --- a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 +++ /dev/null @@ -1,76 +0,0 @@ -<# -.SYNOPSIS -Retrieves the staging library details for a specific environment in a Microsoft Fabric workspace. - -.DESCRIPTION -The Get-FabricEnvironmentStagingLibrary function interacts with the Microsoft Fabric API to fetch information -about staging libraries associated with a specified environment. It ensures token validity and handles API errors gracefully. - -.PARAMETER WorkspaceId -The unique identifier of the workspace containing the target environment. - -.PARAMETER EnvironmentId -The unique identifier of the environment for which staging library details are being retrieved. - -.EXAMPLE - Get-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" - -Retrieves the staging libraries for the specified environment in the given workspace. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. -- Uses `Test-TokenExpired` to validate the token before making API calls. - -Author: Tiago Balabuch -#> -function Get-FabricEnvironmentStagingLibrary { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/libraries" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 5: Handle results - return $response.customLibraries - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve environment spark compute. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 b/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 deleted file mode 100644 index cbc94d55..00000000 --- a/FabricTools/tiago/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 +++ /dev/null @@ -1,78 +0,0 @@ -<# -.SYNOPSIS -Retrieves staging Spark compute details for a specific environment in a Microsoft Fabric workspace. - -.DESCRIPTION -The Get-FabricEnvironmentStagingSparkCompute function interacts with the Microsoft Fabric API to fetch information -about staging Spark compute configurations for a specified environment. It ensures token validity and handles API errors gracefully. - -.PARAMETER WorkspaceId -The unique identifier of the workspace containing the target environment. - -.PARAMETER EnvironmentId -The unique identifier of the environment for which staging Spark compute details are being retrieved. - -.EXAMPLE -Get-FabricEnvironmentStagingSparkCompute -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" - -Retrieves the staging Spark compute configurations for the specified environment in the given workspace. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. -- Uses `Test-TokenExpired` to validate the token before making API calls. - -Author: Tiago Balabuch -#> - - -function Get-FabricEnvironmentStagingSparkCompute { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/sparkcompute" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 5: Handle results - return $response - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve environment spark compute. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Environment/New-FabricEnvironment.ps1 b/FabricTools/tiago/Public/Environment/New-FabricEnvironment.ps1 deleted file mode 100644 index 009a5aeb..00000000 --- a/FabricTools/tiago/Public/Environment/New-FabricEnvironment.ps1 +++ /dev/null @@ -1,123 +0,0 @@ -<# -.SYNOPSIS -Creates a new environment in a specified workspace. - -.DESCRIPTION -The `Add-FabricEnvironment` function creates a new environment within a given workspace by making a POST request to the Fabric API. The environment can optionally include a description. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace where the environment will be created. - -.PARAMETER EnvironmentName -(Mandatory) The name of the environment to be created. Only alphanumeric characters, spaces, and underscores are allowed. - -.PARAMETER EnvironmentDescription -(Optional) A description of the environment. - -.EXAMPLE -Add-FabricEnvironment -WorkspaceId "12345" -EnvironmentName "DevEnv" -EnvironmentDescription "Development Environment" - -Creates an environment named "DevEnv" in workspace "12345" with the specified description. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricEnvironment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EnvironmentName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EnvironmentName - } - - if ($EnvironmentDescription) { - $body.description = $EnvironmentDescription - } - - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Environment '$EnvironmentName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Environment '$EnvironmentName' creation accepted. Provisioning in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create environment. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Environment/Publish-FabricEnvironment.ps1 b/FabricTools/tiago/Public/Environment/Publish-FabricEnvironment.ps1 deleted file mode 100644 index bf9fffbc..00000000 --- a/FabricTools/tiago/Public/Environment/Publish-FabricEnvironment.ps1 +++ /dev/null @@ -1,104 +0,0 @@ -<# -.SYNOPSIS -Publishes a staging environment in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function interacts with the Microsoft Fabric API to initiate the publishing process for a staging environment. -It validates the authentication token, constructs the API request, and handles both immediate and long-running operations. - - -.PARAMETER WorkspaceId -The unique identifier of the workspace containing the staging environment. - -.PARAMETER EnvironmentId -The unique identifier of the staging environment to be published. - -.EXAMPLE -Publish-FabricEnvironment -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" - -Initiates the publishing process for the specified staging environment. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. -- Uses `Test-TokenExpired` to validate the token before making API calls. - -Author: Tiago Balabuch -#> - -function Publish-FabricEnvironment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/publish" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Publish operation request has been submitted successfully for the environment '$EnvironmentId'!" -Level Info - return $response.publishDetails - } - 202 { - Write-Message -Message "Publish operation accepted. Publishing in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create environment. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Environment/Remove-FabricEnvironment.ps1 b/FabricTools/tiago/Public/Environment/Remove-FabricEnvironment.ps1 deleted file mode 100644 index 4e1f734e..00000000 --- a/FabricTools/tiago/Public/Environment/Remove-FabricEnvironment.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -<# -.SYNOPSIS -Deletes an environment from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricEnvironment` function sends a DELETE request to the Fabric API to remove a specified environment from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the environment to delete. - -.PARAMETER EnvironmentId -(Mandatory) The ID of the environment to be deleted. - -.EXAMPLE -Remove-FabricEnvironment -WorkspaceId "12345" -EnvironmentId "67890" - -Deletes the environment with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricEnvironment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 b/FabricTools/tiago/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 deleted file mode 100644 index ebfd16f5..00000000 --- a/FabricTools/tiago/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 +++ /dev/null @@ -1,85 +0,0 @@ - -<# -.SYNOPSIS -Deletes a specified library from the staging environment in a Microsoft Fabric workspace. - -.DESCRIPTION -This function allows for the deletion of a library from the staging environment, one file at a time. -It ensures token validity, constructs the appropriate API request, and handles both success and failure responses. - -.PARAMETER WorkspaceId -The unique identifier of the workspace from which the library is to be deleted. - -.PARAMETER EnvironmentId -The unique identifier of the staging environment containing the library. - -.PARAMETER LibraryName -The name of the library to be deleted from the environment. - -.EXAMPLE -Remove-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" -LibraryName "library-to-delete" - -Deletes the specified library from the staging environment in the specified workspace. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. -- This function currently supports deleting one library at a time. -Author: Tiago Balabuch - -#> - - -function Remove-FabricEnvironmentStagingLibrary { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$LibraryName - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/libraries?libraryToDelete={3}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId, $LibraryName - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Staging library $LibraryName for the Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Environment/Stop-FabricEnvironmentPublish.ps1 b/FabricTools/tiago/Public/Environment/Stop-FabricEnvironmentPublish.ps1 deleted file mode 100644 index 9276ea0a..00000000 --- a/FabricTools/tiago/Public/Environment/Stop-FabricEnvironmentPublish.ps1 +++ /dev/null @@ -1,76 +0,0 @@ -<# -.SYNOPSIS -Cancels the publish operation for a specified environment in Microsoft Fabric. - -.DESCRIPTION -This function sends a cancel publish request to the Microsoft Fabric API for a given environment. -It ensures that the token is valid before making the request and handles both successful and error responses. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the environment exists. - -.PARAMETER EnvironmentId -The unique identifier of the environment for which the publish operation is to be canceled. - -.EXAMPLE -Stop-FabricEnvironmentPublish -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" - -Cancels the publish operation for the specified environment. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> -function Stop-FabricEnvironmentPublish{ - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/cancelPublish" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Publication for environment '$EnvironmentId' has been successfully canceled." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to cancel publication for environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Environment/Update-FabricEnvironment.ps1 b/FabricTools/tiago/Public/Environment/Update-FabricEnvironment.ps1 deleted file mode 100644 index 2c9364b0..00000000 --- a/FabricTools/tiago/Public/Environment/Update-FabricEnvironment.ps1 +++ /dev/null @@ -1,108 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric Environment. - -.DESCRIPTION -The `Update-FabricEnvironment` function updates the name and/or description of a specified Fabric Environment by making a PATCH request to the API. - -.PARAMETER EnvironmentId -The unique identifier of the Environment to be updated. - -.PARAMETER EnvironmentName -The new name for the Environment. - -.PARAMETER EnvironmentDescription -(Optional) The new description for the Environment. - -.EXAMPLE -Update-FabricEnvironment -EnvironmentId "Environment123" -EnvironmentName "NewEnvironmentName" - -Updates the name of the Environment with the ID "Environment123" to "NewEnvironmentName". - -.EXAMPLE -Update-FabricEnvironment -EnvironmentId "Environment123" -EnvironmentName "NewName" -EnvironmentDescription "Updated description" - -Updates both the name and description of the Environment "Environment123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricEnvironment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EnvironmentName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EnvironmentName - } - - if ($EnvironmentDescription) { - $body.description = $EnvironmentDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Environment '$EnvironmentName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Environment. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 b/FabricTools/tiago/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 deleted file mode 100644 index bd542623..00000000 --- a/FabricTools/tiago/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 +++ /dev/null @@ -1,177 +0,0 @@ -<# -.SYNOPSIS -Updates the Spark compute configuration in the staging environment for a given workspace. - -.DESCRIPTION -This function sends a PATCH request to the Microsoft Fabric API to update the Spark compute settings -for a specified environment, including instance pool, driver and executor configurations, and dynamic allocation settings. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the environment exists. - -.PARAMETER EnvironmentId -The unique identifier of the environment where the Spark compute settings will be updated. - -.PARAMETER InstancePoolName -The name of the instance pool to be used for Spark compute. - -.PARAMETER InstancePoolType -The type of instance pool (either 'Workspace' or 'Capacity'). - -.PARAMETER DriverCores -The number of cores to allocate to the driver. - -.PARAMETER DriverMemory -The amount of memory to allocate to the driver. - -.PARAMETER ExecutorCores -The number of cores to allocate to each executor. - -.PARAMETER ExecutorMemory -The amount of memory to allocate to each executor. - -.PARAMETER DynamicExecutorAllocationEnabled -Boolean flag to enable or disable dynamic executor allocation. - -.PARAMETER DynamicExecutorAllocationMinExecutors -The minimum number of executors when dynamic allocation is enabled. - -.PARAMETER DynamicExecutorAllocationMaxExecutors -The maximum number of executors when dynamic allocation is enabled. - -.PARAMETER RuntimeVersion -The Spark runtime version to use. - -.PARAMETER SparkProperties -A hashtable of additional Spark properties to configure. - -.EXAMPLE -Update-FabricEnvironmentStagingSparkCompute -WorkspaceId "workspace-12345" -EnvironmentId "env-67890" -InstancePoolName "pool1" -InstancePoolType "Workspace" -DriverCores 4 -DriverMemory "16GB" -ExecutorCores 8 -ExecutorMemory "32GB" -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 2 -DynamicExecutorAllocationMaxExecutors 10 -RuntimeVersion "3.1" -SparkProperties @{ "spark.executor.memoryOverhead"="4GB" } - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> -function Update-FabricEnvironmentStagingSparkCompute { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$InstancePoolName, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Workspace', 'Capacity')] - [string]$InstancePoolType, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$DriverCores, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$DriverMemory, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$ExecutorCores, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ExecutorMemory, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [bool]$DynamicExecutorAllocationEnabled, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$DynamicExecutorAllocationMinExecutors, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$DynamicExecutorAllocationMaxExecutors, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$RuntimeVersion, - - [Parameter(Mandatory = $true)] - [System.Object]$SparkProperties - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/sparkcompute" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - instancePool = @{ - name = $InstancePoolName - type = $InstancePoolType - } - driverCores = $DriverCores - driverMemory = $DriverMemory - executorCores = $ExecutorCores - executorMemory = $ExecutorMemory - dynamicExecutorAllocation = @{ - enabled = $DynamicExecutorAllocationEnabled - minExecutors = $DynamicExecutorAllocationMinExecutors - maxExecutors = $DynamicExecutorAllocationMaxExecutors - } - runtimeVersion = $RuntimeVersion - sparkProperties = $SparkProperties - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 4 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Environment staging Spark compute updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update environment staging Spark compute. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 b/FabricTools/tiago/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 deleted file mode 100644 index d8b09960..00000000 --- a/FabricTools/tiago/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -<# -.SYNOPSIS -Uploads a library to the staging environment in a Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to upload a library to the specified -environment staging area for the given workspace. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the environment exists. - -.PARAMETER EnvironmentId -The unique identifier of the environment where the library will be uploaded. - -.EXAMPLE -Upload-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "env-67890" - -.NOTES -- This is not working code. It is a placeholder for future development. Fabric documentation is missing some important details on how to upload libraries. -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> -function Upload-FabricEnvironmentStagingLibrary { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/libraries" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Environment staging library uploaded successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to upload environment staging library. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouse.ps1 b/FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouse.ps1 deleted file mode 100644 index dabafa46..00000000 --- a/FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouse.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves Eventhouse details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves Eventhouse details from a specified workspace using either the provided EventhouseId or EventhouseName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Eventhouse exists. This parameter is mandatory. - -.PARAMETER EventhouseId - The unique identifier of the Eventhouse to retrieve. This parameter is optional. - -.PARAMETER EventhouseName - The name of the Eventhouse to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" - This example retrieves the Eventhouse details for the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseName "My Eventhouse" - This example retrieves the Eventhouse details for the Eventhouse named "My Eventhouse" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricEventhouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventhouseName - ) - try { - - # Step 1: Handle ambiguous input - if ($EventhouseId -and $EventhouseName) { - Write-Message -Message "Both 'EventhouseId' and 'EventhouseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $eventhouses = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId - # Step 3: Loop to retrieve data with continuation token - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $eventhouses += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $eventhouse = if ($EventhouseId) { - $eventhouses | Where-Object { $_.Id -eq $EventhouseId } - } - elseif ($EventhouseName) { - $eventhouses | Where-Object { $_.DisplayName -eq $EventhouseName } - } - else { - # Return all eventhouses if no filter is provided - Write-Message -Message "No filter provided. Returning all Eventhouses." -Level Debug - $eventhouses - } - - # Step 9: Handle results - if ($eventhouse) { - Write-Message -Message "Eventhouse found in the Workspace '$WorkspaceId'." -Level Debug - return $eventhouse - } - else { - Write-Message -Message "No Eventhouse found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 b/FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 deleted file mode 100644 index e42a8a17..00000000 --- a/FabricTools/tiago/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -<# -.SYNOPSIS - Retrieves the definition of an Eventhouse from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves the definition of an Eventhouse from a specified workspace using the provided EventhouseId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Eventhouse exists. This parameter is mandatory. - -.PARAMETER EventhouseId - The unique identifier of the Eventhouse to retrieve the definition for. This parameter is optional. - -.PARAMETER EventhouseFormat - The format in which to retrieve the Eventhouse definition. This parameter is optional. - -.EXAMPLE - Get-FabricEventhouseDefinition -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" - This example retrieves the definition of the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricEventhouseDefinition -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" -EventhouseFormat "json" - This example retrieves the definition of the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345" in JSON format. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricEventhouseDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseFormat - ) - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId - - if ($EventhouseFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $EventhouseFormat - } - - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "Eventhouse '$EventhouseId' definition retrieved successfully!" -Level Debug - return $response - } - 202 { - - Write-Message -Message "Getting Eventhouse '$EventhouseId' definition request accepted. Retrieving in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Eventhouse/New-FabricEventhouse.ps1 b/FabricTools/tiago/Public/Eventhouse/New-FabricEventhouse.ps1 deleted file mode 100644 index de5979b9..00000000 --- a/FabricTools/tiago/Public/Eventhouse/New-FabricEventhouse.ps1 +++ /dev/null @@ -1,193 +0,0 @@ -<# -.SYNOPSIS - Creates a new Eventhouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Eventhouse - in the specified workspace. It supports optional parameters for Eventhouse description and path definitions. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Eventhouse will be created. This parameter is mandatory. - -.PARAMETER EventhouseName - The name of the Eventhouse to be created. This parameter is mandatory. - -.PARAMETER EventhouseDescription - An optional description for the Eventhouse. - -.PARAMETER EventhousePathDefinition - An optional path to the Eventhouse definition file to upload. - -.PARAMETER EventhousePathPlatformDefinition - An optional path to the platform-specific definition file to upload. - -.EXAMPLE - New-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseName "New Eventhouse" -EventhouseDescription "Description of the new Eventhouse" - This example creates a new Eventhouse named "New Eventhouse" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function New-FabricEventhouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventhouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhousePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhousePathPlatformDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EventhouseName - } - - if ($EventhouseDescription) { - $body.description = $EventhouseDescription - } - if ($EventhousePathDefinition) { - $eventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition - - if (-not [string]::IsNullOrEmpty($eventhouseEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "EventhouseProperties.json" - payload = $eventhouseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error - return $null - } - } - - if ($EventhousePathPlatformDefinition) { - $eventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($eventhouseEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $eventhouseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Eventhouse '$EventhouseName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Eventhouse '$EventhouseName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Eventhouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventhouse/Remove-FabricEventhouse.ps1 b/FabricTools/tiago/Public/Eventhouse/Remove-FabricEventhouse.ps1 deleted file mode 100644 index 0bdfd275..00000000 --- a/FabricTools/tiago/Public/Eventhouse/Remove-FabricEventhouse.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS - Removes an Eventhouse from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an Eventhouse - from the specified workspace using the provided WorkspaceId and EventhouseId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the Eventhouse will be removed. - -.PARAMETER EventhouseId - The unique identifier of the Eventhouse to be removed. - -.EXAMPLE - Remove-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" - This example removes the Eventhouse with ID "eventhouse-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricEventhouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle response - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Eventhouse '$EventhouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Eventhouse '$EventhouseId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouse.ps1 b/FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouse.ps1 deleted file mode 100644 index 6968c89c..00000000 --- a/FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouse.ps1 +++ /dev/null @@ -1,105 +0,0 @@ -<# -.SYNOPSIS - Updates an existing Eventhouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Eventhouse - in the specified workspace. It supports optional parameters for Eventhouse description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Eventhouse exists. This parameter is optional. - -.PARAMETER EventhouseId - The unique identifier of the Eventhouse to be updated. This parameter is mandatory. - -.PARAMETER EventhouseName - The new name of the Eventhouse. This parameter is mandatory. - -.PARAMETER EventhouseDescription - An optional new description for the Eventhouse. - -.EXAMPLE - Update-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" -EventhouseName "Updated Eventhouse" -EventhouseDescription "Updated description" - This example updates the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricEventhouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventhouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseDescription - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EventhouseName - } - - if ($EventhouseDescription) { - $body.description = $EventhouseDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Eventhouse '$EventhouseName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 b/FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 deleted file mode 100644 index aa1bf713..00000000 --- a/FabricTools/tiago/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 +++ /dev/null @@ -1,168 +0,0 @@ -<# -.SYNOPSIS - Updates the definition of an existing Eventhouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Eventhouse - in the specified workspace. It supports optional parameters for Eventhouse definition and platform-specific definition. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Eventhouse exists. This parameter is mandatory. - -.PARAMETER EventhouseId - The unique identifier of the Eventhouse to be updated. This parameter is mandatory. - -.PARAMETER EventhousePathDefinition - An optional path to the Eventhouse definition file to upload. - -.PARAMETER EventhousePathPlatformDefinition - An optional path to the platform-specific definition file to upload. - -.EXAMPLE - Update-FabricEventhouseDefinition -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" -EventhousePathDefinition "C:\Path\To\EventhouseDefinition.json" - This example updates the definition of the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345" using the provided definition file. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricEventhouseDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventhousePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhousePathPlatformDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId - - #if ($UpdateMetadata -eq $true) { - if($EventhousePathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - if ($EventhousePathDefinition) { - $EventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition - - if (-not [string]::IsNullOrEmpty($EventhouseEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "EventhouseProperties.json" - payload = $EventhouseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error - return $null - } - } - - if ($EventhousePathPlatformDefinition) { - $EventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($EventhouseEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $EventhouseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for Eventhouse '$EventhouseId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for Eventhouse '$EventhouseId' accepted. Operation in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventstream/Get-FabricEventstream.ps1 b/FabricTools/tiago/Public/Eventstream/Get-FabricEventstream.ps1 deleted file mode 100644 index 28f6448a..00000000 --- a/FabricTools/tiago/Public/Eventstream/Get-FabricEventstream.ps1 +++ /dev/null @@ -1,159 +0,0 @@ -<# -.SYNOPSIS -Retrieves an Eventstream or a list of Eventstreams from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricEventstream` function sends a GET request to the Fabric API to retrieve Eventstream details for a given workspace. It can filter the results by `EventstreamName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query Eventstreams. - -.PARAMETER EventstreamName -(Optional) The name of the specific Eventstream to retrieve. - -.EXAMPLE -Get-FabricEventstream -WorkspaceId "12345" -EventstreamName "Development" - -Retrieves the "Development" Eventstream from workspace "12345". - -.EXAMPLE -Get-FabricEventstream -WorkspaceId "12345" - -Retrieves all Eventstreams in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricEventstream { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventstreamName - ) - - try { - # Step 1: Handle ambiguous input - if ($EventstreamId -and $EventstreamName) { - Write-Message -Message "Both 'EventstreamId' and 'EventstreamName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $continuationToken = $null - $eventstreams = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Step 3: Loop to retrieve data with continuation token - - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $eventstreams += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - - # Step 8: Filter results based on provided parameters - $eventstream = if ($EventstreamId) { - $eventstreams | Where-Object { $_.Id -eq $EventstreamId } - } - elseif ($EventstreamName) { - $eventstreams | Where-Object { $_.DisplayName -eq $EventstreamName } - } - else { - # Return all eventstreams if no filter is provided - Write-Message -Message "No filter provided. Returning all Eventstreams." -Level Debug - $eventstreams - } - - # Step 9: Handle results - if ($eventstream) { - Write-Message -Message "Eventstream found matching the specified criteria." -Level Debug - return $eventstream - } - else { - Write-Message -Message "No Eventstream found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 b/FabricTools/tiago/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 deleted file mode 100644 index eb070a7b..00000000 --- a/FabricTools/tiago/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 +++ /dev/null @@ -1,121 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the definition of a Eventstream from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the Eventstream's content or metadata from a workspace. -Handles both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the Eventstream definition is to be retrieved. - -.PARAMETER EventstreamId -(Optional)The unique identifier of the Eventstream whose definition needs to be retrieved. - -.PARAMETER EventstreamFormat -Specifies the format of the Eventstream definition. Currently, only 'ipynb' is supported. -Default: 'ipynb'. - -.EXAMPLE -Get-FabricEventstreamDefinition -WorkspaceId "12345" -EventstreamId "67890" - -Retrieves the definition of the Eventstream with ID `67890` from the workspace with ID `12345` in the `ipynb` format. - -.EXAMPLE -Get-FabricEventstreamDefinition -WorkspaceId "12345" - -Retrieves the definitions of all Eventstreams in the workspace with ID `12345` in the `ipynb` format. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. - -#> -function Get-FabricEventstreamDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamFormat - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/Eventstreams/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId - - if ($EventstreamFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $EventstreamFormat - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "Eventstream '$EventstreamId' definition retrieved successfully!" -Level Info - return $response.definition.parts - } - 202 { - - Write-Message -Message "Getting Eventstream '$EventstreamId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Eventstream/New-FabricEventstream.ps1 b/FabricTools/tiago/Public/Eventstream/New-FabricEventstream.ps1 deleted file mode 100644 index f5def5b5..00000000 --- a/FabricTools/tiago/Public/Eventstream/New-FabricEventstream.ps1 +++ /dev/null @@ -1,188 +0,0 @@ -<# -.SYNOPSIS -Creates a new Eventstream in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new Eventstream -in the specified workspace. It supports optional parameters for Eventstream description -and path definitions for the Eventstream content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the Eventstream will be created. - -.PARAMETER EventstreamName -The name of the Eventstream to be created. - -.PARAMETER EventstreamDescription -An optional description for the Eventstream. - -.PARAMETER EventstreamPathDefinition -An optional path to the Eventstream definition file (e.g., .ipynb file) to upload. - -.PARAMETER EventstreamPathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricEventstream -WorkspaceId "workspace-12345" -EventstreamName "New Eventstream" -EventstreamPathDefinition "C:\Eventstreams\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricEventstream { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventstreamName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EventstreamName - } - - if ($EventstreamDescription) { - $body.description = $EventstreamDescription - } - - if ($EventstreamPathDefinition) { - $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition - - if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "eventstream" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "eventstream.json" - payload = $EventstreamEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error - return $null - } - } - - if ($EventstreamPathPlatformDefinition) { - $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "eventstream" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $EventstreamEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Eventstream '$EventstreamName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Eventstream '$EventstreamName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Eventstream. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventstream/Remove-FabricEventstream.ps1 b/FabricTools/tiago/Public/Eventstream/Remove-FabricEventstream.ps1 deleted file mode 100644 index ef59265f..00000000 --- a/FabricTools/tiago/Public/Eventstream/Remove-FabricEventstream.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS -Deletes an Eventstream from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricEventstream` function sends a DELETE request to the Fabric API to remove a specified Eventstream from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the Eventstream to delete. - -.PARAMETER EventstreamId -(Mandatory) The ID of the Eventstream to be deleted. - -.EXAMPLE -Remove-FabricEventstream -WorkspaceId "12345" -EventstreamId "67890" - -Deletes the Eventstream with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricEventstream { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Eventstream '$EventstreamId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Eventstream '$EventstreamId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventstream/Update-FabricEventstream.ps1 b/FabricTools/tiago/Public/Eventstream/Update-FabricEventstream.ps1 deleted file mode 100644 index 28103bf3..00000000 --- a/FabricTools/tiago/Public/Eventstream/Update-FabricEventstream.ps1 +++ /dev/null @@ -1,107 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric Eventstream. - -.DESCRIPTION -The `Update-FabricEventstream` function updates the name and/or description of a specified Fabric Eventstream by making a PATCH request to the API. - -.PARAMETER EventstreamId -The unique identifier of the Eventstream to be updated. - -.PARAMETER EventstreamName -The new name for the Eventstream. - -.PARAMETER EventstreamDescription -(Optional) The new description for the Eventstream. - -.EXAMPLE -Update-FabricEventstream -EventstreamId "Eventstream123" -EventstreamName "NewEventstreamName" - -Updates the name of the Eventstream with the ID "Eventstream123" to "NewEventstreamName". - -.EXAMPLE -Update-FabricEventstream -EventstreamId "Eventstream123" -EventstreamName "NewName" -EventstreamDescription "Updated description" - -Updates both the name and description of the Eventstream "Eventstream123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricEventstream { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventstreamName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EventstreamName - } - - if ($EventstreamDescription) { - $body.description = $EventstreamDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Eventstream '$EventstreamName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 b/FabricTools/tiago/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 deleted file mode 100644 index 0929734e..00000000 --- a/FabricTools/tiago/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 +++ /dev/null @@ -1,180 +0,0 @@ -<# -.SYNOPSIS -Updates the definition of a Eventstream in a Microsoft Fabric workspace. - -.DESCRIPTION -This function allows updating the content or metadata of a Eventstream in a Microsoft Fabric workspace. -The Eventstream content can be provided as file paths, and metadata updates can optionally be enabled. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace where the Eventstream resides. - -.PARAMETER EventstreamId -(Mandatory) The unique identifier of the Eventstream to be updated. - -.PARAMETER EventstreamPathDefinition -(Mandatory) The file path to the Eventstream content definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER EventstreamPathPlatformDefinition -(Optional) The file path to the Eventstream's platform-specific definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER UpdateMetadata -(Optional)A boolean flag indicating whether to update the Eventstream's metadata. -Default: `$false`. - -.EXAMPLE -Update-FabricEventstreamDefinition -WorkspaceId "12345" -EventstreamId "67890" -EventstreamPathDefinition "C:\Eventstreams\Eventstream.ipynb" - -Updates the content of the Eventstream with ID `67890` in the workspace `12345` using the specified Eventstream file. - -.EXAMPLE -Update-FabricEventstreamDefinition -WorkspaceId "12345" -EventstreamId "67890" -EventstreamPathDefinition "C:\Eventstreams\Eventstream.ipynb" -UpdateMetadata $true - -Updates both the content and metadata of the Eventstream with ID `67890` in the workspace `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- The Eventstream content is encoded as Base64 before being sent to the Fabric API. -- This function handles asynchronous operations and retrieves operation results if required. - -Author: Tiago Balabuch - -#> - -function Update-FabricEventstreamDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId - - if($EventstreamPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - if ($EventstreamPathDefinition) { - $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition - - if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "eventstream.json" - payload = $EventstreamEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error - return $null - } - } - - if ($EventstreamPathPlatformDefinition) { - $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $EventstreamEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for Eventstream '$EventstreamId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for Eventstream '$EventstreamId' accepted. Operation in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/External Data Share/Get-FabricExternalDataShares.ps1 b/FabricTools/tiago/Public/External Data Share/Get-FabricExternalDataShares.ps1 deleted file mode 100644 index d9434787..00000000 --- a/FabricTools/tiago/Public/External Data Share/Get-FabricExternalDataShares.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -<# -.SYNOPSIS - Retrieves External Data Shares details from a specified Microsoft Fabric. - -.DESCRIPTION - This function retrieves External Data Shares details. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.EXAMPLE - Get-FabricExternalDataShares - This example retrieves the External Data Shares details - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricExternalDataShares { - [CmdletBinding()] - param ( ) - - try { - - # Validate authentication token before proceeding - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API endpoint URI for retrieving external data shares - Write-Message -Message "Constructing API endpoint URI..." -Level Debug - $apiEndpointURI = "{0}/admin/items/externalDataShares" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Invoke the API request to retrieve external data shares - $externalDataShares = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - # Return the retrieved external data shares - Write-Message -Message "Successfully retrieved external data shares." -Level Debug - return $externalDataShares - } - catch { - # Capture and log detailed error information if the API request fails - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 b/FabricTools/tiago/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 deleted file mode 100644 index 0d7f4cb2..00000000 --- a/FabricTools/tiago/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -<# -.SYNOPSIS - Retrieves External Data Shares details from a specified Microsoft Fabric. - -.DESCRIPTION - This function retrieves External Data Shares details. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.EXAMPLE - Get-FabricExternalDataShares - This example retrieves the External Data Shares details - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Revoke-FabricExternalDataShares { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ItemId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ExternalDataShareId - ) - - try { - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Constructing API endpoint URI..." -Level Debug - $apiEndpointURI = "{0}/admin/workspaces/{1}/items/{2}/externalDataShares/{3}/revoke" -f $FabricConfig.BaseUrl, $WorkspaceId, $ItemId, $ExternalDataShareId - - $externalDataShares = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post - - # Step 4: Return retrieved data - Write-Message -Message "Successfully revoked external data shares." -Level Info - return $externalDataShares - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 b/FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 deleted file mode 100644 index 66087294..00000000 --- a/FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 +++ /dev/null @@ -1,155 +0,0 @@ -<# -.SYNOPSIS -Retrieves an KQLDashboard or a list of KQLDashboards from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricKQLDashboard` function sends a GET request to the Fabric API to retrieve KQLDashboard details for a given workspace. It can filter the results by `KQLDashboardName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query KQLDashboards. - -.PARAMETER KQLDashboardName -(Optional) The name of the specific KQLDashboard to retrieve. - -.EXAMPLE -Get-FabricKQLDashboard -WorkspaceId "12345" -KQLDashboardName "Development" - -Retrieves the "Development" KQLDashboard from workspace "12345". - -.EXAMPLE -Get-FabricKQLDashboard -WorkspaceId "12345" - -Retrieves all KQLDashboards in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricKQLDashboard { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDashboardName - ) - - try { - # Step 1: Handle ambiguous input - if ($KQLDashboardId -and $KQLDashboardName) { - Write-Message -Message "Both 'KQLDashboardId' and 'KQLDashboardName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $KQLDashboards = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $KQLDashboards += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $KQLDashboard = if ($KQLDashboardId) { - $KQLDashboards | Where-Object { $_.Id -eq $KQLDashboardId } - } - elseif ($KQLDashboardName) { - $KQLDashboards | Where-Object { $_.DisplayName -eq $KQLDashboardName } - } - else { - # Return all KQLDashboards if no filter is provided - Write-Message -Message "No filter provided. Returning all KQLDashboards." -Level Debug - $KQLDashboards - } - - # Step 9: Handle results - if ($KQLDashboard) { - Write-Message -Message "KQLDashboard found matching the specified criteria." -Level Debug - return $KQLDashboard - } - else { - Write-Message -Message "No KQLDashboard found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 b/FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 deleted file mode 100644 index 4f0cf5d1..00000000 --- a/FabricTools/tiago/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 +++ /dev/null @@ -1,120 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the definition of a KQLDashboard from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the KQLDashboard's content or metadata from a workspace. -Handles both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the KQLDashboard definition is to be retrieved. - -.PARAMETER KQLDashboardId -(Optional)The unique identifier of the KQLDashboard whose definition needs to be retrieved. - -.PARAMETER KQLDashboardFormat -Specifies the format of the KQLDashboard definition. - -.EXAMPLE -Get-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" - -Retrieves the definition of the KQLDashboard with ID `67890` from the workspace with ID `12345` in the `ipynb` format. - -.EXAMPLE -Get-FabricKQLDashboardDefinition -WorkspaceId "12345" - -Retrieves the definitions of all KQLDashboards in the workspace with ID `12345` in the `ipynb` format. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. - -#> -function Get-FabricKQLDashboardDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardFormat - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - - if ($KQLDashboardFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLDashboardFormat - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "KQLDashboard '$KQLDashboardId' definition retrieved successfully!" -Level Debug - return $response.definition.parts - } - 202 { - - Write-Message -Message "Getting KQLDashboard '$KQLDashboardId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 b/FabricTools/tiago/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 deleted file mode 100644 index 50694d70..00000000 --- a/FabricTools/tiago/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 +++ /dev/null @@ -1,188 +0,0 @@ -<# -.SYNOPSIS -Creates a new KQLDashboard in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLDashboard -in the specified workspace. It supports optional parameters for KQLDashboard description -and path definitions for the KQLDashboard content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the KQLDashboard will be created. - -.PARAMETER KQLDashboardName -The name of the KQLDashboard to be created. - -.PARAMETER KQLDashboardDescription -An optional description for the KQLDashboard. - -.PARAMETER KQLDashboardPathDefinition -An optional path to the KQLDashboard definition file (e.g., .ipynb file) to upload. - -.PARAMETER KQLDashboardPathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricKQLDashboard -WorkspaceId "workspace-12345" -KQLDashboardName "New KQLDashboard" -KQLDashboardPathDefinition "C:\KQLDashboards\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricKQLDashboard { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDashboardName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $KQLDashboardName - } - - if ($KQLDashboardDescription) { - $body.description = $KQLDashboardDescription - } - - if ($KQLDashboardPathDefinition) { - $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition - - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "KQLDashboard" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "RealTimeDashboard.json" - payload = $KQLDashboardEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error - return $null - } - } - - if ($KQLDashboardPathPlatformDefinition) { - $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = $null - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLDashboardEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "KQLDashboard '$KQLDashboardName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "KQLDashboard '$KQLDashboardName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create KQLDashboard. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 b/FabricTools/tiago/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 deleted file mode 100644 index 166e7635..00000000 --- a/FabricTools/tiago/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS -Deletes an KQLDashboard from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricKQLDashboard` function sends a DELETE request to the Fabric API to remove a specified KQLDashboard from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the KQLDashboard to delete. - -.PARAMETER KQLDashboardId -(Mandatory) The ID of the KQLDashboard to be deleted. - -.EXAMPLE -Remove-FabricKQLDashboard -WorkspaceId "12345" -KQLDashboardId "67890" - -Deletes the KQLDashboard with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricKQLDashboard { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "KQLDashboard '$KQLDashboardId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete KQLDashboard '$KQLDashboardId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 b/FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 deleted file mode 100644 index 874a614b..00000000 --- a/FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 +++ /dev/null @@ -1,107 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric KQLDashboard. - -.DESCRIPTION -The `Update-FabricKQLDashboard` function updates the name and/or description of a specified Fabric KQLDashboard by making a PATCH request to the API. - -.PARAMETER KQLDashboardId -The unique identifier of the KQLDashboard to be updated. - -.PARAMETER KQLDashboardName -The new name for the KQLDashboard. - -.PARAMETER KQLDashboardDescription -(Optional) The new description for the KQLDashboard. - -.EXAMPLE -Update-FabricKQLDashboard -KQLDashboardId "KQLDashboard123" -KQLDashboardName "NewKQLDashboardName" - -Updates the name of the KQLDashboard with the ID "KQLDashboard123" to "NewKQLDashboardName". - -.EXAMPLE -Update-FabricKQLDashboard -KQLDashboardId "KQLDashboard123" -KQLDashboardName "NewName" -KQLDashboardDescription "Updated description" - -Updates both the name and description of the KQLDashboard "KQLDashboard123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricKQLDashboard { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDashboardName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $KQLDashboardName - } - - if ($KQLDashboardDescription) { - $body.description = $KQLDashboardDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "KQLDashboard '$KQLDashboardName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 b/FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 deleted file mode 100644 index 14b423fc..00000000 --- a/FabricTools/tiago/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 +++ /dev/null @@ -1,166 +0,0 @@ -<# -.SYNOPSIS -Updates the definition of a KQLDashboard in a Microsoft Fabric workspace. - -.DESCRIPTION -This function allows updating the content or metadata of a KQLDashboard in a Microsoft Fabric workspace. -The KQLDashboard content can be provided as file paths, and metadata updates can optionally be enabled. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace where the KQLDashboard resides. - -.PARAMETER KQLDashboardId -(Mandatory) The unique identifier of the KQLDashboard to be updated. - -.PARAMETER KQLDashboardPathDefinition -(Mandatory) The file path to the KQLDashboard content definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER KQLDashboardPathPlatformDefinition -(Optional) The file path to the KQLDashboard's platform-specific definition file. The content will be encoded as Base64 and sent in the request. - - -.EXAMPLE -Update-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" -KQLDashboardPathDefinition "C:\KQLDashboards\KQLDashboard.ipynb" - -Updates the content of the KQLDashboard with ID `67890` in the workspace `12345` using the specified KQLDashboard file. - -.EXAMPLE -Update-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" -KQLDashboardPathDefinition "C:\KQLDashboards\KQLDashboard.ipynb" - -Updates both the content and metadata of the KQLDashboard with ID `67890` in the workspace `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- The KQLDashboard content is encoded as Base64 before being sent to the Fabric API. -- This function handles asynchronous operations and retrieves operation results if required. - -Author: Tiago Balabuch - -#> - -function Update-FabricKQLDashboardDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/KQLDashboards/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - - if($KQLDashboardPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - format = $null - parts = @() - } - } - - if ($KQLDashboardPathDefinition) { - $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition - - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "RealTimeDashboard.json" - payload = $KQLDashboardEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error - return $null - } - } - - if ($KQLDashboardPathPlatformDefinition) { - $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLDashboardEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for KQLDashboard '$KQLDashboardId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for KQLDashboard '$KQLDashboardId' accepted. Operation in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - $operationResult = Get-FabricLongRunningOperation -operationId $operationId - - # Handle operation result - if ($operationResult.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - - $result = Get-FabricLongRunningOperationResult -operationId $operationId - return $result.definition.parts - } - else { - Write-Message -Message "Operation Failed" -Level Debug - return $operationResult.definition.parts - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabase.ps1 b/FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabase.ps1 deleted file mode 100644 index 94677fe6..00000000 --- a/FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,153 +0,0 @@ -<# -.SYNOPSIS -Retrieves an KQLDatabase or a list of KQLDatabases from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricKQLDatabase` function sends a GET request to the Fabric API to retrieve KQLDatabase details for a given workspace. It can filter the results by `KQLDatabaseName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query KQLDatabases. - -.PARAMETER KQLDatabaseName -(Optional) The name of the specific KQLDatabase to retrieve. - -.EXAMPLE -Get-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseName "Development" - -Retrieves the "Development" KQLDatabase from workspace "12345". - -.EXAMPLE -Get-FabricKQLDatabase -WorkspaceId "12345" - -Retrieves all KQLDatabases in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> -function Get-FabricKQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDatabaseName - ) - - try { - # Step 1: Handle ambiguous input - if ($KQLDatabaseId -and $KQLDatabaseName) { - Write-Message -Message "Both 'KQLDatabaseId' and 'KQLDatabaseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $KQLDatabases = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $KQLDatabases += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $KQLDatabase = if ($KQLDatabaseId) { - $KQLDatabases | Where-Object { $_.Id -eq $KQLDatabaseId } - } - elseif ($KQLDatabaseName) { - $KQLDatabases | Where-Object { $_.DisplayName -eq $KQLDatabaseName } - } - else { - # Return all KQLDatabases if no filter is provided - Write-Message -Message "No filter provided. Returning all KQLDatabases." -Level Debug - $KQLDatabases - } - - # Step 9: Handle results - if ($KQLDatabase) { - Write-Message -Message "KQLDatabase found matching the specified criteria." -Level Debug - return $KQLDatabase - } - else { - Write-Message -Message "No KQLDatabase found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 b/FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 deleted file mode 100644 index a3d56115..00000000 --- a/FabricTools/tiago/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 +++ /dev/null @@ -1,129 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the definition of a KQLDatabase from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the KQLDatabase's content or metadata from a workspace. -It supports retrieving KQLDatabase definitions in the Jupyter KQLDatabase (`ipynb`) format. -Handles both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the KQLDatabase definition is to be retrieved. - -.PARAMETER KQLDatabaseId -(Optional)The unique identifier of the KQLDatabase whose definition needs to be retrieved. - -.PARAMETER KQLDatabaseFormat -Specifies the format of the KQLDatabase definition. Currently, only 'ipynb' is supported. - - -.EXAMPLE -Get-FabricKQLDatabaseDefinition -WorkspaceId "12345" -KQLDatabaseId "67890" - -Retrieves the definition of the KQLDatabase with ID `67890` from the workspace with ID `12345` in the `ipynb` format. - -.EXAMPLE -Get-FabricKQLDatabaseDefinition -WorkspaceId "12345" - -Retrieves the definitions of all KQLDatabases in the workspace with ID `12345` in the `ipynb` format. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. - -#> -function Get-FabricKQLDatabaseDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseFormat - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/KQLDatabases/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId - - if ($KQLDatabaseFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLDatabaseFormat - } - - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "KQLDatabase '$KQLDatabaseId' definition retrieved successfully!" -Level Debug - return $response - } - 202 { - - Write-Message -Message "Getting KQLDatabase '$KQLDatabaseId' definition request accepted. Retrieving in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Database/New-FabricKQLDatabase.ps1 b/FabricTools/tiago/Public/KQL Database/New-FabricKQLDatabase.ps1 deleted file mode 100644 index 21098872..00000000 --- a/FabricTools/tiago/Public/KQL Database/New-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,288 +0,0 @@ -<# -.SYNOPSIS -Creates a new KQLDatabase in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLDatabase -in the specified workspace. It supports optional parameters for KQLDatabase description -and path definitions for the KQLDatabase content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the KQLDatabase will be created. - -.PARAMETER KQLDatabaseName -The name of the KQLDatabase to be created. - -.PARAMETER KQLDatabaseDescription -An optional description for the KQLDatabase. - -.PARAMETER KQLDatabasePathDefinition -An optional path to the KQLDatabase definition file (e.g., .ipynb file) to upload. - -.PARAMETER KQLDatabasePathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricKQLDatabase -WorkspaceId "workspace-12345" -KQLDatabaseName "New KQLDatabase" -KQLDatabasePathDefinition "C:\KQLDatabases\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Precedent Request Body - - Definition file high priority. - - CreationPayload is evaluate only if Definition file is not provided. - - invitationToken has priority over all other payload fields. - -Author: Tiago Balabuch - -#> - -function New-FabricKQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDatabaseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$parentEventhouseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet("ReadWrite", "Shortcut")] - [string]$KQLDatabaseType, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLInvitationToken, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLSourceClusterUri, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLSourceDatabaseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathPlatformDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathSchemaDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body ### This is working - $body = @{ - displayName = $KQLDatabaseName - } - - if ($KQLDatabaseDescription) { - $body.description = $KQLDatabaseDescription - } - - if ($KQLDatabasePathDefinition) { - $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition - - $body.definition = @{ - parts = @() - } - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { - - - # Add new part to the parts array - $body.definition.parts += @{ - path = "DatabaseProperties.json" - payload = $KQLDatabaseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error - return $null - } - - if ($KQLDatabasePathPlatformDefinition) { - $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLDatabaseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - - } - if ($KQLDatabasePathSchemaDefinition) { - $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { - - # Add new part to the parts array - $body.definition.parts += @{ - path = "DatabaseSchema.kql" - payload = $KQLDatabaseEncodedSchemaContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in schema definition." -Level Error - return $null - } - } - - } - else { - if ($KQLDatabaseType -eq "Shortcut") { - if (-not $parentEventhouseId) { - Write-Message -Message "Error: 'parentEventhouseId' is required for Shortcut type." -Level Error - return $null - } - if (-not ($KQLInvitationToken -or $KQLSourceClusterUri -or $KQLSourceDatabaseName)) { - Write-Message -Message "Error: Provide either 'KQLInvitationToken', 'KQLSourceClusterUri', or 'KQLSourceDatabaseName'." -Level Error - return $null - } - if ($KQLInvitationToken) { - Write-Message -Message "Info: 'KQLInvitationToken' is provided." -Level Warning - - if ($KQLSourceClusterUri) { - Write-Message -Message "Warning: 'KQLSourceClusterUri' is ignored when 'KQLInvitationToken' is provided." -Level Warning - #$KQLSourceClusterUri = $null - } - if ($KQLSourceDatabaseName) { - Write-Message -Message "Warning: 'KQLSourceDatabaseName' is ignored when 'KQLInvitationToken' is provided." -Level Warning - #$KQLSourceDatabaseName = $null - } - } - if ($KQLSourceClusterUri -and -not $KQLSourceDatabaseName) { - Write-Message -Message "Error: 'KQLSourceDatabaseName' is required when 'KQLSourceClusterUri' is provided." -Level Error - return $null - } - } - - # Validate ReadWrite type database - if ($KQLDatabaseType -eq "ReadWrite" -and -not $parentEventhouseId) { - Write-Message -Message "Error: 'parentEventhouseId' is required for ReadWrite type." -Level Error - return $null - } - - $body.creationPayload = @{ - databaseType = $KQLDatabaseType - parentEventhouseItemId = $parentEventhouseId - } - - if ($KQLDatabaseType -eq "Shortcut") { - if ($KQLInvitationToken) { - - $body.creationPayload.invitationToken = $KQLInvitationToken - } - if ($KQLSourceClusterUri -and -not $KQLInvitationToken) { - $body.creationPayload.sourceClusterUri = $KQLSourceClusterUri - } - if ($KQLSourceDatabaseName -and -not $KQLInvitationToken) { - $body.creationPayload.sourceDatabaseName = $KQLSourceDatabaseName - } - } - - - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "KQLDatabase '$KQLDatabaseName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "KQLDatabase '$KQLDatabaseName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create KQLDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Database/Remove-FabricKQLDatabase.ps1 b/FabricTools/tiago/Public/KQL Database/Remove-FabricKQLDatabase.ps1 deleted file mode 100644 index 2b5d69f2..00000000 --- a/FabricTools/tiago/Public/KQL Database/Remove-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -<# -.SYNOPSIS -Deletes an KQLDatabase from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricKQLDatabase` function sends a DELETE request to the Fabric API to remove a specified KQLDatabase from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the KQLDatabase to delete. - -.PARAMETER KQLDatabaseId -(Mandatory) The ID of the KQLDatabase to be deleted. - -.EXAMPLE -Remove-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseId "67890" - -Deletes the KQLDatabase with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricKQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "KQLDatabase '$KQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete KQLDatabase '$KQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabase.ps1 b/FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabase.ps1 deleted file mode 100644 index 4c15f3a2..00000000 --- a/FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,108 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric KQLDatabase. - -.DESCRIPTION -The `Update-FabricKQLDatabase` function updates the name and/or description of a specified Fabric KQLDatabase by making a PATCH request to the API. - -.PARAMETER KQLDatabaseId -The unique identifier of the KQLDatabase to be updated. - -.PARAMETER KQLDatabaseName -The new name for the KQLDatabase. - -.PARAMETER KQLDatabaseDescription -(Optional) The new description for the KQLDatabase. - -.EXAMPLE -Update-FabricKQLDatabase -KQLDatabaseId "KQLDatabase123" -KQLDatabaseName "NewKQLDatabaseName" - -Updates the name of the KQLDatabase with the ID "KQLDatabase123" to "NewKQLDatabaseName". - -.EXAMPLE -Update-FabricKQLDatabase -KQLDatabaseId "KQLDatabase123" -KQLDatabaseName "NewName" -KQLDatabaseDescription "Updated description" - -Updates both the name and description of the KQLDatabase "KQLDatabase123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricKQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDatabaseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $KQLDatabaseName - } - - if ($KQLDatabaseDescription) { - $body.description = $KQLDatabaseDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "KQLDatabase '$KQLDatabaseName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 b/FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 deleted file mode 100644 index 35df15fe..00000000 --- a/FabricTools/tiago/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 +++ /dev/null @@ -1,202 +0,0 @@ -<# -.SYNOPSIS -Updates the definition of a KQLDatabase in a Microsoft Fabric workspace. - -.DESCRIPTION -This function allows updating the content or metadata of a KQLDatabase in a Microsoft Fabric workspace. -The KQLDatabase content can be provided as file paths, and metadata updates can optionally be enabled. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace where the KQLDatabase resides. - -.PARAMETER KQLDatabaseId -(Mandatory) The unique identifier of the KQLDatabase to be updated. - -.PARAMETER KQLDatabasePathDefinition -(Mandatory) The file path to the KQLDatabase content definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER KQLDatabasePathPlatformDefinition -(Optional) The file path to the KQLDatabase's platform-specific definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER UpdateMetadata -(Optional)A boolean flag indicating whether to update the KQLDatabase's metadata. -Default: `$false`. - -.EXAMPLE -Update-FabricKQLDatabaseDefinition -WorkspaceId "12345" -KQLDatabaseId "67890" -KQLDatabasePathDefinition "C:\KQLDatabases\KQLDatabase.ipynb" - -Updates the content of the KQLDatabase with ID `67890` in the workspace `12345` using the specified KQLDatabase file. - -.EXAMPLE -Update-FabricKQLDatabaseDefinition -WorkspaceId "12345" -KQLDatabaseId "67890" -KQLDatabasePathDefinition "C:\KQLDatabases\KQLDatabase.ipynb" -UpdateMetadata $true - -Updates both the content and metadata of the KQLDatabase with ID `67890` in the workspace `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- The KQLDatabase content is encoded as Base64 before being sent to the Fabric API. -- This function handles asynchronous operations and retrieves operation results if required. - -Author: Tiago Balabuch - -#> - -function Update-FabricKQLDatabaseDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathPlatformDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathSchemaDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId - - if($KQLDatabasePathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - if ($KQLDatabasePathDefinition) { - $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "DatabaseProperties.json" - payload = $KQLDatabaseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error - return $null - } - } - - if ($KQLDatabasePathPlatformDefinition) { - $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLDatabaseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - if ($KQLDatabasePathSchemaDefinition) { - $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { - - # Add new part to the parts array - $body.definition.parts += @{ - path = "DatabaseSchema.kql" - payload = $KQLDatabaseEncodedSchemaContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in schema definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for KQLDatabase '$KQLDatabaseId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for KQLDatabase '$KQLDatabaseId' accepted. Operation in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - Write-Message -Message "Operation completed successfully." -Level Info - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 b/FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 deleted file mode 100644 index b1c06d8b..00000000 --- a/FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,154 +0,0 @@ -<# -.SYNOPSIS -Retrieves an KQLQueryset or a list of KQLQuerysets from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricKQLQueryset` function sends a GET request to the Fabric API to retrieve KQLQueryset details for a given workspace. It can filter the results by `KQLQuerysetName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query KQLQuerysets. - -.PARAMETER KQLQuerysetName -(Optional) The name of the specific KQLQueryset to retrieve. - -.EXAMPLE -Get-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetName "Development" - -Retrieves the "Development" KQLQueryset from workspace "12345". - -.EXAMPLE -Get-FabricKQLQueryset -WorkspaceId "12345" - -Retrieves all KQLQuerysets in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricKQLQueryset { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLQuerysetName - ) - - try { - # Step 1: Handle ambiguous input - if ($KQLQuerysetId -and $KQLQuerysetName) { - Write-Message -Message "Both 'KQLQuerysetId' and 'KQLQuerysetName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $KQLQuerysets = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets" -f $FabricConfig.BaseUrl, $WorkspaceId - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $KQLQuerysets += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $KQLQueryset = if ($KQLQuerysetId) { - $KQLQuerysets | Where-Object { $_.Id -eq $KQLQuerysetId } - } - elseif ($KQLQuerysetName) { - $KQLQuerysets | Where-Object { $_.DisplayName -eq $KQLQuerysetName } - } - else { - # Return all KQLQuerysets if no filter is provided - Write-Message -Message "No filter provided. Returning all KQLQuerysets." -Level Debug - $KQLQuerysets - } - - # Step 9: Handle results - if ($KQLQueryset) { - Write-Message -Message "KQLQueryset found matching the specified criteria." -Level Debug - return $KQLQueryset - } - else { - Write-Message -Message "No KQLQueryset found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 b/FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 deleted file mode 100644 index 79e88196..00000000 --- a/FabricTools/tiago/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 +++ /dev/null @@ -1,120 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the definition of a KQLQueryset from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the KQLQueryset's content or metadata from a workspace. -Handles both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the KQLQueryset definition is to be retrieved. - -.PARAMETER KQLQuerysetId -(Optional)The unique identifier of the KQLQueryset whose definition needs to be retrieved. - -.PARAMETER KQLQuerysetFormat -Specifies the format of the KQLQueryset definition. - -.EXAMPLE -Get-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" - -Retrieves the definition of the KQLQueryset with ID `67890` from the workspace with ID `12345` in the `ipynb` format. - -.EXAMPLE -Get-FabricKQLQuerysetDefinition -WorkspaceId "12345" - -Retrieves the definitions of all KQLQuerysets in the workspace with ID `12345` in the `ipynb` format. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. - -#> -function Get-FabricKQLQuerysetDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetFormat - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId - - if ($KQLQuerysetFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLQuerysetFormat - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "KQLQueryset '$KQLQuerysetId' definition retrieved successfully!" -Level Debug - return $response.definition.parts - } - 202 { - - Write-Message -Message "Getting KQLQueryset '$KQLQuerysetId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/KQL Queryset/New-FabricKQLQueryset.ps1 b/FabricTools/tiago/Public/KQL Queryset/New-FabricKQLQueryset.ps1 deleted file mode 100644 index fea1665c..00000000 --- a/FabricTools/tiago/Public/KQL Queryset/New-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,188 +0,0 @@ -<# -.SYNOPSIS -Creates a new KQLQueryset in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLQueryset -in the specified workspace. It supports optional parameters for KQLQueryset description -and path definitions for the KQLQueryset content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the KQLQueryset will be created. - -.PARAMETER KQLQuerysetName -The name of the KQLQueryset to be created. - -.PARAMETER KQLQuerysetDescription -An optional description for the KQLQueryset. - -.PARAMETER KQLQuerysetPathDefinition -An optional path to the KQLQueryset definition file (e.g., .ipynb file) to upload. - -.PARAMETER KQLQuerysetPathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricKQLQueryset -WorkspaceId "workspace-12345" -KQLQuerysetName "New KQLQueryset" -KQLQuerysetPathDefinition "C:\KQLQuerysets\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricKQLQueryset { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLQuerysetName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $KQLQuerysetName - } - - if ($KQLQuerysetDescription) { - $body.description = $KQLQuerysetDescription - } - - if ($KQLQuerysetPathDefinition) { - $KQLQuerysetEncodedContent = Convert-ToBase64 -filePath $KQLQuerysetPathDefinition - - if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = $null - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "RealTimeQueryset.json" - payload = $KQLQuerysetEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLQueryset definition." -Level Error - return $null - } - } - - if ($KQLQuerysetPathPlatformDefinition) { - $KQLQuerysetEncodedPlatformContent = Convert-ToBase64 -filePath $KQLQuerysetPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = $null - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLQuerysetEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "KQLQueryset '$KQLQuerysetName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "KQLQueryset '$KQLQuerysetName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create KQLQueryset. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 b/FabricTools/tiago/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 deleted file mode 100644 index d9a48c5d..00000000 --- a/FabricTools/tiago/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS -Deletes an KQLQueryset from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricKQLQueryset` function sends a DELETE request to the Fabric API to remove a specified KQLQueryset from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the KQLQueryset to delete. - -.PARAMETER KQLQuerysetId -(Mandatory) The ID of the KQLQueryset to be deleted. - -.EXAMPLE -Remove-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetId "67890" - -Deletes the KQLQueryset with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricKQLQueryset { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "KQLQueryset '$KQLQuerysetId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete KQLQueryset '$KQLQuerysetId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 b/FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 deleted file mode 100644 index 4377448c..00000000 --- a/FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,107 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric KQLQueryset. - -.DESCRIPTION -The `Update-FabricKQLQueryset` function updates the name and/or description of a specified Fabric KQLQueryset by making a PATCH request to the API. - -.PARAMETER KQLQuerysetId -The unique identifier of the KQLQueryset to be updated. - -.PARAMETER KQLQuerysetName -The new name for the KQLQueryset. - -.PARAMETER KQLQuerysetDescription -(Optional) The new description for the KQLQueryset. - -.EXAMPLE -Update-FabricKQLQueryset -KQLQuerysetId "KQLQueryset123" -KQLQuerysetName "NewKQLQuerysetName" - -Updates the name of the KQLQueryset with the ID "KQLQueryset123" to "NewKQLQuerysetName". - -.EXAMPLE -Update-FabricKQLQueryset -KQLQuerysetId "KQLQueryset123" -KQLQuerysetName "NewName" -KQLQuerysetDescription "Updated description" - -Updates both the name and description of the KQLQueryset "KQLQueryset123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricKQLQueryset { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLQuerysetName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $KQLQuerysetName - } - - if ($KQLQuerysetDescription) { - $body.description = $KQLQuerysetDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "KQLQueryset '$KQLQuerysetName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 b/FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 deleted file mode 100644 index 6189f659..00000000 --- a/FabricTools/tiago/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 +++ /dev/null @@ -1,166 +0,0 @@ -<# -.SYNOPSIS -Updates the definition of a KQLQueryset in a Microsoft Fabric workspace. - -.DESCRIPTION -This function allows updating the content or metadata of a KQLQueryset in a Microsoft Fabric workspace. -The KQLQueryset content can be provided as file paths, and metadata updates can optionally be enabled. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace where the KQLQueryset resides. - -.PARAMETER KQLQuerysetId -(Mandatory) The unique identifier of the KQLQueryset to be updated. - -.PARAMETER KQLQuerysetPathDefinition -(Mandatory) The file path to the KQLQueryset content definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER KQLQuerysetPathPlatformDefinition -(Optional) The file path to the KQLQueryset's platform-specific definition file. The content will be encoded as Base64 and sent in the request. - - -.EXAMPLE -Update-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" -KQLQuerysetPathDefinition "C:\KQLQuerysets\KQLQueryset.ipynb" - -Updates the content of the KQLQueryset with ID `67890` in the workspace `12345` using the specified KQLQueryset file. - -.EXAMPLE -Update-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" -KQLQuerysetPathDefinition "C:\KQLQuerysets\KQLQueryset.ipynb" - -Updates both the content and metadata of the KQLQueryset with ID `67890` in the workspace `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- The KQLQueryset content is encoded as Base64 before being sent to the Fabric API. -- This function handles asynchronous operations and retrieves operation results if required. - -Author: Tiago Balabuch - -#> - -function Update-FabricKQLQuerysetDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId - - if($KQLQuerysetPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - format = $null - parts = @() - } - } - - if ($KQLQuerysetPathDefinition) { - $KQLQuerysetEncodedContent = Convert-ToBase64 -filePath $KQLQuerysetPathDefinition - - if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "RealTimeQueryset.json" - payload = $KQLQuerysetEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLQueryset definition." -Level Error - return $null - } - } - - if ($KQLQuerysetPathPlatformDefinition) { - $KQLQuerysetEncodedPlatformContent = Convert-ToBase64 -filePath $KQLQuerysetPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLQuerysetEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for KQLQueryset '$KQLQuerysetId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for KQLQueryset '$KQLQuerysetId' accepted. Operation in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - $operationResult = Get-FabricLongRunningOperation -operationId $operationId - - # Handle operation result - if ($operationResult.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - - $result = Get-FabricLongRunningOperationResult -operationId $operationId - return $result.definition.parts - } - else { - Write-Message -Message "Operation Failed" -Level Debug - return $operationResult.definition.parts - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouse.ps1 b/FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouse.ps1 deleted file mode 100644 index ecdeb17f..00000000 --- a/FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouse.ps1 +++ /dev/null @@ -1,154 +0,0 @@ -<# -.SYNOPSIS -Retrieves an Lakehouse or a list of Lakehouses from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricLakehouse` function sends a GET request to the Fabric API to retrieve Lakehouse details for a given workspace. It can filter the results by `LakehouseName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query Lakehouses. - -.PARAMETER LakehouseName -(Optional) The name of the specific Lakehouse to retrieve. - -.EXAMPLE -Get-FabricLakehouse -WorkspaceId "12345" -LakehouseName "Development" - -Retrieves the "Development" Lakehouse from workspace "12345". - -.EXAMPLE -Get-FabricLakehouse -WorkspaceId "12345" - -Retrieves all Lakehouses in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricLakehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$LakehouseName - ) - - try { - # Step 1: Handle ambiguous input - if ($LakehouseId -and $LakehouseName) { - Write-Message -Message "Both 'LakehouseId' and 'LakehouseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $lakehouses = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/lakehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $lakehouses += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $lakehouse = if ($LakehouseId) { - $lakehouses | Where-Object { $_.Id -eq $LakehouseId } - } - elseif ($LakehouseName) { - $lakehouses | Where-Object { $_.DisplayName -eq $LakehouseName } - } - else { - # Return all lakehouses if no filter is provided - Write-Message -Message "No filter provided. Returning all Lakehouses." -Level Debug - $lakehouses - } - - # Step 9: Handle results - if ($Lakehouse) { - Write-Message -Message "Lakehouse found matching the specified criteria." -Level Debug - return $Lakehouse - } - else { - Write-Message -Message "No Lakehouse found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouseTable.ps1 b/FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouseTable.ps1 deleted file mode 100644 index e35ce0dd..00000000 --- a/FabricTools/tiago/Public/Lakehouse/Get-FabricLakehouseTable.ps1 +++ /dev/null @@ -1,104 +0,0 @@ - - -function Get-FabricLakehouseTable { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - - - # Step 2: Initialize variables - $continuationToken = $null - $tables = @() - $maxResults = 100 - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - $baseApiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/tables?maxResults={3}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId, $maxResults - - # Step 3: Loop to retrieve data with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - do { - # Step 4: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}&continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 5: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "API response code: $statusCode" -Level Debug - # Step 6: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 7: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $tables += $response.data - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - # Step 9: Handle results - if ($tables) { - Write-Message -Message "Tables found in the Lakehouse '$LakehouseId'." -Level Debug - return $tables - } - else { - Write-Message -Message "No tables found matching in the Lakehouse '$LakehouseId'." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Lakehouse/Load-FabricLakehouseTable.ps1 b/FabricTools/tiago/Public/Lakehouse/Load-FabricLakehouseTable.ps1 deleted file mode 100644 index b1ca0822..00000000 --- a/FabricTools/tiago/Public/Lakehouse/Load-FabricLakehouseTable.ps1 +++ /dev/null @@ -1,139 +0,0 @@ -function Load-FabricLakehouseTable { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$TableName, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('File', 'Folder')] - [string]$PathType, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$RelativePath, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('CSV', 'Parquet')] - [string]$FileFormat, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CsvDelimiter = ",", - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$CsvHeader = $false, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('append', 'overwrite')] - [string]$Mode = "append", - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$Recursive = $false - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/tables/{3}/load" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId, $TableName - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - relativePath = $RelativePath - pathType = $PathType - mode = $Mode - recursive = $Recursive - formatOptions = @{ - format = $FileFormat - } - } - - if ($FileFormat -eq "CSV") { - $body.formatOptions.delimiter = $CsvDelimiter - $body.formatOptions.hasHeader = $CsvHeader - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 202) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 5: Handle and log the response - switch ($statusCode) { - 202 { - Write-Message -Message "Load table '$TableName' request accepted. Load table operation in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Load table '$TableName' operation complete successfully!" -Level Info - return $operationStatus - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - throw "API request failed with status code $statusCode." - } - } - - # Step 6: Handle results - - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Lakehouse/New-FabricLakehouse.ps1 b/FabricTools/tiago/Public/Lakehouse/New-FabricLakehouse.ps1 deleted file mode 100644 index f24c8feb..00000000 --- a/FabricTools/tiago/Public/Lakehouse/New-FabricLakehouse.ps1 +++ /dev/null @@ -1,136 +0,0 @@ -<# -.SYNOPSIS -Creates a new Lakehouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new Lakehouse -in the specified workspace. It supports optional parameters for Lakehouse description -and path definitions for the Lakehouse content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the Lakehouse will be created. - -.PARAMETER LakehouseName -The name of the Lakehouse to be created. - -.PARAMETER LakehouseDescription -An optional description for the Lakehouse. - -.PARAMETER LakehouseEnableSchemas -An optional path to enable schemas in the Lakehouse - -.EXAMPLE - Add-FabricLakehouse -WorkspaceId "workspace-12345" -LakehouseName "New Lakehouse" -LakehouseEnableSchemas $true - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricLakehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$LakehouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$LakehouseEnableSchemas = $false - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $LakehouseName - } - - if ($LakehouseDescription) { - $body.description = $LakehouseDescription - } - - if ($true -eq $LakehouseEnableSchemas) { - $body.creationPayload = @{ - enableSchemas = $LakehouseEnableSchemas - } - } - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Lakehouse '$LakehouseName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Lakehouse '$LakehouseName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Lakehouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Lakehouse/Remove-FabricLakehouse.ps1 b/FabricTools/tiago/Public/Lakehouse/Remove-FabricLakehouse.ps1 deleted file mode 100644 index 254bdde6..00000000 --- a/FabricTools/tiago/Public/Lakehouse/Remove-FabricLakehouse.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS -Deletes an Lakehouse from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricLakehouse` function sends a DELETE request to the Fabric API to remove a specified Lakehouse from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the Lakehouse to delete. - -.PARAMETER LakehouseId -(Mandatory) The ID of the Lakehouse to be deleted. - -.EXAMPLE -Remove-FabricLakehouse -WorkspaceId "12345" -LakehouseId "67890" - -Deletes the Lakehouse with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricLakehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Lakehouse '$LakehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Lakehouse '$LakehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 b/FabricTools/tiago/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 deleted file mode 100644 index c680beba..00000000 --- a/FabricTools/tiago/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 +++ /dev/null @@ -1,160 +0,0 @@ -function Start-FabricLakehouseTableMaintenance { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('TableMaintenance')] - [string]$JobType = "TableMaintenance", - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SchemaName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$TableName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$IsVOrder, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [array]$ColumnsZOrderBy, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern("^\d+:[0-1][0-9]|2[0-3]:[0-5][0-9]:[0-5][0-9]$")] - [string]$retentionPeriod, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$waitForCompletion = $false - - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - - $lakehouse = Get-FabricLakehouse -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId - if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and -not $SchemaName) { - Write-Error "The Lakehouse '$lakehouse.displayName' has schema enabled, but no schema name was provided. Please specify the 'SchemaName' parameter to proceed." - return - } - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/jobs/instances?jobType={3}" -f $FabricConfig.BaseUrl, $WorkspaceId , $LakehouseId, $JobType - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - executionData = @{ - tableName = $TableName - optimizeSettings = @{} - } - } - if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and $SchemaName) { - $body.executionData.schemaName = $SchemaName - } - - if ($IsVOrder) { - $body.executionData.optimizeSettings.vOrder = $IsVOrder - } - - if ($ColumnsZOrderBy) { - # Ensure $ColumnsZOrderBy is an array - if (-not ($ColumnsZOrderBy -is [array])) { - $ColumnsZOrderBy = $ColumnsZOrderBy -split "," - } - # Add it to the optimizeSettings in the request body - $body.executionData.optimizeSettings.zOrderBy = $ColumnsZOrderBy - } - - - - if ($retentionPeriod) { - - if (-not $body.executionData.PSObject.Properties['vacuumSettings']) { - $body.executionData.vacuumSettings = @{ - retentionPeriod = @() - } - } - $body.executionData.vacuumSettings.retentionPeriod = $retentionPeriod - - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Table maintenance job successfully initiated for Lakehouse '$lakehouse.displayName'." -Level Info - return $response - } - 202 { - Write-Message -Message "Table maintenance job accepted and is now running in the background. Job execution is in progress." -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - - if ($waitForCompletion -eq $true) { - Write-Message -Message "Getting Long Running Operation status" -Level Debug - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - return $operationStatus - } - else { - Write-Message -Message "The operation is running asynchronously." -Level Info - Write-Message -Message "Use the returned details to check the operation status." -Level Info - Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info - $operationDetails = [PSCustomObject]@{ - OperationId = $operationId - Location = $location - RetryAfter = $retryAfter - } - return $operationDetails - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to start table maintenance job. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Lakehouse/Update-FabricLakehouse.ps1 b/FabricTools/tiago/Public/Lakehouse/Update-FabricLakehouse.ps1 deleted file mode 100644 index c4b9879b..00000000 --- a/FabricTools/tiago/Public/Lakehouse/Update-FabricLakehouse.ps1 +++ /dev/null @@ -1,108 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric Lakehouse. - -.DESCRIPTION -The `Update-FabricLakehouse` function updates the name and/or description of a specified Fabric Lakehouse by making a PATCH request to the API. - -.PARAMETER LakehouseId -The unique identifier of the Lakehouse to be updated. - -.PARAMETER LakehouseName -The new name for the Lakehouse. - -.PARAMETER LakehouseDescription -(Optional) The new description for the Lakehouse. - -.EXAMPLE -Update-FabricLakehouse -LakehouseId "Lakehouse123" -LakehouseName "NewLakehouseName" - -Updates the name of the Lakehouse with the ID "Lakehouse123" to "NewLakehouseName". - -.EXAMPLE -Update-FabricLakehouse -LakehouseId "Lakehouse123" -LakehouseName "NewName" -LakehouseDescription "Updated description" - -Updates both the name and description of the Lakehouse "Lakehouse123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricLakehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$LakehouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $LakehouseName - } - - if ($LakehouseDescription) { - $body.description = $LakehouseDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Lakehouse '$LakehouseName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/ML Experiment/Get-FabricMLExperiment.ps1 b/FabricTools/tiago/Public/ML Experiment/Get-FabricMLExperiment.ps1 deleted file mode 100644 index 52e57f8e..00000000 --- a/FabricTools/tiago/Public/ML Experiment/Get-FabricMLExperiment.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves ML Experiment details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves ML Experiment details from a specified workspace using either the provided MLExperimentId or MLExperimentName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the ML Experiment exists. This parameter is mandatory. - -.PARAMETER MLExperimentId - The unique identifier of the ML Experiment to retrieve. This parameter is optional. - -.PARAMETER MLExperimentName - The name of the ML Experiment to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentId "experiment-67890" - This example retrieves the ML Experiment details for the experiment with ID "experiment-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentName "My ML Experiment" - This example retrieves the ML Experiment details for the experiment named "My ML Experiment" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricMLExperiment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MLExperimentId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$MLExperimentName - ) - - try { - # Step 1: Handle ambiguous input - if ($MLExperimentId -and $MLExperimentName) { - Write-Message -Message "Both 'MLExperimentId' and 'MLExperimentName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $MLExperiments = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/mlExperiments" -f $FabricConfig.BaseUrl, $WorkspaceId - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $MLExperiments += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $MLExperiment = if ($MLExperimentId) { - $MLExperiments | Where-Object { $_.Id -eq $MLExperimentId } - } - elseif ($MLExperimentName) { - $MLExperiments | Where-Object { $_.DisplayName -eq $MLExperimentName } - } - else { - # Return all MLExperiments if no filter is provided - Write-Message -Message "No filter provided. Returning all MLExperiments." -Level Debug - $MLExperiments - } - - # Step 9: Handle results - if ($MLExperiment) { - Write-Message -Message "ML Experiment found matching the specified criteria." -Level Debug - return $MLExperiment - } - else { - Write-Message -Message "No ML Experiment found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve ML Experiment. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/ML Experiment/New-FabricMLExperiment.ps1 b/FabricTools/tiago/Public/ML Experiment/New-FabricMLExperiment.ps1 deleted file mode 100644 index 0a4d28be..00000000 --- a/FabricTools/tiago/Public/ML Experiment/New-FabricMLExperiment.ps1 +++ /dev/null @@ -1,130 +0,0 @@ -<# -.SYNOPSIS - Creates a new ML Experiment in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new ML Experiment - in the specified workspace. It supports optional parameters for ML Experiment description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the ML Experiment will be created. This parameter is mandatory. - -.PARAMETER MLExperimentName - The name of the ML Experiment to be created. This parameter is mandatory. - -.PARAMETER MLExperimentDescription - An optional description for the ML Experiment. - -.EXAMPLE - New-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentName "New ML Experiment" -MLExperimentDescription "Description of the new ML Experiment" - This example creates a new ML Experiment named "New ML Experiment" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function New-FabricMLExperiment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$MLExperimentName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MLExperimentDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mlExperiments" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $MLExperimentName - } - - if ($MLExperimentDescription) { - $body.description = $MLExperimentDescription - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "ML Experiment '$MLExperimentName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "ML Experiment '$MLExperimentName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create ML Experiment. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/ML Experiment/Remove-FabricMLExperiment.ps1 b/FabricTools/tiago/Public/ML Experiment/Remove-FabricMLExperiment.ps1 deleted file mode 100644 index 7c8cbb20..00000000 --- a/FabricTools/tiago/Public/ML Experiment/Remove-FabricMLExperiment.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -<# -.SYNOPSIS - Removes an ML Experiment from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an ML Experiment - from the specified workspace using the provided WorkspaceId and MLExperimentId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the MLExperiment will be removed. - -.PARAMETER MLExperimentId - The unique identifier of the MLExperiment to be removed. - -.EXAMPLE - Remove-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentId "experiment-67890" - This example removes the MLExperiment with ID "experiment-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricMLExperiment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MLExperimentId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mlExperiments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLExperimentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "ML Experiment '$MLExperimentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete ML Experiment '$MLExperimentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/ML Experiment/Update-FabricMLExperiment.ps1 b/FabricTools/tiago/Public/ML Experiment/Update-FabricMLExperiment.ps1 deleted file mode 100644 index f9c0a9e5..00000000 --- a/FabricTools/tiago/Public/ML Experiment/Update-FabricMLExperiment.ps1 +++ /dev/null @@ -1,105 +0,0 @@ -<# -.SYNOPSIS - Updates an existing ML Experiment in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Experiment - in the specified workspace. It supports optional parameters for ML Experiment description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the ML Experiment exists. This parameter is optional. - -.PARAMETER MLExperimentId - The unique identifier of the ML Experiment to be updated. This parameter is mandatory. - -.PARAMETER MLExperimentName - The new name of the ML Experiment. This parameter is mandatory. - -.PARAMETER MLExperimentDescription - An optional new description for the ML Experiment. - -.EXAMPLE - Update-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentId "experiment-67890" -MLExperimentName "Updated ML Experiment" -MLExperimentDescription "Updated description" - This example updates the ML Experiment with ID "experiment-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricMLExperiment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MLExperimentId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$MLExperimentName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MLExperimentDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mlExperiments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLExperimentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $MLExperimentName - } - - if ($MLExperimentDescription) { - $body.description = $MLExperimentDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "ML Experiment '$MLExperimentName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update ML Experiment. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/ML Model/Get-FabricMLModel.ps1 b/FabricTools/tiago/Public/ML Model/Get-FabricMLModel.ps1 deleted file mode 100644 index 7d15c5e8..00000000 --- a/FabricTools/tiago/Public/ML Model/Get-FabricMLModel.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves ML Model details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves ML Model details from a specified workspace using either the provided MLModelId or MLModelName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the ML Model exists. This parameter is mandatory. - -.PARAMETER MLModelId - The unique identifier of the ML Model to retrieve. This parameter is optional. - -.PARAMETER MLModelName - The name of the ML Model to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricMLModel -WorkspaceId "workspace-12345" -MLModelId "model-67890" - This example retrieves the ML Model details for the model with ID "model-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricMLModel -WorkspaceId "workspace-12345" -MLModelName "My ML Model" - This example retrieves the ML Model details for the model named "My ML Model" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricMLModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MLModelId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$MLModelName - ) - - try { - # Step 1: Handle ambiguous input - if ($MLModelId -and $MLModelName) { - Write-Message -Message "Both 'MLModelId' and 'MLModelName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $MLModels = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/mlModels" -f $FabricConfig.BaseUrl, $WorkspaceId - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $MLModels += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $MLModel = if ($MLModelId) { - $MLModels | Where-Object { $_.Id -eq $MLModelId } - } - elseif ($MLModelName) { - $MLModels | Where-Object { $_.DisplayName -eq $MLModelName } - } - else { - # Return all MLModels if no filter is provided - Write-Message -Message "No filter provided. Returning all MLModels." -Level Debug - $MLModels - } - - # Step 9: Handle results - if ($MLModel) { - Write-Message -Message "ML Model found matching the specified criteria." -Level Debug - return $MLModel - } - else { - Write-Message -Message "No ML Model found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve ML Model. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/ML Model/New-FabricMLModel.ps1 b/FabricTools/tiago/Public/ML Model/New-FabricMLModel.ps1 deleted file mode 100644 index cf4ffac0..00000000 --- a/FabricTools/tiago/Public/ML Model/New-FabricMLModel.ps1 +++ /dev/null @@ -1,130 +0,0 @@ -<# -.SYNOPSIS - Creates a new ML Model in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new ML Model - in the specified workspace. It supports optional parameters for ML Model description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the ML Model will be created. This parameter is mandatory. - -.PARAMETER MLModelName - The name of the ML Model to be created. This parameter is mandatory. - -.PARAMETER MLModelDescription - An optional description for the ML Model. - -.EXAMPLE - New-FabricMLModel -WorkspaceId "workspace-12345" -MLModelName "New ML Model" -MLModelDescription "Description of the new ML Model" - This example creates a new ML Model named "New ML Model" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function New-FabricMLModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$MLModelName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MLModelDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mlModels" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $MLModelName - } - - if ($MLModelDescription) { - $body.description = $MLModelDescription - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "ML Model '$MLModelName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "ML Model '$MLModelName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create ML Model. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/ML Model/Remove-FabricMLModel.ps1 b/FabricTools/tiago/Public/ML Model/Remove-FabricMLModel.ps1 deleted file mode 100644 index f1491d73..00000000 --- a/FabricTools/tiago/Public/ML Model/Remove-FabricMLModel.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -<# -.SYNOPSIS - Removes an ML Model from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an ML Model - from the specified workspace using the provided WorkspaceId and MLModelId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the ML Model will be removed. - -.PARAMETER MLModelId - The unique identifier of the ML Model to be removed. - -.EXAMPLE - Remove-FabricMLModel -WorkspaceId "workspace-12345" -MLModelId "model-67890" - This example removes the ML Model with ID "model-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricMLModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MLModelId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mlModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLModelId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "ML Model '$MLModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete ML Model '$MLModelId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/ML Model/Update-FabricMLModel.ps1 b/FabricTools/tiago/Public/ML Model/Update-FabricMLModel.ps1 deleted file mode 100644 index 50e2d6a0..00000000 --- a/FabricTools/tiago/Public/ML Model/Update-FabricMLModel.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -<# -.SYNOPSIS - Updates an existing ML Model in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Model - in the specified workspace. It supports optional parameters for ML Model description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the ML Model exists. This parameter is optional. - -.PARAMETER MLModelId - The unique identifier of the ML Model to be updated. This parameter is mandatory. - -.PARAMETER MLModelDescription - New description for the ML Model. - -.EXAMPLE - Update-FabricMLModel -WorkspaceId "workspace-12345" -MLModelId "model-67890" -MLModelName "Updated ML Model" -MLModelDescription "Updated description" - This example updates the ML Model with ID "model-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricMLModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MLModelId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MLModelDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mlModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLModelId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - description = $MLModelDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "ML Model '$MLModelId' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update ML Model. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 b/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 deleted file mode 100644 index e5d93177..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 +++ /dev/null @@ -1,157 +0,0 @@ -<# -.SYNOPSIS -Retrieves an MirroredDatabase or a list of MirroredDatabases from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricMirroredDatabase` function sends a GET request to the Fabric API to retrieve MirroredDatabase details for a given workspace. It can filter the results by `MirroredDatabaseName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query MirroredDatabases. - -.PARAMETER MirroredDatabaseName -(Optional) The name of the specific MirroredDatabase to retrieve. - -.EXAMPLE -Get-FabricMirroredDatabase -WorkspaceId "12345" -MirroredDatabaseName "Development" - -Retrieves the "Development" MirroredDatabase from workspace "12345". - -.EXAMPLE -Get-FabricMirroredDatabase -WorkspaceId "12345" - -Retrieves all MirroredDatabases in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricMirroredDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$MirroredDatabaseName - ) - - try { - # Step 1: Handle ambiguous input - if ($MirroredDatabaseId -and $MirroredDatabaseName) { - Write-Message -Message "Both 'MirroredDatabaseId' and 'MirroredDatabaseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $continuationToken = $null - $MirroredDatabases = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Step 3: Loop to retrieve data with continuation token - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $MirroredDatabases += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - - # Step 8: Filter results based on provided parameters - $MirroredDatabase = if ($MirroredDatabaseId) { - $MirroredDatabases | Where-Object { $_.Id -eq $MirroredDatabaseId } - } - elseif ($MirroredDatabaseName) { - $MirroredDatabases | Where-Object { $_.DisplayName -eq $MirroredDatabaseName } - } - else { - # Return all MirroredDatabases if no filter is provided - Write-Message -Message "No filter provided. Returning all MirroredDatabases." -Level Debug - $MirroredDatabases - } - - # Step 9: Handle results - if ($MirroredDatabase) { - Write-Message -Message "MirroredDatabase found matching the specified criteria." -Level Debug - return $MirroredDatabase - } - else { - Write-Message -Message "No MirroredDatabase found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 b/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 deleted file mode 100644 index 2a7700cf..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 +++ /dev/null @@ -1,109 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the definition of a MirroredDatabase from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the MirroredDatabase's content or metadata from a workspace. -Handles both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the MirroredDatabase definition is to be retrieved. - -.PARAMETER MirroredDatabaseId -(Optional)The unique identifier of the MirroredDatabase whose definition needs to be retrieved. - -.EXAMPLE -Get-FabricMirroredDatabaseDefinition -WorkspaceId "12345" -MirroredDatabaseId "67890" - -Retrieves the definition of the MirroredDatabase with ID `67890` from the workspace with ID `12345`. - -.EXAMPLE -Get-FabricMirroredDatabaseDefinition -WorkspaceId "12345" - -Retrieves the definitions of all MirroredDatabases in the workspace with ID `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. - -#> -function Get-FabricMirroredDatabaseDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "MirroredDatabase '$MirroredDatabaseId' definition retrieved successfully!" -Level Debug - return $response.definition.parts - } - 202 { - - Write-Message -Message "Getting MirroredDatabase '$MirroredDatabaseId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 b/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 deleted file mode 100644 index 200788c3..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 +++ /dev/null @@ -1,52 +0,0 @@ -function Get-FabricMirroredDatabaseStatus { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getMirroringStatus" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 9: Handle results - - Write-Message -Message "Returning status of MirroredDatabases." -Level Debug - return $response - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 b/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 deleted file mode 100644 index f37c4cfe..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 +++ /dev/null @@ -1,97 +0,0 @@ -function Get-FabricMirroredDatabaseTableStatus { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId - ) - - try { - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $continuationToken = $null - $MirroredDatabaseTableStatus = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getTablesMirroringStatus" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - - # Step 3: Loop to retrieve data with continuation token - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $MirroredDatabaseTableStatus += $response.data - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 9: Handle results - # Return all Mirrored Database Table Status - Write-Message -Message "No filter provided. Returning all MirroredDatabases." -Level Debug - $MirroredDatabaseTableStatus - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 b/FabricTools/tiago/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 deleted file mode 100644 index 70b4710d..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 +++ /dev/null @@ -1,186 +0,0 @@ -<# -.SYNOPSIS -Creates a new MirroredDatabase in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new MirroredDatabase -in the specified workspace. It supports optional parameters for MirroredDatabase description -and path definitions for the MirroredDatabase content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the MirroredDatabase will be created. - -.PARAMETER MirroredDatabaseName -The name of the MirroredDatabase to be created. - -.PARAMETER MirroredDatabaseDescription -An optional description for the MirroredDatabase. - -.PARAMETER MirroredDatabasePathDefinition -An optional path to the MirroredDatabase definition file to upload. - -.PARAMETER MirroredDatabasePathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricMirroredDatabase -WorkspaceId "workspace-12345" -MirroredDatabaseName "New MirroredDatabase" -MirroredDatabasePathDefinition "C:\MirroredDatabases\example.json" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricMirroredDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$MirroredDatabaseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabasePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabasePathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $MirroredDatabaseName - } - - if ($MirroredDatabaseDescription) { - $body.description = $MirroredDatabaseDescription - } - - if ($MirroredDatabasePathDefinition) { - $MirroredDatabaseEncodedContent = Convert-ToBase64 -filePath $MirroredDatabasePathDefinition - - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "mirroredDatabase.json" - payload = $MirroredDatabaseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error - return $null - } - } - - if ($MirroredDatabasePathPlatformDefinition) { - $MirroredDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $MirroredDatabasePathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "MirroredDatabase" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $MirroredDatabaseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation Failed" -Level Debug - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create MirroredDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 b/FabricTools/tiago/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 deleted file mode 100644 index 701e513d..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -<# -.SYNOPSIS -Deletes an MirroredDatabase from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricMirroredDatabase` function sends a DELETE request to the Fabric API to remove a specified MirroredDatabase from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the MirroredDatabase to delete. - -.PARAMETER MirroredDatabaseId -(Mandatory) The ID of the MirroredDatabase to be deleted. - -.EXAMPLE -Remove-FabricMirroredDatabase -WorkspaceId "12345" -MirroredDatabaseId "67890" - -Deletes the MirroredDatabase with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch -#> - -function Remove-FabricMirroredDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "MirroredDatabase '$MirroredDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete MirroredDatabase '$MirroredDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 b/FabricTools/tiago/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 deleted file mode 100644 index 19f3bce3..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -function Start-FabricMirroredDatabaseMirroring{ - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/startMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 9: Handle results - Write-Message -Message "Database mirroring started successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info - return - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to start MirroredDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 b/FabricTools/tiago/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 deleted file mode 100644 index 77d7404e..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 +++ /dev/null @@ -1,53 +0,0 @@ - - -function Stop-FabricMirroredDatabaseMirroring{ - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/stopMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 9: Handle results - Write-Message -Message "Database mirroring stopped successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info - return - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to stop MirroredDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 b/FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 deleted file mode 100644 index 09691385..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 +++ /dev/null @@ -1,107 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric MirroredDatabase. - -.DESCRIPTION -The `Update-FabricMirroredDatabase` function updates the name and/or description of a specified Fabric MirroredDatabase by making a PATCH request to the API. - -.PARAMETER MirroredDatabaseId -The unique identifier of the MirroredDatabase to be updated. - -.PARAMETER MirroredDatabaseName -The new name for the MirroredDatabase. - -.PARAMETER MirroredDatabaseDescription -(Optional) The new description for the MirroredDatabase. - -.EXAMPLE -Update-FabricMirroredDatabase -MirroredDatabaseId "MirroredDatabase123" -MirroredDatabaseName "NewMirroredDatabaseName" - -Updates the name of the MirroredDatabase with the ID "MirroredDatabase123" to "NewMirroredDatabaseName". - -.EXAMPLE -Update-FabricMirroredDatabase -MirroredDatabaseId "MirroredDatabase123" -MirroredDatabaseName "NewName" -MirroredDatabaseDescription "Updated description" - -Updates both the name and description of the MirroredDatabase "MirroredDatabase123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricMirroredDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$MirroredDatabaseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $MirroredDatabaseName - } - - if ($MirroredDatabaseDescription) { - $body.description = $MirroredDatabaseDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 b/FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 deleted file mode 100644 index 5fc7fb71..00000000 --- a/FabricTools/tiago/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 +++ /dev/null @@ -1,168 +0,0 @@ -<# -.SYNOPSIS -Updates the definition of a MirroredDatabase in a Microsoft Fabric workspace. - -.DESCRIPTION -This function allows updating the content or metadata of a MirroredDatabase in a Microsoft Fabric workspace. -The MirroredDatabase content can be provided as file paths, and metadata updates can optionally be enabled. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace where the MirroredDatabase resides. - -.PARAMETER MirroredDatabaseId -(Mandatory) The unique identifier of the MirroredDatabase to be updated. - -.PARAMETER MirroredDatabasePathDefinition -(Mandatory) The file path to the MirroredDatabase content definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER MirroredDatabasePathPlatformDefinition -(Optional) The file path to the MirroredDatabase's platform-specific definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER UpdateMetadata -(Optional)A boolean flag indicating whether to update the MirroredDatabase's metadata. -Default: `$false`. - -.EXAMPLE -Update-FabricMirroredDatabaseDefinition -WorkspaceId "12345" -MirroredDatabaseId "67890" -MirroredDatabasePathDefinition "C:\MirroredDatabases\MirroredDatabase.json" - -Updates the content of the MirroredDatabase with ID `67890` in the workspace `12345` using the specified MirroredDatabase file. - -.EXAMPLE -Update-FabricMirroredDatabaseDefinition -WorkspaceId "12345" -MirroredDatabaseId "67890" -MirroredDatabasePathDefinition "C:\MirroredDatabases\MirroredDatabase.json" -UpdateMetadata $true - -Updates both the content and metadata of the MirroredDatabase with ID `67890` in the workspace `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- The MirroredDatabase content is encoded as Base64 before being sent to the Fabric API. -- This function handles asynchronous operations and retrieves operation results if required. - -Author: Tiago Balabuch - -#> - -function Update-FabricMirroredDatabaseDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabaseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabasePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredDatabasePathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - - if($MirroredDatabasePathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - if ($MirroredDatabasePathDefinition) { - $MirroredDatabaseEncodedContent = Convert-ToBase64 -filePath $MirroredDatabasePathDefinition - - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "MirroredDatabase.json" - payload = $MirroredDatabaseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error - return $null - } - } - - if ($MirroredDatabasePathPlatformDefinition) { - $MirroredDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $MirroredDatabasePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $MirroredDatabaseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for MirroredDatabase '$MirroredDatabaseId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for MirroredDatabase '$MirroredDatabaseId' accepted. Operation in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - $operationResult = Get-FabricLongRunningOperation -operationId $operationId - - # Handle operation result - if ($operationResult.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - - $result = Get-FabricLongRunningOperationResult -operationId $operationId - return $result.definition.parts - } - else { - Write-Message -Message "Operation Failed" -Level Debug - return $operationResult.definition.parts - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 b/FabricTools/tiago/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 deleted file mode 100644 index a5775d3f..00000000 --- a/FabricTools/tiago/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 +++ /dev/null @@ -1,157 +0,0 @@ -<# -.SYNOPSIS -Retrieves an MirroredWarehouse or a list of MirroredWarehouses from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricMirroredWarehouse` function sends a GET request to the Fabric API to retrieve MirroredWarehouse details for a given workspace. It can filter the results by `MirroredWarehouseName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query MirroredWarehouses. - -.PARAMETER MirroredWarehouseName -(Optional) The name of the specific MirroredWarehouse to retrieve. - -.EXAMPLE -Get-FabricMirroredWarehouse -WorkspaceId "12345" -MirroredWarehouseName "Development" - -Retrieves the "Development" MirroredWarehouse from workspace "12345". - -.EXAMPLE -Get-FabricMirroredWarehouse -WorkspaceId "12345" - -Retrieves all MirroredWarehouses in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricMirroredWarehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$MirroredWarehouseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$MirroredWarehouseName - ) - - try { - # Step 1: Handle ambiguous input - if ($MirroredWarehouseId -and $MirroredWarehouseName) { - Write-Message -Message "Both 'MirroredWarehouseId' and 'MirroredWarehouseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $continuationToken = $null - $MirroredWarehouses = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/MirroredWarehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Step 3: Loop to retrieve data with continuation token - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $MirroredWarehouses += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - - # Step 8: Filter results based on provided parameters - $MirroredWarehouse = if ($MirroredWarehouseId) { - $MirroredWarehouses | Where-Object { $_.Id -eq $MirroredWarehouseId } - } - elseif ($MirroredWarehouseName) { - $MirroredWarehouses | Where-Object { $_.DisplayName -eq $MirroredWarehouseName } - } - else { - # Return all MirroredWarehouses if no filter is provided - Write-Message -Message "No filter provided. Returning all MirroredWarehouses." -Level Debug - $MirroredWarehouses - } - - # Step 9: Handle results - if ($MirroredWarehouse) { - Write-Message -Message "MirroredWarehouse found matching the specified criteria." -Level Debug - return $MirroredWarehouse - } - else { - Write-Message -Message "No MirroredWarehouse found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve MirroredWarehouse. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Notebook/Get-FabricNotebook.ps1 b/FabricTools/tiago/Public/Notebook/Get-FabricNotebook.ps1 deleted file mode 100644 index bd759f9a..00000000 --- a/FabricTools/tiago/Public/Notebook/Get-FabricNotebook.ps1 +++ /dev/null @@ -1,155 +0,0 @@ -<# -.SYNOPSIS -Retrieves an Notebook or a list of Notebooks from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricNotebook` function sends a GET request to the Fabric API to retrieve Notebook details for a given workspace. It can filter the results by `NotebookName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query Notebooks. - -.PARAMETER NotebookName -(Optional) The name of the specific Notebook to retrieve. - -.EXAMPLE -Get-FabricNotebook -WorkspaceId "12345" -NotebookName "Development" - -Retrieves the "Development" Notebook from workspace "12345". - -.EXAMPLE -Get-FabricNotebook -WorkspaceId "12345" - -Retrieves all Notebooks in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricNotebook { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$NotebookName - ) - - try { - # Step 1: Handle ambiguous input - if ($NotebookId -and $NotebookName) { - Write-Message -Message "Both 'NotebookId' and 'NotebookName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $notebooks = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/notebooks" -f $FabricConfig.BaseUrl, $WorkspaceId - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $notebooks += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $notebook = if ($NotebookId) { - $notebooks | Where-Object { $_.Id -eq $NotebookId } - } - elseif ($NotebookName) { - $notebooks | Where-Object { $_.DisplayName -eq $NotebookName } - } - else { - # Return all notebooks if no filter is provided - Write-Message -Message "No filter provided. Returning all Notebooks." -Level Debug - $notebooks - } - - # Step 9: Handle results - if ($notebook) { - Write-Message -Message "Notebook found matching the specified criteria." -Level Debug - return $notebook - } - else { - Write-Message -Message "No notebook found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Notebook. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Notebook/Get-FabricNotebookDefinition.ps1 b/FabricTools/tiago/Public/Notebook/Get-FabricNotebookDefinition.ps1 deleted file mode 100644 index 009eb88d..00000000 --- a/FabricTools/tiago/Public/Notebook/Get-FabricNotebookDefinition.ps1 +++ /dev/null @@ -1,131 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the definition of a notebook from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the notebook's content or metadata from a workspace. -It supports retrieving notebook definitions in the Jupyter Notebook (`ipynb`) format. -Handles both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the notebook definition is to be retrieved. - -.PARAMETER NotebookId -(Optional)The unique identifier of the notebook whose definition needs to be retrieved. - -.PARAMETER NotebookFormat -Specifies the format of the notebook definition. Currently, only 'ipynb' is supported. -Default: 'ipynb'. - -.EXAMPLE -Get-FabricNotebookDefinition -WorkspaceId "12345" -NotebookId "67890" - -Retrieves the definition of the notebook with ID `67890` from the workspace with ID `12345` in the `ipynb` format. - -.EXAMPLE -Get-FabricNotebookDefinition -WorkspaceId "12345" - -Retrieves the definitions of all notebooks in the workspace with ID `12345` in the `ipynb` format. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. - -#> -function Get-FabricNotebookDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('ipynb')] - [string]$NotebookFormat = 'ipynb' - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId - - if ($NotebookFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $NotebookFormat - } - - - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "Notebook '$NotebookId' definition retrieved successfully!" -Level Debug - return $response - } - 202 { - - Write-Message -Message "Getting notebook '$NotebookId' definition request accepted. Retrieving in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - #[string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Notebook. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Notebook/New-FabricNotebook.ps1 b/FabricTools/tiago/Public/Notebook/New-FabricNotebook.ps1 deleted file mode 100644 index eec94a4e..00000000 --- a/FabricTools/tiago/Public/Notebook/New-FabricNotebook.ps1 +++ /dev/null @@ -1,193 +0,0 @@ -<# -.SYNOPSIS -Creates a new notebook in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new notebook -in the specified workspace. It supports optional parameters for notebook description -and path definitions for the notebook content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the notebook will be created. - -.PARAMETER NotebookName -The name of the notebook to be created. - -.PARAMETER NotebookDescription -An optional description for the notebook. - -.PARAMETER NotebookPathDefinition -An optional path to the notebook definition file (e.g., .ipynb file) to upload. - -.PARAMETER NotebookPathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricNotebook -WorkspaceId "workspace-12345" -NotebookName "New Notebook" -NotebookPathDefinition "C:\notebooks\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricNotebook { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$NotebookName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/notebooks" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $NotebookName - } - - if ($NotebookDescription) { - $body.description = $NotebookDescription - } - - if ($NotebookPathDefinition) { - $notebookEncodedContent = Convert-ToBase64 -filePath $NotebookPathDefinition - - if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "ipynb" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "notebook-content.py" - payload = $notebookEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in notebook definition." -Level Error - return $null - } - } - - if ($NotebookPathPlatformDefinition) { - $notebookEncodedPlatformContent = Convert-ToBase64 -filePath $NotebookPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "ipynb" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $notebookEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Notebook '$NotebookName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Notebook/New-FabricNotebookNEW.ps1 b/FabricTools/tiago/Public/Notebook/New-FabricNotebookNEW.ps1 deleted file mode 100644 index c29b71f7..00000000 --- a/FabricTools/tiago/Public/Notebook/New-FabricNotebookNEW.ps1 +++ /dev/null @@ -1,158 +0,0 @@ -<# -.SYNOPSIS -Creates a new notebook in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new notebook -in the specified workspace. It supports optional parameters for notebook description -and path definitions for the notebook content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the notebook will be created. - -.PARAMETER NotebookName -The name of the notebook to be created. - -.PARAMETER NotebookDescription -An optional description for the notebook. - -.PARAMETER NotebookPathDefinition -An optional path to the notebook definition file (e.g., .ipynb file) to upload. - -.PARAMETER NotebookPathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricNotebook -WorkspaceId "workspace-12345" -NotebookName "New Notebook" -NotebookPathDefinition "C:\notebooks\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricNotebookNEW { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$NotebookName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookPathDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/notebooks" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $NotebookName - } - - if ($NotebookDescription) { - $body.description = $NotebookDescription - } - - if ($NotebookPathDefinition) { - if (-not $body.definition) { - $body.definition = @{ - format = "ipynb" - parts = @() - } - } - $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $NotebookPathDefinition - # Add new part to the parts array - $body.definition.parts = $jsonObjectParts.parts - } - # Check if any path is .platform - foreach ($part in $jsonObjectParts.parts) { - if ($part.path -eq ".platform") { - $hasPlatformFile = $true - Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Notebook '$NotebookName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Notebook/Remove-FabricNotebook.ps1 b/FabricTools/tiago/Public/Notebook/Remove-FabricNotebook.ps1 deleted file mode 100644 index ec22fad3..00000000 --- a/FabricTools/tiago/Public/Notebook/Remove-FabricNotebook.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS -Deletes an Notebook from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricNotebook` function sends a DELETE request to the Fabric API to remove a specified Notebook from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the Notebook to delete. - -.PARAMETER NotebookId -(Mandatory) The ID of the Notebook to be deleted. - -.EXAMPLE -Remove-FabricNotebook -WorkspaceId "12345" -NotebookId "67890" - -Deletes the Notebook with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricNotebook { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$NotebookId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Notebook '$NotebookId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete notebook '$NotebookId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Notebook/Update-FabricNotebook.ps1 b/FabricTools/tiago/Public/Notebook/Update-FabricNotebook.ps1 deleted file mode 100644 index c5cc8203..00000000 --- a/FabricTools/tiago/Public/Notebook/Update-FabricNotebook.ps1 +++ /dev/null @@ -1,107 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric Notebook. - -.DESCRIPTION -The `Update-FabricNotebook` function updates the name and/or description of a specified Fabric Notebook by making a PATCH request to the API. - -.PARAMETER NotebookId -The unique identifier of the Notebook to be updated. - -.PARAMETER NotebookName -The new name for the Notebook. - -.PARAMETER NotebookDescription -(Optional) The new description for the Notebook. - -.EXAMPLE -Update-FabricNotebook -NotebookId "Notebook123" -NotebookName "NewNotebookName" - -Updates the name of the Notebook with the ID "Notebook123" to "NewNotebookName". - -.EXAMPLE -Update-FabricNotebook -NotebookId "Notebook123" -NotebookName "NewName" -NotebookDescription "Updated description" - -Updates both the name and description of the Notebook "Notebook123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricNotebook { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$NotebookId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$NotebookName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $NotebookName - } - - if ($NotebookDescription) { - $body.description = $NotebookDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Notebook '$NotebookName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Notebook/Update-FabricNotebookDefinition.ps1 b/FabricTools/tiago/Public/Notebook/Update-FabricNotebookDefinition.ps1 deleted file mode 100644 index e6272644..00000000 --- a/FabricTools/tiago/Public/Notebook/Update-FabricNotebookDefinition.ps1 +++ /dev/null @@ -1,179 +0,0 @@ -<# -.SYNOPSIS -Updates the definition of a notebook in a Microsoft Fabric workspace. - -.DESCRIPTION -This function allows updating the content or metadata of a notebook in a Microsoft Fabric workspace. -The notebook content can be provided as file paths, and metadata updates can optionally be enabled. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace where the notebook resides. - -.PARAMETER NotebookId -(Mandatory) The unique identifier of the notebook to be updated. - -.PARAMETER NotebookPathDefinition -(Mandatory) The file path to the notebook content definition file. The content will be encoded as Base64 and sent in the request. - -.PARAMETER NotebookPathPlatformDefinition -(Optional) The file path to the notebook's platform-specific definition file. The content will be encoded as Base64 and sent in the request. - -.EXAMPLE -Update-FabricNotebookDefinition -WorkspaceId "12345" -NotebookId "67890" -NotebookPathDefinition "C:\Notebooks\Notebook.ipynb" - -Updates the content of the notebook with ID `67890` in the workspace `12345` using the specified notebook file. - -.EXAMPLE -Update-FabricNotebookDefinition -WorkspaceId "12345" -NotebookId "67890" -NotebookPathDefinition "C:\Notebooks\Notebook.ipynb" -NotebookPathPlatformDefinition "C:\Notebooks\.platform" - -Updates both the content and metadata of the notebook with ID `67890` in the workspace `12345`. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- The notebook content is encoded as Base64 before being sent to the Fabric API. -- This function handles asynchronous operations and retrieves operation results if required. - -Author: Tiago Balabuch - -#> - -function Update-FabricNotebookDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$NotebookId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$NotebookPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$NotebookPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId - - if ($NotebookPathPlatformDefinition) { - $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - format = "ipynb" - parts = @() - } - } - - if ($NotebookPathDefinition) { - $notebookEncodedContent = Convert-ToBase64 -filePath $NotebookPathDefinition - - if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "notebook-content.py" - payload = $notebookEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in notebook definition." -Level Error - return $null - } - } - - if ($NotebookPathPlatformDefinition) { - $notebookEncodedPlatformContent = Convert-ToBase64 -filePath $NotebookPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $notebookEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for notebook '$NotebookId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for notebook '$NotebookId' accepted. Operation in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 b/FabricTools/tiago/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 deleted file mode 100644 index fc52257d..00000000 --- a/FabricTools/tiago/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 +++ /dev/null @@ -1,155 +0,0 @@ -<# -.SYNOPSIS - Retrieves paginated report details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves paginated report details from a specified workspace using either the provided PaginatedReportId or PaginatedReportName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the paginated reports exist. This parameter is mandatory. - -.PARAMETER PaginatedReportId - The unique identifier of the paginated report to retrieve. This parameter is optional. - -.PARAMETER PaginatedReportName - The name of the paginated report to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricPaginatedReports -WorkspaceId "workspace-12345" -PaginatedReportId "report-67890" - This example retrieves the paginated report details for the report with ID "report-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricPaginatedReports -WorkspaceId "workspace-12345" -PaginatedReportName "My Paginated Report" - This example retrieves the paginated report details for the report named "My Paginated Report" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricPaginatedReport { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$PaginatedReportId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$PaginatedReportName - ) - - try { - # Step 1: Handle ambiguous input - if ($PaginatedReportId -and $PaginatedReportName) { - Write-Message -Message "Both 'PaginatedReportId' and 'PaginatedReportName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $PaginatedReports = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/paginatedReports" -f $FabricConfig.BaseUrl, $WorkspaceId - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $PaginatedReports += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $PaginatedReport = if ($PaginatedReportId) { - $PaginatedReports | Where-Object { $_.Id -eq $PaginatedReportId } - } - elseif ($PaginatedReportName) { - $PaginatedReports | Where-Object { $_.DisplayName -eq $PaginatedReportName } - } - else { - # Return all PaginatedReports if no filter is provided - Write-Message -Message "No filter provided. Returning all Paginated Reports." -Level Debug - $PaginatedReports - } - - # Step 9: Handle results - if ($PaginatedReport) { - Write-Message -Message "Paginated Report found matching the specified criteria." -Level Debug - return $PaginatedReport - } - else { - Write-Message -Message "No Paginated Report found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Paginated Report. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 b/FabricTools/tiago/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 deleted file mode 100644 index 00d0c59d..00000000 --- a/FabricTools/tiago/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 +++ /dev/null @@ -1,105 +0,0 @@ -<# -.SYNOPSIS - Updates an existing paginated report in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing paginated report - in the specified workspace. It supports optional parameters for paginated report description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the paginated report exists. This parameter is optional. - -.PARAMETER PaginatedReportId - The unique identifier of the paginated report to be updated. This parameter is mandatory. - -.PARAMETER PaginatedReportName - The new name of the paginated report. This parameter is mandatory. - -.PARAMETER PaginatedReportDescription - An optional new description for the paginated report. - -.EXAMPLE - Update-FabricPaginatedReport -WorkspaceId "workspace-12345" -PaginatedReportId "report-67890" -PaginatedReportName "Updated Paginated Report" -PaginatedReportDescription "Updated description" - This example updates the paginated report with ID "report-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricPaginatedReport { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$PaginatedReportId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$PaginatedReportName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$PaginatedReportDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/paginatedReports/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $PaginatedReportId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $PaginatedReportName - } - - if ($PaginatedReportDescription) { - $body.description = $PaginatedReportDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Paginated Report '$PaginatedReportName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Paginated Report. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Reflex/Get-FabricReflex.ps1 b/FabricTools/tiago/Public/Reflex/Get-FabricReflex.ps1 deleted file mode 100644 index 9e274d3c..00000000 --- a/FabricTools/tiago/Public/Reflex/Get-FabricReflex.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves Reflex details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves Reflex details from a specified workspace using either the provided ReflexId or ReflexName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Reflex exists. This parameter is mandatory. - -.PARAMETER ReflexId - The unique identifier of the Reflex to retrieve. This parameter is optional. - -.PARAMETER ReflexName - The name of the Reflex to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricReflex -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" - This example retrieves the Reflex details for the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricReflex -WorkspaceId "workspace-12345" -ReflexName "My Reflex" - This example retrieves the Reflex details for the Reflex named "My Reflex" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricReflex { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$ReflexName - ) - try { - - # Step 1: Handle ambiguous input - if ($ReflexId -and $ReflexName) { - Write-Message -Message "Both 'ReflexId' and 'ReflexName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $Reflexes = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/reflexes" -f $FabricConfig.BaseUrl, $WorkspaceId - # Step 3: Loop to retrieve data with continuation token - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $Reflexes += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $Reflex = if ($ReflexId) { - $Reflexes | Where-Object { $_.Id -eq $ReflexId } - } - elseif ($ReflexName) { - $Reflexes | Where-Object { $_.DisplayName -eq $ReflexName } - } - else { - # Return all Reflexes if no filter is provided - Write-Message -Message "No filter provided. Returning all Reflexes." -Level Debug - $Reflexes - } - - # Step 9: Handle results - if ($Reflex) { - Write-Message -Message "Reflex found in the Workspace '$WorkspaceId'." -Level Debug - return $Reflex - } - else { - Write-Message -Message "No Reflex found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Reflex. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Reflex/Get-FabricReflexDefinition.ps1 b/FabricTools/tiago/Public/Reflex/Get-FabricReflexDefinition.ps1 deleted file mode 100644 index 121677e4..00000000 --- a/FabricTools/tiago/Public/Reflex/Get-FabricReflexDefinition.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -<# -.SYNOPSIS - Retrieves the definition of an Reflex from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves the definition of an Reflex from a specified workspace using the provided ReflexId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Reflex exists. This parameter is mandatory. - -.PARAMETER ReflexId - The unique identifier of the Reflex to retrieve the definition for. This parameter is optional. - -.PARAMETER ReflexFormat - The format in which to retrieve the Reflex definition. This parameter is optional. - -.EXAMPLE - Get-FabricReflexDefinition -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" - This example retrieves the definition of the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricReflexDefinition -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" -ReflexFormat "json" - This example retrieves the definition of the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345" in JSON format. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricReflexDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexFormat - ) - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId - - if ($ReflexFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $ReflexFormat - } - - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "Reflex '$ReflexId' definition retrieved successfully!" -Level Debug - return $response.definition.parts - } - 202 { - - Write-Message -Message "Getting Reflex '$ReflexId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Reflex. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Reflex/New-FabricReflex.ps1 b/FabricTools/tiago/Public/Reflex/New-FabricReflex.ps1 deleted file mode 100644 index e71b6502..00000000 --- a/FabricTools/tiago/Public/Reflex/New-FabricReflex.ps1 +++ /dev/null @@ -1,193 +0,0 @@ -<# -.SYNOPSIS - Creates a new Reflex in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Reflex - in the specified workspace. It supports optional parameters for Reflex description and path definitions. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Reflex will be created. This parameter is mandatory. - -.PARAMETER ReflexName - The name of the Reflex to be created. This parameter is mandatory. - -.PARAMETER ReflexDescription - An optional description for the Reflex. - -.PARAMETER ReflexPathDefinition - An optional path to the Reflex definition file to upload. - -.PARAMETER ReflexPathPlatformDefinition - An optional path to the platform-specific definition file to upload. - -.EXAMPLE - New-FabricReflex -WorkspaceId "workspace-12345" -ReflexName "New Reflex" -ReflexDescription "Description of the new Reflex" - This example creates a new Reflex named "New Reflex" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function New-FabricReflex { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$ReflexName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexPathPlatformDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reflexes" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $ReflexName - } - - if ($ReflexDescription) { - $body.description = $ReflexDescription - } - if ($ReflexPathDefinition) { - $ReflexEncodedContent = Convert-ToBase64 -filePath $ReflexPathDefinition - - if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "ReflexEntities.json" - payload = $ReflexEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error - return $null - } - } - - if ($ReflexPathPlatformDefinition) { - $ReflexEncodedPlatformContent = Convert-ToBase64 -filePath $ReflexPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $ReflexEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Reflex '$ReflexName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Reflex '$ReflexName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Reflex. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Reflex/Remove-FabricReflex.ps1 b/FabricTools/tiago/Public/Reflex/Remove-FabricReflex.ps1 deleted file mode 100644 index 884f4ab5..00000000 --- a/FabricTools/tiago/Public/Reflex/Remove-FabricReflex.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS - Removes an Reflex from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an Reflex - from the specified workspace using the provided WorkspaceId and ReflexId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the Reflex will be removed. - -.PARAMETER ReflexId - The unique identifier of the Reflex to be removed. - -.EXAMPLE - Remove-FabricReflex -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" - This example removes the Reflex with ID "Reflex-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricReflex { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReflexId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle response - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Reflex '$ReflexId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Reflex '$ReflexId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Reflex/Update-FabricReflex.ps1 b/FabricTools/tiago/Public/Reflex/Update-FabricReflex.ps1 deleted file mode 100644 index a600d2fc..00000000 --- a/FabricTools/tiago/Public/Reflex/Update-FabricReflex.ps1 +++ /dev/null @@ -1,105 +0,0 @@ -<# -.SYNOPSIS - Updates an existing Reflex in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Reflex - in the specified workspace. It supports optional parameters for Reflex description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Reflex exists. This parameter is optional. - -.PARAMETER ReflexId - The unique identifier of the Reflex to be updated. This parameter is mandatory. - -.PARAMETER ReflexName - The new name of the Reflex. This parameter is mandatory. - -.PARAMETER ReflexDescription - An optional new description for the Reflex. - -.EXAMPLE - Update-FabricReflex -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" -ReflexName "Updated Reflex" -ReflexDescription "Updated description" - This example updates the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricReflex { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReflexId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$ReflexName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexDescription - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $ReflexName - } - - if ($ReflexDescription) { - $body.description = $ReflexDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Reflex '$ReflexName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Reflex/Update-FabricReflexDefinition.ps1 b/FabricTools/tiago/Public/Reflex/Update-FabricReflexDefinition.ps1 deleted file mode 100644 index 45582d9a..00000000 --- a/FabricTools/tiago/Public/Reflex/Update-FabricReflexDefinition.ps1 +++ /dev/null @@ -1,168 +0,0 @@ -<# -.SYNOPSIS - Updates the definition of an existing Reflex in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Reflex - in the specified workspace. It supports optional parameters for Reflex definition and platform-specific definition. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Reflex exists. This parameter is mandatory. - -.PARAMETER ReflexId - The unique identifier of the Reflex to be updated. This parameter is mandatory. - -.PARAMETER ReflexPathDefinition - An optional path to the Reflex definition file to upload. - -.PARAMETER ReflexPathPlatformDefinition - An optional path to the platform-specific definition file to upload. - -.EXAMPLE - Update-FabricReflexDefinition -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" -ReflexPathDefinition "C:\Path\To\ReflexDefinition.json" - This example updates the definition of the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345" using the provided definition file. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricReflexDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReflexId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReflexPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReflexPathPlatformDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId - - #if ($UpdateMetadata -eq $true) { - if($ReflexPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - if ($ReflexPathDefinition) { - $ReflexEncodedContent = Convert-ToBase64 -filePath $ReflexPathDefinition - - if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "ReflexEntities.json" - payload = $ReflexEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error - return $null - } - } - - if ($ReflexPathPlatformDefinition) { - $ReflexEncodedPlatformContent = Convert-ToBase64 -filePath $ReflexPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $ReflexEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for Reflex '$ReflexId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for Reflex '$ReflexId' accepted. Operation in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Report/Get-FabricReport.ps1 b/FabricTools/tiago/Public/Report/Get-FabricReport.ps1 deleted file mode 100644 index 0f655be2..00000000 --- a/FabricTools/tiago/Public/Report/Get-FabricReport.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves Report details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves Report details from a specified workspace using either the provided ReportId or ReportName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Report exists. This parameter is mandatory. - -.PARAMETER ReportId - The unique identifier of the Report to retrieve. This parameter is optional. - -.PARAMETER ReportName - The name of the Report to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricReport -WorkspaceId "workspace-12345" -ReportId "Report-67890" - This example retrieves the Report details for the Report with ID "Report-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricReport -WorkspaceId "workspace-12345" -ReportName "My Report" - This example retrieves the Report details for the Report named "My Report" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricReport { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReportId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$ReportName - ) - try { - - # Step 1: Handle ambiguous input - if ($ReportId -and $ReportName) { - Write-Message -Message "Both 'ReportId' and 'ReportName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $Reports = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/reports" -f $FabricConfig.BaseUrl, $WorkspaceId - # Step 3: Loop to retrieve data with continuation token - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $Reports += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $Report = if ($ReportId) { - $Reports | Where-Object { $_.Id -eq $ReportId } - } - elseif ($ReportName) { - $Reports | Where-Object { $_.DisplayName -eq $ReportName } - } - else { - # Return all Reports if no filter is provided - Write-Message -Message "No filter provided. Returning all Reports." -Level Debug - $Reports - } - - # Step 9: Handle results - if ($Report) { - Write-Message -Message "Report found in the Workspace '$WorkspaceId'." -Level Debug - return $Report - } - else { - Write-Message -Message "No Report found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Report. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Report/Get-FabricReportDefinition.ps1 b/FabricTools/tiago/Public/Report/Get-FabricReportDefinition.ps1 deleted file mode 100644 index ef06367d..00000000 --- a/FabricTools/tiago/Public/Report/Get-FabricReportDefinition.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -<# -.SYNOPSIS - Retrieves the definition of an Report from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves the definition of an Report from a specified workspace using the provided ReportId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Report exists. This parameter is mandatory. - -.PARAMETER ReportId - The unique identifier of the Report to retrieve the definition for. This parameter is optional. - -.PARAMETER ReportFormat - The format in which to retrieve the Report definition. This parameter is optional. - -.EXAMPLE - Get-FabricReportDefinition -WorkspaceId "workspace-12345" -ReportId "Report-67890" - This example retrieves the definition of the Report with ID "Report-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricReportDefinition -WorkspaceId "workspace-12345" -ReportId "Report-67890" -ReportFormat "json" - This example retrieves the definition of the Report with ID "Report-67890" in the workspace with ID "workspace-12345" in JSON format. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricReportDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReportId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReportFormat - ) - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId - - if ($ReportFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $ReportFormat - } - - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "Report '$ReportId' definition retrieved successfully!" -Level Debug - return $response - } - 202 { - - Write-Message -Message "Getting Report '$ReportId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Report. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Report/New-FabricReport.ps1 b/FabricTools/tiago/Public/Report/New-FabricReport.ps1 deleted file mode 100644 index 9e61167c..00000000 --- a/FabricTools/tiago/Public/Report/New-FabricReport.ps1 +++ /dev/null @@ -1,150 +0,0 @@ -<# -.SYNOPSIS - Creates a new Report in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Report - in the specified workspace. It supports optional parameters for Report description and path definitions. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Report will be created. This parameter is mandatory. - -.PARAMETER ReportName - The name of the Report to be created. This parameter is mandatory. - -.PARAMETER ReportDescription - An optional description for the Report. - -.PARAMETER ReportPathDefinition - A mandatory path to the folder that contains Report definition files to upload. - - -.EXAMPLE - New-FabricReport -WorkspaceId "workspace-12345" -ReportName "New Report" -ReportDescription "Description of the new Report" - This example creates a new Report named "New Report" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function New-FabricReport { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$ReportName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReportDescription, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReportPathDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reports" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $ReportName - } - - if ($ReportDescription) { - $body.description = $ReportDescription - } - if ($ReportPathDefinition) { - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $ReportPathDefinition - # Add new part to the parts array - $body.definition.parts = $jsonObjectParts.parts - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Report '$ReportName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Report '$ReportName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Report. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Report/Remove-FabricReport.ps1 b/FabricTools/tiago/Public/Report/Remove-FabricReport.ps1 deleted file mode 100644 index 3ab69774..00000000 --- a/FabricTools/tiago/Public/Report/Remove-FabricReport.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS - Removes an Report from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an Report - from the specified workspace using the provided WorkspaceId and ReportId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the Report will be removed. - -.PARAMETER ReportId - The unique identifier of the Report to be removed. - -.EXAMPLE - Remove-FabricReport -WorkspaceId "workspace-12345" -ReportId "Report-67890" - This example removes the Report with ID "Report-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricReport { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReportId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle response - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Report '$ReportId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Report '$ReportId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Report/Update-FabricReport.ps1 b/FabricTools/tiago/Public/Report/Update-FabricReport.ps1 deleted file mode 100644 index 08b852ea..00000000 --- a/FabricTools/tiago/Public/Report/Update-FabricReport.ps1 +++ /dev/null @@ -1,105 +0,0 @@ -<# -.SYNOPSIS - Updates an existing Report in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Report - in the specified workspace. It supports optional parameters for Report description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Report exists. This parameter is optional. - -.PARAMETER ReportId - The unique identifier of the Report to be updated. This parameter is mandatory. - -.PARAMETER ReportName - The new name of the Report. This parameter is mandatory. - -.PARAMETER ReportDescription - An optional new description for the Report. - -.EXAMPLE - Update-FabricReport -WorkspaceId "workspace-12345" -ReportId "Report-67890" -ReportName "Updated Report" -ReportDescription "Updated description" - This example updates the Report with ID "Report-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricReport { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReportId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$ReportName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$ReportDescription - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $ReportName - } - - if ($ReportDescription) { - $body.description = $ReportDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Report '$ReportName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Report/Update-FabricReportDefinition.ps1 b/FabricTools/tiago/Public/Report/Update-FabricReportDefinition.ps1 deleted file mode 100644 index 1a7bb73f..00000000 --- a/FabricTools/tiago/Public/Report/Update-FabricReportDefinition.ps1 +++ /dev/null @@ -1,145 +0,0 @@ -<# -.SYNOPSIS - Updates the definition of an existing Report in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Report - in the specified workspace. It supports optional parameters for Report definition and platform-specific definition. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Report exists. This parameter is mandatory. - -.PARAMETER ReportId - The unique identifier of the Report to be updated. This parameter is mandatory. - -.PARAMETER ReportPathDefinition - A mandatory path to the Report definition file to upload. - -.EXAMPLE - Update-FabricReportDefinition -WorkspaceId "workspace-12345" -ReportId "Report-67890" -ReportPathDefinition "C:\Path\To\ReportDefinition.json" - This example updates the definition of the Report with ID "Report-67890" in the workspace with ID "workspace-12345" using the provided definition file. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricReportDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReportId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$ReportPathDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/Reports/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId - - #if ($UpdateMetadata -eq $true) { - - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - if ($ReportPathDefinition) { - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $ReportPathDefinition - # Add new part to the parts array - $body.definition.parts = $jsonObjectParts.parts - } - # Check if any path is .platform - foreach ($part in $jsonObjectParts.parts) { - if ($part.path -eq ".platform") { - $hasPlatformFile = $true - Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug - } - } - - if($hasPlatformFile -eq $true) { - $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for Report '$ReportId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for Report '$ReportId' accepted. Operation in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Update definition operation for Report '$ReportId' succeeded!" -Level Info - return $operationStatus - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 b/FabricTools/tiago/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 deleted file mode 100644 index 6b760c91..00000000 --- a/FabricTools/tiago/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 +++ /dev/null @@ -1,154 +0,0 @@ -<# -.SYNOPSIS -Retrieves SQL Endpoints from a specified workspace in Fabric. - -.DESCRIPTION -The Get-FabricSQLEndpoint function retrieves SQL Endpoints from a specified workspace in Fabric. -It supports filtering by SQL Endpoint ID or SQL Endpoint Name. If both filters are provided, -an error message is returned. The function handles token validation, API requests with continuation -tokens, and processes the response to return the desired SQL Endpoint(s). - -.PARAMETER WorkspaceId -The ID of the workspace from which to retrieve SQL Endpoints. This parameter is mandatory. - -.PARAMETER SQLEndpointId -The ID of the SQL Endpoint to retrieve. This parameter is optional but cannot be used together with SQLEndpointName. - -.PARAMETER SQLEndpointName -The name of the SQL Endpoint to retrieve. This parameter is optional but cannot be used together with SQLEndpointId. - -.EXAMPLE -Get-FabricSQLEndpoint -WorkspaceId "workspace123" -SQLEndpointId "endpoint456" - -.EXAMPLE -Get-FabricSQLEndpoint -WorkspaceId "workspace123" -SQLEndpointName "MySQLEndpoint" - -.NOTES -- This function requires the FabricConfig object to be properly configured with BaseUrl and FabricHeaders. -- The function uses continuation tokens to handle paginated API responses. -- If no filter parameters are provided, all SQL Endpoints in the specified workspace are returned. - -#> -function Get-FabricSQLEndpoint { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SQLEndpointId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SQLEndpointName - ) - - try { - # Step 1: Handle ambiguous input - if ($SQLEndpointId -and $SQLEndpointName) { - Write-Message -Message "Both 'SQLEndpointId' and 'SQLEndpointName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $SQLEndpoints = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/SQLEndpoints" -f $FabricConfig.BaseUrl, $WorkspaceId - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $SQLEndpoints += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $SQLEndpoint = if ($SQLEndpointId) { - $SQLEndpoints | Where-Object { $_.Id -eq $SQLEndpointId } - } - elseif ($SQLEndpointName) { - $SQLEndpoints | Where-Object { $_.DisplayName -eq $SQLEndpointName } - } - else { - # Return all SQLEndpoints if no filter is provided - Write-Message -Message "No filter provided. Returning all Paginated Reports." -Level Debug - $SQLEndpoints - } - - # Step 9: Handle results - if ($SQLEndpoint) { - Write-Message -Message "Paginated Report found matching the specified criteria." -Level Debug - return $SQLEndpoint - } - else { - Write-Message -Message "No Paginated Report found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Paginated Report. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModel.ps1 b/FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModel.ps1 deleted file mode 100644 index f0611bdf..00000000 --- a/FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModel.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves SemanticModel details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves SemanticModel details from a specified workspace using either the provided SemanticModelId or SemanticModelName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SemanticModel exists. This parameter is mandatory. - -.PARAMETER SemanticModelId - The unique identifier of the SemanticModel to retrieve. This parameter is optional. - -.PARAMETER SemanticModelName - The name of the SemanticModel to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" - This example retrieves the SemanticModel details for the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelName "My SemanticModel" - This example retrieves the SemanticModel details for the SemanticModel named "My SemanticModel" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricSemanticModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SemanticModelName - ) - try { - - # Step 1: Handle ambiguous input - if ($SemanticModelId -and $SemanticModelName) { - Write-Message -Message "Both 'SemanticModelId' and 'SemanticModelName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $SemanticModels = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/semanticModels" -f $FabricConfig.BaseUrl, $WorkspaceId - # Step 3: Loop to retrieve data with continuation token - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $SemanticModels += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $SemanticModel = if ($SemanticModelId) { - $SemanticModels | Where-Object { $_.Id -eq $SemanticModelId } - } - elseif ($SemanticModelName) { - $SemanticModels | Where-Object { $_.DisplayName -eq $SemanticModelName } - } - else { - # Return all SemanticModels if no filter is provided - Write-Message -Message "No filter provided. Returning all SemanticModels." -Level Debug - $SemanticModels - } - - # Step 9: Handle results - if ($SemanticModel) { - Write-Message -Message "SemanticModel found in the Workspace '$WorkspaceId'." -Level Debug - return $SemanticModel - } - else { - Write-Message -Message "No SemanticModel found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve SemanticModel. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 b/FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 deleted file mode 100644 index fa38d546..00000000 --- a/FabricTools/tiago/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 +++ /dev/null @@ -1,125 +0,0 @@ -<# -.SYNOPSIS - Retrieves the definition of an SemanticModel from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves the definition of an SemanticModel from a specified workspace using the provided SemanticModelId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SemanticModel exists. This parameter is mandatory. - -.PARAMETER SemanticModelId - The unique identifier of the SemanticModel to retrieve the definition for. This parameter is optional. - -.PARAMETER SemanticModelFormat - The format in which to retrieve the SemanticModel definition. This parameter is optional. - -.EXAMPLE - Get-FabricSemanticModelDefinition -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" - This example retrieves the definition of the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricSemanticModelDefinition -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" -SemanticModelFormat "json" - This example retrieves the definition of the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345" in JSON format. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricSemanticModelDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('TMDL', 'TMSL')] - [string]$SemanticModelFormat = "TMDL" - ) - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId - - if ($SemanticModelFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $SemanticModelFormat - } - - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "SemanticModel '$SemanticModelId' definition retrieved successfully!" -Level Debug - return $response - } - 202 { - - Write-Message -Message "Getting SemanticModel '$SemanticModelId' definition request accepted. Retrieving in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve SemanticModel. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Semantic Model/New-FabricSemanticModel.ps1 b/FabricTools/tiago/Public/Semantic Model/New-FabricSemanticModel.ps1 deleted file mode 100644 index 4ff05a02..00000000 --- a/FabricTools/tiago/Public/Semantic Model/New-FabricSemanticModel.ps1 +++ /dev/null @@ -1,145 +0,0 @@ -<# -.SYNOPSIS - Creates a new SemanticModel in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new SemanticModel - in the specified workspace. It supports optional parameters for SemanticModel description and path definitions. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SemanticModel will be created. This parameter is mandatory. - -.PARAMETER SemanticModelName - The name of the SemanticModel to be created. This parameter is mandatory. - -.PARAMETER SemanticModelDescription - An optional description for the SemanticModel. - -.PARAMETER SemanticModelPathDefinition - An optional path to the SemanticModel definition file to upload. - -.EXAMPLE - New-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelName "New SemanticModel" -SemanticModelDescription "Description of the new SemanticModel" - This example creates a new SemanticModel named "New SemanticModel" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function New-FabricSemanticModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SemanticModelName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelDescription, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelPathDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $SemanticModelName - definition = @{ - parts = @() - }} - - $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $SemanticModelPathDefinition - # Add new part to the parts array - $body.definition.parts = $jsonObjectParts.parts - - if ($SemanticModelDescription) { - $body.description = $SemanticModelDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "SemanticModel '$SemanticModelName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "SemanticModel '$SemanticModelName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create SemanticModel. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Semantic Model/Remove-FabricSemanticModel.ps1 b/FabricTools/tiago/Public/Semantic Model/Remove-FabricSemanticModel.ps1 deleted file mode 100644 index 4e12b229..00000000 --- a/FabricTools/tiago/Public/Semantic Model/Remove-FabricSemanticModel.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS - Removes an SemanticModel from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an SemanticModel - from the specified workspace using the provided WorkspaceId and SemanticModelId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the SemanticModel will be removed. - -.PARAMETER SemanticModelId - The unique identifier of the SemanticModel to be removed. - -.EXAMPLE - Remove-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" - This example removes the SemanticModel with ID "SemanticModel-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricSemanticModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle response - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "SemanticModel '$SemanticModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete SemanticModel '$SemanticModelId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModel.ps1 b/FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModel.ps1 deleted file mode 100644 index 12974b58..00000000 --- a/FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModel.ps1 +++ /dev/null @@ -1,105 +0,0 @@ -<# -.SYNOPSIS - Updates an existing SemanticModel in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing SemanticModel - in the specified workspace. It supports optional parameters for SemanticModel description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SemanticModel exists. This parameter is optional. - -.PARAMETER SemanticModelId - The unique identifier of the SemanticModel to be updated. This parameter is mandatory. - -.PARAMETER SemanticModelName - The new name of the SemanticModel. This parameter is mandatory. - -.PARAMETER SemanticModelDescription - An optional new description for the SemanticModel. - -.EXAMPLE - Update-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" -SemanticModelName "Updated SemanticModel" -SemanticModelDescription "Updated description" - This example updates the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricSemanticModel { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SemanticModelName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelDescription - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $SemanticModelName - } - - if ($SemanticModelDescription) { - $body.description = $SemanticModelDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "SemanticModel '$SemanticModelName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 b/FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 deleted file mode 100644 index 67e146ea..00000000 --- a/FabricTools/tiago/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 +++ /dev/null @@ -1,135 +0,0 @@ -<# -.SYNOPSIS - Updates the definition of an existing SemanticModel in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SemanticModel - in the specified workspace. It supports optional parameters for SemanticModel definition and platform-specific definition. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SemanticModel exists. This parameter is mandatory. - -.PARAMETER SemanticModelId - The unique identifier of the SemanticModel to be updated. This parameter is mandatory. - -.PARAMETER SemanticModelPathDefinition - An optional path to the SemanticModel definition file to upload. - -.EXAMPLE - Update-FabricSemanticModelDefinition -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" -SemanticModelPathDefinition "C:\Path\To\SemanticModelDefinition.json" - This example updates the definition of the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345" using the provided definition file. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricSemanticModelDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SemanticModelPathDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/SemanticModels/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId - - # Step 3: Construct the request body - $body = @{ - definition = @{ - parts = @() - } - } - - $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $SemanticModelPathDefinition - # Add new part to the parts array - $body.definition.parts = $jsonObjectParts.parts - # Check if any path is .platform - foreach ($part in $jsonObjectParts.parts) { - if ($part.path -eq ".platform") { - $hasPlatformFile = $true - Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug - } - } - - if ($hasPlatformFile -eq $true) { - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for SemanticModel '$SemanticModelId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for SemanticModel '$SemanticModelId' accepted. Operation in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Update definition operation for Semantic Model '$SemanticModelId' succeeded!" -Level Info - return $operationStatus - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 b/FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 deleted file mode 100644 index 09524fb4..00000000 --- a/FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves Spark Job Definition details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves SparkJobDefinition details from a specified workspace using either the provided SparkJobDefinitionId or SparkJobDefinitionName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionId - The unique identifier of the SparkJobDefinition to retrieve. This parameter is optional. - -.PARAMETER SparkJobDefinitionName - The name of the SparkJobDefinition to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" - This example retrieves the SparkJobDefinition details for the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionName "My SparkJobDefinition" - This example retrieves the SparkJobDefinition details for the SparkJobDefinition named "My SparkJobDefinition" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricSparkJobDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SparkJobDefinitionName - ) - try { - - # Step 1: Handle ambiguous input - if ($SparkJobDefinitionId -and $SparkJobDefinitionName) { - Write-Message -Message "Both 'SparkJobDefinitionId' and 'SparkJobDefinitionName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $SparkJobDefinitions = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions" -f $FabricConfig.BaseUrl, $WorkspaceId - # Step 3: Loop to retrieve data with continuation token - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $SparkJobDefinitions += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $SparkJobDefinition = if ($SparkJobDefinitionId) { - $SparkJobDefinitions | Where-Object { $_.Id -eq $SparkJobDefinitionId } - } - elseif ($SparkJobDefinitionName) { - $SparkJobDefinitions | Where-Object { $_.DisplayName -eq $SparkJobDefinitionName } - } - else { - # Return all SparkJobDefinitions if no filter is provided - Write-Message -Message "No filter provided. Returning all SparkJobDefinitions." -Level Debug - $SparkJobDefinitions - } - - # Step 9: Handle results - if ($SparkJobDefinition) { - Write-Message -Message "Spark Job Definition found in the Workspace '$WorkspaceId'." -Level Debug - return $SparkJobDefinition - } - else { - Write-Message -Message "No Spark Job Definition found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve SparkJobDefinition. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 b/FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 deleted file mode 100644 index b1db8a91..00000000 --- a/FabricTools/tiago/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -<# -.SYNOPSIS - Retrieves the definition of an SparkJobDefinition from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves the definition of an SparkJobDefinition from a specified workspace using the provided SparkJobDefinitionId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionId - The unique identifier of the SparkJobDefinition to retrieve the definition for. This parameter is optional. - -.PARAMETER SparkJobDefinitionFormat - The format in which to retrieve the SparkJobDefinition definition. This parameter is optional. - -.EXAMPLE - Get-FabricSparkJobDefinitionDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" - This example retrieves the definition of the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricSparkJobDefinitionDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" -SparkJobDefinitionFormat "json" - This example retrieves the definition of the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345" in JSON format. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricSparkJobDefinitionDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('SparkJobDefinitionV1')] - [string]$SparkJobDefinitionFormat = "SparkJobDefinitionV1" - ) - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId - - if ($SparkJobDefinitionFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $SparkJobDefinitionFormat - } - - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "Spark Job Definition '$SparkJobDefinitionId' definition retrieved successfully!" -Level Debug - return $response.definition.parts - } - 202 { - - Write-Message -Message "Getting Spark Job Definition '$SparkJobDefinitionId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Spark Job Definition. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 b/FabricTools/tiago/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 deleted file mode 100644 index 99d992ef..00000000 --- a/FabricTools/tiago/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 +++ /dev/null @@ -1,194 +0,0 @@ -<# -.SYNOPSIS - Creates a new SparkJobDefinition in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new SparkJobDefinition - in the specified workspace. It supports optional parameters for SparkJobDefinition description and path definitions. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SparkJobDefinition will be created. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionName - The name of the SparkJobDefinition to be created. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionDescription - An optional description for the SparkJobDefinition. - -.PARAMETER SparkJobDefinitionPathDefinition - An optional path to the SparkJobDefinition definition file to upload. - -.PARAMETER SparkJobDefinitionPathPlatformDefinition - An optional path to the platform-specific definition file to upload. - -.EXAMPLE - New-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionName "New SparkJobDefinition" -SparkJobDefinitionDescription "Description of the new SparkJobDefinition" - This example creates a new SparkJobDefinition named "New SparkJobDefinition" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function New-FabricSparkJobDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SparkJobDefinitionName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionPathPlatformDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $SparkJobDefinitionName - } - - if ($SparkJobDefinitionDescription) { - $body.description = $SparkJobDefinitionDescription - } - if ($SparkJobDefinitionPathDefinition) { - $SparkJobDefinitionEncodedContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathDefinition - - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "SparkJobDefinitionV1" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "SparkJobDefinitionProperties.json" - payload = $SparkJobDefinitionEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error - return $null - } - } - - if ($SparkJobDefinitionPathPlatformDefinition) { - $SparkJobDefinitionEncodedPlatformContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "SparkJobDefinitionV1" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $SparkJobDefinitionEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Spark Job Definition. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 b/FabricTools/tiago/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 deleted file mode 100644 index 68f8e184..00000000 --- a/FabricTools/tiago/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -<# -.SYNOPSIS - Removes an SparkJobDefinition from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an SparkJobDefinition - from the specified workspace using the provided WorkspaceId and SparkJobDefinitionId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the SparkJobDefinition will be removed. - -.PARAMETER SparkJobDefinitionId - The unique identifier of the SparkJobDefinition to be removed. - -.EXAMPLE - Remove-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" - This example removes the SparkJobDefinition with ID "SparkJobDefinition-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Remove-FabricSparkJobDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle response - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Spark Job Definition '$SparkJobDefinitionId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete SparkJobDefinition '$SparkJobDefinitionId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 b/FabricTools/tiago/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 deleted file mode 100644 index 43bb3dac..00000000 --- a/FabricTools/tiago/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 +++ /dev/null @@ -1,118 +0,0 @@ -<# -.SYNOPSIS - Starts a Fabric Spark Job Definition on demand. - -.DESCRIPTION - This function initiates a Spark Job Definition on demand within a specified workspace. - It constructs the appropriate API endpoint URL and makes a POST request to start the job. - The function can optionally wait for the job to complete based on the 'waitForCompletion' parameter. - -.PARAMETER WorkspaceId - The ID of the workspace where the Spark Job Definition is located. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionId - The ID of the Spark Job Definition to be started. This parameter is mandatory. - -.PARAMETER JobType - The type of job to be started. The default value is 'sparkjob'. This parameter is optional. - -.PARAMETER waitForCompletion - A boolean flag indicating whether to wait for the job to complete. The default value is $false. This parameter is optional. - -.EXAMPLE - Start-FabricSparkJobDefinitionOnDemand -WorkspaceId "workspace123" -SparkJobDefinitionId "jobdef456" -waitForCompletion $true - -.NOTES - Ensure that the necessary authentication tokens are valid before running this function. - The function logs detailed messages for debugging and informational purposes. -#> -function Start-FabricSparkJobDefinitionOnDemand { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('sparkjob')] - [string]$JobType = "sparkjob", - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$waitForCompletion = $false - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/jobs/instances?jobType={3}" -f $FabricConfig.BaseUrl, $WorkspaceId , $SparkJobDefinitionId, $JobType - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Spark Job Definition on demand successfully initiated for SparkJobDefinition '$SparkJobDefinition.displayName'." -Level Info - return $response - } - 202 { - Write-Message -Message "Spark Job Definition on demand accepted and is now running in the background. Job execution is in progress." -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - - if ($waitForCompletion -eq $true) { - Write-Message -Message "Getting Long Running Operation status" -Level Debug - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - return $operationStatus - } - else { - Write-Message -Message "The operation is running asynchronously." -Level Info - Write-Message -Message "Use the returned details to check the operation status." -Level Info - Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info - $operationDetails = [PSCustomObject]@{ - OperationId = $operationId - Location = $location - RetryAfter = $retryAfter - } - return $operationDetails - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to start Spark Job Definition on demand. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 b/FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 deleted file mode 100644 index 9b2c641a..00000000 --- a/FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 +++ /dev/null @@ -1,104 +0,0 @@ -<# -.SYNOPSIS - Updates an existing SparkJobDefinition in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing SparkJobDefinition - in the specified workspace. It supports optional parameters for SparkJobDefinition description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is optional. - -.PARAMETER SparkJobDefinitionId - The unique identifier of the SparkJobDefinition to be updated. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionName - The new name of the SparkJobDefinition. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionDescription - An optional new description for the SparkJobDefinition. - -.EXAMPLE - Update-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" -SparkJobDefinitionName "Updated SparkJobDefinition" -SparkJobDefinitionDescription "Updated description" - This example updates the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Update-FabricSparkJobDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SparkJobDefinitionName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionDescription - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $SparkJobDefinitionName - } - - if ($SparkJobDefinitionDescription) { - $body.description = $SparkJobDefinitionDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update SparkJobDefinition. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 b/FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 deleted file mode 100644 index cca084d7..00000000 --- a/FabricTools/tiago/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 +++ /dev/null @@ -1,168 +0,0 @@ -<# -.SYNOPSIS - Updates the definition of an existing SparkJobDefinition in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SparkJobDefinition - in the specified workspace. It supports optional parameters for SparkJobDefinition definition and platform-specific definition. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionId - The unique identifier of the SparkJobDefinition to be updated. This parameter is mandatory. - -.PARAMETER SparkJobDefinitionPathDefinition - An optional path to the SparkJobDefinition definition file to upload. - -.PARAMETER SparkJobDefinitionPathPlatformDefinition - An optional path to the platform-specific definition file to upload. - -.EXAMPLE - Update-FabricSparkJobDefinitionDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" -SparkJobDefinitionPathDefinition "C:\Path\To\SparkJobDefinitionDefinition.json" - This example updates the definition of the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345" using the provided definition file. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Update-FabricSparkJobDefinitionDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkJobDefinitionPathPlatformDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId - - #if ($UpdateMetadata -eq $true) { - if($SparkJobDefinitionPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - definition = @{ - format = "SparkJobDefinitionV1" - parts = @() - } - } - - if ($SparkJobDefinitionPathDefinition) { - $SparkJobDefinitionEncodedContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathDefinition - - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = "SparkJobDefinitionV1.json" - payload = $SparkJobDefinitionEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error - return $null - } - } - - if ($SparkJobDefinitionPathPlatformDefinition) { - $SparkJobDefinitionEncodedPlatformContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) { - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $SparkJobDefinitionEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Update definition for Spark Job Definition '$SparkJobDefinitionId' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Update definition for Spark Job Definition '$SparkJobDefinitionId' accepted. Operation in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Spark Job Definition. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark/Get-FabricSparkCustomPool.ps1 b/FabricTools/tiago/Public/Spark/Get-FabricSparkCustomPool.ps1 deleted file mode 100644 index 6bf0441e..00000000 --- a/FabricTools/tiago/Public/Spark/Get-FabricSparkCustomPool.ps1 +++ /dev/null @@ -1,161 +0,0 @@ -<# -.SYNOPSIS - Retrieves Spark custom pools from a specified workspace. - -.DESCRIPTION - This function retrieves all Spark custom pools from a specified workspace using the provided WorkspaceId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - The function supports filtering by SparkCustomPoolId or SparkCustomPoolName, but not both simultaneously. - -.PARAMETER WorkspaceId - The ID of the workspace from which to retrieve Spark custom pools. This parameter is mandatory. - -.PARAMETER SparkCustomPoolId - The ID of the specific Spark custom pool to retrieve. This parameter is optional. - -.PARAMETER SparkCustomPoolName - The name of the specific Spark custom pool to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricSparkCustomPool -WorkspaceId "12345" - This example retrieves all Spark custom pools from the workspace with ID "12345". - -.EXAMPLE - Get-FabricSparkCustomPool -WorkspaceId "12345" -SparkCustomPoolId "pool1" - This example retrieves the Spark custom pool with ID "pool1" from the workspace with ID "12345". - -.EXAMPLE - Get-FabricSparkCustomPool -WorkspaceId "12345" -SparkCustomPoolName "MyPool" - This example retrieves the Spark custom pool with name "MyPool" from the workspace with ID "12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Handles continuation tokens to retrieve all Spark custom pools if there are multiple pages of results. - - Author: Tiago Balabuch -#> -function Get-FabricSparkCustomPool { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SparkCustomPoolId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SparkCustomPoolName - ) - - try { - # Step 1: Handle ambiguous input - if ($SparkCustomPoolId -and $SparkCustomPoolName) { - Write-Message -Message "Both 'SparkCustomPoolId' and 'SparkCustomPoolName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $SparkCustomPools = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/spark/pools" -f $FabricConfig.BaseUrl, $WorkspaceId - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $SparkCustomPools += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $SparkCustomPool = if ($SparkCustomPoolId) { - $SparkCustomPools | Where-Object { $_.id -eq $SparkCustomPoolId } - } - elseif ($SparkCustomPoolName) { - $SparkCustomPools | Where-Object { $_.name -eq $SparkCustomPoolName } - } - else { - # Return all SparkCustomPools if no filter is provided - Write-Message -Message "No filter provided. Returning all SparkCustomPools." -Level Debug - $SparkCustomPools - } - - # Step 9: Handle results - if ($SparkCustomPool) { - Write-Message -Message "SparkCustomPool found matching the specified criteria." -Level Debug - return $SparkCustomPool - } - else { - Write-Message -Message "No SparkCustomPool found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve SparkCustomPool. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Spark/Get-FabricSparkSettings.ps1 b/FabricTools/tiago/Public/Spark/Get-FabricSparkSettings.ps1 deleted file mode 100644 index 11c5c51f..00000000 --- a/FabricTools/tiago/Public/Spark/Get-FabricSparkSettings.ps1 +++ /dev/null @@ -1,119 +0,0 @@ -<# -.SYNOPSIS - Retrieves Spark settings from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves Spark settings from a specified workspace using the provided WorkspaceId. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which to retrieve Spark settings. This parameter is mandatory. - -.EXAMPLE - Get-FabricSparkSettings -WorkspaceId "workspace-12345" - This example retrieves the Spark settings for the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricSparkSettings { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId - ) - - try { - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $SparkSettings = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/spark/settings" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $SparkSettings += $response - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 9: Handle results - if ($SparkSettings) { - Write-Message -Message " Returning all Spark Settings." -Level Debug - # Return all Spark Settings - return $SparkSettings - } - else { - Write-Message -Message "No SparkSettings found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve SparkSettings. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Spark/New-FabricSparkCustomPool.ps1 b/FabricTools/tiago/Public/Spark/New-FabricSparkCustomPool.ps1 deleted file mode 100644 index 848677ce..00000000 --- a/FabricTools/tiago/Public/Spark/New-FabricSparkCustomPool.ps1 +++ /dev/null @@ -1,190 +0,0 @@ -<# -.SYNOPSIS - Creates a new Spark custom pool in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Spark custom pool - in the specified workspace. It supports various parameters for Spark custom pool configuration. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Spark custom pool will be created. This parameter is mandatory. - -.PARAMETER SparkCustomPoolName - The name of the Spark custom pool to be created. This parameter is mandatory. - -.PARAMETER NodeFamily - The family of nodes to be used in the Spark custom pool. This parameter is mandatory and must be 'MemoryOptimized'. - -.PARAMETER NodeSize - The size of the nodes to be used in the Spark custom pool. This parameter is mandatory and must be one of 'Large', 'Medium', 'Small', 'XLarge', 'XXLarge'. - -.PARAMETER AutoScaleEnabled - Specifies whether auto-scaling is enabled for the Spark custom pool. This parameter is mandatory. - -.PARAMETER AutoScaleMinNodeCount - The minimum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. - -.PARAMETER AutoScaleMaxNodeCount - The maximum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationEnabled - Specifies whether dynamic executor allocation is enabled for the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationMinExecutors - The minimum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationMaxExecutors - The maximum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. - -.EXAMPLE - New-FabricSparkCustomPool -WorkspaceId "workspace-12345" -SparkCustomPoolName "New Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 - This example creates a new Spark custom pool named "New Spark Pool" in the workspace with ID "workspace-12345" with the specified configuration. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> - -function New-FabricSparkCustomPool { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$SparkCustomPoolName, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('MemoryOptimized')] - [string]$NodeFamily, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Large', 'Medium', 'Small', 'XLarge', 'XXLarge')] - [string]$NodeSize, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [bool]$AutoScaleEnabled, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$AutoScaleMinNodeCount, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$AutoScaleMaxNodeCount, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [bool]$DynamicExecutorAllocationEnabled, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$DynamicExecutorAllocationMinExecutors, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$DynamicExecutorAllocationMaxExecutors - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/spark/pools" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - name = $SparkCustomPoolName - nodeFamily = $NodeFamily - nodeSize = $NodeSize - autoScale = @{ - enabled = $AutoScaleEnabled - minNodeCount = $AutoScaleMinNodeCount - maxNodeCount = $AutoScaleMaxNodeCount - } - dynamicExecutorAllocation = @{ - enabled = $DynamicExecutorAllocationEnabled - minExecutors = $DynamicExecutorAllocationMinExecutors - maxExecutors = $DynamicExecutorAllocationMaxExecutors - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "SparkCustomPool '$SparkCustomPoolName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "SparkCustomPool '$SparkCustomPoolName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create SparkCustomPool. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark/Remove-FabricSparkCustomPool.ps1 b/FabricTools/tiago/Public/Spark/Remove-FabricSparkCustomPool.ps1 deleted file mode 100644 index 2b748e65..00000000 --- a/FabricTools/tiago/Public/Spark/Remove-FabricSparkCustomPool.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -<# -.SYNOPSIS - Removes a Spark custom pool from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove a Spark custom pool - from the specified workspace using the provided WorkspaceId and SparkCustomPoolId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the Spark custom pool will be removed. - -.PARAMETER SparkCustomPoolId - The unique identifier of the Spark custom pool to be removed. - -.EXAMPLE - Remove-FabricSparkCustomPool -WorkspaceId "workspace-12345" -SparkCustomPoolId "pool-67890" - This example removes the Spark custom pool with ID "pool-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricSparkCustomPool { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SparkCustomPoolId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/spark/pools/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkCustomPoolId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Spark Custom Pool '$SparkCustomPoolId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete SparkCustomPool '$SparkCustomPoolId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark/Update-FabricSparkCustomPool.ps1 b/FabricTools/tiago/Public/Spark/Update-FabricSparkCustomPool.ps1 deleted file mode 100644 index 9e4bcfd5..00000000 --- a/FabricTools/tiago/Public/Spark/Update-FabricSparkCustomPool.ps1 +++ /dev/null @@ -1,164 +0,0 @@ -<# -.SYNOPSIS - Updates an existing Spark custom pool in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool - in the specified workspace. It supports various parameters for Spark custom pool configuration. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Spark custom pool exists. This parameter is mandatory. - -.PARAMETER SparkCustomPoolId - The unique identifier of the Spark custom pool to be updated. This parameter is mandatory. - -.PARAMETER InstancePoolName - The new name of the Spark custom pool. This parameter is mandatory. - -.PARAMETER NodeFamily - The family of nodes to be used in the Spark custom pool. This parameter is mandatory and must be 'MemoryOptimized'. - -.PARAMETER NodeSize - The size of the nodes to be used in the Spark custom pool. This parameter is mandatory and must be one of 'Large', 'Medium', 'Small', 'XLarge', 'XXLarge'. - -.PARAMETER AutoScaleEnabled - Specifies whether auto-scaling is enabled for the Spark custom pool. This parameter is mandatory. - -.PARAMETER AutoScaleMinNodeCount - The minimum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. - -.PARAMETER AutoScaleMaxNodeCount - The maximum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationEnabled - Specifies whether dynamic executor allocation is enabled for the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationMinExecutors - The minimum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationMaxExecutors - The maximum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. - -.EXAMPLE - Update-FabricSparkCustomPool -WorkspaceId "workspace-12345" -SparkCustomPoolId "pool-67890" -InstancePoolName "Updated Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 - This example updates the Spark custom pool with ID "pool-67890" in the workspace with ID "workspace-12345" with a new name and configuration. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricSparkCustomPool { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SparkCustomPoolId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$InstancePoolName, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('MemoryOptimized')] - [string]$NodeFamily, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Large', 'Medium', 'Small', 'XLarge', 'XXLarge')] - [string]$NodeSize, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [bool]$AutoScaleEnabled, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$AutoScaleMinNodeCount, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$AutoScaleMaxNodeCount, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [bool]$DynamicExecutorAllocationEnabled, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$DynamicExecutorAllocationMinExecutors, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [int]$DynamicExecutorAllocationMaxExecutors - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/spark/pools/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkCustomPoolId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - name = $InstancePoolName - nodeFamily = $NodeFamily - nodeSize = $NodeSize - autoScale = @{ - enabled = $AutoScaleEnabled - minNodeCount = $AutoScaleMinNodeCount - maxNodeCount = $AutoScaleMaxNodeCount - } - dynamicExecutorAllocation = @{ - enabled = $DynamicExecutorAllocationEnabled - minExecutors = $DynamicExecutorAllocationMinExecutors - maxExecutors = $DynamicExecutorAllocationMaxExecutors - } - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Spark Custom Pool '$SparkCustomPoolName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update SparkCustomPool. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Spark/Update-FabricSparkSettings.ps1 b/FabricTools/tiago/Public/Spark/Update-FabricSparkSettings.ps1 deleted file mode 100644 index 9320af81..00000000 --- a/FabricTools/tiago/Public/Spark/Update-FabricSparkSettings.ps1 +++ /dev/null @@ -1,189 +0,0 @@ -<# -.SYNOPSIS - Updates an existing Spark custom pool in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool - in the specified workspace. It supports various parameters for Spark custom pool configuration. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Spark custom pool exists. This parameter is mandatory. - -.PARAMETER SparkSettingsId - The unique identifier of the Spark custom pool to be updated. This parameter is mandatory. - -.PARAMETER InstancePoolName - The new name of the Spark custom pool. This parameter is mandatory. - -.PARAMETER NodeFamily - The family of nodes to be used in the Spark custom pool. This parameter is mandatory and must be 'MemoryOptimized'. - -.PARAMETER NodeSize - The size of the nodes to be used in the Spark custom pool. This parameter is mandatory and must be one of 'Large', 'Medium', 'Small', 'XLarge', 'XXLarge'. - -.PARAMETER AutoScaleEnabled - Specifies whether auto-scaling is enabled for the Spark custom pool. This parameter is mandatory. - -.PARAMETER AutoScaleMinNodeCount - The minimum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. - -.PARAMETER AutoScaleMaxNodeCount - The maximum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationEnabled - Specifies whether dynamic executor allocation is enabled for the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationMinExecutors - The minimum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. - -.PARAMETER DynamicExecutorAllocationMaxExecutors - The maximum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. - -.EXAMPLE - Update-FabricSparkSettings -WorkspaceId "workspace-12345" -SparkSettingsId "pool-67890" -InstancePoolName "Updated Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 - This example updates the Spark custom pool with ID "pool-67890" in the workspace with ID "workspace-12345" with a new name and configuration. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricSparkSettings { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$automaticLogEnabled, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$notebookInteractiveRunEnabled, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$customizeComputeEnabled, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$defaultPoolName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Workspace', 'Capacity')] - [string]$defaultPoolType, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [int]$starterPoolMaxNode, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [int]$starterPoolMaxExecutors, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EnvironmentRuntimeVersion - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/spark/settings" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkSettingsId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - # Construct the request body with optional properties - - $body = @{} - - if ($PSBoundParameters.ContainsKey('automaticLogEnabled')) { - $body.automaticLog = @{ - enabled = $automaticLogEnabled - } - } - - if ($PSBoundParameters.ContainsKey('notebookInteractiveRunEnabled')) { - $body.highConcurrency = @{ - notebookInteractiveRunEnabled = $notebookInteractiveRunEnabled - } - } - - if ($PSBoundParameters.ContainsKey('customizeComputeEnabled') ) { - $body.pool = @{ - customizeComputeEnabled = $customizeComputeEnabled - } - } - if ($PSBoundParameters.ContainsKey('defaultPoolName') -or $PSBoundParameters.ContainsKey('defaultPoolType')) { - if ($PSBoundParameters.ContainsKey('defaultPoolName') -and $PSBoundParameters.ContainsKey('defaultPoolType')) { - $body.pool = @{ - defaultPool = @{ - name = $defaultPoolName - type = $defaultPoolType - } - } - } else { - Write-Message -Message "Both 'defaultPoolName' and 'defaultPoolType' must be provided together." -Level Error - throw - } - } - - if ($PSBoundParameters.ContainsKey('EnvironmentName') -or $PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) { - $body.environment = @{ - name = $EnvironmentName - } - } - if ($PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) { - $body.environment = @{ - runtimeVersion = $EnvironmentRuntimeVersion - } - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Spark Custom Pool '$SparkSettingsName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update SparkSettings. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 b/FabricTools/tiago/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 deleted file mode 100644 index e36f1f3a..00000000 --- a/FabricTools/tiago/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -<# -.SYNOPSIS -Retrieves tenant setting overrides for a specific capacity or all capacities in the Fabric tenant. - -.DESCRIPTION -The `Get-FabricCapacityTenantSettingOverrides` function retrieves tenant setting overrides for a specific capacity or all capacities in the Fabric tenant by making a GET request to the appropriate API endpoint. If a `capacityId` is provided, the function retrieves overrides for that specific capacity. Otherwise, it retrieves overrides for all capacities. - -.PARAMETER capacityId -The ID of the capacity for which tenant setting overrides should be retrieved. If not provided, overrides for all capacities will be retrieved. - -.EXAMPLE -Get-FabricCapacityTenantSettingOverrides - -Returns all capacities tenant setting overrides. - -.EXAMPLE -Get-FabricCapacityTenantSettingOverrides -capacityId "12345" - -Returns tenant setting overrides for the capacity with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> -function Get-FabricCapacityTenantSettingOverrides { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$capacityId - ) - - try { - # Step 1: Validate authentication token before making API requests - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Step 2: Construct the API endpoint URL for retrieving capacity tenant setting overrides - if ($capacityId) { - $apiEndpointURI = "{0}/admin/capacities/{1}/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl, $capacityId - $message = "Successfully retrieved tenant setting overrides for capacity ID: $capacityId." - } else { - $apiEndpointURI = "{0}/admin/capacities/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl - $message = "Successfully retrieved capacity tenant setting overrides." - } - Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Invoke the Fabric API to retrieve capacity tenant setting overrides - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - # Step 4: Check if any capacity tenant setting overrides were retrieved and handle results accordingly - if ($response) { - Write-Message -Message $message -Level Debug - return $response - } - else { - Write-Message -Message "No capacity tenant setting overrides found." -Level Warning - return $null - } - } - catch { - # Step 5: Log detailed error information if the API request fails - $errorDetails = $_.Exception.Message - Write-Message -Message "Error retrieving capacity tenant setting overrides: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 b/FabricTools/tiago/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 deleted file mode 100644 index 22ce3d3a..00000000 --- a/FabricTools/tiago/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -<# -.SYNOPSIS -Retrieves tenant setting overrides for a specific domain or all capacities in the Fabric tenant. - -.DESCRIPTION -The `Get-FabricDomainTenantSettingOverrides` function retrieves tenant setting overrides for all domains in the Fabric tenant by making a GET request to the designated API endpoint. The function ensures token validity before making the request and handles the response appropriately. - -.EXAMPLE -Get-FabricDomainTenantSettingOverrides - -Fetches tenant setting overrides for all domains in the Fabric tenant. - -.NOTES -- Requires the `$FabricConfig` global configuration, which must include `BaseUrl` and `FabricHeaders`. -- Ensures token validity by invoking `Test-TokenExpired` before making the API request. -- Logs detailed messages for debugging and error handling. - -Author: Tiago Balabuch -#> -function Get-FabricDomainTenantSettingOverrides { - [CmdletBinding()] - param ( ) - - try { - # Step 1: Validate authentication token before making API requests - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Step 2: Construct the API endpoint URL for retrieving domain tenant setting overrides - $apiEndpointURI = "{0}/admin/domains/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl - Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Invoke the Fabric API to retrieve domain tenant setting overrides - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - # Step 4: Check if any domain tenant setting overrides were retrieved and handle results accordingly - if ($response) { - Write-Message -Message "Successfully retrieved domain tenant setting overrides." -Level Debug - return $response - } - else { - Write-Message -Message "No domain tenant setting overrides found." -Level Warning - return $null - } - } - catch { - # Step 5: Log detailed error information if the API request fails - $errorDetails = $_.Exception.Message - Write-Message -Message "Error retrieving domain tenant setting overrides: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Tenant/Get-FabricTenantSetting.ps1 b/FabricTools/tiago/Public/Tenant/Get-FabricTenantSetting.ps1 deleted file mode 100644 index 655e1c9c..00000000 --- a/FabricTools/tiago/Public/Tenant/Get-FabricTenantSetting.ps1 +++ /dev/null @@ -1,78 +0,0 @@ -<# -.SYNOPSIS -Retrieves tenant settings from the Fabric environment. - -.DESCRIPTION -The `Get-FabricTenantSetting` function retrieves tenant settings for a Fabric environment by making a GET request to the appropriate API endpoint. Optionally, it filters the results by the `SettingTitle` parameter. - -.PARAMETER SettingTitle -(Optional) The title of a specific tenant setting to filter the results. - -.EXAMPLE -Get-FabricTenantSetting - -Returns all tenant settings. - -.EXAMPLE -Get-FabricTenantSetting -SettingTitle "SomeSetting" - -Returns the tenant setting with the title "SomeSetting". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Is-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricTenantSetting { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$SettingTitle - ) - - try { - # Step 1: Validate authentication token before making API requests - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Step 2: Construct the API endpoint URL for retrieving tenant settings - $apiEndpointURI = "{0}/admin/tenantsettings" -f $FabricConfig.BaseUrl - Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Invoke the Fabric API to retrieve tenant settings - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - # Step 4: Filter tenant settings based on the provided SettingTitle parameter (if specified) - $settings = if ($SettingTitle) { - Write-Message -Message "Filtering tenant settings by title: '$SettingTitle'" -Level Debug - $response.tenantSettings | Where-Object { $_.title -eq $SettingTitle } - } - else { - Write-Message -Message "No filter specified. Retrieving all tenant settings." -Level Debug - $response.tenantSettings - } - - # Step 5: Check if any tenant settings were found and return results accordingly - if ($settings) { - Write-Message -Message "Tenant settings successfully retrieved." -Level Debug - return $settings - } - else { - Write-Message -Message "No tenant settings found matching the specified criteria." -Level Warning - return $null - } - } - catch { - # Step 6: Log detailed error information if the API request fails - $errorDetails = $_.Exception.Message - Write-Message -Message "Error retrieving tenant settings: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 b/FabricTools/tiago/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 deleted file mode 100644 index c04e8ed1..00000000 --- a/FabricTools/tiago/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -<# -.SYNOPSIS -Retrieves tenant setting overrides for all workspaces in the Fabric tenant. - -.DESCRIPTION -The `Get-FabricWorkspaceTenantSettingOverrides` function retrieves tenant setting overrides for all workspaces in the Fabric tenant by making a GET request to the appropriate API endpoint. The function validates the authentication token before making the request and handles the response accordingly. - -.EXAMPLE -Get-FabricWorkspaceTenantSettingOverrides - -Returns all workspaces tenant setting overrides. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> -function Get-FabricWorkspaceTenantSettingOverrides { - [CmdletBinding()] - param ( ) - - try { - # Step 1: Validate authentication token before making API requests - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Step 2: Construct the API endpoint URL for retrieving workspaces tenant setting overrides - $apiEndpointURI = "{0}/admin/workspaces/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl - Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Invoke the Fabric API to retrieve workspaces tenant setting overrides - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - # Step 4: Check if any workspaces tenant setting overrides were retrieved and handle results accordingly - if ($response) { - Write-Message -Message "Successfully retrieved workspaces tenant setting overrides." -Level Debug - return $response - } - else { - Write-Message -Message "No workspaces tenant setting overrides found." -Level Warning - return $null - } - } - catch { - # Step 5: Log detailed error information if the API request fails - $errorDetails = $_.Exception.Message - Write-Message -Message "Error retrieving workspaces tenant setting overrides: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 b/FabricTools/tiago/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 deleted file mode 100644 index 7665ddea..00000000 --- a/FabricTools/tiago/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -<# -.SYNOPSIS -Removes a tenant setting override from a specific capacity in the Fabric tenant. - -.DESCRIPTION -The `Revoke-FabricCapacityTenantSettingOverrides` function deletes a specific tenant setting override for a given capacity in the Fabric tenant by making a DELETE request to the appropriate API endpoint. - -.PARAMETER capacityId -The unique identifier of the capacity from which the tenant setting override will be removed. - -.PARAMETER tenantSettingName -The name of the tenant setting override to be removed. - -.EXAMPLE -Revoke-FabricCapacityTenantSettingOverrides -capacityId "12345" -tenantSettingName "ExampleSetting" - -Removes the tenant setting override named "ExampleSetting" from the capacity with ID "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> -function Revoke-FabricCapacityTenantSettingOverrides { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$capacityId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$tenantSettingName - ) - try { - # Step 1: Validate authentication token before making API requests - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Step 2: Construct the API endpoint URL for retrieving capacity tenant setting overrides - $apiEndpointURI = "{0}/admin/capacities/{1}/delegatedTenantSettingOverrides/{2}" -f $FabricConfig.BaseUrl, $capacityId, $tenantSettingName - Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Invoke the Fabric API to retrieve capacity tenant setting overrides - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Delete - - Write-Message -Message "Successfully removed the tenant setting override '$tenantSettingName' from the capacity with ID '$capacityId'." -Level Info - return $response - } - catch { - # Step 5: Log detailed error information if the API request fails - $errorDetails = $_.Exception.Message - Write-Message -Message "Error retrieving capacity tenant setting overrides: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 b/FabricTools/tiago/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 deleted file mode 100644 index f352b771..00000000 --- a/FabricTools/tiago/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 +++ /dev/null @@ -1,136 +0,0 @@ -<# -.SYNOPSIS -Updates tenant setting overrides for a specified capacity ID. - -.DESCRIPTION -The `Update-FabricCapacityTenantSettingOverrides` function updates tenant setting overrides in a Fabric environment by making a POST request to the appropriate API endpoint. It allows specifying settings such as enabling tenant settings, delegating to a workspace, and including or excluding security groups. - -.PARAMETER CapacityId -(Mandatory) The ID of the capacity for which the tenant setting overrides are being updated. - -.PARAMETER SettingTitle -(Mandatory) The title of the tenant setting to be updated. - -.PARAMETER EnableTenantSetting -(Mandatory) Indicates whether the tenant setting should be enabled. - -.PARAMETER DelegateToWorkspace -(Optional) Specifies the workspace to which the setting should be delegated. - -.PARAMETER EnabledSecurityGroups -(Optional) A JSON array of security groups to be enabled, each containing `graphId` and `name` properties. - -.PARAMETER ExcludedSecurityGroups -(Optional) A JSON array of security groups to be excluded, each containing `graphId` and `name` properties. - -.EXAMPLE -Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" - -Updates the tenant setting "SomeSetting" for the capacity with ID "12345" and enables it. - -.EXAMPLE -Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" -EnabledSecurityGroups @(@{graphId="1";name="Group1"},@{graphId="2";name="Group2"}) - -Updates the tenant setting "SomeSetting" for the capacity with ID "12345", enables it, and specifies security groups to include. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricCapacityTenantSettingOverrides { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$CapacityId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$SettingTitle, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [bool]$EnableTenantSetting, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$DelegateToWorkspace, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Object]$EnabledSecurityGroups, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Object]$ExcludedSecurityGroups - ) - - try { - # Validate authentication token - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Validate Security Groups if provided - if ($EnabledSecurityGroups) { - foreach ($enabledGroup in $EnabledSecurityGroups) { - if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) { - throw "Each enabled security group must contain 'graphId' and 'name' properties." - } - } - } - - if ($ExcludedSecurityGroups) { - foreach ($excludedGroup in $ExcludedSecurityGroups) { - if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) { - throw "Each excluded security group must contain 'graphId' and 'name' properties." - } - } - } - - # Construct API endpoint URL - $apiEndpointURI = "{0}/admin/capacities/{1}/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl, $CapacityId - Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug - - # Construct request body - $body = @{ - EnableTenantSetting = $EnableTenantSetting - SettingTitle = $SettingTitle - } - - if ($DelegateToWorkspace) { - $body.delegateToWorkspace = $DelegateToWorkspace - } - - if ($EnabledSecurityGroups) { - $body.enabledSecurityGroups = $EnabledSecurityGroups - } - - if ($ExcludedSecurityGroups) { - $body.excludedSecurityGroups = $ExcludedSecurityGroups - } - - # Convert body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 4 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Invoke Fabric API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson - - Write-Message -Message "Successfully updated capacity tenant setting overrides for CapacityId: $CapacityId and SettingTitle: $SettingTitle." -Level Info - return $response - } - catch { - $errorDetails = $_.Exception.Message - Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error - } -} - diff --git a/FabricTools/tiago/Public/Tenant/Update-FabricTenantSetting.ps1 b/FabricTools/tiago/Public/Tenant/Update-FabricTenantSetting.ps1 deleted file mode 100644 index 0a701151..00000000 --- a/FabricTools/tiago/Public/Tenant/Update-FabricTenantSetting.ps1 +++ /dev/null @@ -1,164 +0,0 @@ -<# -.SYNOPSIS -Updates tenant setting overrides for a specified capacity ID. - -.DESCRIPTION -The `Update-FabricCapacityTenantSettingOverrides` function updates tenant setting overrides in a Fabric environment by making a POST request to the appropriate API endpoint. It allows specifying settings such as enabling tenant settings, delegating to a workspace, and including or excluding security groups. - -.PARAMETER CapacityId -(Mandatory) The ID of the capacity for which the tenant setting overrides are being updated. - -.PARAMETER SettingTitle -(Mandatory) The title of the tenant setting to be updated. - -.PARAMETER EnableTenantSetting -(Mandatory) Indicates whether the tenant setting should be enabled. - -.PARAMETER DelegateToWorkspace -(Optional) Specifies the workspace to which the setting should be delegated. - -.PARAMETER EnabledSecurityGroups -(Optional) A JSON array of security groups to be enabled, each containing `graphId` and `name` properties. - -.PARAMETER ExcludedSecurityGroups -(Optional) A JSON array of security groups to be excluded, each containing `graphId` and `name` properties. - -.EXAMPLE -Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" - -Updates the tenant setting "SomeSetting" for the capacity with ID "12345" and enables it. - -.EXAMPLE -Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" -EnabledSecurityGroups @(@{graphId="1";name="Group1"},@{graphId="2";name="Group2"}) - -Updates the tenant setting "SomeSetting" for the capacity with ID "12345", enables it, and specifies security groups to include. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Update-FabricCapacityTenantSettingOverrides { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$TenantSettingName, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [bool]$EnableTenantSetting, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$DelegateToCapacity, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$DelegateToDomain, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$DelegateToWorkspace, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Object]$EnabledSecurityGroups, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Object]$ExcludedSecurityGroups, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Object]$Properties - ) - - try { - # Validate authentication token - Write-Message -Message "Validating authentication token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Authentication token is valid." -Level Debug - - # Validate Security Groups if provided - if ($EnabledSecurityGroups) { - foreach ($enabledGroup in $EnabledSecurityGroups) { - if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) { - throw "Each enabled security group must contain 'graphId' and 'name' properties." - } - } - } - - if ($ExcludedSecurityGroups) { - foreach ($excludedGroup in $ExcludedSecurityGroups) { - if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) { - throw "Each excluded security group must contain 'graphId' and 'name' properties." - } - } - } - - # Validate Security Groups if provided - if ($Properties) { - foreach ($property in $Properties) { - if (-not ($property.PSObject.Properties.Name -contains 'name' -and $property.PSObject.Properties.Name -contains 'type' -and $property.PSObject.Properties.Name -contains 'value')) { - throw "Each property object must include 'name', 'type', and 'value' properties to be valid." - } - } - } - - # Construct API endpoint URL - $apiEndpointURI = "{0}/admin/tenantsettings/{1}/update" -f $FabricConfig.BaseUrl, $TenantSettingName - Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug - - # Construct request body - $body = @{ - EnableTenantSetting = $EnableTenantSetting - } - - if ($DelegateToCapacity) { - $body.delegateToCapacity = $DelegateToCapacity - } - - if ($DelegateToDomain) { - $body.delegateToDomain = $DelegateToDomain - } - - if ($DelegateToWorkspace) { - $body.delegateToWorkspace = $DelegateToWorkspace - } - - if ($EnabledSecurityGroups) { - $body.enabledSecurityGroups = $EnabledSecurityGroups - } - - if ($ExcludedSecurityGroups) { - $body.excludedSecurityGroups = $ExcludedSecurityGroups - } - - if ($Properties) { - $body.properties = $Properties - } - - # Convert body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 5 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Invoke Fabric API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson - - Write-Message -Message "Successfully updated tenant setting." -Level Info - return $response - } - catch { - $errorDetails = $_.Exception.Message - Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error - } -} - diff --git a/FabricTools/tiago/Public/Users/Get-FabricUserListAccessEntities.ps1 b/FabricTools/tiago/Public/Users/Get-FabricUserListAccessEntities.ps1 deleted file mode 100644 index 8d15cdaa..00000000 --- a/FabricTools/tiago/Public/Users/Get-FabricUserListAccessEntities.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -<# -.SYNOPSIS - Retrieves access entities for a specified user in Microsoft Fabric. - -.DESCRIPTION - This function retrieves a list of access entities associated with a specified user in Microsoft Fabric. - It supports filtering by entity type and handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER UserId - The unique identifier of the user whose access entities are to be retrieved. This parameter is mandatory. - -.PARAMETER Type - The type of access entity to filter the results by. This parameter is optional and supports predefined values such as 'CopyJob', 'Dashboard', 'DataPipeline', etc. - -.EXAMPLE - Get-FabricUserListAccessEntities -UserId "user-12345" - This example retrieves all access entities associated with the user having ID "user-12345". - -.EXAMPLE - Get-FabricUserListAccessEntities -UserId "user-12345" -Type "Dashboard" - This example retrieves only the 'Dashboard' access entities associated with the user having ID "user-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricUserListAccessEntities { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$UserId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidateSet('CopyJob', ' Dashboard', 'DataPipeline', 'Datamart', 'Environment', 'Eventhouse', 'Eventstream', 'GraphQLApi', 'KQLDashboard', 'KQLDatabase', 'KQLQueryset', 'Lakehouse', 'MLExperiment', 'MLModel', 'MirroredDatabase', 'MountedDataFactory', 'Notebook', 'PaginatedReport', 'Reflex', 'Report', 'SQLDatabase', 'SQLEndpoint', 'SemanticModel', 'SparkJobDefinition', 'VariableLibrary', 'Warehouse')] - [string]$Type - ) - - try { - - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - - # Step 4: Loop to retrieve all capacities with continuation token - $apiEndpointURI = "{0}admin/users/{1}/access" -f $FabricConfig.BaseUrl, $UserId - if ($Type) { - $apiEndpointURI += "?type=$Type" - } - - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - return $response - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Warehouse. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Utils/Convert-FromBase64.ps1 b/FabricTools/tiago/Public/Utils/Convert-FromBase64.ps1 deleted file mode 100644 index 5aa99e13..00000000 --- a/FabricTools/tiago/Public/Utils/Convert-FromBase64.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -<# -.SYNOPSIS - Decodes a Base64-encoded string into its original text representation. - -.DESCRIPTION - The Convert-FromBase64 function takes a Base64-encoded string as input, decodes it into a byte array, - and converts it back into a UTF-8 encoded string. It is useful for reversing Base64 encoding applied - to text or other data. - -.PARAMETER Base64String - The Base64-encoded string that you want to decode. - -.EXAMPLE - Convert-FromBase64 -Base64String "SGVsbG8sIFdvcmxkIQ==" - - Output: - Hello, World! - -.EXAMPLE - $encodedString = "U29tZSBlbmNvZGVkIHRleHQ=" - Convert-FromBase64 -Base64String $encodedString - - Output: - Some encoded text - -.NOTES - - This function assumes the Base64 input is a valid UTF-8 encoded string. - - Any decoding errors will throw a descriptive error message. - -.AUTHOR -Tiago Balabuch -#> -function Convert-FromBase64 { - param ( - [Parameter(Mandatory = $true)] - [string]$Base64String - ) - - try { - # Step 1: Convert the Base64 string to a byte array - $bytes = [Convert]::FromBase64String($Base64String) - - # Step 2: Convert the byte array back to a UTF-8 string - $decodedString = [System.Text.Encoding]::UTF8.GetString($bytes) - - # Step 3: Return the decoded string - return $decodedString - } - catch { - # Step 4: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while decoding from Base64: $errorDetails" -Level Error - throw "An error occurred while decoding from Base64: $_" - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Utils/Convert-ToBase64.ps1 b/FabricTools/tiago/Public/Utils/Convert-ToBase64.ps1 deleted file mode 100644 index 8b152567..00000000 --- a/FabricTools/tiago/Public/Utils/Convert-ToBase64.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -<# -.SYNOPSIS - Encodes the content of a file into a Base64-encoded string. - -.DESCRIPTION - The Convert-ToBase64 function takes a file path as input, reads the file's content as a byte array, - and converts it into a Base64-encoded string. This is useful for embedding binary data (e.g., images, - documents) in text-based formats such as JSON or XML. - -.PARAMETER filePath - The full path to the file whose contents you want to encode into Base64. - -.EXAMPLE - Convert-ToBase64 -filePath "C:\Path\To\File.txt" - - Output: - VGhpcyBpcyBhbiBlbmNvZGVkIGZpbGUu - -.EXAMPLE - $encodedContent = Convert-ToBase64 -filePath "C:\Path\To\Image.jpg" - $encodedContent | Set-Content -Path "C:\Path\To\EncodedImage.txt" - - This saves the Base64-encoded content of the image to a text file. - -.NOTES - - Ensure the file exists at the specified path before running this function. - - Large files may cause memory constraints due to full loading into memory. - -.AUTHOR -Tiago Balabuch -#> -function Convert-ToBase64 { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$filePath - ) - try { - - # Step 1: Reading all the bytes from the file - #$bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString) - Write-Message -Message "Reading all the bytes from the file specified: $filePath" -Level Debug - $fileBytes = [System.IO.File]::ReadAllBytes($filePath) - - # Step 2: Convert the byte array to Base64 string - Write-Message -Message "Convert the byte array to Base64 string" -Level Debug - $base64String = [Convert]::ToBase64String($fileBytes) - - # Step 3: Return the encoded string - Write-Message -Message "Return the encoded string for the file: $filePath" -Level Debug - return $base64String - } - catch { - # Step 4: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while encoding to Base64: $errorDetails" -Level Error - throw "An error occurred while encoding to Base64: $_" - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperation.ps1 b/FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperation.ps1 deleted file mode 100644 index 4fa2cc07..00000000 --- a/FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperation.ps1 +++ /dev/null @@ -1,88 +0,0 @@ -<# -.SYNOPSIS -Monitors the status of a long-running operation in Microsoft Fabric. - -.DESCRIPTION -The Get-FabricLongRunningOperation function queries the Microsoft Fabric API to check the status of a -long-running operation. It periodically polls the operation until it reaches a terminal state (Succeeded or Failed). - -.PARAMETER operationId -The unique identifier of the long-running operation to be monitored. - -.PARAMETER retryAfter -The interval (in seconds) to wait between polling the operation status. The default is 5 seconds. - -.EXAMPLE -Get-FabricLongRunningOperation -operationId "12345-abcd-67890-efgh" -retryAfter 10 - -This command polls the status of the operation with the given operationId every 10 seconds until it completes. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - -.AUTHOR -Tiago Balabuch - -#> -function Get-FabricLongRunningOperation { - param ( - [Parameter(Mandatory = $false)] - [string]$operationId, - - [Parameter(Mandatory = $false)] - [string]$location, - - [Parameter(Mandatory = $false)] - [int]$retryAfter = 5 - ) - - # Step 1: Construct the API URL - if ($location) { - # Use the Location header to define the operationUrl - $apiEndpointUrl = $location - } - else { - $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}" -f $operationId - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - try { - do { - - # Step 2: Wait before the next request - if ($retryAfter) { - Start-Sleep -Seconds $retryAfter - } - else { - Start-Sleep -Seconds 5 # Default retry interval if no Retry-After header - } - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -ResponseHeadersVariable responseHeader ` - -StatusCodeVariable statusCode - - # Step 3: Parse the response - $jsonOperation = $response | ConvertTo-Json - $operation = $jsonOperation | ConvertFrom-Json - - # Log status for debugging - Write-Message -Message "Operation Status: $($operation.status)" -Level Debug - - - } while ($operation.status -notin @("Succeeded", "Completed", "Failed")) - - # Step 5: Return the operation result - return $operation - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while checking the operation: $errorDetails" -Level Error - throw - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperationResult.ps1 b/FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperationResult.ps1 deleted file mode 100644 index 00000c6c..00000000 --- a/FabricTools/tiago/Public/Utils/Get-FabricLongRunningOperationResult.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -<# -.SYNOPSIS -Retrieves the result of a completed long-running operation from the Microsoft Fabric API. - -.DESCRIPTION -The Get-FabricLongRunningOperationResult function queries the Microsoft Fabric API to fetch the result -of a specific long-running operation. This is typically used after confirming the operation has completed successfully. - -.PARAMETER operationId -The unique identifier of the completed long-running operation whose result you want to retrieve. - -.EXAMPLE -Get-FabricLongRunningOperationResult -operationId "12345-abcd-67890-efgh" - -This command fetches the result of the operation with the specified operationId. - -.NOTES -- Ensure the Fabric API headers (e.g., authorization tokens) are defined in $FabricConfig.FabricHeaders. -- This function does not handle polling. Ensure the operation is in a terminal state before calling this function. - -.AUTHOR -Tiago Balabuch - -#> -function Get-FabricLongRunningOperationResult { - param ( - [Parameter(Mandatory = $true)] - [string]$operationId - ) - - # Step 1: Construct the API URL - $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}/result" -f $operationId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - try { - # Step 2: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - - # Step 3: Return the result - Write-Message -Message "Result response code: $statusCode" -Level Debug - Write-Message -Message "Result return: $response" -Level Debug - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Debug - Write-Message -Message "Error: $($response.message)" -Level Debug - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Debug - Write-Message "Error Code: $($response.errorCode)" -Level Debug - } - - return $response - } - catch { - # Step 3: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while returning the operation result: $errorDetails" -Level Error - throw - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Utils/Invoke-FabricAPIRequest.ps1 b/FabricTools/tiago/Public/Utils/Invoke-FabricAPIRequest.ps1 deleted file mode 100644 index c2d4f894..00000000 --- a/FabricTools/tiago/Public/Utils/Invoke-FabricAPIRequest.ps1 +++ /dev/null @@ -1,133 +0,0 @@ -function Invoke-FabricAPIRequest { - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [hashtable]$Headers, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$BaseURI, - - [Parameter(Mandatory = $true)] - [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] - [string] $Method, - - [Parameter(Mandatory = $false)] - [string] $Body, - - [Parameter(Mandatory = $false)] - [string] $ContentType = "application/json; charset=utf-8" - ) - - $continuationToken = $null - $results = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - do { - $apiEndpointURI = $BaseURI - if ($null -ne $continuationToken) { - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - - if ($BaseURI -like "*`?*") { - # URI already has parameters, append with & - $apiEndpointURI = "$BaseURI&continuationToken=$encodedToken" - } - else { - # No existing parameters, append with ? - $apiEndpointURI = "$BaseURI?continuationToken=$encodedToken" - } - } - Write-Message -Message "Calling API: $apiEndpointURI" -Level Debug - - $invokeParams = @{ - Headers = $Headers - Uri = $apiEndpointURI - Method = $Method - ErrorAction = 'Stop' - SkipHttpErrorCheck = $true - ResponseHeadersVariable = 'responseHeader' - StatusCodeVariable = 'statusCode' - # TimeoutSec = $timeoutSec - } - - if ($method -in @('Post', 'Put', 'Patch') -and $body) { - $invokeParams.Body = $body - $invokeParams.ContentType = $contentType - } - - $response = Invoke-RestMethod @invokeParams - switch ($statusCode) { - - 200 { - Write-Message -Message "API call succeeded." -Level Debug - # Step 5: Handle and log the response - if ($response) { - if ($response.PSObject.Properties.Name -contains 'value') { - $results += $response.value - } - elseif ($response.PSObject.Properties.Name -contains 'accessEntities') { - $results += $response.accessEntities - } - else { - $results += $response - } - $continuationToken = $response.PSObject.Properties.Match("continuationToken") ? $response.continuationToken : $null - } - else { - Write-Message -Message "No data in response" -Level Debug - $continuationToken = $null - } - } - 201 { - Write-Message -Message "Resource created successfully." -Level Info - return $response - } - 202 { - # Step 6: Handle long-running operations - Write-Message -Message "Request accepted. Provisioning in progress." -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - # Need to implement a retry mechanism for long running operations - # [string]$retryAfter = $responseHeader["Retry-After"] - Write-Message -Message "Operation ID: '$operationId', Location: '$location'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation succeeded. Fetching result." -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - 400 { $errorMsg = "Bad Request" } - 401 { $errorMsg = "Unauthorized" } - 403 { $errorMsg = "Forbidden" } - 404 { $errorMsg = "Not Found" } - 409 { $errorMsg = "Conflict" } - 429 { $errorMsg = "Too Many Requests" } - 500 { $errorMsg = "Internal Server Error" } - default { $errorMsg = "Unexpected response code: $statusCode" } - } - - if ($statusCode -notin 200, 201, 202) { - Write-Message -Message "$errorMsg : $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message -Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } while ($null -ne $continuationToken) - - return $results -} diff --git a/FabricTools/tiago/Public/Utils/Set-FabricApiHeaders.ps1 b/FabricTools/tiago/Public/Utils/Set-FabricApiHeaders.ps1 deleted file mode 100644 index e1c7a43d..00000000 --- a/FabricTools/tiago/Public/Utils/Set-FabricApiHeaders.ps1 +++ /dev/null @@ -1,116 +0,0 @@ -<# -.SYNOPSIS -Sets the Fabric API headers with a valid token for the specified Azure tenant. - -.DESCRIPTION -The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. -It also updates the token expiration time and global tenant ID. - -.PARAMETER TenantId -The Azure tenant ID for which the access token is requested. - -.PARAMETER AppId -The Azure app ID for which the service principal access token is requested. - -.PARAMETER AppSecret -The Azure App secret for which the service principal access token is requested. - -.EXAMPLE -Set-FabricApiHeaders -TenantId "your-tenant-id" - -Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. - -.EXAMPLE -$tenantId = "999999999-99999-99999-9999-999999999999" -$appId = "888888888-88888-88888-8888-888888888888" -$appSecret = "your-app-secret" -$secureAppSecret = $appSecret | ConvertTo-SecureString -AsPlainText -Force - -Set-FabricApiHeader -TenantId $tenantId -AppId $appId -AppSecret $secureAppSecret -Logs in to Azure with the specified tenant ID, retrieves an access token for the service principal, and configures the Fabric headers. - -.NOTES -- Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). -- Relies on a global `$FabricConfig` object for storing headers and token metadata. - -.AUTHOR -Tiago Balabuch -#> - -function Set-FabricApiHeaders { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$TenantId, - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$AppId, - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Security.SecureString]$AppSecret - ) - - try { - # Step 1: Connect to the Azure account - Write-Message -Message "Logging in to Azure tenant: $TenantId" -Level Info - - # Step 2: Performing validation checks on the parameters passed to a function or script. - # Checks if 'AppId' is provided without 'AppSecret' and vice versa. - if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { - Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error - throw "AppSecret is required when using AppId." - } - if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { - Write-Message -Message "AppId is required when using AppSecret." -Level Error - throw "AppId is required when using AppId." - } - # Step 3: Connect to the Azure account - # Using AppId and AppSecret - if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { - - Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug - Write-Message -Message "Logging in using the AppId: $AppId" -Level Info - $psCredential = [pscredential]::new($AppId, $AppSecret) - Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId - - } - # Using the current user - else { - - Write-Message -Message "Logging in using the current user" -Level Debug - Write-Message -Message "Logging in using the current user" -Level Info - Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null - } - - ## Step 4: Retrieve the access token for the Fabric API - Write-Message -Message "Retrieve the access token for the Fabric API: $TenantId" -Level Debug - $fabricToken = Get-AzAccessToken -AsSecureString -ResourceUrl $FabricConfig.ResourceUrl -ErrorAction Stop -WarningAction SilentlyContinue - - ## Step 5: Extract the plain token from the secure string - Write-Message -Message "Extract the plain token from the secure string" -Level Debug - $plainToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( - [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($fabricToken.Token) - ) - - ## Step 6: Set the headers in the global configuration - Write-Message -Message "Set the headers in the global configuration" -Level Debug - $FabricConfig.FabricHeaders = @{ - 'Content-Type' = 'application/json' - 'Authorization' = "Bearer $plainToken" - } - - ## Step 7: Update token metadata in the global configuration - Write-Message -Message "Update token metadata in the global configuration" -Level Debug - $FabricConfig.TokenExpiresOn = $fabricToken.ExpiresOn - $FabricConfig.TenantIdGlobal = $TenantId - - Write-Message -Message "Fabric token successfully configured." -Level Info - } - catch { - # Step 8: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to set Fabric token: $errorDetails" -Level Error - throw "Unable to configure Fabric token. Ensure tenant and API configurations are correct." - } -} diff --git a/FabricTools/tiago/Public/Warehouse/Get-FabricWarehouse.ps1 b/FabricTools/tiago/Public/Warehouse/Get-FabricWarehouse.ps1 deleted file mode 100644 index 304ede6f..00000000 --- a/FabricTools/tiago/Public/Warehouse/Get-FabricWarehouse.ps1 +++ /dev/null @@ -1,101 +0,0 @@ -<# -.SYNOPSIS - Retrieves warehouse details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves warehouse details from a specified workspace using either the provided WarehouseId or WarehouseName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the warehouse exists. This parameter is mandatory. - -.PARAMETER WarehouseId - The unique identifier of the warehouse to retrieve. This parameter is optional. - -.PARAMETER WarehouseName - The name of the warehouse to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseId "warehouse-67890" - This example retrieves the warehouse details for the warehouse with ID "warehouse-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseName "My Warehouse" - This example retrieves the warehouse details for the warehouse named "My Warehouse" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricWarehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WarehouseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WarehouseName - ) - - try { - # Step 1: Handle ambiguous input - if ($WarehouseId -and $WarehouseName) { - Write-Message -Message "Both 'WarehouseId' and 'WarehouseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - - - # Step 4: Loop to retrieve all capacities with continuation token - $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - - $Warehouses = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get ` - -Body $null - - # Step 8: Filter results based on provided parameters - $Warehouse = if ($WarehouseId) { - $Warehouses | Where-Object { $_.Id -eq $WarehouseId } - } - elseif ($WarehouseName) { - $Warehouses | Where-Object { $_.DisplayName -eq $WarehouseName } - } - else { - # Return all Warehouses if no filter is provided - Write-Message -Message "No filter provided. Returning all Warehouses." -Level Debug - $Warehouses - } - - # Step 9: Handle results - if ($Warehouse) { - Write-Message -Message "Warehouse found matching the specified criteria." -Level Debug - return $Warehouse - } - else { - Write-Message -Message "No Warehouse found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Warehouse. Error: $errorDetails" -Level Error - } - -} diff --git a/FabricTools/tiago/Public/Warehouse/New-FabricWarehouse.ps1 b/FabricTools/tiago/Public/Warehouse/New-FabricWarehouse.ps1 deleted file mode 100644 index 2b8a49af..00000000 --- a/FabricTools/tiago/Public/Warehouse/New-FabricWarehouse.ps1 +++ /dev/null @@ -1,83 +0,0 @@ -<# -.SYNOPSIS - Creates a new warehouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new warehouse - in the specified workspace. It supports optional parameters for warehouse description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the warehouse will be created. This parameter is mandatory. - -.PARAMETER WarehouseName - The name of the warehouse to be created. This parameter is mandatory. - -.PARAMETER WarehouseDescription - An optional description for the warehouse. - -.EXAMPLE - New-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseName "New Warehouse" -WarehouseDescription "Description of the new warehouse" - This example creates a new warehouse named "New Warehouse" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function New-FabricWarehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WarehouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WarehouseDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $WarehouseName - } - - if ($WarehouseDescription) { - $body.description = $WarehouseDescription - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson - - Write-Message -Message "Data Warehouse created successfully!" -Level Info - return $response - - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Warehouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Warehouse/Remove-FabricWarehouse.ps1 b/FabricTools/tiago/Public/Warehouse/Remove-FabricWarehouse.ps1 deleted file mode 100644 index 42b245f0..00000000 --- a/FabricTools/tiago/Public/Warehouse/Remove-FabricWarehouse.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -<# -.SYNOPSIS - Removes a warehouse from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove a warehouse - from the specified workspace using the provided WorkspaceId and WarehouseId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the warehouse will be removed. - -.PARAMETER WarehouseId - The unique identifier of the warehouse to be removed. - -.EXAMPLE - Remove-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseId "warehouse-67890" - This example removes the warehouse with ID "warehouse-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Remove-FabricWarehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WarehouseId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/warehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WarehouseId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Delete ` - - Write-Message -Message "Warehouse '$WarehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - return $response - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Warehouse '$WarehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Warehouse/Update-FabricWarehouse.ps1 b/FabricTools/tiago/Public/Warehouse/Update-FabricWarehouse.ps1 deleted file mode 100644 index fd6e2856..00000000 --- a/FabricTools/tiago/Public/Warehouse/Update-FabricWarehouse.ps1 +++ /dev/null @@ -1,91 +0,0 @@ -<# -.SYNOPSIS - Updates an existing warehouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing warehouse - in the specified workspace. It supports optional parameters for warehouse description. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the warehouse exists. This parameter is optional. - -.PARAMETER WarehouseId - The unique identifier of the warehouse to be updated. This parameter is mandatory. - -.PARAMETER WarehouseName - The new name of the warehouse. This parameter is mandatory. - -.PARAMETER WarehouseDescription - An optional new description for the warehouse. - -.EXAMPLE - Update-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseId "warehouse-67890" -WarehouseName "Updated Warehouse" -WarehouseDescription "Updated description" - This example updates the warehouse with ID "warehouse-67890" in the workspace with ID "workspace-12345" with a new name and description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Update-FabricWarehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WarehouseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WarehouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WarehouseDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/warehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WarehouseId - Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $WarehouseName - } - - if ($WarehouseDescription) { - $body.description = $WarehouseDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Patch ` - -Body $bodyJson - - # Step 6: Handle results - Write-Message -Message "Warehouse '$WarehouseName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update Warehouse. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 b/FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 deleted file mode 100644 index cc042432..00000000 --- a/FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 +++ /dev/null @@ -1,101 +0,0 @@ -<# -.SYNOPSIS -Provisions an identity for a Fabric workspace. - -.DESCRIPTION -The `Add-FabricWorkspaceIdentity` function provisions an identity for a specified workspace by making an API call. - -.PARAMETER WorkspaceId -The unique identifier of the workspace for which the identity will be provisioned. - -.EXAMPLE -Add-FabricWorkspaceIdentity -WorkspaceId "workspace123" - -Provisions a Managed Identity for the workspace with ID "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Add-FabricWorkspaceIdentity { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/provisionIdentity" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Workspace identity was successfully provisioned for workspace '$WorkspaceId'." -Level Info - return $response - } - 202 { - Write-Message -Message "Workspace identity provisioning accepted for workspace '$WorkspaceId'. Provisioning in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 5: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to provision workspace identity. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 b/FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index 4d29053b..00000000 --- a/FabricTools/tiago/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -<# -.SYNOPSIS -Assigns a role to a principal for a specified Fabric workspace. - -.DESCRIPTION -The `Add-FabricWorkspaceRoleAssignments` function assigns a role (e.g., Admin, Contributor, Member, Viewer) to a principal (e.g., User, Group, ServicePrincipal) in a Fabric workspace by making a POST request to the API. - -.PARAMETER WorkspaceId -The unique identifier of the workspace. - -.PARAMETER PrincipalId -The unique identifier of the principal (User, Group, etc.) to assign the role. - -.PARAMETER PrincipalType -The type of the principal. Allowed values: Group, ServicePrincipal, ServicePrincipalProfile, User. - -.PARAMETER WorkspaceRole -The role to assign to the principal. Allowed values: Admin, Contributor, Member, Viewer. - -.EXAMPLE -Add-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -PrincipalId "principal123" -PrincipalType "User" -WorkspaceRole "Admin" - -Assigns the Admin role to the user with ID "principal123" in the workspace "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Add-FabricWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$PrincipalId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Group', 'ServicePrincipal', 'ServicePrincipalProfile', 'User')] - [string]$PrincipalType, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Admin', 'Contributor', 'Member', 'Viewer')] - [string]$WorkspaceRole - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - principal = @{ - id = $PrincipalId - type = $PrincipalType - } - role = $WorkspaceRole - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 4 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 201) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle empty response - if (-not $response) { - Write-Message -Message "No data returned from the API." -Level Warning - return $null - } - - Write-Message -Message "Role '$WorkspaceRole' assigned to principal '$PrincipalId' successfully in workspace '$WorkspaceId'." -Level Info - return $response - - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to assign role. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 b/FabricTools/tiago/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 deleted file mode 100644 index 0754a94b..00000000 --- a/FabricTools/tiago/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 +++ /dev/null @@ -1,83 +0,0 @@ -<# -.SYNOPSIS -Assigns a Fabric workspace to a specified capacity. - -.DESCRIPTION -The `Assign-FabricWorkspaceCapacity` function sends a POST request to assign a workspace to a specific capacity. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to be assigned. - -.PARAMETER CapacityId -The unique identifier of the capacity to which the workspace should be assigned. - -.EXAMPLE -Assign-FabricWorkspaceCapacity -WorkspaceId "workspace123" -CapacityId "capacity456" - -Assigns the workspace with ID "workspace123" to the capacity "capacity456". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Assign-FabricWorkspaceCapacity { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$CapacityId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/assignToCapacity" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - capacityId = $CapacityId - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 4 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 202) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Successfully assigned workspace with ID '$WorkspaceId' to capacity with ID '$CapacityId'." -Level Info - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to assign workspace with ID '$WorkspaceId' to capacity with ID '$CapacityId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Get-FabricWorkspace.ps1 b/FabricTools/tiago/Public/Workspace/Get-FabricWorkspace.ps1 deleted file mode 100644 index 77797006..00000000 --- a/FabricTools/tiago/Public/Workspace/Get-FabricWorkspace.ps1 +++ /dev/null @@ -1,149 +0,0 @@ -<# -.SYNOPSIS -Retrieves details of a Microsoft Fabric workspace by its ID or name. - -.DESCRIPTION -The `Get-FabricWorkspace` function fetches workspace details from the Fabric API. It supports filtering by WorkspaceId or WorkspaceName. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to retrieve. - -.PARAMETER WorkspaceName -The display name of the workspace to retrieve. - -.EXAMPLE -Get-FabricWorkspace -WorkspaceId "workspace123" - -Fetches details of the workspace with ID "workspace123". - -.EXAMPLE -Get-FabricWorkspace -WorkspaceName "MyWorkspace" - -Fetches details of the workspace with the name "MyWorkspace". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Returns the matching workspace details or all workspaces if no filter is provided. - -Author: Tiago Balabuch -#> - -function Get-FabricWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WorkspaceName - ) - - try { - # Step 1: Handle ambiguous input - if ($WorkspaceId -and $WorkspaceName) { - Write-Message -Message "Both 'WorkspaceId' and 'WorkspaceName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $workspaces = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $workspaces += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $workspace = if ($WorkspaceId) { - $workspaces | Where-Object { $_.Id -eq $WorkspaceId } - } - elseif ($WorkspaceName) { - $workspaces | Where-Object { $_.DisplayName -eq $WorkspaceName } - } - else { - # Return all workspaces if no filter is provided - Write-Message -Message "No filter provided. Returning all workspaces." -Level Debug - $workspaces - } - - # Step 9: Handle results - if ($workspace) { - Write-Message -Message "Workspace found matching the specified criteria." -Level Debug - return $workspace - } - else { - Write-Message -Message "No workspace found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve workspace. Error: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/FabricTools/tiago/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 b/FabricTools/tiago/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index 02b2f585..00000000 --- a/FabricTools/tiago/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,153 +0,0 @@ -<# -.SYNOPSIS -Retrieves role assignments for a specified Fabric workspace. - -.DESCRIPTION -The `Get-FabricWorkspaceRoleAssignments` function fetches the role assignments associated with a Fabric workspace by making a GET request to the API. If `WorkspaceRoleAssignmentId` is provided, it retrieves the specific role assignment. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to fetch role assignments for. - -.PARAMETER WorkspaceRoleAssignmentId -(Optional) The unique identifier of a specific role assignment to retrieve. - -.EXAMPLE -Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" - -Fetches all role assignments for the workspace with the ID "workspace123". - -.EXAMPLE -Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "role123" - -Fetches the role assignment with the ID "role123" for the workspace "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Get-FabricWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceRoleAssignmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $workspaceRoles = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $workspaceRoles += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - # Step 8: Filter results based on provided parameters - $roleAssignments = if ($WorkspaceRoleAssignmentId) { - $workspaceRoles | Where-Object { $_.Id -eq $WorkspaceRoleAssignmentId } - } - else { - $workspaceRoles - } - - # Step 9: Handle results - if ($roleAssignments) { - Write-Message -Message "Found $($roleAssignments.Count) role assignments for WorkspaceId '$WorkspaceId'." -Level Debug - # Transform data into custom objects - $results = foreach ($obj in $roleAssignments) { - [PSCustomObject]@{ - ID = $obj.id - PrincipalId = $obj.principal.id - DisplayName = $obj.principal.displayName - Type = $obj.principal.type - UserPrincipalName = $obj.principal.userDetails.userPrincipalName - aadAppId = $obj.principal.servicePrincipalDetails.aadAppId - Role = $obj.role - } - } - return $results - } - else { - if ($WorkspaceRoleAssignmentId) { - Write-Message -Message "No role assignment found with ID '$WorkspaceRoleAssignmentId' for WorkspaceId '$WorkspaceId'." -Level Warning - } - else { - Write-Message -Message "No role assignments found for WorkspaceId '$WorkspaceId'." -Level Warning - } - return @() - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/New-FabricWorkspace.ps1 b/FabricTools/tiago/Public/Workspace/New-FabricWorkspace.ps1 deleted file mode 100644 index 4da24893..00000000 --- a/FabricTools/tiago/Public/Workspace/New-FabricWorkspace.ps1 +++ /dev/null @@ -1,122 +0,0 @@ -<# -.SYNOPSIS -Creates a new Fabric workspace with the specified display name. - -.DESCRIPTION -The `Add-FabricWorkspace` function creates a new workspace in the Fabric platform by sending a POST request to the API. It validates the display name and handles both success and error responses. - -.PARAMETER WorkspaceName -The display name of the workspace to be created. Must only contain alphanumeric characters, spaces, and underscores. - -.EXAMPLE -Add-FabricWorkspace -WorkspaceName "NewWorkspace" - -Creates a workspace named "NewWorkspace". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function New-FabricWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WorkspaceName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CapacityId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $WorkspaceName - } - - if ($WorkspaceDescription) { - $body.description = $WorkspaceDescription - } - - if ($CapacityId) { - $body.capacityId = $CapacityId - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Workspace '$WorkspaceName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Workspace '$WorkspaceName' creation accepted. Provisioning in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create workspace. Error: $errorDetails" -Level Error - - } -} diff --git a/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspace.ps1 b/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspace.ps1 deleted file mode 100644 index d65a2be7..00000000 --- a/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspace.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -<# -.SYNOPSIS -Deletes an existing Fabric workspace by its workspace ID. - -.DESCRIPTION -The `Remove-FabricWorkspace` function deletes a workspace in the Fabric platform by sending a DELETE request to the API. It validates the workspace ID and handles both success and error responses. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to be deleted. - -.EXAMPLE -Remove-FabricWorkspace -WorkspaceId "workspace123" - -Deletes the workspace with the ID "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> -function Remove-FabricWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Workspace '$WorkspaceId' deleted successfully!" -Level Info - return $null - - } - catch { - # Step 5: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error - return $null - } -} diff --git a/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 b/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 deleted file mode 100644 index 2a55d5d4..00000000 --- a/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 +++ /dev/null @@ -1,94 +0,0 @@ -<# -.SYNOPSIS -Deprovisions the Managed Identity for a specified Fabric workspace. - -.DESCRIPTION -The `Remove-FabricWorkspaceCapacity` function deprovisions the Managed Identity from the given workspace by calling the appropriate API endpoint. - -.PARAMETER WorkspaceId -The unique identifier of the workspace from which the identity will be removed. - -.EXAMPLE -Remove-FabricWorkspaceCapacity -WorkspaceId "workspace123" - -Deprovisions the Managed Identity for the workspace with ID "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Remove-FabricWorkspaceIdentity { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/deprovisionIdentity" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle and log the response - switch ($statusCode) { - 200 { - Write-Message -Message "Workspace identity was successfully deprovisioned for workspace '$WorkspaceId'." -Level Info - return $response.value - } - 202 { - Write-Message -Message "Workspace identity deprovisioning accepted for workspace '$WorkspaceId'. Deprovisioning in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 5: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to deprovision workspace identity. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 b/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index 36be3a18..00000000 --- a/FabricTools/tiago/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -<# -.SYNOPSIS -Removes a role assignment from a Fabric workspace. - -.DESCRIPTION -The `Remove-FabricWorkspaceRoleAssignment` function deletes a specific role assignment from a Fabric workspace by making a DELETE request to the API. - -.PARAMETER WorkspaceId -The unique identifier of the workspace. - -.PARAMETER WorkspaceRoleAssignmentId -The unique identifier of the role assignment to be removed. - -.EXAMPLE -Remove-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "role123" - -Removes the role assignment with the ID "role123" from the workspace "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceRoleAssignmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WorkspaceRoleAssignmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Role assignment '$WorkspaceRoleAssignmentId' successfully removed from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to remove role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 b/FabricTools/tiago/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 deleted file mode 100644 index 8c8ce8bf..00000000 --- a/FabricTools/tiago/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -<# -.SYNOPSIS -Unassigns a Fabric workspace from its capacity. - -.DESCRIPTION -The `Unassign-FabricWorkspaceCapacity` function sends a POST request to unassign a workspace from its assigned capacity. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to be unassigned from its capacity. - -.EXAMPLE -Unassign-FabricWorkspaceCapacity -WorkspaceId "workspace123" - -Unassigns the workspace with ID "workspace123" from its capacity. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Unassign-FabricWorkspaceCapacity { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/unassignFromCapacity" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Message - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - - # Step 4: Validate the response code - if ($statusCode -ne 202) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Workspace capacity has been successfully unassigned from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to unassign workspace from capacity. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Update-FabricWorkspace.ps1 b/FabricTools/tiago/Public/Workspace/Update-FabricWorkspace.ps1 deleted file mode 100644 index 77b1056a..00000000 --- a/FabricTools/tiago/Public/Workspace/Update-FabricWorkspace.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -<# -.SYNOPSIS -Updates the properties of a Fabric workspace. - -.DESCRIPTION -The `Update-FabricWorkspace` function updates the name and/or description of a specified Fabric workspace by making a PATCH request to the API. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to be updated. - -.PARAMETER WorkspaceName -The new name for the workspace. - -.PARAMETER WorkspaceDescription -(Optional) The new description for the workspace. - -.EXAMPLE -Update-FabricWorkspace -WorkspaceId "workspace123" -WorkspaceName "NewWorkspaceName" - -Updates the name of the workspace with the ID "workspace123" to "NewWorkspaceName". - -.EXAMPLE -Update-FabricWorkspace -WorkspaceId "workspace123" -WorkspaceName "NewName" -WorkspaceDescription "Updated description" - -Updates both the name and description of the workspace "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Update-FabricWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WorkspaceName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceDescription - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $WorkspaceName - } - - if ($WorkspaceDescription) { - $body.description = $WorkspaceDescription - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle results - Write-Message -Message "Workspace '$WorkspaceName' updated successfully!" -Level Info - return $response - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update workspace. Error: $errorDetails" -Level Error - } -} diff --git a/FabricTools/tiago/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 b/FabricTools/tiago/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index f619e461..00000000 --- a/FabricTools/tiago/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,104 +0,0 @@ -<# -.SYNOPSIS -Updates the role assignment for a specific principal in a Fabric workspace. - -.DESCRIPTION -The `Update-FabricWorkspaceRoleAssignment` function updates the role assigned to a principal in a workspace by making a PATCH request to the API. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the role assignment exists. - -.PARAMETER WorkspaceRoleAssignmentId -The unique identifier of the role assignment to be updated. - -.PARAMETER WorkspaceRole -The new role to assign to the principal. Must be one of the following: -- Admin -- Contributor -- Member -- Viewer - -.EXAMPLE -Update-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "assignment456" -WorkspaceRole "Admin" - -Updates the role assignment to "Admin" for the specified workspace and role assignment. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Update-FabricWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceRoleAssignmentId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Admin', 'Contributor', 'Member', 'Viewer')] - [string]$WorkspaceRole - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WorkspaceRoleAssignmentId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Message - - # Step 3: Construct the request body - $body = @{ - role = $WorkspaceRole - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 4 -Compress - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle empty response - if (-not $response) { - Write-Message -Message "No data returned from the API." -Level Warning - return $null - } - - Write-Message -Message "Role assignment $WorkspaceRoleAssignmentId updated successfully in workspace '$WorkspaceId'." -Level Info - return $response - - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to update role assignment. Error: $errorDetails" -Level Error - } -} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3ce21bc7..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Data Platform Community - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 5eb44acb..00000000 --- a/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# FabricTools PowerShell Module - -[![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/FabricTools?label=PowerShell%20Gallery&color=blue)](https://www.powershellgallery.com/packages/FabricTools) -![PowerShell Gallery Downloads](https://img.shields.io/powershellgallery/dt/FabricTools?label=PSGallery%20downloads) - -drawing - -**FabricTools** is a PowerShell module to able to do more with Microsoft Fabric and Power BI. -It allows for various administrative tasks to be automated and integrated into workflows. - -We are at an early stage of development and the module is in its **Public PREVIEW**. -Do NOT use it with Production environments. -
-
- -## Features - -- Manage Microsoft Fabric workspaces and datasets. -- Assign Microsoft Fabric workspaces to capacities. -- Retrieve and manipulate Microsoft Fabric tenant settings. -- Handle Microsoft Fabric access tokens for authentication. -- Suspend and resume Microsoft Fabric capacities. -- Fabric-friendly aliases for lots of the old PowerBI cmdlets - -## Getting Started - -These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. - -### Prerequisites - -- PowerShell 5.1 or higher -- Access to PowerBI service and Azure subscription (for certain functions) -- Necessary permissions to manage PowerBI workspaces and Fabric capacities -- The following PowerShell modules: MicrosoftPowerBIMgmt, Az.Accounts, Az.Resources - -### Installing - -To install the FabricTools module, you can install it from the PowerShell Gallery: - -```powershell -Install-Module FabricTools -``` - -Or clone the repository to your local machine and import the module: - -```powershell -# Clone the repository -git clone https://github.com/dataplat/FabricTools.git - -# Import the module -Import-Module ./FabricTools/FabricTools.psm1 -``` - - - -## Usage - -Once imported, you can call any of the functions provided by the module. For example: - -```powershell -# Assign a workspace to a capacity -Register-FabricWorkspaceToCapacity -WorkspaceId "Workspace-GUID" -CapacityId "Capacity-GUID" -``` - -Refer to the individual function documentation for detailed usage instructions. - -Every now and again the authentication token might time out. Run this to get a new one: -```powershell -Set-FabricAuthToken -``` - -If you want to change user context run this: -```powershell -Set-FabricAuthToken -reset -``` - - -## Release Notes - -The entire history of changes to this module can be find here: [Release Notes](ReleaseNotes.md) - - -## Contributing - -Contributions to FabricTools are welcome. - -> Note: In this early stage of development, we are working hard to build strong foundations for this module and further contribution guidance to ensure the quality of this code. **Therefore, please get in touch with us before you commit any code and create a PR.** - -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. - -## Authors - -- **Ioana Bouariu** - *Initial work* - [Jojobit](https://github.com/Jojobit) -- **Frank Geisler** - *Author of RTI functions* - [Frank Geisler](https://github.com/Frank-Geisler) -- **Kamil Nowinski** - *Refactoring, unification, further commands* - [NowinskiK](https://github.com/NowinskiK) - -See also the list of [contributors](https://github.com/dataplat/FabricTools/contributors) who participated in this project. - -## License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## Acknowledgments - -- GitHub Copilot and ChatGPT for helping with the documentation -- [**Rui Romano**](https://github.com/RuiRomano) - His work on a [Fabric PowerShell module](https://github.com/RuiRomano/fabricps-pbip) has been included into this module with his permission. Thanks, Rui! diff --git a/ReleaseNotes.md b/ReleaseNotes.md deleted file mode 100644 index cdd10135..00000000 --- a/ReleaseNotes.md +++ /dev/null @@ -1,54 +0,0 @@ -# Release Notes - -## Version 0.10.0: -* Added new functions: -- `New-FabricLakehouse` -- `Get-FabricConnection` - -## Version 0.9.0: -* Added few new functions: - - 'Get-FabricSQLDatabase', - - 'Remove-FabricSQLDatabase', - - 'Get-FabricCapacitySkus', - - 'Confirm-FabricAuthToken' -* Unified objects in module scope (`FabricSession`, `AzureSession`) -* Module scope holds all api urls, header params for requests -* Unified function to authenticate to Fabric API with bearer token -* Auto-refresh token when expires -* Removed `$authToken` as input param in several functions -* Removed usage of `$env:azToken` from several functions -* Get-FabricItem accepts Workspaces as input-pipeline -* Minor cosmetic changes - -## Version 0.8.0.0: -- Import original code from FabTools project authored by [Ioana Bouariu](https://github.com/Jojobit) -- Import functions from powerrti project authored by [Frank Geisler](https://github.com/Frank-Geisler) -- Rename project to FabricTools with new guid -- Refactoring of few functions -- Documentation generated & helper folder with scripts - -## Version 0.7.0.4: -- Fix a bug where the Fabric API didn't have a result URI - -## Version 0.7.0.3: -- Fixed the functions related to checking, pausing and activating Fabric capacities in Azure - -## Version 0.7.0.2: -- Fixed a bug that made the the module return an error on the first attempt to get data from the Rest API. - -## Version 0.7.0.1: -- Removed the parameter outfile in the function Invoke-FabricAPIRequest, as it led to an error in PowerShell version 7.4 - -## Version 0.7.0: -- The official Rest API for Microsoft Fabric is now Public. This means that there are a lot of new possibilities for this module. -- After a great talk with Rui Romano, he's graciously allowed us to integrate the functions from his project: fabricps-pbip ([GitHub Repository](https://github.com/RuiRomano/fabricps-pbip)) into Fabtools. -- Lots of new functions to make it easier to work with Microsoft Fabric. -- It is now possible to export and import items from a workspace. Currently that includes reports (pbip), semantic models (datasets), spark jobs, and notebooks (ipynb). -- It is now possible to register and unregister a workspace to/from a capacity. -- Several functions have been rewritten to use the new fabric API endpoint rather than the old PowerBI API endpoint. - -## Version 0.6.0: -- Added Get-AllFabricCapacities function to get all capacities in a tenant. -- Added Invoke-FabricDatasetRefresh function to refresh a dataset. -- Changed the main functions to be with the Fabric prefix instead of Fab, and added Fab as aliases. -- Added IconUri to the manifest. diff --git a/Test/SamplePBIP/Sales.Dataset/.pbi/editorSettings.json b/Test/SamplePBIP/Sales.Dataset/.pbi/editorSettings.json deleted file mode 100644 index 6b092b69..00000000 --- a/Test/SamplePBIP/Sales.Dataset/.pbi/editorSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": "1.0", - "showHiddenFields": true, - "parallelQueryLoading": true, - "relationshipImportEnabled": true, - "shouldNotifyUserOfNameConflictResolution": true -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Dataset/definition.pbidataset b/Test/SamplePBIP/Sales.Dataset/definition.pbidataset deleted file mode 100644 index 8ca3cc6a..00000000 --- a/Test/SamplePBIP/Sales.Dataset/definition.pbidataset +++ /dev/null @@ -1,6 +0,0 @@ -{ - "version": "1.0", - "settings": { - "qnaEnabled": true - } -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Dataset/diagramLayout.json b/Test/SamplePBIP/Sales.Dataset/diagramLayout.json deleted file mode 100644 index fcb1ab4c..00000000 --- a/Test/SamplePBIP/Sales.Dataset/diagramLayout.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "version": "1.1.0", - "diagrams": [ - { - "ordinal": 0, - "scrollPosition": { - "x": 326, - "y": 0 - }, - "nodes": [ - { - "location": { - "x": 326.31359098587973, - "y": 3.6666666666666856 - }, - "nodeIndex": "Calendar", - "size": { - "height": 187, - "width": 254 - }, - "zIndex": 7 - }, - { - "location": { - "x": 664.04633538628775, - "y": 322.16666666666669 - }, - "nodeIndex": "Sales", - "size": { - "height": 481, - "width": 276 - }, - "zIndex": 3 - }, - { - "location": { - "x": 967.4, - "y": 50 - }, - "nodeIndex": "Customer", - "size": { - "height": 200, - "width": 234 - }, - "zIndex": 6 - }, - { - "location": { - "x": 1145.8, - "y": 368 - }, - "nodeIndex": "Product", - "size": { - "height": 497, - "width": 232 - }, - "zIndex": 5 - }, - { - "location": { - "x": 1774.1, - "y": 0 - }, - "nodeIndex": "Dynamic Measure", - "size": { - "height": 300, - "width": 234 - }, - "zIndex": 1 - }, - { - "location": { - "x": 1456.3000000000002, - "y": 55 - }, - "nodeIndex": "Smart Calcs", - "size": { - "height": 128, - "width": 234 - }, - "zIndex": 2 - }, - { - "location": { - "x": 232.86667175292951, - "y": 322 - }, - "nodeIndex": "Store", - "size": { - "height": 296, - "width": 234 - }, - "zIndex": 4 - }, - { - "location": { - "x": 2149.5, - "y": 0 - }, - "nodeIndex": "About", - "size": { - "height": 152, - "width": 234 - }, - "zIndex": 0 - }, - { - "location": { - "x": 2200.6333282470705, - "y": 356.5 - }, - "nodeIndex": "Parameter - Dimension", - "nodeLineageTag": "29294de5-e93a-47f4-bd78-26ec8efe7786", - "size": { - "height": 152, - "width": 234 - }, - "zIndex": 0 - }, - { - "location": { - "x": 2251.766656494141, - "y": 356.5 - }, - "nodeIndex": "Parameter - Measure", - "nodeLineageTag": "eee26640-bfec-44ed-b1e7-d56562bc25ed", - "size": { - "height": 152, - "width": 234 - }, - "zIndex": 0 - } - ], - "name": "All tables", - "zoomValue": 100, - "pinKeyFieldsToTop": false, - "showExtraHeaderInfo": false, - "hideKeyFieldsWhenCollapsed": false, - "tablesLocked": false - } - ], - "selectedDiagram": "All tables", - "defaultDiagram": "All tables" -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Dataset/item.config.json b/Test/SamplePBIP/Sales.Dataset/item.config.json deleted file mode 100644 index a525c9c1..00000000 --- a/Test/SamplePBIP/Sales.Dataset/item.config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "version": "1.0", - "logicalId": "7b1d84cb-2da6-446a-9bc0-218da23c7fb0" -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Dataset/item.metadata.json b/Test/SamplePBIP/Sales.Dataset/item.metadata.json deleted file mode 100644 index 777a2ffd..00000000 --- a/Test/SamplePBIP/Sales.Dataset/item.metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "dataset", - "displayName": "Sales" -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Dataset/model.bim b/Test/SamplePBIP/Sales.Dataset/model.bim deleted file mode 100644 index 227c508d..00000000 --- a/Test/SamplePBIP/Sales.Dataset/model.bim +++ /dev/null @@ -1,15833 +0,0 @@ -{ - "compatibilityLevel": 1567, - "model": { - "annotations": [ - { - "name": "PBIDesktopVersion", - "value": "2.122.442.0 (23.10)" - }, - { - "name": "__PBI_TimeIntelligenceEnabled", - "value": "0" - }, - { - "name": "PBI_QueryOrder", - "value": "[\"RangeStart\",\"RangeEnd\",\"Environment\",\"Randomizer\",\"Calendar\",\"Sales\",\"Product\",\"Customer\",\"Store\",\"RAW-Product\",\"RAW-Store\",\"RAW-Customer\",\"RAW-Sales\",\"RAW-SalesDateAdjustedAndSalesRandomized\",\"RAW-CurrencyExchange\",\"About\"]" - }, - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"PARTITION_NAME_SHOULD_MATCH_TABLE_NAME_FOR_SINGLE_PARTITION_TABLES\"]}" - }, - { - "name": "__TEdtr", - "value": "1" - }, - { - "name": "PBI_ProTooling", - "value": "[\"DevMode\"]" - } - ], - "culture": "en-US", - "cultures": [ - { - "name": "en-US", - "linguisticMetadata": { - "content": { - "DynamicImprovement": "HighConfidence", - "Entities": { - "calendar": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar" - } - }, - "State": "Generated", - "Terms": [ - { - "calendar": { - "State": "Generated" - } - }, - { - "almanac": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "datebook": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "agenda": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "logbook": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "diary": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "schedule": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "timetable": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "calendar.date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "date": { - "State": "Generated" - } - }, - { - "moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "calendar.date_id": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "DateId" - } - }, - "State": "Generated", - "Terms": [ - { - "date id": { - "State": "Generated" - } - }, - { - "DateId": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "date identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "date identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "date identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "date credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "moment id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "period id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "calendar.day": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Day" - } - }, - "State": "Generated", - "Terms": [ - { - "day": { - "State": "Generated" - } - } - ] - }, - "calendar.day_relative": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Day (Relative)" - } - }, - "State": "Generated", - "Terms": [ - { - "day (relative)": { - "State": "Generated" - } - }, - { - "day": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "calendar.month": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Month" - } - }, - "State": "Generated", - "Terms": [ - { - "month": { - "State": "Generated" - } - }, - { - "mth": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - } - ] - }, - "calendar.month1": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Month (Year)" - } - }, - "State": "Generated", - "Terms": [ - { - "month": { - "State": "Generated" - } - }, - { - "month (Year)": { - "State": "Generated" - } - }, - { - "mth": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "mth (year)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - }, - { - "month ( yr )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ], - "Units": [ - "year" - ] - }, - "calendar.month_": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Month (#)" - } - }, - "State": "Generated", - "Terms": [ - { - "month (#)": { - "State": "Generated" - } - }, - { - "month": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "mth (#)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - }, - { - "month ( no )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "month ( num )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "month ( number )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - } - ] - }, - "calendar.month_long": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Month (Long)" - } - }, - "State": "Generated", - "Terms": [ - { - "month (long)": { - "State": "Generated" - } - }, - { - "month": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "mth (long)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ] - }, - "calendar.month_relative": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Month (Relative)" - } - }, - "State": "Generated", - "Terms": [ - { - "month (relative)": { - "State": "Generated" - } - }, - { - "month": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "mth (relative)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ] - }, - "calendar.month_start_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Month Start Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "month start date": { - "State": "Generated" - } - }, - { - "start date": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "month start moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "start moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "month start period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "start period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "month commencement date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.615 - } - }, - { - "month inception date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.615 - } - }, - { - "month kickoff date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.615 - } - }, - { - "mth start date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - }, - { - "commencement date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "inception date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - } - ] - }, - "calendar.month_year_id": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "MonthYearId" - } - }, - "State": "Generated", - "Terms": [ - { - "month year id": { - "State": "Generated" - } - }, - { - "MonthYearId": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "month year": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "year id": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "month year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "month year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "month year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "month yr": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "month year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "mth year id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "calendar.quarter": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Quarter" - } - }, - "State": "Generated", - "Terms": [ - { - "quarter": { - "State": "Generated" - } - }, - { - "qtr": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.818 - } - } - ] - }, - "calendar.quarter1": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Quarter (Year)" - } - }, - "State": "Generated", - "Terms": [ - { - "quarter": { - "State": "Generated" - } - }, - { - "quarter (Year)": { - "State": "Generated" - } - }, - { - "qtr": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.818 - } - }, - { - "qtr (year)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.686 - } - }, - { - "quarter ( yr )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ], - "Units": [ - "year" - ] - }, - "calendar.quarter_year_id": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "QuarterYearId" - } - }, - "State": "Generated", - "Terms": [ - { - "quarter year id": { - "State": "Generated" - } - }, - { - "QuarterYearId": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "quarter year": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "year id": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "quarter year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "quarter year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "quarter year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "quarter yr": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "quarter year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "qtr year id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.677 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "calendar.semester": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Semester" - } - }, - "State": "Generated", - "Terms": [ - { - "semester": { - "State": "Generated" - } - } - ] - }, - "calendar.semester1": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Semester (Year)" - } - }, - "State": "Generated", - "Terms": [ - { - "semester": { - "State": "Generated" - } - }, - { - "semester (Year)": { - "State": "Generated" - } - }, - { - "semester ( yr )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ], - "Units": [ - "year" - ] - }, - "calendar.semester_year_id": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "SemesterYearId" - } - }, - "State": "Generated", - "Terms": [ - { - "semester year id": { - "State": "Generated" - } - }, - { - "SemesterYearId": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "semester year": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "year id": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "semester year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "semester year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "semester year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "semester yr": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "semester year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "semester yr id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "calendar.week": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Week" - } - }, - "State": "Generated", - "Terms": [ - { - "week": { - "State": "Generated" - } - } - ] - }, - "calendar.week1": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Week (Year)" - } - }, - "State": "Generated", - "Terms": [ - { - "week": { - "State": "Generated" - } - }, - { - "week (Year)": { - "State": "Generated" - } - }, - { - "week ( yr )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ], - "Units": [ - "year" - ] - }, - "calendar.week_day": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Week Day" - } - }, - "State": "Generated", - "Terms": [ - { - "week day": { - "State": "Generated" - } - } - ] - }, - "calendar.week_day_": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Week Day (#)" - } - }, - "State": "Generated", - "Terms": [ - { - "week day (#)": { - "State": "Generated" - } - }, - { - "week day": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "week day ( no )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.605 - } - }, - { - "week day ( num )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.605 - } - }, - { - "week day ( number )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.605 - } - } - ] - }, - "calendar.week_end_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Week End Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "week end date": { - "State": "Generated" - } - }, - { - "end date": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "week end moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "end moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "week end period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "end period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "week culmination date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.615 - } - }, - { - "week completion date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - }, - { - "week conclusion date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - }, - { - "week expiration date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - }, - { - "culmination date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "completion date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - } - ] - }, - "calendar.week_relative": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Week (Relative)" - } - }, - "State": "Generated", - "Terms": [ - { - "week (relative)": { - "State": "Generated" - } - }, - { - "week": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "calendar.week_start_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Week Start Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "week start date": { - "State": "Generated" - } - }, - { - "start date": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "week start moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "start moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "week start period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "start period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "week commencement date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.615 - } - }, - { - "week inception date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.615 - } - }, - { - "week kickoff date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.615 - } - }, - { - "commencement date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "inception date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "kickoff date": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - } - ] - }, - "calendar.week_year_id": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "WeekYearId" - } - }, - "State": "Generated", - "Terms": [ - { - "week year id": { - "State": "Generated" - } - }, - { - "WeekYearId": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "week year": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "year id": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "week year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "week year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "week year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.762 - } - }, - { - "week yr": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "year identifier": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "week year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "year credential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "week yr id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "calendar.work_day": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Work Day" - } - }, - "State": "Generated", - "Terms": [ - { - "work day": { - "State": "Generated" - } - } - ] - }, - "calendar.year": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Year" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "year": { - "State": "Generated" - } - }, - { - "yr": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - } - ] - }, - "calendar.year_relative": { - "Definition": { - "Binding": { - "ConceptualEntity": "Calendar", - "ConceptualProperty": "Year (Relative)" - } - }, - "State": "Generated", - "Terms": [ - { - "year (relative)": { - "State": "Generated" - } - }, - { - "year": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "yr (relative)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ] - }, - "customer": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer" - } - }, - "State": "Generated", - "Terms": [ - { - "customer": { - "State": "Generated" - } - }, - { - "client": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "consumer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "user": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "buyer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "patron": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "purchaser": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "shopper": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - } - ] - }, - "customer.address": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Address" - } - }, - "State": "Generated", - "Terms": [ - { - "address": { - "State": "Generated" - } - }, - { - "location": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "direction": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "residence": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "contact": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "place": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "customer.age": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Age" - } - }, - "State": "Generated", - "Terms": [ - { - "age": { - "State": "Generated" - } - }, - { - "oldness": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "stage": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "phase": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "era": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "epoch": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - } - ] - }, - "customer.birthday": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Birthday" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "birthday": { - "State": "Generated" - } - }, - { - "date": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "birthdate": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "anniversary": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "centenary": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "bicentenary": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "centennial": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "bicentennial": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - } - ] - }, - "customer.city": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "City" - } - }, - "SemanticType": "Location", - "State": "Generated", - "Terms": [ - { - "city": { - "State": "Generated" - } - }, - { - "location": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "metropolis": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "municipality": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "town": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "metropolitan": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "customer.continent": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Continent" - } - }, - "SemanticType": "Location", - "State": "Generated", - "Terms": [ - { - "continent": { - "State": "Generated" - } - }, - { - "landmass": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "landform": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "region": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "land": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "island": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "mainland": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "zone": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "customer.country": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Country" - } - }, - "SemanticType": "Location", - "State": "Generated", - "Terms": [ - { - "country": { - "State": "Generated" - } - }, - { - "nation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "location": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - } - ] - }, - "customer.country_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Country Code" - } - }, - "SemanticType": "Location", - "State": "Generated", - "Terms": [ - { - "country code": { - "State": "Generated" - } - }, - { - "country id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "country key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "nation code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "location code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - } - ] - }, - "customer.customer": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Customer" - } - }, - "NameType": "Name", - "State": "Generated", - "Terms": [ - { - "customer": { - "State": "Generated" - } - }, - { - "customer name": { - "State": "Generated" - } - }, - { - "client": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "consumer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "user": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "buyer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "patron": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "purchaser": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "shopper": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - } - ] - }, - "customer.customer_key": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "CustomerKey" - } - }, - "State": "Generated", - "Terms": [ - { - "customer key": { - "State": "Generated" - } - }, - { - "CustomerKey": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "key": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "client key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "consumer key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "user key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "buyer key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "patron key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "purchaser key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "shopper key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "main": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "basic": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "fundamental": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "customer.customers": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "# Customers" - } - }, - "State": "Generated", - "Terms": [ - { - "# customers": { - "State": "Generated" - } - }, - { - "no customer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "num customer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "number customer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "# clientele": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# patron": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# client": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# consumer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# punter": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# regular": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "# custom": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "customer.gender": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Gender" - } - }, - "State": "Generated", - "Terms": [ - { - "gender": { - "State": "Generated" - } - }, - { - "sexuality": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "sex": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "customer.state": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "State" - } - }, - "SemanticType": "Location", - "State": "Generated", - "Terms": [ - {}, - { - "state or province": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "location": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "province": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "territory": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "nation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "condition": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "customer.state_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "State Code" - } - }, - "SemanticType": "Location", - "State": "Generated", - "Terms": [ - { - "state code": { - "State": "Generated" - } - }, - { - "state or province": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "state id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "state key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "location code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "province code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "territory code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "nation code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "condition code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - } - ] - }, - "customer.v___Customers_FormatString__customers_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "_# Customers FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_# customers FormatString": { - "State": "Generated" - } - }, - { - "_ no customer formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ num customer formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ number customer formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "customer.zip_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Customer", - "ConceptualProperty": "Zip Code" - } - }, - "SemanticType": "Location", - "State": "Generated", - "Terms": [ - { - "zip code": { - "State": "Generated" - } - }, - { - "postal code": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "zip id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "zip key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "location": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.54 - } - }, - { - "postcode": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.54 - } - }, - { - "post code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.524 - } - } - ] - }, - "dynamic_measure": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - } - }, - "State": "Generated", - "Terms": [ - { - "dynamic measure": { - "State": "Generated" - } - }, - { - "dynamic degree": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "dynamic quantity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "dynamic quota": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "dynamic extent": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "dynamic amount": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "dynamic portion": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "dynamic ration": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "dynamic size": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "dynamic_measure.area": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Area" - } - }, - "State": "Generated", - "Terms": [ - { - "area": { - "State": "Generated" - } - }, - { - "region": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "locale": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "locality": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "neighbourhood": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "district": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "field": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "neighborhood": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "section": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "space": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "dynamic_measure.code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Code" - } - }, - "State": "Generated", - "Terms": [ - { - "code": { - "State": "Generated" - } - } - ] - }, - "dynamic_measure.format": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Format" - } - }, - "State": "Generated", - "Terms": [ - { - "format": { - "State": "Generated" - } - }, - { - "arrangement": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "setup": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "presentation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "organization": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "layout": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "system": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "set-up": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "plan": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "design": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "structure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "dynamic_measure.measure": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Measure" - } - }, - "State": "Generated", - "Terms": [ - { - "measure": { - "State": "Generated" - } - }, - { - "degree": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "quota": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "extent": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "portion": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "ration": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "size": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "dynamic_measure.order": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Order" - } - }, - "State": "Generated", - "Terms": [ - { - "order": { - "State": "Generated" - } - }, - { - "instruction": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "direction": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "edict": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "command": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "directive": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "demand": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "mandate": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "imperative": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "stability": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "harmony": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - } - ] - }, - "dynamic_measure.v_Value_Avg_per_Month_FormatString_value_avg_per_month_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "_Value Avg per Month FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_value avg per month FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "dynamic_measure.v_Value_Daily_Max_FormatString_value_daily_max_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "_Value Daily Max FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_value daily max FormatString": { - "State": "Generated" - } - }, - { - "_value daily maximum formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.605 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "dynamic_measure.v_Value_FormatString_value_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "_Value FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_value FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "dynamic_measure.v_Value_Normalized__by_date__FormatString_value_normalized_by_date_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "_Value Normalized (by date) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_value normalized (by date) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "dynamic_measure.v_Value____Δ_ly__FormatString_value__δ_ly_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "_Value % (Δ ly) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_value % (δ ly) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "dynamic_measure.v_Value__ly__FormatString_value_ly_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "_Value (ly) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_value (ly) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "dynamic_measure.v_Value__ytd__FormatString_value_ytd_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "_Value (ytd) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_value (ytd) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "dynamic_measure.value": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Value" - } - }, - "State": "Generated", - "Terms": [ - { - "value": { - "State": "Generated" - } - }, - { - "assessment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "worth": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "charge": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "importance": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "significance": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "usefulness": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "consequence": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "use": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "meaning": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "merit": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - } - ] - }, - "dynamic_measure.value__δ_ly": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Value % (Δ ly)" - } - }, - "State": "Generated", - "Terms": [ - { - "value % (δ ly)": { - "State": "Generated" - } - }, - { - "value %": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "dynamic_measure.value_avg_per_month": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Value Avg per Month" - } - }, - "State": "Generated", - "Terms": [ - { - "value avg per month": { - "State": "Generated" - } - }, - { - "value average per month": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.748 - } - }, - { - "value avg per mth": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - } - ] - }, - "dynamic_measure.value_daily_max": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Value Daily Max" - } - }, - "State": "Generated", - "Terms": [ - { - "value daily max": { - "State": "Generated" - } - }, - { - "daily max": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "value daily maximum": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.739 - } - }, - { - "daily maximum": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "dynamic_measure.value_ly": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Value (ly)" - } - }, - "State": "Generated", - "Terms": [ - { - "value (ly)": { - "State": "Generated" - } - }, - { - "value": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "dynamic_measure.value_normalized_by_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Value Normalized (by date)" - } - }, - "State": "Generated", - "Terms": [ - { - "value normalized (by date)": { - "State": "Generated" - } - }, - { - "value normalized": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "dynamic_measure.value_ytd": { - "Definition": { - "Binding": { - "ConceptualEntity": "Dynamic Measure", - "ConceptualProperty": "Value (ytd)" - } - }, - "State": "Generated", - "Terms": [ - { - "value (ytd)": { - "State": "Generated" - } - }, - { - "value": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "value ( year to date )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.514 - } - } - ] - }, - "product": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product" - } - }, - "State": "Generated", - "Terms": [ - { - "product": { - "State": "Generated" - } - }, - { - "artifact": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "item": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "merchandise": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "produce": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "product.brand": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Brand" - } - }, - "State": "Generated", - "Terms": [ - { - "brand": { - "State": "Generated" - } - }, - { - "variety": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "marque": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "make": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "kind": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "trademark": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "type": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "style": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "class": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "strain": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "cast": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "product.category": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Category" - } - }, - "State": "Generated", - "Terms": [ - { - "category": { - "State": "Generated" - } - }, - { - "classification": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "class": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "type": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "grouping": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "kind": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "product.category_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Category Code" - } - }, - "State": "Generated", - "Terms": [ - { - "category code": { - "State": "Generated" - } - }, - { - "category id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "category key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "classification code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "class code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "group code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "type code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "grouping code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "kind code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - } - ] - }, - "product.color": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Color" - } - }, - "State": "Generated", - "Terms": [ - { - "color": { - "State": "Generated" - } - }, - { - "hue": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "tint": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "shade": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "dye": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "paint": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "pigment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "product.manufacturer": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Manufacturer" - } - }, - "State": "Generated", - "Terms": [ - { - "manufacturer": { - "State": "Generated" - } - }, - { - "builder": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "producer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "constructer": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "creator": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "industrialist": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "maker": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "company": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "firm": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "product.product": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Product" - } - }, - "NameType": "Name", - "State": "Generated", - "Terms": [ - { - "product": { - "State": "Generated" - } - }, - { - "product name": { - "State": "Generated" - } - }, - { - "artifact": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "item": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "merchandise": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "produce": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "product.product_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Product Code" - } - }, - "NameType": "Identifier", - "State": "Generated", - "Terms": [ - { - "product code": { - "State": "Generated" - } - }, - { - "code": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "product id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "artifact code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "item code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "merchandise code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "produce code": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - } - ] - }, - "product.product_key": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "ProductKey" - } - }, - "State": "Generated", - "Terms": [ - { - "product key": { - "State": "Generated" - } - }, - { - "ProductKey": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "key": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "artifact key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "item key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "merchandise key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "produce key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "main": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "basic": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "fundamental": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "central": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "major": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "keynote": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "product.products": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "# Products" - } - }, - "State": "Generated", - "Terms": [ - { - "# products": { - "State": "Generated" - } - }, - { - "# artifact": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "# item": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "# merchandise": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "# produce": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "# goods": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.625 - } - }, - { - "no product": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "num product": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "number product": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - } - ] - }, - "product.subcategory": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Subcategory" - } - }, - "State": "Generated", - "Terms": [ - { - "subcategory": { - "State": "Generated" - } - } - ] - }, - "product.subcategory_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Subcategory Code" - } - }, - "State": "Generated", - "Terms": [ - { - "subcategory code": { - "State": "Generated" - } - }, - { - "subcategory id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "subcategory key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "product.unit_cost": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Unit Cost" - } - }, - "State": "Generated", - "Terms": [ - { - "unit cost": { - "State": "Generated" - } - }, - { - "cost": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "module cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "element cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "entity cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "group cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "component cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "constituent cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "item cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "part cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "section cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "unit charge": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "product.unit_price": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Unit Price" - } - }, - "State": "Generated", - "Terms": [ - { - "unit price": { - "State": "Generated" - } - }, - { - "price": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "module price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "element price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "entity price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "group price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "component price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "constituent price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "item price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "part price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "section price": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "unit value": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "product.v___Products_FormatString__products_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "_# Products FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_# products FormatString": { - "State": "Generated" - } - }, - { - "_# artifact formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.617 - } - }, - { - "_ no product formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ num product formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ number product formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_# item formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_# merchandise formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_# produce formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_# goods formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.514 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "product.weight": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Weight" - } - }, - "State": "Generated", - "Terms": [ - { - "weight": { - "State": "Generated" - } - }, - { - "heaviness": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "weightiness": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "mass": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "bulk": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "heft": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "encumbrance": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "burden": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "load": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - } - ] - }, - "product.weight_unit_measure": { - "Definition": { - "Binding": { - "ConceptualEntity": "Product", - "ConceptualProperty": "Weight Unit Measure" - } - }, - "State": "Generated", - "Terms": [ - { - "weight unit measure": { - "State": "Generated" - } - }, - { - "unit measure": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "weight module measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - }, - { - "weight element measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - }, - { - "weight entity measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.609 - } - }, - { - "module measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "element measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "entity measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "weight group measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.591 - } - }, - { - "weight component measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.591 - } - }, - { - "weight constituent measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.591 - } - }, - { - "weight item measure": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.591 - } - } - ] - }, - "sale": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales" - } - }, - "State": "Generated", - "Terms": [ - { - "sale": { - "State": "Generated" - } - }, - { - "auction": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "transaction": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "deal": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "trade": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "vending": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "retailing": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - }, - { - "selling": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.452 - } - } - ] - }, - "sale.currency_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Currency Code" - } - }, - "State": "Generated", - "Terms": [ - { - "currency code": { - "State": "Generated" - } - }, - { - "currency id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "currency key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "sale.customer_key": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "CustomerKey" - } - }, - "State": "Generated", - "Terms": [ - { - "customer key": { - "State": "Generated" - } - }, - { - "CustomerKey": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "key": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "client key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "consumer key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "user key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "buyer key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "patron key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "purchaser key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "shopper key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "main": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "basic": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "fundamental": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.customers_with_sales": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "# Customers (with Sales)" - } - }, - "State": "Generated", - "Terms": [ - { - "# customers (with sales)": { - "State": "Generated" - } - }, - { - "# customers": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "sale.delivery_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Delivery Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "delivery date": { - "State": "Generated" - } - }, - { - "date": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "delivery moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "delivery period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "sale.exchange_rate": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Exchange Rate" - } - }, - "State": "Generated", - "Terms": [ - { - "exchange rate": { - "State": "Generated" - } - }, - { - "rate": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "exchange degree": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "exchange frequency": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "exchange percentage": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "exchange ratio": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "exchange quotient": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "degree": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "frequency": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "ratio": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "quotient": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "exchange amount": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "sale.line_number": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Line Number" - } - }, - "State": "Generated", - "Terms": [ - { - "line number": { - "State": "Generated" - } - }, - { - "line no": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "sale.margin": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Margin" - } - }, - "State": "Generated", - "Terms": [ - { - "margin": { - "State": "Generated" - } - }, - { - "boundary": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "perimeter": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "periphery": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "border": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "brim": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "sideline": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "edge": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "verge": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "fringe": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "side": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ] - }, - "sale.margin_ly": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Margin (ly)" - } - }, - "State": "Generated", - "Terms": [ - { - "margin (ly)": { - "State": "Generated" - } - }, - { - "margin": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "sale.net_price": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Net Price" - } - }, - "State": "Generated", - "Terms": [ - { - "net price": { - "State": "Generated" - } - }, - { - "price": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "net value": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net worth": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net fee": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net charge": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net amount": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net bill": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net rate": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net expense": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "net outlay": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "sale.order_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Order Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "order date": { - "State": "Generated" - } - }, - { - "date": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "order moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "order period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "sale.order_number": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Order Number" - } - }, - "State": "Generated", - "Terms": [ - { - "order number": { - "State": "Generated" - } - }, - { - "order no": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "sale.product_key": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "ProductKey" - } - }, - "State": "Generated", - "Terms": [ - { - "product key": { - "State": "Generated" - } - }, - { - "ProductKey": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "key": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "artifact key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "item key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "merchandise key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "produce key": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "main": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "basic": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "fundamental": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "central": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "major": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "keynote": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.products_with_sales": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "# Products (with Sales)" - } - }, - "State": "Generated", - "Terms": [ - { - "# products (with sales)": { - "State": "Generated" - } - }, - { - "# products": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "sale.quantity": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Quantity" - } - }, - "State": "Generated", - "Terms": [ - { - "quantity": { - "State": "Generated" - } - }, - { - "extent": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "magnitude": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "size": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "capacity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "mass": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.sales": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "# Sales" - } - }, - "State": "Generated", - "Terms": [ - { - "# sales": { - "State": "Generated" - } - }, - { - "# sale": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - }, - { - "no sale": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "num sale": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "number sale": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - } - ] - }, - "sale.sales_amount": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount": { - "State": "Generated" - } - }, - { - "sales": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "sale avg. mth": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.771 - } - }, - { - "sale avg. month": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "sale average . month": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "sale amount": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "sale quantity": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "sale volume": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "sale expanse": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "sale extent": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "sale sum": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "sale total": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "sale.sales_amount_LY": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount (LY)" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount (LY)": { - "State": "Generated" - } - }, - { - "sales amount": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "sale amount (ly)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.605 - } - } - ] - }, - "sale.sales_amount_YTD": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount (YTD)" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount (YTD)": { - "State": "Generated" - } - }, - { - "sales amount": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "sale amount (ytd)": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.605 - } - }, - { - "sale amount ( year to date )": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.528 - } - } - ] - }, - "sale.sales_amount_YTD_LY": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount (YTD, LY)" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount (YTD, LY)": { - "State": "Generated" - } - }, - { - "sales amount": { - "State": "Generated", - "Weight": 0.75 - } - } - ] - }, - "sale.sales_amount___δ_LY": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount (% Δ LY)" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount (% δ LY)": { - "State": "Generated" - } - }, - { - "sales amount": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "sales amount (% δ LY)": { - "State": "Generated", - "Weight": 0.97 - } - } - ] - }, - "sale.sales_amount__δ_LY": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount (Δ LY)" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount (δ LY)": { - "State": "Generated" - } - }, - { - "sales amount": { - "State": "Generated", - "Weight": 0.75 - } - }, - { - "sales amount (δ LY)": { - "State": "Generated", - "Weight": 0.97 - } - } - ] - }, - "sale.sales_amount__δ_YTD_LY": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount % (Δ YTD, LY)" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount % (δ YTD, LY)": { - "State": "Generated" - } - }, - { - "sales amount %": { - "State": "Generated", - "Weight": 0.75 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.sales_amount_avg_per_day": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount Avg per Day" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount avg per day": { - "State": "Generated" - } - }, - { - "sale amount average per day": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.757 - } - }, - { - "sale amount avg per day": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.605 - } - } - ] - }, - "sale.sales_amount_δ_YTD_LY": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Amount (Δ YTD, LY)" - } - }, - "State": "Generated", - "Terms": [ - { - "sales amount (δ YTD, LY)": { - "State": "Generated" - } - }, - { - "sales amount": { - "State": "Generated", - "Weight": 0.75 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.sales_qty": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Sales Qty" - } - }, - "State": "Generated", - "Terms": [ - { - "sales qty": { - "State": "Generated" - } - }, - { - "qty avg. mth": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.771 - } - }, - { - "qty average . month": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "sale qty": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - } - ] - }, - "sale.store_key": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "StoreKey" - } - }, - "State": "Generated", - "Terms": [ - { - "store key": { - "State": "Generated" - } - }, - { - "StoreKey": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "key": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "main": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "basic": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "fundamental": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "central": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "major": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "keynote": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "essential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "store solution": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.475 - } - }, - { - "store explanation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.475 - } - }, - { - "store basis": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.475 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.unit_cost": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "Unit Cost" - } - }, - "State": "Generated", - "Terms": [ - { - "unit cost": { - "State": "Generated" - } - }, - { - "cost": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "module cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "element cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "entity cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.6 - } - }, - { - "group cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "component cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "constituent cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "item cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "part cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "section cost": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "unit charge": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Margin_FormatString_margin_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Margin FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_margin FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Margin__ly__FormatString_margin_ly_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Margin (ly) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_margin (ly) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount_Avg_per_Day_FormatString_sales_amount_avg_per_day_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount Avg per Day FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount avg per day FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount_FormatString_sales_amount_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount FormatString": { - "State": "Generated" - } - }, - { - "_ sale amount formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount__LY__FormatString_sales_amount_LY_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount (LY) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount (LY) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount__YTD__FormatString_sales_amount_YTD_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount (YTD) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount (YTD) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount__YTD__LY__FormatString_sales_amount_YTD_LY_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount (YTD, LY) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount (YTD, LY) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount_____Δ_LY__FormatString_sales_amount___δ_LY_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount (% Δ LY) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount (% δ LY) FormatString": { - "State": "Generated" - } - }, - { - "_sales amount (% δ LY) FormatString": { - "State": "Generated", - "Weight": 0.97 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount____Δ_YTD__LY__FormatString_sales_amount__δ_YTD_LY_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount % (Δ YTD, LY) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount % (δ YTD, LY) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount___Δ_LY__FormatString_sales_amount__δ_LY_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount (Δ LY) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount (δ LY) FormatString": { - "State": "Generated" - } - }, - { - "_sales amount (δ LY) FormatString": { - "State": "Generated", - "Weight": 0.97 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Amount__Δ_YTD__LY__FormatString_sales_amount_δ_YTD_LY_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Amount (Δ YTD, LY) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales amount (δ YTD, LY) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v_Sales_Qty_FormatString_sales_qty_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_Sales Qty FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_sales qty FormatString": { - "State": "Generated" - } - }, - { - "_ sale qty formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v___Customers__with_Sales__FormatString__customers_with_sales_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_# Customers (with Sales) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_# customers (with sales) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v___Products__with_Sales__FormatString__products_with_sales_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_# Products (with Sales) FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_# products (with sales) FormatString": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "sale.v___Sales_FormatString__sales_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Sales", - "ConceptualProperty": "_# Sales FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_# sales FormatString": { - "State": "Generated" - } - }, - { - "_ no sale formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ num sale formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ number sale formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_# sale formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "smart_calc": { - "Definition": { - "Binding": { - "ConceptualEntity": "Smart Calcs" - } - }, - "State": "Generated", - "Terms": [ - { - "smart calc": { - "State": "Generated" - } - } - ] - }, - "smart_calc.ordinal": { - "Definition": { - "Binding": { - "ConceptualEntity": "Smart Calcs", - "ConceptualProperty": "Ordinal" - } - }, - "State": "Generated", - "Terms": [ - { - "ordinal": { - "State": "Generated" - } - } - ], - "Visibility": { - "Value": "Hidden" - } - }, - "smart_calc.smart_calc": { - "Definition": { - "Binding": { - "ConceptualEntity": "Smart Calcs", - "ConceptualProperty": "Smart Calc" - } - }, - "NameType": "Name", - "State": "Generated", - "Terms": [ - { - "smart calc": { - "State": "Generated" - } - }, - { - "smart calc name": { - "State": "Generated" - } - } - ] - }, - "store": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store" - } - }, - "State": "Generated", - "Terms": [ - { - "store": { - "State": "Generated" - } - }, - { - "accumulation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "collection": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "stock": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "supply": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "stockpile": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "hoard": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "mass": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "pile": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "storehouse": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "storeroom": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - } - ] - }, - "store.close_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "Close Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "close date": { - "State": "Generated" - } - }, - { - "date": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "close moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "close period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "store.country": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "Country" - } - }, - "State": "Generated", - "Terms": [ - { - "country": { - "State": "Generated" - } - }, - { - "nation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "location": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - } - ] - }, - "store.open_date": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "Open Date" - } - }, - "SemanticType": "Time", - "State": "Generated", - "Terms": [ - { - "open date": { - "State": "Generated" - } - }, - { - "date": { - "State": "Generated", - "Weight": 0.7 - } - }, - { - "open moment": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - }, - { - "open period": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.727 - } - } - ] - }, - "store.square_meter": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "Square Meters" - } - }, - "State": "Generated", - "Terms": [ - { - "square meter": { - "State": "Generated" - } - }, - { - "meter": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "square rhythm": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "square tempo": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "square cadence": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "rhythm": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "tempo": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "cadence": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "square beat": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "square pulse": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "square pattern": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "square stress": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - } - ] - }, - "store.state": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "State" - } - }, - "State": "Generated", - "Terms": [ - {}, - { - "location": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "province": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "territory": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "nation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "condition": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "store.status": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "Status" - } - }, - "State": "Generated", - "Terms": [ - { - "status": { - "State": "Generated" - } - }, - { - "importance": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "prestige": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "prominence": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.736 - } - }, - { - "condition": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "class": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "grade": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "level": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - }, - { - "position": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.714 - } - } - ] - }, - "store.store": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "Store" - } - }, - "NameType": "Name", - "State": "Generated", - "Terms": [ - { - "store": { - "State": "Generated" - } - }, - { - "store name": { - "State": "Generated" - } - }, - { - "accumulation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "collection": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.491 - } - }, - { - "stock": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "supply": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "stockpile": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "hoard": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "mass": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "pile": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "storehouse": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - }, - { - "storeroom": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.466 - } - } - ] - }, - "store.store_code": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "Store Code" - } - }, - "NameType": "Identifier", - "State": "Generated", - "Terms": [ - { - "store code": { - "State": "Generated" - } - }, - { - "code": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "store id": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.75 - } - } - ] - }, - "store.store_key": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "StoreKey" - } - }, - "State": "Generated", - "Terms": [ - { - "store key": { - "State": "Generated" - } - }, - { - "StoreKey": { - "State": "Generated", - "Type": "Noun", - "Weight": 0.99 - } - }, - { - "key": { - "State": "Generated", - "Weight": 0.97 - } - }, - { - "main": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "basic": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "fundamental": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "central": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "major": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "keynote": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "essential": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.476 - } - }, - { - "store solution": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.475 - } - }, - { - "store explanation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.475 - } - }, - { - "store basis": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.475 - } - } - ] - }, - "store.stores": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "# Stores" - } - }, - "State": "Generated", - "Terms": [ - { - "# stores": { - "State": "Generated" - } - }, - { - "no store": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "num store": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "number store": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.582 - } - }, - { - "# goods": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# foods": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# vittles": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.5 - } - }, - { - "# supply": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "# provision": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "# ration": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.485 - } - }, - { - "# accumulation": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.475 - } - } - ] - }, - "store.v___Stores_FormatString__stores_FormatString": { - "Definition": { - "Binding": { - "ConceptualEntity": "Store", - "ConceptualProperty": "_# Stores FormatString" - } - }, - "State": "Generated", - "Terms": [ - { - "_# stores FormatString": { - "State": "Generated" - } - }, - { - "_ no store formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ num store formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - }, - { - "_ number store formatstring": { - "Source": { - "Agent": "OfficeThesaurus" - }, - "State": "Suggested", - "Type": "Noun", - "Weight": 0.599 - } - } - ], - "Visibility": { - "Value": "Hidden" - } - } - }, - "Language": "en-US", - "Relationships": { - "calendar_has_date": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.date" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.date": { - "Target": { - "Entity": "calendar.date" - } - } - }, - "State": "Generated" - }, - "calendar_has_day": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.day" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.day": { - "Target": { - "Entity": "calendar.day" - } - } - }, - "State": "Generated" - }, - "calendar_has_day_relative": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.day_relative" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.day_relative": { - "Target": { - "Entity": "calendar.day_relative" - } - } - }, - "State": "Generated" - }, - "calendar_has_month": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.month" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.month": { - "Target": { - "Entity": "calendar.month" - } - } - }, - "State": "Generated" - }, - "calendar_has_month1": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.month1" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.month1": { - "Target": { - "Entity": "calendar.month1" - } - } - }, - "State": "Generated" - }, - "calendar_has_month_": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.month_" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.month_": { - "Target": { - "Entity": "calendar.month_" - } - } - }, - "State": "Generated" - }, - "calendar_has_month_long": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.month_long" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.month_long": { - "Target": { - "Entity": "calendar.month_long" - } - } - }, - "State": "Generated" - }, - "calendar_has_month_relative": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.month_relative" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.month_relative": { - "Target": { - "Entity": "calendar.month_relative" - } - } - }, - "State": "Generated" - }, - "calendar_has_month_start_date": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.month_start_date" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.month_start_date": { - "Target": { - "Entity": "calendar.month_start_date" - } - } - }, - "State": "Generated" - }, - "calendar_has_quarter": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.quarter" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.quarter": { - "Target": { - "Entity": "calendar.quarter" - } - } - }, - "State": "Generated" - }, - "calendar_has_quarter1": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.quarter1" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.quarter1": { - "Target": { - "Entity": "calendar.quarter1" - } - } - }, - "State": "Generated" - }, - "calendar_has_sale_sales_amount_avg_per_day": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount_avg_per_day" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "sale.sales_amount_avg_per_day": { - "Target": { - "Entity": "sale.sales_amount_avg_per_day" - } - } - }, - "State": "Generated" - }, - "calendar_has_semester": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.semester" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.semester": { - "Target": { - "Entity": "calendar.semester" - } - } - }, - "State": "Generated" - }, - "calendar_has_semester1": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.semester1" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.semester1": { - "Target": { - "Entity": "calendar.semester1" - } - } - }, - "State": "Generated" - }, - "calendar_has_week": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.week" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week": { - "Target": { - "Entity": "calendar.week" - } - } - }, - "State": "Generated" - }, - "calendar_has_week1": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.week1" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week1": { - "Target": { - "Entity": "calendar.week1" - } - } - }, - "State": "Generated" - }, - "calendar_has_week_day": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.week_day" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week_day": { - "Target": { - "Entity": "calendar.week_day" - } - } - }, - "State": "Generated" - }, - "calendar_has_week_day_": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.week_day_" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week_day_": { - "Target": { - "Entity": "calendar.week_day_" - } - } - }, - "State": "Generated" - }, - "calendar_has_week_end_date": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.week_end_date" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week_end_date": { - "Target": { - "Entity": "calendar.week_end_date" - } - } - }, - "State": "Generated" - }, - "calendar_has_week_relative": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.week_relative" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week_relative": { - "Target": { - "Entity": "calendar.week_relative" - } - } - }, - "State": "Generated" - }, - "calendar_has_week_start_date": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.week_start_date" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week_start_date": { - "Target": { - "Entity": "calendar.week_start_date" - } - } - }, - "State": "Generated" - }, - "calendar_has_work_day": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.work_day" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.work_day": { - "Target": { - "Entity": "calendar.work_day" - } - } - }, - "State": "Generated" - }, - "calendar_has_year": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.year" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.year": { - "Target": { - "Entity": "calendar.year" - } - } - }, - "State": "Generated" - }, - "calendar_has_year_relative": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar.year_relative" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.year_relative": { - "Target": { - "Entity": "calendar.year_relative" - } - } - }, - "State": "Generated" - }, - "calendar_is_ended_on_week_end_date": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "calendar" - }, - "Verbs": [ - { - "end": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.9 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week_end_date": { - "Target": { - "Entity": "calendar.week_end_date" - } - } - }, - "SemanticSlots": { - "When": { - "Role": "calendar.week_end_date" - } - }, - "State": "Generated" - }, - "calendar_is_started_on_month_start_date": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "calendar" - }, - "Verbs": [ - { - "start": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.9 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.month_start_date": { - "Target": { - "Entity": "calendar.month_start_date" - } - } - }, - "SemanticSlots": { - "When": { - "Role": "calendar.month_start_date" - } - }, - "State": "Generated" - }, - "calendar_is_started_on_week_start_date": { - "Binding": { - "ConceptualEntity": "Calendar" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "calendar" - }, - "Verbs": [ - { - "start": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.9 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "calendar.week_start_date": { - "Target": { - "Entity": "calendar.week_start_date" - } - } - }, - "SemanticSlots": { - "When": { - "Role": "calendar.week_start_date" - } - }, - "State": "Generated" - }, - "customer_has_address": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.address" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.address": { - "Target": { - "Entity": "customer.address" - } - } - }, - "State": "Generated" - }, - "customer_has_birthday": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.birthday" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.birthday": { - "Target": { - "Entity": "customer.birthday" - } - } - }, - "State": "Generated" - }, - "customer_has_city": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.city" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.city": { - "Target": { - "Entity": "customer.city" - } - } - }, - "State": "Generated" - }, - "customer_has_continent": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.continent" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.continent": { - "Target": { - "Entity": "customer.continent" - } - } - }, - "State": "Generated" - }, - "customer_has_country": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.country" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.country": { - "Target": { - "Entity": "customer.country" - } - } - }, - "State": "Generated" - }, - "customer_has_country_code": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.country_code" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.country_code": { - "Target": { - "Entity": "customer.country_code" - } - } - }, - "State": "Generated" - }, - "customer_has_customer_key": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.customer_key" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.customer_key": { - "Target": { - "Entity": "customer.customer_key" - } - } - }, - "State": "Generated" - }, - "customer_has_customers": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.customers" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.customers": { - "Target": { - "Entity": "customer.customers" - } - } - }, - "State": "Generated" - }, - "customer_has_gender": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.gender" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.gender": { - "Target": { - "Entity": "customer.gender" - } - } - }, - "State": "Generated" - }, - "customer_has_state": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.state" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.state": { - "Target": { - "Entity": "customer.state" - } - } - }, - "State": "Generated" - }, - "customer_has_state_code": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.state_code" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.state_code": { - "Target": { - "Entity": "customer.state_code" - } - } - }, - "State": "Generated" - }, - "customer_has_zip_code": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer.zip_code" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.zip_code": { - "Target": { - "Entity": "customer.zip_code" - } - } - }, - "State": "Generated" - }, - "customer_in_address": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.address" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.address": { - "Target": { - "Entity": "customer.address" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.address" - } - }, - "State": "Generated" - }, - "customer_in_city": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.city" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.city": { - "Target": { - "Entity": "customer.city" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.city" - } - }, - "State": "Generated" - }, - "customer_in_continent": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.continent" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.continent": { - "Target": { - "Entity": "customer.continent" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.continent" - } - }, - "State": "Generated" - }, - "customer_in_country": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.country" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.country": { - "Target": { - "Entity": "customer.country" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.country" - } - }, - "State": "Generated" - }, - "customer_in_country_code": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.country_code" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.country_code": { - "Target": { - "Entity": "customer.country_code" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.country_code" - } - }, - "State": "Generated" - }, - "customer_in_state": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.state" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.state": { - "Target": { - "Entity": "customer.state" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.state" - } - }, - "State": "Generated" - }, - "customer_in_state_code": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.state_code" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.state_code": { - "Target": { - "Entity": "customer.state_code" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.state_code" - } - }, - "State": "Generated" - }, - "customer_in_zip_code": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "customer.zip_code" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.zip_code": { - "Target": { - "Entity": "customer.zip_code" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "customer.zip_code" - } - }, - "State": "Generated" - }, - "customer_is_born_on_birthday": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "customer" - }, - "Verbs": [ - { - "bear": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.9 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.birthday": { - "Target": { - "Entity": "customer.birthday" - } - } - }, - "SemanticSlots": { - "When": { - "Role": "customer.birthday" - } - }, - "State": "Generated" - }, - "customer_is_named_customer": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Name": { - "Name": { - "Role": "customer.customer" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "customer.customer" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.customer": { - "Target": { - "Entity": "customer.customer" - } - } - }, - "State": "Generated" - }, - "customer_is_old": { - "Binding": { - "ConceptualEntity": "Customer" - }, - "Phrasings": [ - { - "Adjective": { - "Adjectives": [ - { - "old": { - "State": "Generated" - } - } - ], - "Antonyms": [ - { - "young": { - "State": "Generated" - } - } - ], - "Measurement": { - "Role": "customer.age" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "customer.age" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "customer.age": { - "Target": { - "Entity": "customer.age" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_area": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.area" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.area": { - "Target": { - "Entity": "dynamic_measure.area" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_code": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.code" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.code": { - "Target": { - "Entity": "dynamic_measure.code" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_format": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.format" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.format": { - "Target": { - "Entity": "dynamic_measure.format" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_measure": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.measure" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.measure": { - "Target": { - "Entity": "dynamic_measure.measure" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_order": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.order" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.order": { - "Target": { - "Entity": "dynamic_measure.order" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_value": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.value" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.value": { - "Target": { - "Entity": "dynamic_measure.value" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_value__δ_ly": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.value__δ_ly" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.value__δ_ly": { - "Target": { - "Entity": "dynamic_measure.value__δ_ly" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_value_avg_per_month": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.value_avg_per_month" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.value_avg_per_month": { - "Target": { - "Entity": "dynamic_measure.value_avg_per_month" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_value_daily_max": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.value_daily_max" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.value_daily_max": { - "Target": { - "Entity": "dynamic_measure.value_daily_max" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_value_ly": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.value_ly" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.value_ly": { - "Target": { - "Entity": "dynamic_measure.value_ly" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_value_normalized_by_date": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.value_normalized_by_date" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.value_normalized_by_date": { - "Target": { - "Entity": "dynamic_measure.value_normalized_by_date" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_has_value_ytd": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "dynamic_measure.value_ytd" - }, - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.value_ytd": { - "Target": { - "Entity": "dynamic_measure.value_ytd" - } - } - }, - "State": "Generated" - }, - "dynamic_measure_in_area": { - "Binding": { - "ConceptualEntity": "Dynamic Measure" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "dynamic_measure.area" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "dynamic_measure" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "dynamic_measure": { - "Target": { - "Entity": "dynamic_measure" - } - }, - "dynamic_measure.area": { - "Target": { - "Entity": "dynamic_measure.area" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "dynamic_measure.area" - } - }, - "State": "Generated" - }, - "product_has_brand": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.brand" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.brand": { - "Target": { - "Entity": "product.brand" - } - } - }, - "State": "Generated" - }, - "product_has_category": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.category" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.category": { - "Target": { - "Entity": "product.category" - } - } - }, - "State": "Generated" - }, - "product_has_category_code": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.category_code" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.category_code": { - "Target": { - "Entity": "product.category_code" - } - } - }, - "State": "Generated" - }, - "product_has_color": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.color" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.color": { - "Target": { - "Entity": "product.color" - } - } - }, - "State": "Generated" - }, - "product_has_product_key": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.product_key" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.product_key": { - "Target": { - "Entity": "product.product_key" - } - } - }, - "State": "Generated" - }, - "product_has_products": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.products" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.products": { - "Target": { - "Entity": "product.products" - } - } - }, - "State": "Generated" - }, - "product_has_subcategory": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.subcategory" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.subcategory": { - "Target": { - "Entity": "product.subcategory" - } - } - }, - "State": "Generated" - }, - "product_has_subcategory_code": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.subcategory_code" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.subcategory_code": { - "Target": { - "Entity": "product.subcategory_code" - } - } - }, - "State": "Generated" - }, - "product_has_weight_unit_measure": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product.weight_unit_measure" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.weight_unit_measure": { - "Target": { - "Entity": "product.weight_unit_measure" - } - } - }, - "State": "Generated" - }, - "product_is_expensive": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Adjective": { - "Adjectives": [ - { - "expensive": { - "State": "Generated" - } - } - ], - "Antonyms": [ - { - "cheap": { - "State": "Generated" - } - } - ], - "Measurement": { - "Role": "product.unit_cost" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "product.unit_cost" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.unit_cost": { - "Target": { - "Entity": "product.unit_cost" - } - } - }, - "State": "Generated" - }, - "product_is_expensive1": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Adjective": { - "Adjectives": [ - { - "expensive": { - "State": "Generated" - } - } - ], - "Antonyms": [ - { - "cheap": { - "State": "Generated" - } - } - ], - "Measurement": { - "Role": "product.unit_price" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "product.unit_price" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.unit_price": { - "Target": { - "Entity": "product.unit_price" - } - } - }, - "State": "Generated" - }, - "product_is_heavy": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Adjective": { - "Adjectives": [ - { - "heavy": { - "State": "Generated" - } - } - ], - "Antonyms": [ - { - "light": { - "State": "Generated" - } - } - ], - "Measurement": { - "Role": "product.weight" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "product.weight" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.weight": { - "Target": { - "Entity": "product.weight" - } - } - }, - "State": "Generated" - }, - "product_is_named_product": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Name": { - "Name": { - "Role": "product.product" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "product.product" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.product": { - "Target": { - "Entity": "product.product" - } - } - }, - "State": "Generated" - }, - "product_is_named_product_code": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "Name": { - "Name": { - "Role": "product.product_code" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "product.product_code" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.product_code": { - "Target": { - "Entity": "product.product_code" - } - } - }, - "State": "Generated" - }, - "product_manufacturer_manufacture_product": { - "Binding": { - "ConceptualEntity": "Product" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "product" - }, - "Subject": { - "Role": "product.manufacturer" - }, - "Verbs": [ - { - "manufacture": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.75 - }, - { - "Attribute": { - "Object": { - "Role": "product" - }, - "Subject": { - "Role": "product.manufacturer" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "product.manufacturer" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "product.manufacturer": { - "Target": { - "Entity": "product.manufacturer" - } - } - }, - "State": "Generated" - }, - "sale_has_calendar": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "calendar" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "sale" - }, - "Subject": { - "Role": "calendar" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "calendar": { - "Target": { - "Entity": "calendar" - } - }, - "sale": { - "Target": { - "Entity": "sale" - } - } - }, - "State": "Generated" - }, - "sale_has_currency_code": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.currency_code" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.currency_code": { - "Target": { - "Entity": "sale.currency_code" - } - } - }, - "State": "Generated" - }, - "sale_has_customer": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "customer" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "sale" - }, - "Subject": { - "Role": "customer" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "customer": { - "Target": { - "Entity": "customer" - } - }, - "sale": { - "Target": { - "Entity": "sale" - } - } - }, - "State": "Generated" - }, - "sale_has_customers_with_sales": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.customers_with_sales" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.customers_with_sales": { - "Target": { - "Entity": "sale.customers_with_sales" - } - } - }, - "State": "Generated" - }, - "sale_has_delivery_date": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.delivery_date" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.delivery_date": { - "Target": { - "Entity": "sale.delivery_date" - } - } - }, - "State": "Generated" - }, - "sale_has_exchange_rate": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.exchange_rate" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.exchange_rate": { - "Target": { - "Entity": "sale.exchange_rate" - } - } - }, - "State": "Generated" - }, - "sale_has_line_number": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.line_number" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.line_number": { - "Target": { - "Entity": "sale.line_number" - } - } - }, - "State": "Generated" - }, - "sale_has_margin": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.margin" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.margin": { - "Target": { - "Entity": "sale.margin" - } - } - }, - "State": "Generated" - }, - "sale_has_margin_ly": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.margin_ly" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.margin_ly": { - "Target": { - "Entity": "sale.margin_ly" - } - } - }, - "State": "Generated" - }, - "sale_has_order_date": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.order_date" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.order_date": { - "Target": { - "Entity": "sale.order_date" - } - } - }, - "State": "Generated" - }, - "sale_has_order_number": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.order_number" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.order_number": { - "Target": { - "Entity": "sale.order_number" - } - } - }, - "State": "Generated" - }, - "sale_has_product": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "product" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "sale" - }, - "Subject": { - "Role": "product" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "product": { - "Target": { - "Entity": "product" - } - }, - "sale": { - "Target": { - "Entity": "sale" - } - } - }, - "State": "Generated" - }, - "sale_has_products_with_sales": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.products_with_sales" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.products_with_sales": { - "Target": { - "Entity": "sale.products_with_sales" - } - } - }, - "State": "Generated" - }, - "sale_has_sales": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales": { - "Target": { - "Entity": "sale.sales" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_amount": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_amount": { - "Target": { - "Entity": "sale.sales_amount" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_amount_LY": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount_LY" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_amount_LY": { - "Target": { - "Entity": "sale.sales_amount_LY" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_amount_YTD": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount_YTD" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_amount_YTD": { - "Target": { - "Entity": "sale.sales_amount_YTD" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_amount_YTD_LY": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount_YTD_LY" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_amount_YTD_LY": { - "Target": { - "Entity": "sale.sales_amount_YTD_LY" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_amount___δ_LY": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount___δ_LY" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_amount___δ_LY": { - "Target": { - "Entity": "sale.sales_amount___δ_LY" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_amount__δ_LY": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount__δ_LY" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_amount__δ_LY": { - "Target": { - "Entity": "sale.sales_amount__δ_LY" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_amount_avg_per_day": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_amount_avg_per_day" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_amount_avg_per_day": { - "Target": { - "Entity": "sale.sales_amount_avg_per_day" - } - } - }, - "State": "Generated" - }, - "sale_has_sales_qty": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "sale.sales_qty" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.sales_qty": { - "Target": { - "Entity": "sale.sales_qty" - } - } - }, - "State": "Generated" - }, - "sale_has_store": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "sale" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "store": { - "Target": { - "Entity": "store" - } - } - }, - "State": "Generated" - }, - "sale_is_expensive": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "Adjective": { - "Adjectives": [ - { - "expensive": { - "State": "Generated" - } - } - ], - "Antonyms": [ - { - "cheap": { - "State": "Generated" - } - } - ], - "Measurement": { - "Role": "sale.net_price" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "sale.net_price" - }, - "Subject": { - "Role": "sale" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.net_price": { - "Target": { - "Entity": "sale.net_price" - } - } - }, - "State": "Generated" - }, - "sale_is_ordered_on_order_date": { - "Binding": { - "ConceptualEntity": "Sales" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "sale" - }, - "Verbs": [ - { - "order": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.9 - } - ], - "Roles": { - "sale": { - "Target": { - "Entity": "sale" - } - }, - "sale.order_date": { - "Target": { - "Entity": "sale.order_date" - } - } - }, - "SemanticSlots": { - "When": { - "Role": "sale.order_date" - } - }, - "State": "Generated" - }, - "smart_calc_is_named_smart_calc": { - "Binding": { - "ConceptualEntity": "Smart Calcs" - }, - "Phrasings": [ - { - "Name": { - "Name": { - "Role": "smart_calc.smart_calc" - }, - "Subject": { - "Role": "smart_calc" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "smart_calc.smart_calc" - }, - "Subject": { - "Role": "smart_calc" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "smart_calc": { - "Target": { - "Entity": "smart_calc" - } - }, - "smart_calc.smart_calc": { - "Target": { - "Entity": "smart_calc.smart_calc" - } - } - }, - "State": "Generated" - }, - "store_has_close_date": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store.close_date" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.close_date": { - "Target": { - "Entity": "store.close_date" - } - } - }, - "State": "Generated" - }, - "store_has_country": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store.country" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.country": { - "Target": { - "Entity": "store.country" - } - } - }, - "State": "Generated" - }, - "store_has_open_date": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store.open_date" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.open_date": { - "Target": { - "Entity": "store.open_date" - } - } - }, - "State": "Generated" - }, - "store_has_square_meter": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store.square_meter" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.square_meter": { - "Target": { - "Entity": "store.square_meter" - } - } - }, - "State": "Generated" - }, - "store_has_state": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store.state" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.state": { - "Target": { - "Entity": "store.state" - } - } - }, - "State": "Generated" - }, - "store_has_store_key": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store.store_key" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.store_key": { - "Target": { - "Entity": "store.store_key" - } - } - }, - "State": "Generated" - }, - "store_has_stores": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Attribute": { - "Object": { - "Role": "store.stores" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.stores": { - "Target": { - "Entity": "store.stores" - } - } - }, - "State": "Generated" - }, - "store_in_country": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "store.country" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.country": { - "Target": { - "Entity": "store.country" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "store.country" - } - }, - "State": "Generated" - }, - "store_in_state": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Preposition": { - "Object": { - "Role": "store.state" - }, - "Prepositions": [ - { - "in": { - "State": "Generated" - } - } - ], - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.state": { - "Target": { - "Entity": "store.state" - } - } - }, - "SemanticSlots": { - "Where": { - "Role": "store.state" - } - }, - "State": "Generated" - }, - "store_is_closed_on_close_date": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "store" - }, - "Verbs": [ - { - "close": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.9 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.close_date": { - "Target": { - "Entity": "store.close_date" - } - } - }, - "SemanticSlots": { - "When": { - "Role": "store.close_date" - } - }, - "State": "Generated" - }, - "store_is_named_store": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Name": { - "Name": { - "Role": "store.store" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "store.store" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.store": { - "Target": { - "Entity": "store.store" - } - } - }, - "State": "Generated" - }, - "store_is_named_store_code": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "Name": { - "Name": { - "Role": "store.store_code" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "store.store_code" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.store_code": { - "Target": { - "Entity": "store.store_code" - } - } - }, - "State": "Generated" - }, - "store_is_opened_on_open_date": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "State": "Generated", - "Verb": { - "Object": { - "Role": "store" - }, - "Verbs": [ - { - "open": { - "State": "Generated" - } - } - ] - }, - "Weight": 0.9 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.open_date": { - "Target": { - "Entity": "store.open_date" - } - } - }, - "SemanticSlots": { - "When": { - "Role": "store.open_date" - } - }, - "State": "Generated" - }, - "store_is_status": { - "Binding": { - "ConceptualEntity": "Store" - }, - "Phrasings": [ - { - "DynamicAdjective": { - "Adjective": { - "Role": "store.status" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - }, - { - "Attribute": { - "Object": { - "Role": "store.status" - }, - "Subject": { - "Role": "store" - } - }, - "State": "Generated", - "Weight": 0.99 - } - ], - "Roles": { - "store": { - "Target": { - "Entity": "store" - } - }, - "store.status": { - "Target": { - "Entity": "store.status" - } - } - }, - "State": "Generated" - } - }, - "Version": "3.1.0" - }, - "contentType": "json" - } - } - ], - "dataAccessOptions": { - "legacyRedirects": true, - "returnErrorValuesAsNull": true - }, - "defaultPowerBIDataSourceVersion": "powerBI_V3", - "discourageImplicitMeasures": true, - "expressions": [ - { - "name": "RAW-Sales", - "annotations": [ - { - "name": "PBI_NavigationStepName", - "value": "Navigation" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "expression": [ - "let", - " Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(\"jL1Lr3XLbh32Vwy319mo96Pp2EYaCRzFhlpCGop0kAiIJeD6Koj+fTjI4qPmWnt/n2VgUVfnzjN3zSoWOTg4+Hd/9+//t7/8459/+Xf/5V//+//5519e/+s//fOfasv/4T/9/V//fP2nP/+ff/p///zLv8n/9h//9X/89V/++59/+V/+/LfXf/vrv/zlTxh/85d/+cd//Ye/wvzf//Xv//mv//TXf3v97T//01//3d/85Z/+4c/Xf/lTLf4P/+O//I+/0nP+8pc///kf/o3+t3/88/Wf/79/+L///p//rz//3X+lf8e//z9ef/fvyxortVd+lZTnHzn/kWY088qjtPTqI71yzeu1XqV+zUX/q/7m8pXxj/ztf/tPr8zPrDXn1F8VD9p/pPxHabc585791Rb+yyPTv7yOr71TetXytUum/2R90aPjM8usI2V9zzT+KMvNTf+Fnsuar83/75VHTq/xqpOeuyY9t42vVfjf1752ut82d/wrEx63/kjrj7zMLPirR1n+5NLyxlt0eWH9zfWr9OuFa9qdFlYfS29Z3OyvTKtZarIX7m29+quM8ZUSnksrMhseXMpXLWT8z//T37zS1+xlyHpsWp9hjy/nu7GZyyu3WcvcYT3o1V9zynLob+20zPNa5k1f3JY5lz/KNLPizyxrz2KPzfyVsaabFjzvTi9d8mu2r96u59LLLHqF87p4bnGzv8YYPawFPtSr5C1rkdf6qliLWb46dsV/+Ft67FdLa59n10xbo50H0r7t0Wx1z0mPo4+e+yj45+o6q5wzbQv8K9L+avh+//lv/yut8pq7y6N7r7rraPvuP0p1c7xmqiXbaow8X/PV+nltM2h/faVlD6eVqueY1Eo7pJwn0nfrbu7XammvbItSadkHvpgelMpLPr56eO+17LTQu8ly6xGJp2XmPtd+dX67XfAH1q+KLaG/GXstX59w5kSvi2UuCe/IW8PM3HNdu73wIfGu9TX7WWYzaltffd3nGv4n6Zu++59KD6WndrzSHLRYuST6kzvecH21vfur96/Wyv1QWrweHiqfTcxFi0auYtl3a2TS/wx6N94UatD5/GqPt91p0n4odibycJOeRLshmavAu2HnzK+KhTaDTkmF7zvfrM3jhngn1nOK9x95urmwhr0280K8wV67yW5Yg1Yg4S/5atV3Qx/r7OLcdTds9nAjmrRY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\", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t]),", - " #\"Split Column by Delimiter\" = Table.SplitColumn(Source, \"Column1\", Splitter.SplitTextByDelimiter(\",\", QuoteStyle.Csv), {\"Column1.1\", \"Column1.2\", \"Column1.3\", \"Column1.4\", \"Column1.5\", \"Column1.6\", \"Column1.7\", \"Column1.8\", \"Column1.9\", \"Column1.10\", \"Column1.11\", \"Column1.12\", \"Column1.13\"}),", - " #\"Promoted Headers1\" = Table.PromoteHeaders(#\"Split Column by Delimiter\", [PromoteAllScalars=true]),", - " #\"Replaced Value\" = Table.ReplaceValue(#\"Promoted Headers1\",\"NULL\",null,Replacer.ReplaceValue,{\"Order Number\", \"Line Number\", \"Order Date\", \"Delivery Date\", \"CustomerKey\", \"StoreKey\", \"ProductKey\", \"Quantity\", \"Unit Price\", \"Net Price\", \"Unit Cost\", \"Currency Code\", \"Exchange Rate\"})", - "in", - " #\"Replaced Value\"" - ], - "kind": "m", - "lineageTag": "a7c64317-d746-4e5a-813d-43bbf0439e23", - "queryGroup": "Raw Data" - }, - { - "name": "RAW-Product", - "annotations": [ - { - "name": "PBI_NavigationStepName", - "value": "Navigation" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "expression": [ - "let", - " Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(\"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\", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t]),", - " #\"Split Column by Delimiter\" = Table.SplitColumn(Source, \"Column1\", Splitter.SplitTextByDelimiter(\",\", QuoteStyle.Csv), {\"Column1.1\", \"Column1.2\", \"Column1.3\", \"Column1.4\", \"Column1.5\", \"Column1.6\", \"Column1.7\", \"Column1.8\", \"Column1.9\", \"Column1.10\", \"Column1.11\", \"Column1.12\", \"Column1.13\", \"Column1.14\"}),", - " #\"Promoted Headers\" = Table.PromoteHeaders(#\"Split Column by Delimiter\", [PromoteAllScalars=true]),", - " #\"Replaced Value\" = Table.ReplaceValue(#\"Promoted Headers\",\"NULL\",null,Replacer.ReplaceValue,{\"ProductKey\", \"Product Code\", \"Product Name\", \"Manufacturer\", \"Brand\", \"Color\", \"Weight Unit Measure\", \"Weight\", \"Unit Cost\", \"Unit Price\", \"Subcategory Code\", \"Subcategory\", \"Category Code\", \"Category\"})", - "in", - " #\"Replaced Value\"" - ], - "kind": "m", - "lineageTag": "20f062e9-ff67-42bf-9c62-97df6dc33a35", - "queryGroup": "Raw Data" - }, - { - "name": "RAW-Customer", - "annotations": [ - { - "name": "PBI_NavigationStepName", - "value": "Navigation" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "expression": [ - "let", - " Source1 = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(\"tL3ZduNIli34K1j1zKiFeeg3kIJIujgoScoViup+gES4iBJFKEHSlYw/uH/S973/IH+sz7YBAEkzU0Wm31qRFe4R4eSRDcfOsPc+//Vf/zE47g/Ve1HfFafesNiti7o3y9+LXrpe18V+3xuUh1NvecgPhTWo1gX/Ze+P8oP/dlAdd4f6dPYb+vvuUO6K3aHXL+vDZp2feulr0VvM/uP/6f3XfyS207st3vNt0Zvm9Ecf83L3WZevm0PPjaxhUdWvhbU81EVx6K3SWT9d9P42uen97VgUu/023617vh87vfShl5Ltdb4t886vnCROfrP93+zQsu3/i/31nzb/v57n9BxmgoPfTWHBt2qXHzb5zvpO/5sfDkW9rape6FnDbbGzHsstfea7tajyda8/SQd3/YfJ5MqcODKZE//m2L85kcIct+dyc2InkksyyY91bk0Ka1HsdsWh2PaC0Fpu8vWPipZSrks/Sx8Go3R63/s+HvS+ly+HqqZv84IoMVgS/mZHZIzCEr/nMUtcj34pLPmW799pF63pcbulwxE61iCvd8VrWVjpz2J3pO1LF4t01HtMe4/FnpZuZ7VfFwb045kWxXZ+s2OFKV7P56ZEXsL3aJg/12Wxtcb7PH/Z03Jb/aLO35/L3as1oEP6gpP2OJ/fDBbz21VvtnzszYpPa1kdacEe6RP2dLDCSG9NGODEOAprgqAXcGti+nHEwqTPzydrUhWfRS9wrFv6ubcFHV+sSi97mD7MhtlinKntiD3DUUloVRLlBrluL2R2eJ4X81W5qWh3yhdrWtU5/fy+R4cmr9/Zl8lTskhnv4/mk2l2cUw827Q3EW0MnRTl3kTMCj90xN7cFHRvlvSxdEIc33osnp/5bZllj4N0uZpkVkZ/Uy+GR5+pNSNI2GI412aETi/mZsRBc1qXL3m9LQ4Ha1z/pFPbSxxr+kIrsttze0bZ4nv2ZPXTp6vrG9Iu6/fEx/W1PcWehL2EmRGEQdRemjWc1zGvD73Ao0t82lXbtbBh+DS9p4Nx+f1BZFiGMGLLoDqbXs+xuQEx7QzbDvzJcke3ojoc2IasNqf35r4+ZsuVNUonN/PZlRFeYPAcCV0Q9zdXYYQb9BzuU0Pft+UqLI50Q0YVXRDPxQV5Pe731U4eSzoX49lcfSR801r4Lrn131z/2owo7jncl4ZB+7zc5QdyVpNyUx3xtCzpn0kb5nQ1hqazGZvOJjaFliRQbwp3pWHkNIak2/KlzOE/aa3p1tKyWaOC3MaJXFn+suHHYzp/mK2sYTZfDLPeMu1xe9rvDRzTQfXIIO83J7m2KaY94j41smmXm8U5bXPrpvrc0dWlrV2V7+9766YuyYsNx5MsvVEvTBAaFiZmHsxW7BC5T4e70og2ubm11cemxKZUz2UBHzbe7cvXHa3VIwUN5LRW88U4tRbzwZ3mmfFdw7kNAubKFOc2pDXhHjUKwpBfHvna39fF7k96eF1y7tv8HxTbHOk2340nk3SRXnjSMDH5cw8Prq1wYW7Uc7grjehZ5l9PJ+FIj23x8kbeK8Gb/ym2g177ybh/+dV+ZDgNOKGe8inBCeXuM/bp2jSR2Bs7D8sNIqCXt15gTU/1ob0wo/nj3x5Sa0SrsLwwJHI9w4nwYYirWAMvpH/NDYmTQPiv/eZAjmJRrlnIEZIT2R7o+u4LfkWW94vxbLgi/6W6H65nWBE/gPNQvSe0ji53pEkYi7jwPj/U5cubdVMeDhQd94LYGtDSVG9FY0m6GM6twSLL7i43JjSdCQeu1HEVZyKhEyfCU8eLz53Y6pTDO1qD425Hcc+Gjqn0pvP5dDFeja6DZNfwyEc+8+iK8+GHFG0IM4KwMWP5diqs+/xzj30hX1qQGaV415aP6WxpTdLhXPGwxLYh8gps+AtX4UjpHXA9aQZdMrYr5KOqw+ZkLY7rfLvFA7vclDXcqFiMx/lsmD6mdFOvDXEdg+NKXF204ca07twQx4siEZLyLGVVkTm9xLeWFCOXPykU4r5iMR6MrPT7fGbhyb08HrHJDhsey1GEGx4eW2FHGIjwa8aTl/ti94PCDByQm6Ku2+VIF7PsOn0KjHEXC3gcxZaQn3RDYUGUNA8JheXPFJ3jjSt6cWzdVjUF5Qd+NChJWfTpVKjfWD82Ow4yRfWUkB2RsIPM5SvxR45U5SU9bunhcOE932vyY03oQ+lB/2GhMSTwDIYg+HHUN5YM4a7UcR2vWZCMzgL8Zo4HHinCjPakfD3u1vlGevT0aZJd+tHElNUiDnWUIQes4H7UIQ8o/NeSHMTNkQzIabtpK/aNI79P77IZpY4XXx6bbkfoaxOkkA4m//LIjdz2OVmXiPimOI3bgtaYjnS/olee7m3eHM/xzXCc0buuTtmMERiyx99cxT2hq+EJNxr58r7O95QeWN+L+rk+vr5uil0vphR/U9ExHdGrL8LzSTa7yWYzemGsm4EujzTske+x6EeROZEdnnCqlImKx+5bsadtoaCwrsiZkRO8PierbNZ/su7n49mlF6H7YfDuLPBQ1Rwo9PKEW40iGfhMy5dNTld4WK1p0yiStlb5B53dfWdx6LlLb8cZeRJlZGqHBo+CbPI3V2FN6JI7FCUQRP7iJr9QCHaqarrGj5u8/kHHpTecL2/nC01I6gamk5KwSEhxfQOn5wmPmri2OCmT4xv2ZF++55RB0SHJ63VBHnabk3e7e1gs0sFlIBY4Ju/hMO+hCMs9eDJeXLDtpLk68nOt2/LwJ5nxXu32eGIG+ftzXa7b2tggnfYXdIMya5gu6NwuNSfWN7y/Pk9kFNco8iiwE9Y5dpNmi1oMxYUrOhonWhyWO1TrdVN0mM/vECdrqkKuKWh14eNcxVoFMXkoaY0vHr9pfjhs6Med5ntE61b/WO86scD9fLGyZvN0MXmgoEB9bI1VIRsPoacqQNDOcY/rOq7ry0oi3aA+LvMnxVDWt2pXiPAI0QAFJDfLq4fYjgzfnyTs5KgqDy79UfH9XiwO7mBTl5T8UzQwOpKH29PZnVX1oZN2L7LpnOIB9TGxE8MdimyY4ipKqnSHfEeWDqUp3/J/4GurdVHTAdj1AptO78dHUUtT6HzM+il25aoWYzofEcv/lW4tpvhOmOGHohiyLJ5zviLLQ4kfMkaBqnylJ0iGBZNsPBzp0gnfMewO2YK6kCI8gS2ymorCjLw5xft7bt2V9RsdHmuCcKUp043TBcusLhyLHRscC89tVcUQym19UUP1HV+UC+efqGfn6/UJUeJ18SEbjPTXJDAleHCu6gQvothD2OE74tFblPjiUb7/LBDB29ZN/rNcd6pDrEj1mE0oPppkT9Z4qb465pp7wl4cVXmZzqtwugGtb1Og2b0W23JH+5MfcFBQVy5em+Iht2mME0v5nq5yZXiQ40j3CKHeHUmDXO+8jvhRU673g/ztGmd3Wm7XaA50FipbzKxvD7PBakxneJUue6t8/57v6Dsjck0G3xYioFOlfxRP+8LTBnHYBnQl4rZbCuUOn1W1/m35XlLmEzksK6aEY1hXCFfmtEDjC0M8z/Q6e8zJqSJLukbCyYaOmzSxwQY9oyn52bI6HHoO5aT7ff7a1DgHo+zxbn59XExJV2iz1VCVicgO4WlDPxQBCh2W456CJfyA5HbOQ4RRlq5Gg/kqs5bzB8rKL0IFU6nEC1lTRHFI4oAuoTAj8MWDk26Lf5A/K9YI2QLrb8cSXbemXpP+QcFapsiD6Tob3FqI26w6qPS1gXCxIT0ITakXqcVtTdempifaGpY10mB+HPoUT4+Hc/VKmGI2yoIddcrloYAl+kMIS3gTgg4aPb7fyx1rBSXReen/Ln2Ea7teCWPhn64sbFAkXHRlA9mjoiharsQgLw/bkygBIBemoO/htcrbXuai/zC4Yy0ItQdxTemwi4KNKlKj3DUQbjYK2ix0SkkFefjqRBsTxtfNoSm9N6v54+yqNWRwY7zurQzQaE2EX6UUSeQVFLIWNXn2VfH8fMRldaxJeUBRcUpW7Zv3L53N5mitLq6MMXXLYmaMsrFKxgifGgdtJa1foFhyosi1eGVtxFFeH6QRN+n38VKU89TNKs+Q4iBBVtexKEEOuD/17MBxZKyGZtUeMfS62KNKECXWfUUXGN9GV0nU1R4Gg2y51ITSTmjeKQpPlAbR4nC/6lHW5lye3nn+Rs6s59PjU9B1akK2aTp7olt0fYds0xZ5ePZURQuKDULuWD3KfkSMMsrfyz0d0OKlen8uenF4VrgYjsmfLeZPl9Xn5MsYXhVDh+gjCgMcrwlcKfmb/99H23ajQbXbIUTxkA3uS/Qk5FKwPs1ovsj6mpTL1JNIWOCoevHo+oeuMMmNkm7ziAKTgq7vuuhFtDP56yuuD+9rzu+zFRmkOSSeqcSG1DhWJuYU1oeeaDjbnrjOq9OWzsMw3/4sd704wttDBuby5e0/0fFQr0diimNZrKZMs8gI7me9IPHtTn6eIYyllHhb/ugkOChDq8LoIDa4+YDnnIrUJqTt4H7VCyO/0ylZ89rWPWVXR0rNXeu+wBbtul28WTZZXoVktqkUHjNDNA4+DKUhcdytaE3pg3nll6woP4qXbX7iZkzms6F1O0k17U3fN3iziAVlqniIliHkntWLHffCm1Hye9y94uekX37kx3fRKrnPFspcj6IhM2xGnf16Lm2NtCESNvS3uLqAqiAmowDgSOvTOPe7h8k3XUXC2HEOWSDiKeJk+A/hRmMvaOL2+7ykWHRSoYlH/uv+uHvZ7Fk28SZQRFnWvw5PPdMRTRx2RFW9iYQWWxgRtSCESUkvP/Iq2ukgFhsvlmKWje/Sy3MZfPHme8o8k37qSLhQ+q9EhJ7WL5QRIED/+5G+Lo7psr4Wn4gP6b6SO6cVQJdGU4j3zVVovGmqmm9I+Y6wJPabgsx+c8ytKdKE2LWy3euWvS58HSbj1WqSAUKl7SaaIlRgqUJ1d8KnlIfDU2w7cC+6JDfF4UCrvuslgdWvq8827U2/Z4uVrlNim5ruAYOIqABvIe2QL2xxZQber/MT8hWKzj5Kyh2CkELWendq+twz9PGe1KWAyNQmQPJAB1WdPESBNCSRedSRgVVm5cvblgF2LkIOMmOaLcb9+V8NO4DnUqMQKOCPQmGHH/pnR/aR8tr6+OMHCprXRYl/qx7h83RbBVyhhRGYKseX+czk+JJTdFzUr8Wp+IkNGgFfJp+5VTpbUrD8pNygIDal/bz5rHrwyZMIUJUTyCbS73AhNYUep31FSQTl+kdaqiYMepjdPEwm6nPifoVpUpeM4FK4V/VdzxdRWT9nNaP7fPujLk5Iuskhs7Vvgvcssx5HmrTKTgzRmCjjKTIaeupi7lt9ssXt4jMLJLh5uaMoDIdlTJ6lQFVE3KBbVGUUcYgfmPI7R1tGS3qxIwzxZMtmxgC5+fZ0PFBM2K02T+fD4VgBUrVNO5JEDDOr6nF65DzFt/vkznhlVVzUQfXxQWtMTsSLLvARk6f57DvZcQk9NIIjOCpBFQNRuBJ70ow25053O3j4GqtPn63IodLZXbai26tNor6o8ap7rjgcwrH6oSfRCfl/A7hLh+BQHOtenNAvWXzK3z06pAvrezrR1Q9NRdaAJ96K/SEXHwvP6seyTPatqtcAI+SoH5IFaC7eVdX2v3OKQrAms3HWX8znOlCVa6qWBawfrtokOpyx8K5B6Eovvy2tFLjyPX9r+seqrhoY3mM2k1n35YGNfJOL93QoBQqDYolSRZOUV50rFA2P7+9FfQImMV3nP4EG5P2Zh9lv898e01W20PTQAtN6oGwXqIF3PsU/3JTIlQmM7Fot8k+sCMUBj+W289RM0rvMWj0tFtl1IuMZjggHDyuBqi6dRWGGL/sBaY7KzA0C1bpH2Wh2rPOXN+HBVunTZL5YGvKHIDbkD2gJqAuZlGgnwqlGUSR82e32tNshGqreC0Dvs235JwVLbcy6epxb0/HkLyJnAemJfnMUYREl2onwqFFM2W2TaOfkTXiF6r+rDXtmbvOypkfws3lpxr+PL12aG5ryS4Z0Vza/PcodBY45djvQiRop3OcOX4hc6j6v36RzB17VmmaT/vwBnbyLdpEJ1e0H7H1RrEYU9BLhWinfaVZjsKUfvUDdoaaTZ42q9Ss/pb3hInuapovr7fBM2xGwIrsKkUh/KuHONLBd3+v69mfWau7TodxRPuUgrfhHB5TQf7ilx9ZUtfNNqQ2ujKdMKGg7Eu5VAyeI/HZvDuV2jS7ebr2le5NEVyCSwXyS9XU9IiN6JGIIH1WQSI9fEgpr4rgp2Y0oj8g3HECyPu7piUdNAmeYtyJm2WKRTp/onKi9fGhqzAC3kChDRXpkEu5gA3JqyVnp7javdyje9bxreE1//juPijRLYwbha84OQPjcxZJzdJoiDYVnL4U13z/TwQEAalgVdJTEJULVe6mCwJseHMDi1KEJvXqJIAJ4SRI0gMm8zinds2ZF8bMAZJI3WxsvfwMywmJx3bczNSX8GGmnEmHk9hxb0AF8r207L8sCMVIfSMXnE9pVyw2dm0pCFR9pR+aPYzXg3DfV3wNHS9BIyBZH2BLJvFMEJZMiR7XqrGk2fVqO/xKqJnTZzVWBemL6bu5Wg8CW4fIyfz8Co0gvLyARqBCtNlXbgr+ZTyZPpvfOCDP3WW9GVQwAL8H2hDW+LFaN0L3kJUQwzmidL6sTvIhI5+MuW2iKE190RtRVK88ne4SvjZp8U1xeHjYzpF5+oFRPYjgfx8tsms6M3ZEoMTUVI3ZqVXUBcFgCaU9bLWEhAB2Vouc5OCO7A3neSh6XIaXg0/FC52SNpvD+psrJYmmEl43ilkQiCjcrgPSqj4L+uwuO4s0DpRhwJZf4a5NH44hKZVUtJDuEf43tFhGA+EiWODe9gLPhZINzkc6+XVb1HFMtzQ90CR+FA47NXSrF8DJG6zMwbbZGb4hSXubVZQy/GGeT1ZUfc6IvoxEl7gxfz90pvU+B8B3Z+pP8hbUoymdAalFgpqCobU6NZzfj2VDXAzE1vBMGD1TVilyn5wiCVejFYZNzUuqd7zcs7azAnQmiS+RZOoE1qdXP0sFIjaiJTJE8q7iqOiJ0GpyGbmXbXrcoQdelOu3oAoQOPXfbNYONyLDxe7agqC1T0b4Mu8SzTlUbPoQhruR9SXj+fFv+RO33hRLe4+sGqZZHy7Q7FPmrTLYQvKa9dLBqvwmJ6kd5yOmSAQBL/v9E77px0zgpTgWGhmGCgOXH4XkpeLApKOsr4XFDdoT36yYJpPQPZMUBOLVXxUYjYoIzKRSBNYVtjiBeUazVOjfZhx6hKuA6Voq6VoOfnD1MlpmuneebatJBouP8hDjKgbAkDCT3GXTJ2+ofrEAQsygb2FfJqB2mq4dLlLhv6mBx1LHSzUf0/aH4/qjNddL3E72B+wMyremx/tg0V2i4SJdLVEsm2ZNuLWztHXLpS1kKqgpfbbIlEhS9QGJ6ZFF8kv+Z16i5+tZ9DTxJU3VNZxSxDRbz5XKs6174pip9wkFGaiCwIyhZYWxLZjpP0Jf5fs8ewUVebj/zZn0GKcepq0snXvDFNmmq9BStCEpWGFNOKtu+LO/7KPJ6T+8ORZLIOv62XfeextP+g8KnOCbnxuNolb+l6yJ4WBH5tqCFwnEe+pwOIYq/oS8ilQZAMpgvZuPlSIFmCWODKRGvhqvanBTACi5WZPtNJWudv1uPFAbke1yZiPw+cuTX6rNuOfGL0XiWWbcpaHLKQ2LMuuBMYmVMDWcieFmRHfruJW5jmr+VO1aJTaz7Y/1CibMErTPY4nxuTJNNJxcYvUDdd6N7LVhaZFSoSt3RGi4Q8LPvA+iz9TCAIHEIu64OadIQSBihT1nwohjClYzXxGmjyp+0T49lAXpjZN3nhy54IFNVq8HqMYW1GoQYwlpB1oocN26JDQy8eEPWgKIesTZTc4opE1uuFGmpbQr1A06JVj3RoEQLiqsTtrIOIo68L96P28/jCa73ipgzpyO80iJdQWw2XSrUmVSLgkslSK9OU/xbnIBYz18L8DwpWUcb8DPf7Ttbkz70F9kl7MdLDDc75FxPVXUYRyMWPGR6CpsI6vhS7taUJ6PXhGx9VVV1/km7kAuA9G06XnAtBTWXzdT/i3iCqGJbYpe4y40oTr0g5EzoZ31XpWSrBZKOp385I4s5eFylkUILJBhdEXnyJg3ilP78k+8SoA3b42uD4IM0isWS1nSsaWF7xioplyZRPI4UwjmCzxX5DZ+LU6dW+Z8Fh5E73Q4Yg5ta5FiW/XR2XaV0TK1j3Cd1pQn3SXC4Itbt5anz8bWybsvdrtzTfR4Vz8/IU8Sy3M3mv3PO1IVXMbUTYodRclSwRrpAgrwVBWCzthVj0NZv8t2u+Fm+ovF01t9gJAcQIP+F5oYf6Q5KBKUDX1rTAgkXxXPx8pJTkrwD64KnRwcgxxq/359rfYun53+woA75iAo6RyGU4HJFgdciYZaH4mMDdLCV7tDkBxIWSLq8NWaULabjVTrMDNWfyESOQWDnqbVsYJVwwaHrtLUous1b3Gs/pF3bvub1rmrxORo6pGdihwY+C71VXQaKGwSRK2Ilgc79oev8BqxnYg2KdV53E/rZ6hrhYMo9IoaHUdJhcIGFx43cVnGAd1uWB3J1TCoFGJROBewuG0/mC2s2X1xBx20TEjdmGBRVQx+1L0HbiiLfkYopJYqSt6ibo81xeVRnFDnRY2jCBZsYS5xWrjobLrkTQeKKaGXDTko2yH/8OLHu05JsKF86rZ9v49lN2n/SkbhC14SFZUBLJSqG0kPB4ooSR7azGaFtcmTpYaBqrd+nk+nTIgVXVtNcN6UhAWfcayIXweaKKMiy28ilRMeD3P5bvj++5cid8eMf6OiicoBgdzBePYm6i9om21hXCFhEp9G5EayuiG68SI0GVQVW2+D4ThnKnjWoXib58XWzLXeydvo9m43mmXU/H9xl15fK0xNnZRKrKlbC3wmOV2y7Uj1D1CWnL3clKvw93+rTecI/mBVNIsvUEQZjHenaNxct0XRXsYbIywiuV2wnridb3f8gb0uvEsWWJ7QyoZV1kpnSkJZmcqOgABqhQzzeVTERcWq4s40d1NeucpH046NglLMLqgwQbykY8X+JKRMGOrAbYkzB6YohAiMtWUFhZdMBQ1Au8tolrKaLO4uHmKqaoe2b0mqW1CulZrAuQvDFjWSEyQARd9XHnvw/elPVcdsiNJmrwe3OFsqMJDZKADE4hKqyDQcsKF0xRYNeB9C9LHcvrN/ci884MxA36acWVuZf4Mx4TLZKhSGGJJIgdsVeIzT37cgAiSBeD8vnZ2yRd4lyHyzmjyuKGXSqUV+AeNRnF+VUQfCKvaTNShqs+bBC/Rug+z69TJSTgNwEDmnvdrz6YzF/MlUcAhPLOGLRi5IDQD5GML5i35cuTyqf1Mf9C0PWOla23xRbyg64ZIEQWmP6Wtcnx0ip5Q1wVT2G3IugfcEUSdWnfHqL6jF9Rr4j5+/56MwfTnXj7KbpYnmjKHs7psIClkStdsaWRLhdP/Ya+tnPck3xbv5W9NzkXP8EJHQARnUCKCZufqLr4qHXKyhfcRCEZxD4fknnJFKIbfwbOX3CmfCqfiIWJJSWJGf87xECumdUG0Ba6ZQN6VLTkij4AHob6C10dB1fh9IiQfeKg6jtfDPu0PSlX5/WtC9YEXqYAb5uSj5MTAqIPCVD0djA4mUOVcKKModgfJGfjCU2EOR8+mpy/nVFMd1x3wuSixIzLQpdZs3DbKzQAVujVtiIsEHC54ZB0KITILxGZ+P0Th9OCcCCKUtwxOT9/YQfFU11OTSZ4nHimeLyxOTkBOcrDhPb65BHSmBZaXlKJvlV1MWPirJoydEHIO5+ko5nS00r3NSdcXXmoPIiGGAxQ4E1aMUmvqSo92/H/LQv14X1HfuHXu9qAZWr/0EDyzbjbXWUDqyS8LqxZyfniJZpke8YgX9GO1F8tjkbQ3OyW6WBQX2hkKKpllFiIHhgcUxu4FLeBxhGvACL40ded/hxkFp64rkbsgNlNJN8oU6mVsHCSyBIYXHiuR2FxeIftCRkxGpTVOuqJicJZFTFWP2A0HVEfBEC65RkDKmlz2pDSrEynKVAWpV0wOLQ1OtDU6BilLlB8VI2jfPF043GGzsm7n7Abpgy18fZEd446QjWTkp2SCclMDcHRj3JT6w/3Byfh0WD7/gLMgIgz6nZ4sjhBFOMDo4krXGNEnI9FGpe0CxpWRUdAlHKe5gxvoOuSWC6aKwnqYpGIcEoeGQJ44I0uK2fHFf3uCkPoObG8Vke3qfHa6FRLw2NKCHed1NhHrBY3ElT+t1q2k7YpaIoh2LyKLbS+p0+rdo1Zdfl0+Q7UMuLy7ZFYJSGDHG5VPAC+mZH8MkSBwDuC9jjKj8+owNHQc6Z/h/FxBRora6VdSkXNBcAPGWyjcMjmGWJE7XkunR72u9zgEBf85p+/tC+4l7cLB6m0/nsRlk4MpIeec9AicihZEHQyxInPhM5g2gI6mgvb5Ro98LAEkCVPhPvPoiQGEodU62gqmNMuWNtyk1BoCCaJW4omRmNnvmyoiXaIxJEHLTOf1uRI3x/zrsk1f61OGFibARGrHer5u04gmmWuHHnoaAYo8l3o4Tex92OtZN5SQuqVfOFIb1zIsOOcWktJWWGoh7BN0s8L24eUQGGpPO8KfByBdBU2TYduFE6HQPJZA0yeuZTBYfGqGXJqUQqPCTOcyjM8X2vrQPf5Nv69P5Bv/M8OkYnUWXr7BHFYukyVZRjIxO8K2LEexXYAFAMwThLvNgRKQw6GFb/uN/DFZM/b3W8B+lCSq6qX8zEALAGRDRSMxMpShaEs8S327iUhYMVL1pxLce4qyo+zRbZY6bPv12jHC+rHanSGNRIBO8s8f2gk3+/iRecEinKqyjeXxTrQsCG1r1vD3AzYN9fipuY0Jkc+qfqvVFwIzhndJpaKDEX31kUPwu8R66tKuvN0sHDQis7H3kmGHyoFbgkzye4Z0nQqWRxTOSW4ZuHlOEdYNSI3HKd/xRZxCgdD5d0dv9KxZNf6UBHdXYEES0Jmz5gyis2g7ysd2twN8OzVKaf3t5OtGmMUbCJq2mrlCuwJsLvRhAb70Q2LBVgNWGg8J7y3Sv9A/4GDADB0IXnnqm0F3G2pCoYhpSz8LpRKMVFvuWU09GxKMrdDyhHIRCumUxQE6Bni8nTjJX2LjorJoZiyDiCGtamI0hoZK3rXwflN1W9f9lQrGaNjrtDC9y8y6BQ+7S0eC6uADKZKo0+65SqFWIdwUSjrEUKbIOf1z++vLGPDoC+qD+ZrseEtowih6flDWoBi/F3ci/qWNPodANWJlGyfOk9EqQ0OuKu5HBWFGVOTtB3JG9wGZIP5vf3WJJLVK3h0Po8kVNFmJTICSoaXXff6Xj9b8XHhlXy8AKdq20P6BLPrPmtdT8H5Vktuf1FM13f4hGcNMqFo7NC2vRY1ii8np+Um0X6hKeZx3V/6ZzADI3EBzkVwUejJMWxu+Wr79Xuk/wuZK0Susp1m9+iX6C4PJ5Jj8dLdEo4MW1N0oylCSUmX1Jsv9MJBKrLopfxA8UrOXuFRQeD+WQy1ip/Gp9mziRRLQoFlolUAO92UtpmupgvEZ8T07Ob8WpEidpQjXkOTFg8jl1VBSzAriZSCdy1pXiu8C0QpKm2a2QlziVkczKePN3o+bbGSDdksZyquePjoklzYinEzZ0u/2gf+fXubY+JD5xjM59PhooxRrFJod13dR2dCKdWCNfaHkJk6W9/4oxWu2JDz/AOclv9Ct8k05D5nALuvlZ8wzcG22yqkqr+Cpy8oKeROZ7UQ5Ws/QLCn9icYf762jbd5svHdKLJiFDX0Xt9Vye15cV4LaUhSXBZO5NtUcS3+XbdbfNnq1mqoyEbWZU2y4hU3F8UpoU4OMW3cbNJC8rniy2o8wVOym1FNmzo/0mszu9jnVKN/RWCKlCzsXBiE2lJHIRXUdziCF4Uaq4MDYgcraHePgwx4ulC0fELERAghlSph486vjAktKXes/DzC7oy5e4VQ0AGFYjaL2W+betTEJq4yOZNrT+hJKDGoru29Ldh2MrDgbBHDqt+2ZTPdDx6oZX+/ZgfZN1n/nsr/6G5P0ahy1CnMRlgWaS/DSM3uiy7dK60H1qrusj3RzovY3ZAG4H7MbIiRavLNdWCcGrU0zlCWCXdbuTJbi2T2YfH3TMB0CT4P6NLHXN6v1pU1rWl/42alhNmt9An0/mJbQab3LMJMt/SCSb9/CvEoEg7t813yQTpf5NEPgEUTr5ApQSKx7Sii2rfFBZErXORjSeT6yqZa8TTxmyEjQoRDjPkPAbbdi7h+i9veBUpfR6yqUNSDHM8G4yyia5PYIxvHd2dwrAOW45ksMGj7VYwb/L9hgl0oAB+Pb9kMr5l0u7X80tMvWJ2cJVAeRzcWNoSyQac0AiFyM+6l9jW8rir0doRBbGbhxkSoms5TBM/yuP6eQoj6FtdwWJzHCeUMhTIQOiz36BCQR5SMWNwPu1n2eo6azbCixMtNxmzIQSHjexoqLhs1JBQekxChHM/Do3UcbZIhw+XzjY2ISADNvdAlaGGEZoZ4vuZxz5nVY6YnE8SXWG+UWWfDa/4R2YQZMCeQLUitivoao4T+KFz2ci6qXM+dWm6K9+fJeT7EWg7XfxorGNEjDKhbGBhRTxpSjP4qNUqn4BDt4dDpReoq1D6cH+fLazF/AH391/QKMVsLl/tU11UoaVNHXQbhUs1g7TVBccjngt0DcazWaqaz2nsfVLmoakih7CjmXETJklbKB3lxe6NiYSNil074GY4SxeP6XUDxDVGkKFOxdYj3+FIlxpGsnDAYgIEkNMcbwisOJNLeRxlmaYjZEx6EnZKlERPnBLpUCO/xUCKgWmLE1voUIHzoJRwPFvM531tLdC0ORGvYaiKPIj0pV+NnfCyuzinOIT1GxRtPMin8SKyRa4eXQddK8/UoOHNRjX0z3Wkt40bvha87ZRNpgw86656bTuvd9ksoxhFc4kSsxGagAlGuNLVxqFkz3Ett9s6/5PrhZxF149cU46JQl2E2KZOA1e1VZJ+IF0i/W1it0JZEAelfGdDAWWPkg66JLX1rdrsWJSE8Xq0PaN0aoTO0jtmTOA1QmrkbV05UQxqSW158B2agz+5iJqc/Aa/+7cHOF5GcNRFbqY6f8AHmKreAY9Mkc6Xnq02c34uXylfpdu0QwC5qHaUAVy2obPZdZASGpWyYl3zGZ6umSqWhC3ZURrSxEwOpHfoPWA6Xi2Gl5GVWd/sArRkSor8RAsTwh4Jz0uRnHuhVTXJ9yAUYozT1Ui+dLbKMIq4T48TpM2VVUJjoYMPF1WVTenZlhPHyLyWjNT06DsTNWOKLZ+f0Y7lAV2ayllOPK/XkIhNqWzM1aNV1G7cs0haFretGo74QCoJ1HO5x1tB8fe2+pQN6vnkfjSeWaMsVahIGuvLXqgTo6X40m0mkXmOTAXk9t2Vh/ydHiwMvboOwHnpXVP9gDM1sU40KRJGxcmhZGSQKwzimhXT4/7vx/JQIfQ8H5DbRzlTNbDua+1CpVKRhzBNWtFhTLXjPsrtK20L4xTTbcO37Rsm7/xhmRkUi4zTWLl0gjLIgVGONMpre+VswuIEQ3Nd+wJTuxo9YI/mj4xscQ2qNfUEOJ1MReBKUGmThvhJE5s3E6imL0NaJda4vyyY3c3v5qsHMumv1ct4vUF1o1BvkJPJ3MD1GzzDvvjYWPfl4QU9PuhM3hQ/O62sdDHJljoJX9skmBe5Om/ox2jJSVsapHrGgBUSzRUpdOpGmXWTPipGbhinGnNWm2JNYhxh6ZXDsE1b6LFEbZVdpw0g/I57Vol/mi/uMpbNKgvxpjqvH+tA2JELEqe0JpJzyJkS6YoiUHJ2XoxRlOVuf0RvmE86nM9m2Uo/9IIcvBH9gkhdjX5xPemAI7cz9oLO7V1V79CCpVj0tN4VJ8mZHdMrpUvnzOw2rjeiqmniNseNHbGCdjLJgVGqAW19rGmTmoSOV8muH4HAOD+WVZxVpxYDQj3pcxNXNtXaznRWb8u3cnsq3/Ke7yhegmmWUty1WFyXH4yzZENXJ3EYkIvxpQOm8KMT+j2TN7nP6/oEcjPXR5c5HYRqGF31ygrjrAkW9SkH7dEe+dLjJsjIBNuD5S3T/KUu1iUbXXqmTZrp241GadKAKy2qovMAhBVpSCxLU5Lt3adchRI5xmbOX/JnPFA8aBjMZ+OlFoJjHOcaM5CUkphpo2TNrfFsp32I+rQsL7nVr8s1+DjLTb7+UdWtm2P0oKdsrhjYEkQGwJ+nnS2E+EXQ2cgUvzUlfX7GmLIj3SPy/eHVMzScz7+Ph4qqt7lrE+nE2yMclUAagsERvEUhBduG5bOQsb2gBaULvNFcQkLtX0yBVKRVtsc0ZsFqczynwY6JbInOLue74Mwo53WPZ5PsEuNnntftsSKvClQRoUUoTHHtsDtuYIuRXC+vjBuEviOoblVTJ5qlyP6vRqeb0JgB0ydTyqbiEsXSDKflezRhCyUFAm44zV8puizlW3RPITe9ipqH6AsGDKIoVdMRt0h4XYrmnPMJbh0qWegCuFoeyvdGK4bXQqzhYv5dq4Bi0tkNmH6psuDpwi00VknWH49g2AxC3xPj5TbonPSrw6ESNWjU47Onq3qESd6esc+VyGY6voLYhvKvcz1wb5QzRNtgkx/2n8zLSLwUT96mbOC8piBh2jIUpdVwJRSlBbGNjPJaGTUhqCq1MoFB6dRfMR4YlRJd6dXcztKVXmGLdMKBL98mOTmLkpItMH+okJ9POk9vvqezAQ/y/lpDyWVNCzUjxg2kFw5CT4Tg6Y7S/d2J4hg82gcEebOKPgxgYnGQ7x8oCn9MH4YjzeKYiOC+ll2G+DeQ3jh07LYzygCRE3oiMWqb3J+PCaMU3uxbB0hRxI0USLlwPEYdfq1OskeOJ5CuOGxU37+hDrw4vu+5MOEVSJ/Rl/rzK/cX6Ac4c7ZbpIR9OwkIndKKhrfE9RmXG7pBIJbNQZVvS8IMx8XF1AyReGAC63PBGNVUeCyL9McRMEcdLNdtXXxiWFRgCTiIDH3nGAx/uSRGJxNEupGVyKgD6YJjJxCJyX1+qEtalGHx8QG433kbNJveLzPdID5jF9RnAZVqJSKwvqQdgdccV0b8ZnySYlu80gsZReczo27mj1CVE4O+L4bzmCpUxonjbug0xrTqg3wO7er4/NxLvItBTfM/xtlCkZGYJzUlOooCXRE3lI42ccJOlfP5RItNz1CgUJ/CgBMhC6ZpYBgKL3yOhqqnQjmiG0pXC3z1RTaNnsoGYwHPVDceBg/TfqYX3DABZrWJPcr0gsXm+PQPGplKQd64xUB4B9Lvu6rO30WPGqWo7w+6dqSxchjpkLL04LiCuYb5M22jabDNj2uKnEY8l47PO6O3D+RLFowqgdhFbZFJwBlpiRpOxrYplBZ1KKvQXQVT9PG0W1PSislW0+LUzPkAcUM9i8YzofH5TGnNHGdXUNjIkFgqnd9gyBpg3p+UMzpXQ+gex5Q/ry77kaH9xTnRDBnDBYobE6LOJaZIm3bnEXO1WDEeXv9soNQkhW6NdncgjW0Md2P17uDkCl/rO17nEayekazu0QRM7CtZo2/Z49KapJgspZM3MqxQwtnWKoBDAn8g7fGTLkxpVYKoscWU4vP38PEpy4Sv/esvIYBcGhJUAB69tCVoJcIYvg2TJJ5ZDTPwKT/a7Trj0Ger7JF8zDBbLEAJJbMQCSPabQnFbPuNPGc1WhTHWLDXHN91JRXzBh1kvM8FEgC8SJSdHGj3hGokJff9dHbdEHBMDNko0cpXOiChSCs6asodjsDxNWeg1bOHegBq4b+CVvIZAlDlfZGURNL7urShbVWq2LIxn0W5o1D3FVrfZ+/05GEFivXlVEfTaXF0BW/UxiLpeT0ncTvCLDeMJgfOJ8AXXOVCRk4gMLMh01fSvKYMzdWJykU4HtLdephS021H9MHOPYFC0pEbn6aT/vhqGYxy44lWId/F90fN97clQvb9QzqTNVP4861vx90LE4FfkjsZ06mgiElXbTGOqcOeaJie2BPpb73OaBgBVxK0zj3Clo7g4F02m1Iqr+rN2KEpqPZ0BMYQqyLdrB8FZ252VLDCLYj3FyLWN093GRqL121qY5WdVZ9UES3ddTe2GzPa6IBNmOznB0CZK6ZjdzZ5ZJVR7n6TPeqQzKFJfiWOdXMlgFOKpYNlwylEdY6NLxjWJSZtOAFexc5ApX46TR+nVvY7WbUcX9E2jNX2KNRBygAzjKVbDQPbvzgqdyUEB7d8GuplhEun5WF2k2qyZSNAx4u1RBKsjnSxIeVB0qI7IRGcbd/3WxS6XUs1hnswp0t1k44vkzJjMhSy/EODH479xpqmh7Yt/5uCSMnZiMLLugb0r76Ps8frooZRepwFLCqdPcCWYulmo0BKk3GOT7/6FBDIMx4wF4mYz28kWumCI22Srki4EqMGixlLTxs77f5Ixcy0Ltes+v+wrd6r1zZ2SjFuSVN3ik2g84Dr4KpAOQEkaxtjZDNPzEkReOUbWh4wKs/ewD5GYYIjd5WnmgK4UMeNdnFOpMuNvWZYDKoqN8d3kNj/3nOicybLfHJj3UFEdKK7Q2Yui62T7Ed4G0u/G4dxSw4QzPGXu/JnfkDP6uzErKBT9qAgjJvUM4RzUakC06Ik0u8mbqtnCt1OTANfFMUaTV+l3MBgMX+60cXZRuWrmE+SVcX9lCdKlpqfdFSWBpttVbQHZlf9hECET3v340c70gAK5GPtUAMjtpgVnnSgXslT8ymhDM8QA5PqSPsUxle8ytF4OKIVur2sszgmpGbI5U1VTyS2ymusSJLLdLVfshDKc67K/zPK5x/utYV/E9jO1vHqgcSX9DSfnFEo68nb4pleSDqZBRLna4bNMluQl8uuaY1GBBBv/Sr9LoVSkqQW2HYbOqTHdU3uflUWr3sEDunPasciGHG10zsmxKvTsjMprCQcIaoaC0LxlOSoBXYoEc4TeqSrLb2MKMoloJy+v1cvb6eGKjFcDuYrtRyja+qihUzKThXDIKSSJLUA7QeZJLJxw6zenzM6rguWWo1Zs1J5cJQupqP0e6ZTYggNBvlspoySGoybLRww/VC2bHlC2FpMdmd9GeAXzutRXN9plU4fWZP8r5elYp6VqEfeu5KzFlB+4Jy17GfFTyhLU1Y1Q3r8eqQDJPtGjyCUX7U8EyNMk6tUqF7JBFMVhRme32B6i6oj9Ipyx21dHSWILUVaslhaisghTkxvZALnq5SncMgM4XyDwInP6TXDqlq/bPIPRL6XLqYPsdebuR5Ha2qTA54eqtVwIzLIlQZ5sgkhODaToticgL45w1w/Zatpqjm6RhRm4OnE80OY4UkzIjlnoenW3xQi0EX/7G/HSoryUowJRPql0pWRKOfrhJzDkIwQHjcI7VaxrQGpLoo1r0P5v05mj/OllSJgOCzS8Ua+VIVgdYVBvj+wBccopH2HXc+5JZhAMTfPefON48VZIVOJB4VR0v/GduCfjfXCnLfEWuUflAfsOxMj2UjeCSaXzOlSqwfhOUYAL3+21UwPTzLWyKC2BsTmqdBBmb6Md4dqv+kll8rk6fc5owVZI+jq00N1qU5ukgGIdNkBRR+epK0Fid3qpN8BXzfMdwfUMbtDF1YMsq8btmCqSXk6PYQgJiOk202aThqCcdynb/lx94JyUHI5Cp6LEYwYGFP9WprmbPuBFnMYUOAkHXDitmNvOKX7ttiy4VUJJvGgDZvv5PVCp/7JGmmV/gKjIiI/yKrODR1kSWgLkoZqwihK1faAbOl8UscixbTCf2VSoa8bK0BBnSfZbEEStLF49hNky8222D0f69ce5dY5BTa1rFI9TMYz61HPvjFO6OBqtKq7HbmYkyPN6YwmuivxPN0fDyVeSfQatygndsgvK8sQz/gmURzf1+mvRlgd4Y8p0HPlvGs2vKlgE9GBxbklm0A4lHEnGsLXQwxtI6Xb1Q1dwD2SRLbQTuT7OKG4hQ9dqMEyAerxFULOjRiaAT/mm4RfIq3mNmWSnmS0kSVteVVO1aIQD7COPeRf4OhOAnJ4l9Kx7Y+Vlzk0JWwxL1CpfC4OivC5oWM3NQdBKpzkn6glBvZ53LB6mEz0ykn+F0xDnagITImlKYGciEd59H/ntCzs00+oTt0yl9cCDullvNUCMUNTT42Tb1TrEsGnJNKYOEpa1w/cwPRlWFefQKgm8Rkg6SZdrJju679ABk20mjiUYHmSxxaSx3XOeGx5zdQ6g+SchznLHgfzaf+ShemYXqFAF+4GAf6otCDpRFQvSEcOBwpzKeTHFboYA9qdmqvxce4XIj2hMmujkNuTHDYyKblsGd1Ampc+qECr5LL3+QQJvcwCEOhf6H16DPeiCxkkm41+sCBqxozJ8K5HwXs3Nclmy/RJKY0Z658iPnfHV08xwa2WztbzPLsL/hlUz8+QxSS/NMk/3irxDM2/pZShLR7uheq2bqNMqRJL9DVKfp4ksoWe71x1zIeAju3QO7mBosZH1d6nyXxxszQQfoxdC97hUvkaZlLYmtScnUbfY1NAEXcNFtJZG6U/fxymN9omihHGywquyhInvQiSukbmxJ1OLCqLFMl87PnEF5aMNG44faL1WZra5o6pQh8HOpaWh+sufbEX+wJn8TudITaRIj8BlPNYbjs9nenDAkXF8VUzxzdJXjFVac2QJE/S1ULfkVWzdPd63FszPmMMUI/O+0hff0O+96+/j2Goq8RQVuRJuhpZIVsmovPaavTQIv5a9DlHB6nZLZ7kqoV+KF9svAXkV05s1HD+48eubd9Mxzc3amaqa9R99HW0c5e2RrLUwiCMgrbPBmHO7fGdnuVNvkVX66pZPkkpJVEMsTV0/LxQ10mKsT3S3YZBq/I9zJ/rkgkpjQooy9b7Tf6J4OFqhmC2yKYZJmPoZggaLlDCBIRUJWgXhkknHNl+G8zQ7hwKDg+iZIay/z3TO1xKoZ7J+FY78sxUaPW0am14KCVlLYzo6eiIx2GFhlW1LyjepMPje+fpbDajbP9Glw0Yk5OIM751J1g6X8rrOuPU+TApOSAiCK9Y39CBTIfX7B8j5zvkykUq5biITBGOl3YpiDvN9O3JusF4iqI+nOiqgHPz2RDoHikxSK9GkxrHy3uBbmRrHEBFRVrRlb/iyhIMRo2KWYhpTeT/Ol53TC7GIAXsGwc3hbpBgsztJdKiKGyZn5jZzUIJ9iDFF2ThwShdLR+V/Cwj3zKIdaobyAokXY0skao9fLBKut3ujhTNBI71uMlrNLd67DG0BpN0ofG7rpFVyAWj1Uh8TzLWIhzvzkS2KWOJ9fxzujtXKLiZz28ozBzPdHQS38QMwBMdqYEXAfpAwh7X87sCpmykAT7b+g65zF1x/NmZNTa7GU/GQxSgLzhIpoc6ZER35R2isyI5axHzlx30u+hWoCJEjwHPT6ZQ4eIvNup2aj6s/j2Qc2dU4TclN54krUVe2OKJ74oSZXk6iZyFdMYqnNBaSBW5v0QrDPlkUhVqCadF+NzItz2njXkB9V5uyo8PDPmCBsgLhVP8xCArSTVdNrNQvsN2R0339CRZjX6WpnwInZHpS7Z7qasCWdtqU73nYn7VcDzpMyTxZRnVhHYEejhWHhAPJkgn63t+cl6M6tcUzNVYjDOQYfawmN/9KyBDFDtctQQ9fIn0s77vNKORgOCalPxsBL9uUCsXw1ei8BHzS/9KrrEzTqH4B8ZP00KwuDL2BCCmM31tqBsYZSxziyGc6hlwniSmRX7SciSgXXpTUqTEwB4XQ86aEeacw/IvjDkT/RtVRZcCTElRiwJXthqh+UTpR7qx7mglKHqavtxUn/m2qUtN6UmeXYv5BSaqhPnpkZy0KPCizrP8A0oMSAsPTAs/335U26bBh/li1iCd3gOWcxlxm5oAfO6bsvwCfy99bBA7zS2uMSl1Vv5Ebw0l1As1sP7Dzc1YMdbLOIzE5zPfVEPOsC/SvQZJ0KHiQlW2fit36JFfMKWn6Q0kJ+W8KA1f2iQcyh9BlSpEAGF/YVBo200en4GEBmhQUX3ukKue1S+n48nwQXNkvcRUILMZTFXVcsQOST8bep1BARhEsvsTqJNtfnzOj++8IXGiVPpTSuKPFzdaoZ7QJDMYc1E7VWU3AvVbGuS3w+YHm2K7L8jvHutXVkK5lKKXR1c1Jts2ITM9hhJVURRiXCXpecM4PEujmXTpc41YG+MCz0ocGT3K80Xa9PU0RQ7TCtm6++1jy6QTjqNWPa3P0qLpywAdYkgQXE6p68+hYGot5umNdT+/lHsyYT64eoZy4K8LSoqwJrEl05OHUWyJAIEH/H1VrdshUZSJ6Ki4gWe65IwyocLlRPQcSJZalDiSYyPGINfr4x6t+3NmwnJER0UxldQ4bTgMddV3hJSSpBYlgRSBv8tLa8KLqGEMtdB9IfF1XDBp+bC4nm8ZGYWuHF1XMcbIAulyKYWU0y04yx+outcNOXsHd/mtq4k/n+oifc84FSvRpYeAmkhmWkQZQXAGNekwbsFOKxj5iA2PLX5iPgpqp2Nto94QU3pcmUxVdYFBQWNQQ18RLKxNvi/wFnUrCbfpZDCf6UbEGAsJia8LLFGHkrS02IZ2QUc2mlJlVvc5cHXx6cukaPp60/lMMUkoNDYWAx0uCq0RyUkjKyQgSQBv6Gclx/EbBQt1uQdc4Hab/0M8jP10MUsfUShMlQ2A0KSox6cua6QXPclQix0/ji+Cy7ucF3vI194Uu7IZ/TRgvuQCpGW6PHGoDflxgRNpQuI0IaWg80ODYV++Y8xG5NOJrDFxXmBwvmezh0zMqlXCFiKTf+X9XxX4kVZFstJitxHPlrMu4e1R7DkBU3cmCHmLnVmOb67kII0oF8a6UqJ26fJIRlrsNox+dIgeTzsm6Xo2kptyDwTaCgEg41DugCHXdLgfST6L6bVq0jEJZF7yaga942fkxeF8oUUwGzPD0NfNaAzoAksGWsx016YCprthqru7VxwPjSbGYr7MFCqdrokJwOeBaebbeZKAFnux31waYIb3m9zK9nRc10CkgjXIgjexMHdzCJpfBfoml+ZoQbGUK0sGWuwlThO13VcfH3Qky3dYsQd0+UIwdDifUJTNokiNModxthLLxVTO3sMeSSfr23JOjWTyZ+tXHBYvtrLd67YDyZo+0Jl9soTsPG8rqkfFfqF8oxYbQ3grOWqx31FKylBfGVC8BD5yZJ8JaUOZ7jJci02NXt/RyTtG2CjpZH2vnRDACzyT/P3juHt9L8pj3vPdyzlyi4dFOrGUnBqD3CSvfIVK94bKl2SnxX7kdUAla0o9ivxYFx9smnviq6qC8zl0eAaXHaLIxNM2Z0OSpBYHdiRVowuIex2Bcz/RT0pO937D5hDKPKh5Dy9fQiPyPtEiwyimluS0mMLh64EfL5salKeIjaMt9h2QOe/cXWQ/JuoIV9xRNYOQNkteWhw5Xge+YY3oCxmuxbegZL0pckmoWT0s7qANwtR/r32/ibzO9CWVw4PplEg+GmU87RhPOVVvlO/3LOtxrceqqNdFEzI9LAfN2DYNtsUk7OgxCReFST5dZElKixOQIhuTTgBxvFYHCmNLgDimFQazSOGoDEj3IaVfmsksJhQSnxGj8isI+yU3LU5iV4T9vHLdrz43aH3b1vRAd4qCqPeGgrWY/87E1651Okw9Vg4SU8W2FEFJZhrGPDvnlez+sYTqGmK48wFLAFEDLTxfQr3kXxizFPFLrQp0sTTC5SbgQEmT/qgKa1Zt+WQLqKW2ImOz4WN2nZL5Rt6gpxvzx45KLA3oVPdHFevWFTkEf0LHSg9v5Y5xN5iSdtaf087osrLIRJLz+HhVVUnDA0JHGOM5TmdEDJ0UCMuwSX9ByEZKfDTTru/mi7mWPG9qTfmBDhIARK6kpmGYaXChFF28M+Z8YF8KY9C6QFwSTmZ2WekJTHWw2NUNA/cojpKkNLKl7ajyczsq8g+04S9I0tN0ki6GihD3C4U+jZsDGlfS0BI/bE+qLAtyHcXQAzqZK5wJM0wC546pjBzxbFkts+BJNlpC2djZtRGj5ELM+Vu/8r5uj96/WbbUUBA8U9Qfc+6XCtFDnl+S0JIgdJq7M6GLa2UMI4G6ztks1Wk6Rkw5fMCInL8wTtULdFolaHVLCloSOW6nTCoGN5OPfUdBBZWVx6J8RXlfztcbz1aY860KI33T3CAuoKjq0yGMlCQ08jwtNg0Tmjvz0Ku6FyeX8coKTB5Kzlary3jFQBfn0D0NzhNHRXrY2LUba1rl2kH+546CuX9gsPUdF5rs6C4vbxS6m0Z6acjOraqAHMAY6W3xFHZrtstyvS559n7JbxqOJ8NsYZgTYOQ3sbm3qofZxcmR/jYJmiF/vAVyX1dY+57PvkmSiu5SDOBC612n32J0uAzBopRQ9DB3WtrSzO6USCdIhZR7yLR2J7LfpJN+dj1ONTbOr2Zhk4Zn5Qv+GZ2owJe9zOLHD1A5H9mcApuyVbpDHXHWVVVXu0PVo0R+vqPAqqx6UyezvFXcG6S9Qb7L1zlfKSulKL18EaVJTXeZDqwvSGdkRRw2WdhN8VyDEn0qoEAdO1x7Id8B2Nmblvt9SV6vPwDk54CkkRK24/sz/czf3e+W8y0yWIOCvlrfwMdfnrDGsduc7I8KT0/+iaigF5KXQ9Wn3L0iOdzv4emgdYmEsbMus2hqucPEYEnkaEF6CVniS0v8OJEvMkUBd5R0oGLiWEvMa5PXBooZjDnTMWGSjCwnC01bEzGQnuq+4IwG0oTQ8dqhbOhF5RCmxYRZemMtyhg7UsLrdxyRXS/t99It5CXz3ir4ZrkL32BIwHo/Sgk+F5IrwhDXbonzd/Vpf8i3EAn58QNsFFpQa1U1lS/2Eux+Ilc9WxRnYLkT03kNuJiMmjzqC4oZ2eL6jfIEJoWipUEv0J89hx4ma1Z0wa39fEsff0AzsWPKvU2HdWAbTPHZzGrlxNCATImFKZ7n2l1TBuiu1GiCe4gN0WNpYmr6ovK9Ul+eZGF5qWlpBOdYlaTivCTCHt+XxM0bDsATeEogy2NyZ88iUvme716qI8bFKM0J+5Z399UN8tUkGdopwTIjc8J2gDV5lgpDIcG6YIPKfJfJYr8WDDeOqEVxj1aW++SanAqbIaSEQsdkiHS0AYbZy4pcfmQBS7pbo/fiuJF1W+7WAPNw7XReVK85+km5PnbfcobG4+Mx0IZaS9J3pOcNfE+Ws0WAP6jePxgNL/RcMqRlnKkegDCznIVjMMNLdEOjYwfxRWNG1Mne6SNyIYaxp7A1tPyk9XZp/bPMgdjuzY67/Ofx0PvdHlh2ZlqMwNbNeYoQZUt3GwROF+EqxiNTEsSqX17oeZYftZb0qxMCrI6vs1PLnpoM8VirTpkUYjmCxpC2tp4ykl2x3x/fWcM6sRyvY4Ta59Lt+W70uYy5qlS1p8vsSJ9L0X58vSCPOYOtJ3bbK7Qm+Y/tP//flw3+O4uxM+j5/tsA8cpz8dIbBk+W87vpbeZz5DRgGt+RnjfoNNsnBfQLKGDZvfbQPzsb0klRw+v+4jLfRRPLN66LUAZXFbNxUKTTDVGGaEGcHA+x7zmuHwKrsdtvsERNUK+8wfGj5Y5MHs5npA9Nn913ksaWVkFgBujMt2LHohX8NNYtPQFsIiIKX+nx+VifrcjMnlKYYDqxmFChLpFiRVzpZ8OwvTrf6JQwLmL1iYzesZ6qGvMgKGFkQLnu9we0CkvTKgR8npR6/oLvSvcaxhJStKjW1fZHZU2qlzfUcBlKUPrVde9xU4I1r3l27Jnl9PXLwcvo6sHMlBf7rtuYI8cdildwuavWa2ijB/TmkPNfA8cjwL4UINAWpfW7xqbMcldGpxLqCNcxHRRX+tjIblP3m/xzZ/ULWqGEnBFdW9AkuWpJb0r+pC7ybef6jvxvljM1bhNvt6gq6fTcuNLDRp6c+J5tS6QaGK8b0e6uTi8V6rPKVyZYWc7KdG3FgHc1C8d3pVuN7aAdOrZBLscDaVqExPrbEQNk12AQs/Ia8s93sIp1Z8WbWmFqevsC7UBItibSycZOC/n6hlbl8mXzCe1Myld+RXwd29oCOm6w9KzobLcchj06YfmBTUbl8FoWTeMWjyjD/JH/owdAXoUZ9S/VAU7Ou7OCqXE9OPhadUZwd6RzjTGwrlsRRemNaegE4WXARrtBl+mVzdY7i9rw3JhsSVydLKFLUZsrnWtMoUP7EOciOQ7onE8KqO81oTWwGvUbhkN3rPCeLDf1TEEs42oph1bRM+xJ55qQ1xAlFDbiBskG5kT5SZiQBf9pIaBspJ+AtTqwLJWe4K457sKKJsZrxGuzagq870lfS6mJ3yl25VAjpBz0ectGZcexJT2IGLV+OOSf+dk77EyscGCKBpJAN3beJa/vSTebhC20dkV/tMwtIPB6cQLJst0632ueHaQXk8C0FEwGXXV3yYX4nvSpSSTZExPG6LP4tcQAXoqOAEqXPn7/lh8qsmJ5J35DThe3fBndWc6jKfGKmXSEckZUSKZI15rELftnWNT06OARHmEOMUVC2BjLsd12xPyqzsut2rM5lAs+mlaHs9dULw5lW74n/W2SSFwGWwp65CbVCY3qMLCy92dGo8Bnf5Yvm/OohJ6be9OTF/G5DqoROzgfwrU6ticBgN+Kmt64fCNKBocNPFvk/IKQ0Xe1Ay9xaSJpSqexPq4L2prbnCkkB373rKqeP3dmuUPTCeHVJCVQlRyr4Ie5jmO3MdoAqkoFmhjFCXGLQyeX/EUrgaiww6OX5pvRqYa6rj47Fom0w5Vlz78dy93LiXzpZ0nxakRulX3kEV0nJhApayiUCm/z4/kZoQzYmGtFsS7H8V00EqU1XkcINz/WgO/e5rUogDMO94FSnv1GDG3luXm+e6/qi+Rv8kXyF2kLXcwgRxqUROcKXNCmREkntm3Ks45/khHAHHzmJ0qBIHDxenFoZ/HI8vqmnYqYnLYqYCJ/5gtymIt8RuQ5j/kWAlPNPJReTHEjPTXfCjZE8YM8yIFFB0taGVrFbu5n/255C2NQbeucrQvNE+FsHc9tcz8WGDygsgYwT8B87QGurdcv8ppewN3pIk5yEEQaw5NIO/+UHmPBDYMVide5RRVeP0AA6cNOvYBcSvZKIQlOinS0iqvk31tOaqrORoxJoTwr2KBAGhMlUhFHzPVZAlrsxnH0C+vmka0lq2F/pLf1Yil7PkNWzMcj9mLHvkjB+Ni1vT4D+24sbPGB8+qRlr4v/S2lht5ZBtZUyT3b9kWZtO1uIOIfbvP9K+Wsl4GtO7L8+69uU6IspbPNkv7XD+SMQHGbFhgVWLOkywv9iPMXyAX/vTcq9h8FLn33VnsDyxmZwknf1snTx9gn6X/9OA67JWO6OwxIhFFy1qLYFzVDd2FzXjeX76FN4crImI2ycXyqunVM1yiQbjeAllN7mdFbz6GxzYVOKKySWekyp9v02/h1B7YA4m96oilvnfXZnvXr425P8cNbL6OgwTXk7l/oJ/uB9L+B38LBeT5G+U4BWXSK6VYwsdy9HQ/kit94q1vSdDQHmswyF/ojHfcFhfVA+uIgiduJO2LGTcECW4qHA6s+FtaaNvOf/wtYSvpFtj8wX7jruOJv5IrNZeOIKXEpW2R0lAPpisPQD85c8YhMAaoT5G+XL82gPJwYjKZ/rHf580m9PEFquX+YPGEY6AYSBdg16ZZDMr0tQL28leB4P3MX5IVByBZoUvw8KesbdLWCO2NhMGI4APVYaj+Q/jhy5ZBJOQZ0jHGxvfhXvQshK9wq9UZgh3TFURA2t2tAvvaQA+iV7tYMg4CBGX73pqPWYQ2P5ZYxR8+u+9Ryl6YDE7IBWkqIdgS2oDTokt0wzZnbc+mhCjqtTIw9pl0qtueh1chy/jBZwSnEGvSKH0gPHCVeRzKCjiwFe/kByIJfUQNDfKculSKbD6T7TQK3QzZkJWxE4xgSlZDbW5bbn4wURP/yjd2k877ht68aP7buvuARCO3GinYA9aQ61vAdq4KLW9vCn3DXK4j4SKNfjwUdkq5D8WlnpqY3KdFeX2TyoXS6lMNEUty6fi9QUDju4XT9yLPG+60IvhHgqe5N8t1yfzdlrD47pqoQE+2w0G3MkN3UxXG/L8j5350OGAxFHpGvg7RC2XSPZ5b3u/GFjnT6A/D1gjTmghRx1l/GRSigy5hYgd3eFjaD7oXLIXRuS7QkJ2LM09hDqNTnwZ4Il4peU4v1KnFZ+pjQ1SP73H/TnUaM2KHEP9AzIxhirstyM4GirQGYZWNaMLPKsfrbqp1JO8y3+QYUoK7ToATRkHY0UF6VrArsCBsjmrANCIj83Voc1+seUKTn7ZUB9+9X/ZWJ0XNx2RBN+1iww8gI329gS5tdx5P7ya/I3H1XN8wZRQxBCSMrAklmHCHzWVMeeiggppXYF6iHYXXc5uX+3HUtv+o1eTrhHxeXVDhQqJqLtUhBRrD+qCjIQTEQAdE///dvk3/+75/F/u9H8KZ/rtUHtG/5xqJbwCXfVaCUEPOopSmOK0LpVfleUQaIaTW8++ZRVqqsHaTk6z9ZxfqyemCMXqNAp1mNkpfghZFFXtvl4QLrkGlaU/jlxi5S9t++FXX1XnTKxfwfdJZnSWGIZwQ9+BFbHhWi2ANvVhoTSK8+YUR/nF327CdJ3GkeUxb2niOAFt/fqR04C8vpmzYqZCdXVSlGzTqSHtUFHbFFUi2KAkP2Asp3qlPewET/Z32fiRWMjIUEPshZBb1zwdIRJnmuVPP+hna5tarzD4qNeo4NzYyud5uWO9a4vfBunjnbYXmyqi7JrAgaK6KzbkvBrhSj72H+qd2NzihDY6OqLkIz11z14rhMtQSDH4WtHeJSr6p36/EEgYwwtu6K3Xt1lOQnHFYcod5sgr36Qf9mzeqE+N8kf6ZEtqp7qUNrc286vqGvK5gGuN3S6TIdTRku5vUzuMHyCPv0LPybXUuuH6KcgYsdkk63O2gbXUtkfvR9NR5C8nlpM3JvcOg1v+ndZz1B7xCy7GOOGh3Q4tj3RiQGH2Sj4mb5ZJb0wj6FZucJ/DDHDBIY5VvDbfVTDvNcHslYTfrnPVr+zJTw+Cx+VCFmAIOLpSOmGL9bTnjN6+IAzuOe/jPn/Jl+BCX0ArBpDy330XiK+VQJVTGZEoxYel+GyW3NAFWOqb0gfva94LIS9m+BERqjVFVcGCW9cNCZY8+NyruIRcyOx4wSoc7zt2O+LV+O72D+vWw0li2+gCQkTJRe+ZKTV46lVw5tWaS7z5+3lTXfvle0Y5H9C1qIYtSQ6nbh4EgvHDpxByC4K/esEMUoNUnYyYL++b/W1RGonlVdAZddQs/0HOOUpFawNL1VXqTTxURpLJYuOYxbMFwfzOoCVYSajizFWQ65xFKyTvgXfAGlJD/kDkzvFRjnvhri45BZ0kNHthy0I+qWHPdUo5HmXYJa8Ip+UoSohbSYHy/0PEMlexY9z1g66MhryVPfjmusCxQG9yhlngPHB5uy+MGzaXUNfPZFhBxpkwXUwGPprCPf6cjl1CW6rzmTkNjtcmiwhGLYAgqL99v8z7y3zI9b2FgwPsR5th+mlvOH8SWztaLoMCtpzJLhT1bvUOy5g47pDnoCUbcQBD2m6nPPQDFnZaCRsSrv2rare+WBi0qkm45i2a+Q9TGEY3sAdhmQgSU3TdMPdd6rfvmXsSkPOHSBciJddexFzdmh3IVB20HTd0N62M8AL/d1tf8oXkQM8pdhL76jwzJEWBrppRO7HVA6LbbVGnKH9GqhPxuEQXeX7tjgr7P9mViusSoUeNrRjpRaJdIh00shZ2AcCgr66A7Vb5j3gJcNiskMxzc8FtuP8469Q2GgOaHydGwhZoB0x4krp6NCQXWb7y2RdCOjAiC5/Nkq5rD0Ktu+lhc5N93lgbH6oJ1sA3BHEjS2yOrDpDrSo51DRMmh5xKvAguuyg+OgTmezqrsye+WPzUFNYHDHgINzUCwylzPtpOoKZqSf3iqKqTBLndyIpx52JWVgurgLeiamBbBC3VjE4EaFFwyssGXSvQMrf5pgaqUfyBN6Dkh5bhoyTDRQ24OKKsvVXG+IZMvWvUJU8NU8tooXBBUMspsbe8qHr+tjgdgXdEb/xWQLK78rgKpMVMSaQpmGwvgD0YpSNoFXXdLLIGsCm3YGBsefn+ZuQwotzSGw3xEqwrW79NLZEvzOjJyyw3WaYkeH5rlMeW8dH4BdmHpxHNdVW/d7P8b3WXHzNhhegVKYGNIRjjSiKBTP8yPSBE43NKJ3V8cCXO4lgp5Qjc6kDwzzwnDJhK+3VaUIxxyCa8Asj5dF9scGFANEieglbn/IlvR1XcDssOTdiRJR9cISJwDHWQKLj2KpS5Whjm56cuuwGB33eq4xkJnyFoCKum4EOGL8L2Ugcvit3ycV0eKFmrgIe2rjnm6O5SvFUZpXTfMKZu6MxZtmFKAKptiBgXSoOScZjWiiJt1qLw4sOa8f0Mr1KfQ+OOyFB8+fhE5JdqJ8i4OsfTB9OhINA7HEdCt3r+gWeOGlCCs2HAollsqs5ShZRut4JOPlPcZVkgv7AVeG3u/FSdK6oEmp6wXuAbyc/u3/66YcV90W/uWNzDiuF0dXyeMyZ64tUe8CnfFz3JngTzKC9KOG11Uryb5z+p0WbsyNxW9SIcmp0QpkFwzskKOnqNopUaPiL6zqt8RTzoxo789M7gwcIeU1e4ZZhgI0eLqwbyLycdMTEsTsvmWquwtoKWRjDPPi6OmsTYqUEwbMrVXD3SzTjFtRoc2vyqlmRsWsS7IjjxI8gkLWujLosJHi2qeH3ihxS+SjLCNR2VoOd+M6+FoG/MJWeO21kSXeKmmfRHSRfbtFuE+YMN2zk+LCwaeMZriWtqqaAob4zWGRA1TkhHxc7pB7tlIYUQVFynYxJ9a/tJUaEBLXjMdlp5kSS7zfL+ddrHaFNv3XE7wpTNDwctpB61mrghTFJfuzKbdSI0hFFflVFVcYIV0qeTYWrHSardjMwJ2EJJwAi/g8wLoGokzqvJof1hR39RpjbVyhxS8BZJaRg6kHS13gwDlsXx9ReOEQitc3f0Lm5wjcI6Lgv5drkgGl3RlTOEcl21T5j04G9K/BkGL8x/lfwK9VtCSUMqBTTrBbTCM7vbtPLAdfEUX4pmGChFLcYnkk3khhUrNYlQYZDGAEqaHcg86NvnL348C0iNJZdyBdapP4b3lmyPbRFduZrZIpxq6ndDxuM93tBQsCoBcFnviXzb0mYwnM6zW+PTzVDBKLcf4xmBipXr2CaUagaSUeWEciTdPKsxXz2ug+5MoZikYeCEv6J5Q1H84nIWwYWZ5fxhrXp52OgJskK408tqMAzW4EsH6Zw2lbMwGjThB9iwfZX5rV2pY1k+UGRrB/rxbolJ/DDClWtoVtGR4cvIArlQouLMpbb8oEdJWBXFcJKvMiyKnzcno5813J6tfMlbOHqBCSBYKoSnWo9ai7J2BKU7i4Fwld4iePskv82JbAv7pbZMSqmhIRmccYmjqsUpCo+7xiaKYlPcoi33vdzuzbONrHPu615gdIelx42ZgpCybjrAbtDIu/Vm3BbkPKHLhwvDTPlptZMlz3ltQsGQbSzsJEwxVNfrYRkmHS+GHCJYWlHOeeALoO+Rh/k32Qezr1H7pBw8kv8y3nfhc6GqSM9V333G9X4ON89mEa2XyhQ0RztZ3ora8PtiyGgKt+gsYCCCYKXv2Ga8kn4drd5ZtRCrHHGSv8HNsYRJpTyIp+Cm4DzUa45hWAFqG/QshDQmb2aZB/QSSbOaDSdR0yqGHgzV64zXI8znBq/KVpX/aViPifHOP2tVp9oQUQkrCGeLZFqt2WpfgJoJ675Cxv4IN7zu6/DiCGa40w/U6/BnQvRjNOa8/KB9mFQQ3+RU4Qt/RSfcwczxpTug2q/IdLat9LmWnlkdywD9ZvwFiMR9FI+Iwz9+ua4XhN8v/w8hTjFkvTSW24ZJFfmOR51wkHsB0ozqXMLA7FGglgUYZ6NuDL4pfjjqaQvFL0s58z5WqOQKlNGDgyigJrPuCAtwW+oK/7a/XI5hazu9GzQ+HVeFUsxOwQ6E0JPC7jddDTjnH+3uPNi62ZtV/Wp6QdBUro1FC+Z0ukZH95evcnk9uRhLQfHLAjbpf/cZkBhnoP3F/HavI0fG/fCyLdMB+A5ri2SiDMVKaQhsL0v4GQ3HFtDRlOmY2wmc4KaVsAtyc9LosLZXLgRIGV5xi2MabugK7i+/OfUl+l+I+dDTNqIon0xWKE52aA92ZQLLRfGzTmdzSsHx+RjPaS6wpfVY7XuiOvudwnpu5d1+UMBKmS6ZkPXsQwpNGRK2kBB/NPsjfP56ZtHdC8eWvU/EJ2dlVxS4BnV3JQ/NDr71I3yo26W8kan4UCnvWsvxHW0P4n4K4PGNTPLJ1AMAAib10wpErbxUKcxSFv+6hjeU7zl+stfiPX/ChNdw0vN2Sm+ZHnow1welBqdTqHyk1qpk2icd7Zc28iWJ7cX4o5P2CHaIVv2WLIh1wFLQ6CwvM5aUEIAeBP4oSdXxFp2y/P/H+/GWMZQT+B0ywRQlchUXSE0dhW4SSnaMB5nBzXk8oeNtyaUb5D1Zn/qvNZ0llVAJfYI90xrETykFMeV2sWbqPjCBxLixRS6YEd8ZnkgfkqjgYl0q64diLr7ppy/LjA4DeyP0fRA30HIxMlaCAqyCqgGTwNtITx2ErwY7WGUgZJVYc4vDOhYTNsLp8EYDdcn43VpNjHaAtdjEvUNiRuK3oEktQBAgSQN7BP/+/Q2F9q04Fpq/Sv2DO57IGM7JHlm0kx0GoxFXmjKgVSgqan3itBAYmkkCzDFMiKbCxhvn2sGcUhAUdnBvapLMb41O6ZsySApYlKWNuypIk3YxMaFtUywO9ASdrlj9XzJMkIWDObd3jjm3X7oKu6I6sIDVyMhKWH6nK2Q6wudISv6WtI1Rg84k/ipcNkyB0ziqndDzeEdWclU5HX3g1Md9OVQvCpkjn2pXS5krNjMWJiqUdnIGN7vL3bVV9aBiu7sBynoy1qUgXbVOiGEiuWWDbrSr9+CcqYJQ5nl4okrL9c/bviLH8ddxf36hCFfBBxQpnEmJxwsaYSM5XrY8IGFjS6MQUc/+6aCFmzW/lyDLYEklbPAmbYJplDZzZtwOfRQZ4ddR6dsmMzoqx9W1rge8gJMbShMTrzG1G/ZKNYfd970KFg7MmNDIcEyt4Ml2gKGZRgSryh2NLGmN8X8JItoDUiOQ0oNe4iyR5xPCy3bmQjkd7YqRnRqFOppK+NJB0s8D1YlHkEIEzvThAWXoBrWWSsOitx6Z4AlqTl3DyXbcWf7fs5RfiQhoGC0oJkmcWkI9v1uInRfUckOCGcUyx2yd0TQUM9pbVco+vx/3huDfG/e6TsTrGJhAqq4QUuUniWeBGYdIZK4Riv9A/9Gz/VxQVYgZ5Vzo5Cgok8yzwbLshJWzoGzkfG+B8a0ivX35kL89juSNLzt19klqO0ZNEgW6AnY+V8KUFkSQCsKHe1qouIS3Uc9D0/wX9KU7WV0UDSJcl9YyCWZkf3uco6q9ZIxnkDOeC6pS9YGpafvboMK6TmfXNBdNUaSo9w5J8RtFJZ8IrZFFEDY7lTgA/1W3xS1lTuaeIxJiXshKc8rHBvZE+lQ6oCFiBg3ull4be3B4QeV+2b+8t36g6JXQONVUdyT2jd0/mNpKGl7HpJ0Hw6yQXQ87gVbOrA0lAo0MihyFz9MOgguo8ZrwAolIe8tcG/KVakO+W+2isLnGuueqqkhWSe0Z7I8tcd+Dt0keMjhSHMbUnz3P8X3FZANSJ1LJ+6NZJrxomsrU+rIvXimKjRX5ilCXPpyM+Kli1dFuUr5veoKreVDU3ch/fjQ6MSYkrpTkpWpSUM1oeWU2X7Zab8vWVIQT/jwLzQk43UPHQyLtJHhqZ16JDOBM+59pp5a6XJK41q6rDWy7v01fI/qnlTY0RNpNAUlaSKUCQTDQyKm4eRXqV2VRkFtuiamDdIkwysUuhQDgz8o0ZFU3lY1yYIV1u5Mg38BY5MWNasfHvsctUx1uXqyZf31vOvVHBmsvbqRxNTGZIlxu5EisvqtiD+vhnz6H3wHqqcGIMS+E/fiVBz4XtVHcbx0S62yhs0RFLzu7CaD+ER2SK7YX/LvWM4xRVWkcwQ/rc2LZ9qZ+2P3aw2J7D1HabtdDiM74C6wtlZtUzCAcjPW5Mu9fkgDlKBDfFttzT73zvl6YYEdOrVsqKk5eRdLMgdkOJJcIqCJwkPVIJQybgXaa7Xfcaum0XnBD9YQUrI2iFzW5QNrspNpBksyD2k6aIhB+/X9DPvUVJP7ksaNHf8sOluMeMfK5394UKIhRzNLBaSTAL4tgLOhc3fS5QKGZo/WJdNP2WW45kfT/WdX7+JCYjyzNjzbiynKoaSzGC5JMFlG42xyTdlnRMBhQglcAD0pUZFDuMx7Du3z5PvYzCeQb9PdfIDCaWZ9ROEEojKoAGxUuSVhYkttfM1djTCbnP/+xdEhFXQCBcCQYM/wdqvxp9DyyFdKaJ345o+15A1S5/qz73byV05Hwh0d0AVzD797nig7S7AQJtjDsxdlu01SSEsZI7Ftp+W01q9Wia8Q2OQ3f4uNud3um86HLzcGm5fxiL5Ayxokq1AISQpLHQDtvuxorT65bVPudKt//+Y+ezISMaVYlAEsXIiiRs+k8vR/QJigL+zPeTX/DOeDGLltQD1QPJCwsdW751wEC8cVDMnpObf4H0sM9x3oodwVpIVhhZEXcIj8yJWaOyRmKeRNFfUbhdfnF7w0A3CABRmuSGhQ791a0SLOjNQ6/U9/4HcNWpZQ+N5XlXp3aIMETywUIHo60bbdv85SSBGFHinGk38ZLoeSPn2xekfD/RVY5QepV0sNDxQxkYcvA7nNnxA9Al/9fzUDkaRJX3oIQjKWKhE0ge6jeK5afAbAHkhVz53ycFRLqprWiQSmZY6CTO+ZCTSQng+0WHVpFwoSbeNx2O2NZKueLGSH/qBk5nlvuRzSh6fUUGHNCzK76OyXHq78rMcoxsZU4tVVZ+YYp0p54XSaFFruy13JSg+2O2lCUwd70Jpaccin+uyQldGqNLt1nIruqaw4a4sUEKDacY6TXK6W6QBYEX/wLZNzXdip0H6UY92pCmWFSjEcwxDV5C78Av5ftGXMNQXUQLJf8r9OJ2fCIa5rQp1SfrVQDugTUZItPLjyrJle+W990YFQY62YEANkgnSnvSyrVWYnZUyTChtu9Yo2q7rt5lccKoEbGyfGMdPOEIfNVQbJ8Mkh7V7zQ8WfKCyYGfaDNS/KEcwYN53Xj3yNiDGCauky3tf6HzRDmwRnySXuBQMsNCv9Psymixip01/yx2e0jZRJbv1a1+d/4K8N3Tqvd0fCM/9+TQ2TFGjlxTVlXM8UMywW9MaGFu/eJwOFkrCgwPgMZ4l2j01aZgaQVF0rtzMYRw+MXV5mrISmuwINLVBg1zXZb9Jkx+mKKkXwAWCmOd4lQAI6S7DSJbitMImSf6TSW08mzoIKxpoeTOfFGEpJOSGSn9rP2nvF4e2ST9Lh1n/+yKTyARF4bBrwjcgDdRqzq6uOLS70a+RL9QOvViTY+76k9Mu7zPPwqtVp+fWa7Rv/i+bk5EhHMqnW4UtpwNKF1R2o3CHfNy7v9P27tkx3FsWaJT8QEEc/nHft4EQBIQAVB4AEVcab2OA3AAcREIZ8ZHFDiDmkOtlZ1q1AjUuj1O7J19zI+7R8DMQxLjVeXK1NWVAKO52bHz2R+3t1fQV57hib0R1hcW02aMbZ+m6WwrCkv1Vpa1L2GkRZ6N88W55gulJQaLkIhrretqvmMKtcmn9S0600qbfRyKoozhSqigMkL4Mk7nARct0Y5ScJYchLF4ZpCOdgQKf0JDWip0QoX0ZVw5ZNYs+O7SobxhA0Nn1e50/jpRJ6NxzA/IQ3EM30aiKmWM+YaUIYWz2+YPWq0LiQqfTP+IWr99oCM7imfwTn2hTnkBL2JZUS5jYYg8JG+bFQsKwlPkvHqZo3V+GQHqvkvMh9GhZxmDqxU4KW1YtWlqyx75U929LJO3a5zMidU/nLnlMdxC7mgJVpagB6XnjBWok8+0y0rT0dq/LJKJ2b8hURE6mKVsRHxha+gPYVXfJqz0+NA8V8zUeFwj1L2aO9qDRI32FosyNlziS1zKEpwZQJfpomJOsb65QYmhtMv3iF/2FL1QSmvoSwkdzOZQ1mkbJt6dnH3ocjB9WZCjWtzGBA0gNzTahWbyZJAgT99F2GC0hN44/pTeE07PziHCTWk9FCF4IfLu/LwGYjh0ePN/JdkoaMCDT0PnREMcLpf1GFEmFX48fY2sUGlycLuDTFOcJGpE78gzF4twvooltPHVFnSgukcY5sX3GFFTGn0DUU54fW+Dy0PYAfdroj6PonFNdGiPD9QGWUvZc68ChwHBsmpdzoxJDsZJIvTgnI7FeWVj6s4WS9CyBNOZINT0Arf4EpWrPel/+wl1CIFLsV3IXhagyk5OGeyHzuaaspEskQyVP0o0Ucs/JMWnUYk1T7wOpSW4uRJjdd7rz51O2dvlJTlu5t/ob3xDOxqgxkV1v6j+4NgWk/U52SGk77x0S7g1b4T/ZXXR82YOZhRJ6IxMEdYM+Jx7o1s5F2MeF1RaCPvLUgrZQ9vRjb6tKLDdUSpvKeB/ambPzOTcOdi6TNTRKC27iOoMWziry3JytcVWPGqmUPilj/TDnU9bxsDRit4eoXtZtJQ20a+HDcCEuaNgQkVg09Rdi75FZbW2W6/mj+ZzoselbLLYzIAukBHqF0qNcksDGrRnTEh/ME/xgMJgsQOctMRZU4oAiPf2Si7afg6luptyG0eP1fr36Sux2B1URRvriyNlFIKXtVAP2NiGw+oP4AR0WuwPheP81CBsWGiE5mVt1gf7yzW3opeAiaWtF0ZCKctxBSrlavr9/y7q2DXOR/XrrYlJkeDlEaqXtXk6sHdGD5An9gtITIB8vOvqHCX5xT8eCRshedEyrIwd15AeBJBjxbZ86T7QCzqL4Un4tkiEtYPZlk/VTqv1PTfpcyoKNoagH2taaHW3ejUEHRcMczE6OLppQvKyrhMvYzTj8fob08HL0ka8q16qrWYoFVvF6a6Uvgizr+l8CK/LOt0f1g9UT6xYN42BWn9hpLSrBvVZQXA8TtdXeF3WwbWFt+Mn7hfBdU3h6g7GSZQRbInjQvwxH98D37AJqVwY+vV59+t7WpD3ExDfC6iW7/XWtqr6IRESykyEvWXLNHfSt1/PkhPKWSHHUnpbQMgBVMulRzXgJyNnfO07RIvRo26JkBvMw0NHFBgSXMtMQryPn7QNDH9SxpXb/CQKpc3sLvDg7QTceLWAIBaL3mChcNnSyYRL8oCz6r76HdZixY+69ZZZrPNL5ZURzhYtoRxaHpzS+WC1WW3VD2ORjacEhdJoih9C07K0W1uwI0zpfwdPNLOGSov5AJQWYcEcJ2pcVsrwNCWwG2gwClvLpak4J/PUEzSo5SQz6V+IHhdJ/nkUk2ZiBsEFVlDKCrq35Qr5T3O3mD6s0ZfI8Phv2PL+vAC08jXIs6Sn/2i08C1jfFBkiELVctnAPOxgTl9A+O6FpnduHyBxr9kalvs2wtNyhRtASarWyKeiwgaQFpW8reb3zQCgHXxzT3c5AfJTF9wSOqrC13JFmZrNibBXQs5NlqiBQsLFrHr2nMKNBPFgR+cKXD4VVuyjF0a4Wk6p3heMBRaRnH+sK3hgqbTQ+6o8LbMZgkxzenGEskXPv5FqhjVPWHCLVfMxOxqYx2ymH/C/He3PKFYwCgVRuiJGGFpOGVEwOsdVmSUfmsUdsiBHb+Xwo/zsRQq+DqZ5GCh+3AHhiJDtLa5KG0WdVmn3RT5O2cJ6gc7Vy4ROqEZv864PYdd0fOuNqvsCj//oOqzvnYUQC5rWYWUdutfwuFzfvCQ/z1iAp9TJdT0d6EqGzgMaeJ/iH8R3q8qwtgkgra5bhBhpi7owlC7ZwUZ39otoqV7Nmtsq1n8/3tGHgFZRHvaOwV2ReKqtMptzViqqWBQvTVm7EMR74bTTb5o+NxFRKTquB7sUIiKK2HRchaPldJkOlL+en9FeXjQAr5bZNi/54IW9VzfQpCdJdjbKa2DcQFBnnk6KULQcZuBDTNrhmoVhVZnlUKC+Y6XRv4SPNxi17oCDZRHpPqxIYiuG0L1j2/QbFQn0YzowNKYIAfVyyhlh2/LyT5I0HXOQwfhVyFrOmM5fu7lJWrn5iu1JMgc01F4moK0VbVhfxAhvyxkr4i+AtTStdDhgOKBeXK3Wz+1wCyMDyrSX9f0sAviAXvQomEL7eieEFKMbJhwuZ3NlN2kpb+luU8R5y2XGxaL5fXrHT5ToglEeMRwcoC89jh8oYqgOjZtluoX0WvNn1R0r/P3arOcP2F67yQhllHRrdBPhhdLr+OsoakrFdHJQjwmny1ldDBW5vGfKxXS14iJZ00X9O9qD73ZJAZgwtA8fTOKzS3UnyvJHcsH9kwICcgPttt4Cdhh81FVir+NFh38hwpklXgihdjlH1Z+MVChDOJpN8SZleeEtplv1ooPlcjqf3jTTAOXBUkI32hhWUQqxQzdHorEbQKZllnG8qFCcs1mwHqT9n+uHRZtub8Dqj+hqjyIu2XAtSJql4yv0Lkf/T2z4GFlHTyQFGghk5uYvtIVPdxkOuRguyVCGK8wuVw7ktxi+dVY/w0Yactr0OuEjJCdfXyZHj/Xq60tsHAnJzg+7DE1VWEqcLo8QuVxpMjMoQXoZcaqch6p+H+v6C8qPTU2/DztMlI2LaTqhPhXiFi3CdjNR1mp7X8ORxRsDWv03xgeHiR4FThe+zRVmHRihcDl6LLJX0wxwQODSnuH5nn+tZh2S+7r2LMHXL+TJjlFC4cXsQgrR9EIKl8txG0NGT/Vs1lvQ6az4cbl3a2NmAYrCmrC5XNlJQKIfOpAD0Pt0LOQ+QnCMji/kurUYtXWL0FUpykzvhbxqWH4r6MeHD9PG2RIqdV1KRQkdEw8oTtscp/alK4yWj1u1+1l2ktjxjr2LEUVRMAuFq0wzUST4CfpfD/3JKNyGWChag9Vq/TSuFTperJVR2wZ0iYXRVaZ575hAKf8t7QM6TPTHzizVBC/17aK5b31oIHD6WqTtzF5SyrLLF0uHzwl9IOF0USkgc2x+9yCwAvkq6FrtZZBdlDHfRuBQhNFVpnSgNsiYV6tqfg8rhKK0xd4zJ81hJZinYHPauFtmRR/831bT5UvvCpAV9HT8GP4RY/UI6dpBMV0WYcpua5iEQH/OelI4eg03u3EH89uKZRw2WnHHO9gYhYlpHCLiC7OrpFuUDYe1yS/AJbG9KbOImJt+xwra6PkvN/FKp+XnRB2MNovTGMagwH5YWQb8PTduTleeKVfs6cyOGnwboXaVVPAMMYfTJayjv3BS7XSW2HEkaDqSxnr6YbjrA4yD8LpKvMS9CQGz71vLPxhLvipWD25u6n9Sp1pmqwTl+zASlFBbpL2yVs/+OwTbcO5Nc2hxt2dUf3QrCjVtLxJ9Plr2eKhuaK5AJ1bIXWWR5ZnMWeZoOSUfUfZl1tkNxB/lkc/VdL6OIf4vk2x0KgYTi/CwlB4eIzyvsshzu9m4vX6Zc/4msi+eq7mEkUY1WwHjPihR09NdqSQH2iDEnQKtUL3ouudbwA9kBwzrU2hXbmqeHOKQ0AeivDekfDLqVIBulAu/z5RgC82LoqDowLytKImdJ4fr5SNENtL90Ypb6ewQHhN7I5FWF2LHLvPs8+bGi5sn5zXVZZ+ndKbbrOmsecBwNeo0iA82bjWax9QSFIV/oYBR1SSk65OGJYm6/IViTMVRzpsbB22V6Q06G8UbsE5BCIOBh1DIX6Uu+4fQmwUIWpXhSz/2Dhr/GIcmY1iDhFujxA68JeP5TGFJT6ErKLAwCoL+gS+z2nN7Xgc6+EuN52865v2ChpzwwEpj+v7yC/269+vZCi69Ov97+nA7dPO8Z1BI89JMrHDAqDYUSsLPixkzEg5pdzDTfoZJb/njFkqYkkVhqlaIYFTMGjeMcLX3C2pY3You3/7UG5SO0XmspgVJyLWppP7elrylpSnHovjCjUcvYZd18btRqLWJ1qiKViOR12blpsPUER5GCMJkTplh28kf2qYJFMw7u06tHW5Y8cMK+YuCc6835d2LDhetFzedF5jkfKH6ff1Mv7dmFT0PwFtFyQr/2hHtHCvbhkglBdYlIdi6fjbhLUwxy/IzpMqPFvf2KniV26DqBY61BGCXlt1WnTW/s5dcq9BWWJtvaYZSXXLwBSzIeTVd/D3RUJ9lBRECWI/EYtdjv5DaAZzgT/VfYbmcJ+m/Ri0zuSsWaloqxByJxU71dIqrZv5SoYm7pEuFzhgyz5+WbdEafRbc56QYpUhrFxPAMDktRWJxWfT8uNZ98KJpeEcMPU0/LvTgvJ9OCG6cTawww0o6ut2WfKJ/F5djxkZHlsqji+nKm09x4+cKBCAEyNWGth9lVKMabpBpiagaYCU+DhdpmvfqKFeP7PqUQI1rouA6+LahXHPq47CHl9ysFw+bQLCjHUq77Twt9CDQZW4ZYrSQQqxI2FN02Vuk278CM97lB+ZY5ykYUhytopBVqJ6f7FMGSuGm1TdUaYadrHecjstR+HcWExKiQs22tDBahHabXm3nKNa+InmhxAc95U71or4B//gVqOYjEsrxr+Kf6TCR37Z8MEqsVT70DP06R157+zQBzXMPzphcDQXFfgtahOkX0eUKPXmQTukXiiCuhN3h4q77Mqf1rKF1Rpo9vybuZHQeXcakS/kC225JpUiXrh/qOYX06vaJozw9Oj8ow8U+hxGkgG35YLQCnW5p2p00X54QQzJL32aPnn5eOjxiR2lbdhgW1FMoLusbilK3QG2yJ0uOxvomWpGeAEZZ/P0Gg1PR7g+tp+WGFWneOX4gsYRh2/ktWGto/nht5oceHu8lTbc9SzGSGZkL+QGiDZ6WHHJ6EmSLvDfYY5zLZ6Tei/nEWrOhqR5OuT8lxfVoxZrGurcFViERVnWDVK9QeVI/f4EEBb19OZD60/kdRzheyafqy5c6lr9RkTYaXLSfNYdeY3yholuQCN4f4wq1SBdFK/57esgXu1TtwsBS5Li5xFxV9C3LA0pNZhW4C7dgmFCGIgUjHRpwyWehxhgACqMUXJvHANmKon8uIVcp5XrhNPzLT8nxdMZIeWjw7wlVCdh+OFMxWI3EXt1JZ57U88WLNAzBcdmLmpzyLf+w6JDNJd4yP0tIJpSDMJ0Qgwe9D8lBf5HDBpV8kSXoapP2ShxLen6A62T3CoCQC0oRYq4j0x+wHfHCTKEow3skAdjkInH+08O8up1Sbc86JoUpk3dAW8ZE1NxviTrZ0WBG8RPiidFZKSTiDjla0mC+nK68Bzq9SSF6hc/CF6/QI+gYXuyQvMM0PDyKsIWEXkowRQ8R5+Sn5aJiaF+xJ385x5TYiDaTLST22rLHKYAlDDHcZ6r5vlHCoPZVzps0RuNGTlcU3VrMhuftMag39IV0upk1jMDDLpNiXPjWI4ND1SnlMIXqVtJDoPhdvKCMoOI+N8/LpvMW0I/PEyRNnybqw1h48ZJ3EZsPW0jMdUqYDRf13aLxZMs8c/YHiXwui+0DXxwJss72jqK8D8d1DVcltdVhx+BoMzm4SD/tmDi0UhSBu6twTyTAlmnWdikvkBNMv1DwWCE/y3X5Gsh4xE3Df5C7FSzYHKqAHIWywnWrGfgNPlGuxt6h6BfoYtPc47xZz1dC9dw2Rx6lizmfGIRdVW0hUbXMJc/+tGheksPm5oZCmrcB3jPyVOcx33fKk6xKuxXlfTuuWq0gpHILo1AUZpkr0k0D2l0wYT0a+k0em4ZorKkNsxkVRO0uQU97nhxNl/N60UA10myf4uvqZvrQbJ9jYEh2xfsySMpFvG/5ZLSQvKdOt+BX4Nu9rkpmdboXl5pWaCYUVujstFQyWkzh3MY0/gRyVMhlrVJ/R8LyMrGjKGoTtZ2llN+2bDJaj+0wR/UMJYBoAZWqz3EPF3Svfp/WXwMp7nGSjgYbk8Ug95riXUsko3WUnVrjumKAGsXTmuoy+jeH7Y3O8mm7tTE6l9EqajFF1UdLJcMisq7z1QO4D7AhXIaAorsHLwXDhq+h3EljNW38zbKsh5N3mhk/L1kxQ6Hlv+EFDzWT2aYUwhnIIaMUhDLntzCEdsVVdrKSIhushD7Pb+v59AEwrOIV0/68QVL1oQqcFXOS2E+j0c4jPEMFCD3NLcOMljNQ5vVikpTInoC5g3aYjVrHzdfPm2ymsx0j6FaXNvQ0YVTURt8sTwfyZk/QfLttZs9IFpK366e635l/kC6AQlSGh3h0jbUE27wQyq7vySZHCGGQonUZ9CRf7qvpYjSr/ZAUV6NU2Sxmy4K8RUuwzZVIwZ/wzAxOqowz1SpLPtfz1XpRY0G3IH5VqxsM6ze+yfEOJz/n7WFCLESKs1ribG5Ub8LL7s0nnvFAafePSjGwnEmoaMebrCWyYvLTdq9n99A7v2qgPreA+l6Z/ajSG2uYhGBxvAaJqkNEjUfoz+BFzOA41OUm3wsNUqnY2BvtQC3htciLXmt8uqC6fFolFC9oS3xH5cexPa7gEUcog8N1kdBaaLF38glB8wWF6VU9fWAKkSrMHlQQjG8ghB4dRA8JroXpv9HxV6hEzV7mcJmquQ+nM7g+Luk/sPW6/yXbYBq2lxolQtrYxIWSNKslsird8zLfes3zD5WnU1PVsBenGB3zM8Lox0hIVWXaMTw8lOYtlYG3cNgorCl+3E3IpDG2Ej6OkbiqC9Ve4o/T28cGVMCrr3SFoCcKxlK2L6toL9wRfPro4BoJr/R9nIzEHuoZ3Z4KUt8UDgesxPfV+r/X01jNgSpoNGNzHu0UtiC2puiWIuLnn5o75qU2VGukWrN/Dsuvjj04KH1GxQELbyoRgkVgGRJmtbHl4Cp7/2WlMALaA0wdpXIWliLCcZU4q8t+0HG2vsXnh8niHRayFwFa67UaQz0lur9GIqyBNkSHNVhME6ZG0ldxNjl5uas9nZrzoYBjrEEU2TXdj+wGDqlEV0PZyNCaTZyVciRltwADishbtDl7leSXO+QJcV1C1RYOh+tWUr6S9TycLjmZT022Py2iUsUgvzl2RuIrRJM7vbVZs2SOR4UZf6mTMu3rik84QptndafEDFLFsD4DUkUrodXoPjN6W315nEMWawaWlqH1/zjXhXneQfAzFeVWAqvNe6dl2osGgBeuhWFLavYUQly0nqBrYm3erSXvup9odn5sQETCndlLw9Eb1wV79dgRiaf0+Ln+rIIq9nZagzydUdK9PViRg7MxWLlMisu/sJAQ7A0LkYhqnQxWDqFRDNXu6t9w9EXN2SJGEUrop79h5OjksJnj7yO332jYHyXZu9ET6xXiIzg8K9HVZXmx0TM5mtUPj1wGp+leZKyjTw3thLUSXZ3uxG6q2RcWEltOchim74Nk6UNaqBZHSLMSXMus71F8elwja6YMoNBqn0r56FCYsMEhJfTWdWsR3Qz0QF/owP7hSeX0b+7hs/g+dWgQyKGkDap5OtDrp7J38TJL3uOJ9aSnEkwkyp/RKukI3Ac3N81qySVQGFtL78+okhaY5WHsHzoVLXmsyDNtu1jL/LAeFAJl+j2g3DxoJ1iMwigmk4U4Af15pxo6K7PmK89LXf7DXuE+sAQ1RuhDtVyxgqJs3lXEaDVeLxpYgBVl4RVxr6rpfPWmNbZFgvKwrtfTDaa9oudnlNVXeoxbiNNOt6jli1EhmqktTjulignCe/1tkqtMJ2f1cjatB2PbZ8pZ6s2BQvErbcxo06SMdW+QSrc8MaxGp5t6ie/wIt8wSxelV76RHKB8v0PSsp0ejNqXW5/Whx5mrEV3a+lxiMc8YaFXkB5BOrGl2hwjjOdM5eGOkgdz0vDghxdkZEG5PEdth/ptTRcXWT76l/QQrhds1f12AQB7TQndZkVKcXe06ah8Oyds92hb5hgtwwykPTG6uKyemyUymr1Br12M8MIxxXUL6YlAXVv4ol5RaaPoVFMhhre47bGd13fTW7R8TqrVpnD+QZKOm3EWMesnZLROgm8xUKfBFHvxQjtT13cYyyU3zXpW/44M4q5eCmSVksvl5AKo2s9TGMtscJezUXa5NrH5nKE4U0rULVyvv825Ja3p/PbgrqEt0qZMPBZ9uWzhOw/LLXjVqT1LsqNddpQqbGdH/1NK1C3KTiW3RW3W9ROUCIxJrh4r5rUtly+dmPFRQ3X7alY9R57to0Rd7Rb0COrG07NdShxWWjTimMeWnDBPVmVl8vMS6W3nLnC+Xv73urlrbprpCsk5fc8gIi4fV2EBeDLsSuHQjpVVlb0mwDvMmRFsqhmsCLNcm31Jgqk0lnOip19KSNZZz1iSoSHcKV7o6lPoo8RzX2gR3p1QJxco5FLCsnaF20SL4AmfMigc7jJuKM5JL/gXT5XftnsbB9BoD+oJdZVLWotEZDO4X2gw0F1KWkkwBYek41nzO3A+bI2Ec31WzR9m0WT0IDH/GhXZM7HuMtLAUgK0zQQtLujXw1nz7HXDr26rRZ+NBqq4vwBTMLGpN4bNpURnW/RVXNfjPqJyjrLi0uxl0qyKmOQ+6vxSIjM96a/ficNF8xVlUwZabRubz6r72ff/SytBXSkk0kEZp3+lh3yXyp4LjoYo/LlUwjItrc1B3919RfoHg6bcmQKy7p7d4EeZ9ey+ouf94p3IdtM/j4V6C+7JUXqwY27X4qVD2gUFLUjCs0v7ecSn6f19NX/BUviXZKlV+zEkVx5WH8jQHf5HgrLLpdP8icrHZkEJ6A2MRibK0muxR1pgyXyUUACkPMulEo6d6oesVOhWPJTAJee3QrlWh3eZfP9fFAaXrfTT2/WXavHf63q1wT3+bReVlTtXQSqrpSVJTHa6p+p40tlZs6Zf5lECwanvTwv8inm1mSqfJ9nhX8Bwh2Q48Ml0tx5tNoCw01lDm0hv1TXlOd+6dH00J7QHo/ZFvs8ZgrjgY0lEdiY1PaQRrYn6hS4x7Cx8OSU1ZtgR/WNS/GusjFEsgxvsYOW0Ctutwnbfh37EMz3VMAqmA6wgpXb2/64h53JwW91NmdY/C/bTqNosRi0KNFMBI5xjl0okdlabV5H4imIL3W36o7DsaScuHqnu1OhwAO2rcBPaYiESiJ2z7czmLcLwktWX1twoAf+P05kjKjGn+Egf1vPb1XRbdvXdLhw5S/MGtfcUnRSJwKUue6zyDFY9L3ewEi7tD6KBTR4Fdzr6/RJwS2fbGEdFyvSh53MVZn+ymo7jbTCY4NZIvC3L/qFmTMvBrP6DfeEmhVa6wz5d1zc3QSb4ON/BFFFyKIU0IZcVaerUZuXPKTmQnhntCKWb6MVTaTvAjY/Axs8TM1pA2SJmtIxAK2wzZhJ1Poag6mNXYPqR/Fw94XnkWLLDoDQf9ZeyUYsrPrFtiC2y1OX9sOLrHIKnTyyfltsf9Sct2LQ1qFVGWYJQzYqsKDpVByhAJcdUBTE/FJH+r0unne8wc2r9rcKQDic0M3RDXqsx8tCzAIJ9UAX8NAfpebFl+XyygwXYGqKFakgsw8kyimyALPFGBT45UGWRlCVn2wy6wqiAbjtOyDCmuc9JOtoPdwwYj9jTOeGY0UpE1sEXs+9Yw+iOogiFNbcHGEdZRJXw6c4It4w2v+/LHy1elqtqBrNl9PBowXux2spiaDT6FE6YZYXSsiEQLa6ojm4llB0sSKGUexXG1pwk9rfRjpCNNTNz+iDCKSvw3G/+/rf1cw0+AdTpE1E37AVwq9u6dXbcgLScJfm4QBm/eUFGGUUQYZQVyjndT7OmPOxc1FQzO6OTt2t2m/S54tGaPaeQ/Q/3RR1R1TwaR8qYjyJyNKGTFToTPNh19TKnt/aF35qSlvG3mJnqenRXPBQrFEWoFhNGWaHzATMTre//Zz2dY1iy6bpyuFgDerQ5ZwSAZFxKiNcQFGTBGiSw6nLQyGRj1iMEcxZkySPGK9P5qn5zTBFksb7bnDX+K8nHU6QxERInrLLCpD2CgsLri/82E6fLTQIiJTBzdhCMcBCPkvxyLLFHU8wG26sGUcV1y9Himk6XaLWYrilDar6C+UcRR+9BnkB5MkiIbYdrJGHWZL0pC+/L1fT5eUmL/XGIqzGxglTTCoRKVphOcvesbnAuFjdUlsKB0/6wz4dmL+XgOI9CmzDHCkMxo2vCCZHgU/UyayBbodweZTpbybSwGYwTEllhSjkfx/BH+r1JTpo197PLcg9sfKNj/jiaKhyhj9EqRASyHd5xq2ZJhVah9gLr9O7KwWwaR0RCrDW9csXRy3z1SBXfx/pmBhffXJXpbr2ry0SPO4CWsQTaUKwX8ljhBp5j72YYnyXvHh685XTq8cewfb7Fo1Stac/mzXTYc/tgPibZ9Q5dzIhZEB4doZHRQvo43+r0NM23eoJ2+6CDs6huUIcdzWr2sR9uiTrYAbIxLoZb0DmtRAIrrbjcmIW0eOw8U4b+erasX0azo8MkH90Qk8YQYQYXRgJqqXoqw6fq+fkFypN3FcylnHHDUd4YsXv3LM/EzNmRnAiTjLakt0Xt9BenlEbTx8icLUXar9ULCrZLPiTZ+ahNXhHzMCropREKmUqVsUM7BanNMQrZB/C35GMSFFanYyKsMZVqwQ76dJ7dYVgEhz7UdPXov43/4TsqLv1ph3Sp5YorVAAqKnWEP6ZS6yS60f+6QTWDeFJQhR4zCDirbh/Xqw0BRneS5LvqDMTaEHIB36mNtSrreJASa713bGayvyr9e0Gp0o6mYxq2u3D4VEpWMkAKADIHzGtNEZainMoznegesfCe6j/mj210tU522De05OUwtsUJdUxlhdg3eG2iW26p0amwudmvUpvjUjDE7OarZGRBQPLxgljVln7EJypygLXRf8HicZc7qvYovvAQ3Al7TLHgru/hPNbNHd2i5HoxfXhc+UEUe9gt4rgj2opRKLB2UfManJE23Kq8EJB4SweqFoC7FhiuwCNguQIBhMEbv0BV6vV8pbhM0rPRioeR/KHxssUpKWUlWvUGutwkx6tMBQ5756VUMR09rhe3YDoDO/Gt2tXfGtc89GPm0Kow9RHqmCrSXh/htPq2fmoopsyXz+DEYO6zDxSdjql6KMrhhEFGK+lz28sKHMvkbL36NgE4a++RFwos4R4tqlQhkymmLw2AuZdcjUKDN82SrBg4Y4W35jwpTkfnGqyQGTw6WIaEXHoCurb52XRZiVs3FfTDIHdWQTd0VW8HuXynhYMLzlaQPgmbjGo9kw36bdMlfZ3lE4UUCgSH1YzyGIq9t+1H+vkr0u1mvdnJ+KhOqRYcxUZ4kcUwHtcJq0ypomebn78sVhT4z6e3T6Ab0pH9UTCh8wVpqO9H8U3IZEqpslfneVhMv6AWO64Wd5inZ1CVPV/Pl3VXmr6nIyqiIhvdDLrJo7g5z1gKRTrUQEIpUzrNSgE3eqw2YsndhDLOcltJ6Wy6wqYBGxGXUxodcngF09ATzV/KdYsqep1F+uNPnyoWBniZOJtTBK4eZmvku3ROfI8wvBp6qEdd/zzmP5hI4ZOV3WrKLvc+WN9Bf+stxApmDb1JZh+yV8rFjGowqBR2mdKF6Q7wFQ8RHuY1JS3a7E3upLAxtCWGC8IvA9/ObjZLB4aZyhV/hbVzkhRXo0oAZcx6WFOpKNQygMI29GiA5PsM9ccFd5tY9jzUk6O/t6Zn/C0eU4p+c6a4Djtz2KodA7u4lJATvpkylOiJQgB7Vl9Xi3sI96RAMOMwAbFGO3VXbV/xox0NSwj2h+0cFI6NxGDGPflmS914/bjDhkItcuL9YEUKb1gWGUkJ3UzZXMb9RxXqZj8ztPQUbMpqAKK62ExcIJVwOTr/8aq2oRyXViFMMwWT6G4wNwWn64wCyOwF4kUmqAB80nyJSZ3/a5ePm40xm9CeE96ZsmUvCnnWrKdLdHqe6wZcHiOl9N06oerMS6g+s0TMEF5OpdHxKGbZDx1C6j0U6YR3RisRYgSlj0Djiuh6pv4aE+9dko3iQjzjOhjoKHMRvplyaf9I4jDAf6oGpWdSOGvprxcDY+Kgo+YZpVCj2C/GQAQnyvQQCelMUSLTaW49zpFQLx/RdIH94EZfnSql6SzSU6dAMmqgrfLYx7FUzwv1TNGBEePIWfNyx6OPZk4BFwbFPy7Y4C3nQ9KYOV5dWYSTwP+zT0qSY3ihFLbYVtGLoATpw3wejfo2qv9CJ1VYZ5RE6S7tF/s42nIuisq0axEePdJzvJwcPU4p719Nn6fDCG9PEvVxFw0iDLGnGtUJ8YzWIgBBwA6uuZcwoUqK209LgXS1mEBf00cwcGeJHvUZbDWZQ2IWWJDE2bLoIZRv65sbjKiG7pkF2L2DCuDqy5TW0iJvt6qAcXKG8f3C0HQKX8t0C7JD28FLyrpxmbOScvZP9FYPuBk7KrTLJB+VnC1crPeCHpCw0XSaq4FG2PRLckJLQr6Agdue3HRYASW0EmBWhIumUyXy+G2357paccQtbJq8ReuB6/2K04Trx+mqvqct2XwbqV4c5Ss6HtoFzzGWUspSTN9jpqx7BpWLNUu6/0EpDRWNm2reb5vF75tBprxKitFeoXYxkAa6L0I/o3xb6Mb9KB4INMi7lhsw7V1tsY+7vlAWtrejuCscNKxmYO+zqNq0BaPVizZt47LxcNHcPr1Knk7N50SNas+7MqZfXWBT2sirs6LsaqKLakX/Mn2fg/nqsZm/0BNANxpVZC0j8NN6vmybqHFA8uEOYTnrYhgSJJjCStPI1V8t7axawNiGTrv6O4a0xSjBU9uYKhcyKiGmaVBK2leK60NaACyPMpNtxD1+0996eGezCMJ9UEOOS3SVjLkNTUwoJAs9TefZplX7SYMUhsI25VX7jYAld59DY2kkwkJP03lu+z7EM4h5ydn6eXkHQr3NFZOEZUkXtBcbIeeUns78apRaqWI4OSR6wk7TRdq3qDAZh+DRKTBqWm0JfzNM+rLBGGujRnq34wgbwwoykXxC2Gka6X8P6rjzfUPvs6NMyUbhTx5WSTXjWT29DctsK3owR8X7PTc4aDSB5UgoRp9oiDi9rlj8oYCtzRGu+aLZjd37C0bcadRKgM6KENO0zqzbAFEcLiocFFcU25UbnRrAGjeep52WtF47Pyyx6oSVpjUlmRuOUL6dqQprxEB5clavHltQ1gZr8MOOIZbzcmWhjaAXUhhoWmvxbm9r+bPmWzVvAHv9MX1Vwe6HPgWw+8I2wwryjXE0PAwwusLU9Qe1uUwMJ4cBjTDMtDYir3rUzOkcoGG4aGqG2pbgEFCOgvJ9wKN6t60bk54l6WjlbKMcW/R+hF2me28srz6JbOX3iulcmv7TM/28QcJyMF3cbcd3dZjko5RJ5Tk5YWMHJ9wybQa5E/faD6oFUAIm/f+JMmljiTdAjUIuo2XpYf36vvnKQmGFw6rqL19wjWKztOxqh72DU9G00sDMQtZgQ+qUJ97zwjq10T/9uXqdQp2ZD0kxmkLBLtmEp1ZUzQu1TNt84L5BTw3H+KkviYo8Td5P53czyrTZRs2v59fqMQKVo+QpH2Ulli5WAOT5pBR6mbbapYNijfPLx/UzRln4UrlODsanRR+SdLzVwaIYEUPCUlhltA4BQ/1EVyi5ni5rFIo2TJiCHntNL+Ci3mZMjdxrjwUO36aspLVInLUQxxoyoXnuS/kwQv1AUeBkTXX/YltPYJxzovNYg8FgCRJordUt/vZoNr1HMXiy5sKsgACTGszw3lIqsFgz+4VZIL9+mvy6fqJ/8lfk16MN3ILl7UJDNFfQWiTkuqy3+f5pSfeH+y4AnS4Q6lywwX1EZVK1BTUtxmWk/ccJm6+WQh/TzvSE56sG7KSf6NQ+Tp8hdp4n100z+wqnB1+HBCMLJdUHu9yFwtBKXokEXWcFsHA4hfn5ZbWib3U/yenF3kN889PeIAEcF0diLCWRLUD66mX2O+eFmOEBOIGjMmA2nE4XT575vEFtOE/yEVqf72moMJkOgUTiLBVHfdN0TT/hsFn8u6LH2+wR3+n49oSCGn+ZNtKatDADeQWgKK+r2Qp5Sgbjj/1WPMrjfkLjX9ofYZGZVPXj3/4dOsTbTA+WSvdj0pvFRAhz2h9hlJks6ymhVxXO7mGzXH1l2fOiTJPjZvEIUpVfSKDTnn5M0tG6FMaDYWHGQtNCcllILoKVZw3/0avq9/puQT/B0IO4HzkzY6OYYIpywikzGcigg6EvmKm3b9f3dGMwoM9+2LOs9MOHUIpN91lYZLQOvZliX9b0YmcGURbVz5I7cdP5fPqlfpicH06EGjK5LODguctNIg++xbwVultCP//45BXmAB9xRTYQHPr+v+6aNdqonxbNdInuLUaYGxT48iDRVzuU5iLsggLrMbKebi50tmaw59VtNb2vJ5mmFB1TECn/pgyj29iTnf5tLo95fRSO1tCGWZPnqjc8nKJfcTSrn+s5HGhhs7pt7HA4XcCN/e8bO5joFda4wq5bj+RLH+jlffdCmZBWDvj+xctd83VeS4fy7ma9SWa7KN5RMThqT5xy6hgqOlJagkTZfEAsP4J+Np0U4LOZiWnoId+HRrLPYsPQhFJoZIZ+3YDex04OLCSaOZbPnC07NNinxzUAyq+0gO07CiM7xmQ4q4GakJLFUnhkBnRu6bS9wOAb8m4YC2X0Sfd7e7w8f2hwhwRBmGWm6MiyrDh7Dq3kb/TvU97wVwSB88+7BplhSR9FcV7oZAZIrCD81hb53iCNrR5hWKKhFEaZKVyf3V88vtCBXTK6ZzZrmHdvKET/TUeJXWVqEU4m8ZV0tyoBKJ9US1h9f1pzgg2jSuZ4J4OkQURIJu8oyE1vm6eNnTovr5NstCZrcfYhupuiNUncVXlnHiODEGwRHQ3QeF1Gu7N+bmWPPEDrCViGcL16tqOTDfpbGX6bsE8Sh3XnvXcIWSw2AWfezivjvSPMZVcRmTU0bkdbQCD0FuHymZ4FYZrRW90r4nE1jwjs5ee0e8Xa9L8kIEPujkfV+bzDXOCSIasTspnRhd4w3j6j+1S/QCR+Pwo6fnwWNLmmWCyMM6O7GX077zmZPjzyxAdMsFcP5cECrYavENP6+4+lYgHhELTcWlqSBGVd9rndNRwAkw/ruxcIPf4wB85wEA5VaTi2wjgzxlrVjaCbG9ErMPQ87UNw2/AzGYKganwaCcOmU5J4S6H2rPq9mWIsBQre3zM6GmUF5jE0FjocwjozlioxYRjP/10BZMR/3iWmUgYqVM1i0NevcLk3Wy2U7f66owMW0TFGB0xYZ8YOnHkP5nOoW1M8zbXLf9R/1g9bwh3tUshmdDLk2rbcHbAfAPKh+LxP/ReGJoSaPYZybSGcGXaSaxtP1foO7OLfa6/vYfVfQY9/SIpRG1FnYuUyKkNhnNE6nNnwBT5bz730LJRhfvjaqiImL2UpvAvTzLisl3M6uBMIe6b2gwLDGxMe3uKNEY6Z6a3n5RnGAGzGwHFX/A0tjcPEnI16BxhmX4T6gXRghWxGQTwTC1Gm5B9T8JoUmFP+NSLVZVKMCqOV7CcXagTSRS2FYGZcKTpSG24kMoTJlAkbZb5v1QqGvcnPuzqlJuq2h50puiX1NoxXq+qW5QZBhFfwhDyvlzD9XsIJnUP9NexJIvnJaZKPAk5tVPdE4exIlC2Vfg3bOME5Qa9Hl2ofXEnrhYzD4vWlMM5MafsSGhYCtwCQ3LUmYVRLJtiPVoZs5FqVR0kxXqlFaemWIp3wzUwvq3TZKh96Ux2F+PJ3LpUeFw8yMUVY3pw27tpUa7XJfjteTO/vEW6KUoWnIOfVLb2bt1sjkHFBB13EZDeobi2FgUbLkZaL75wmZ7QqjwsrWDenmcdkr8qPSXa8Y1wGrGfoMuF+t7HXZrnZbCi3R5TqN0OpwkMz/+818m00kz9NnyHxtJFW04toj3Yhn1wwUSmoXhSymc1UD8DyYzs46y0pb6Kt2EmNf5fkZ6NkKhPjmVHCWArPzOapiM+2XcHj9cy/ACbbHyfQu42HAOOUywq/zOZZZ3tLv69FmVIp79L9KfhhW4owKBnbUvRLGTTVWTHgxCv1Ufg3G8MPKqE3kXFUkeY70aVh9xiNNahuDa5TEYT3OjqUy0nI63VRTZ/+QZHT6jeHMjesQ8s6cpO/CvX0KWbVYqJSp/ZSA5ZeWTFUFuPOGFlLh7hta8Dz9WIB3Xx4Lp29NPOlHJIL+sWbQjnF2a7uBesqhrAqqEOFWmZz1RuYMmyoExCkb9P+tpaKE+vtUCQb7cCVaYwBU6T0WySY5lrc/T7U3t1v5V+9nJ69vd1eF2XCFxTZhU5mc9uJXtb395TRwpOLzgdqwBuPpfoLb15xOurrk8a6ttgXIZRZyvLNxheirXhYNJQZ5UWR7bdT4LxfcoiCQgdXmGW2cHl3iU6nz8i01/D4oQC9CT9mJgwrs2+kSleUSI621k1M5Al3WVhltAw70FMCYpMlOCnaa8pln/pr/E9mcy1vK0RtpvsjzDGr+ieHfVs+0U97qujTUHX+dj2/q1gJPmyIVZyNlqI5N2rD0ualEMes0n3xdVE/PLwkH6bPNSgnMMXY3bumCzP6xlgXM7PjfZC4SoVN1x84pn+39x5WJlOvo/z67it+0T84o9arkEWgMkIis1q5wfT2hucudH1W0znUJhMqu+7q1UoCLGXat0/JRTOdR1iz7iBR46R8G+tgI58WHpk1A1K+d2Ki4seLfeUFRLyb5u53oNV98cM8sk1c70c0iXeRMrPwEEbjFkvANZ1qDuPg4FrNzf2bl0lOMedHE1ivJRF8gHCJJdKaDh9yQNVOcrFYw+8c+mMxQPrh+vaJCqDH6nkDlk67crFLYjCMds7pAwmbzFLh3gWV3tH7qGE/+CJV+of3pYzp6VImXwqTzKIQ3LDbu3xs4G6RG5XDK4DNs2gNwUv9LjGj0/5Sx8z2MGUXIpm1VJFvjnPfUyy/RbGVQRrgxYf6K1hn322LG54hzI+KpHqJhOCwEKuQEEspswxxEUEYbA2Fuk3l8qPHaX3PY5YIb+tjko9IYLImaESgAGN2IZJZV8hqPEngve+2UUbxqelUpg7+Xf1BTyFQv3RSN3Ylu06Kj7uabuEBArr1wh+jdWRdKQzl2EO4HVPZ+KPuv2W0A8rXRMJrmfVH4xI38BZEpCXV2Sxd8ePALu84GN0GiadlPpQW8SxULjwVWtM8Sl69wZByctUs6g0iS3GRmPE4ypj3oDoDtkLiKO15h/xrWRoX3mGXPlG6Px9IJAQ62HhEQiAMMVs6148M5sDn/DRfvbA1dGH3lkerMrYYS1FMWGIuVQKFv1qtYVN6vJii1ZdleQS0+qFZrza/U/rrLnqPV3wJdWsojghHjNbSR/a3Dfxcf14sJshOBhXwUVN7z5qtInjc3MTnJEGWGlaQdysou2LrI4vYX/smiaV0JugxcES7sVpuTnROd2SuyhNGQlBEqiuEEOZSmw00GYArO6vpkKQ2/0EP85KFwIMsCbq5wv+C8eKWMZk3EKFyLzlDKtLUHU+5pcb5PXidM5rPiR7VKYLVQRruTOO06m5JPWH58/TpibLYevnECo9UrO9TxMlzScISwzm+UhtnXd6F+VY6r6Iz4Ojj7kO9ibucQdA5PpSVJSiTbhSgn6oVzG+s2st8p+BBRujuoNEp5C/6I/fGQH7SSTkyfBao7qQU4tXYgFa2QCYZHRyMwnlNHlX0LWlRpSyq7AcHKHThad6wzLChBOkHOQGFZ9mHtJ8pgRbiF+UjksZ7+0XITKLu0lQF/9hoVnlB0lB7kW6NcL5cUQiU4Wr6DHz9Yv3MXddU73U2q6Ijc2DLhP/loMO5yUu4ruA6lCM32r9bp/XeTWGyYCmcMIcWdNvrY6H/FriUQ9F+rzqCZR7zSMa4VBhiThWud6+uWQgz+bVZwzWpTPV2LruZwV4l5myUpJZF5VLxoSTaKtWDM0Ei/UDnlvt9NoO2/uIp0jnR1zs0JoHMDCv6ApkpdDCnjOvnkI9QI4JvFP2oF5CQ9yKlUkYPR47DIRFWOdEXAHRsVi2Ts+YLzELzIv8riOajJB2d4Wj//IUwFrjIEmJ1IczJ35jZ9NK+yFmaZZtQk5/A9L+nv9zGmozNCphZlIVpz2AWCQXMaaW7RO3dHarvCrWNLsvkLznr2MMduQDwBGFJ/SwVzpczRnfJGlSI6uTTgrIxCGJsayEdU0yrNhv0F+nVDoNq6F+HmSuUhMBGqF2HTXu3wA/NC6VBV7eP83rKZE5lin0AiB1jLEIpY+GwFomtNivag8rdeXQ4bzA40WYTG4Vb02pUheFRVIVejjZrWII62KzJsaCiW5DRwyq4G5UDEPLjGuUohsOS3H4ZEk1dZjsfl9WKslIPrC4oQ0iu6+kCbMXlLdrgwRTpXVJ8Gm3XZDGoOeVEtAyJqA4p9cD1AM3Fmxt6+rJC/bgRcxljWlneCwmr5aDM4nTxtFV50+m+mREla6YEexUWS5L4Sum0mLx7uHL1hW3LJrkuA6SECgqKnBL8g+50Gut66hRLamNtCerO4PFB//Vj83uzmr/QoujcM8oNvrbzevLL7Wxdz+pYLnuZFOOOhWlUNYqXVMqSrJgR+USy0xWzDEhdU3rSmXeMm+/kn3ex6MOTlRzrES4Yhdl08D7Xc1iJ0P1e1F/Z5hbKLl+m8xbghsgXyirz00SN4mC81UuoCWkUv3yymiI34qE4nU/vKs60n1n/P7N6wyt6SzLlFIbIR6P4WC8sEGrt8Jbksgg12JI1APCH3ECAetOemsLBut1gEYUsopSezjlPh3EW2CFqo5FCNRql2dWWJgkKr3FvMxVrXmheRBtxKZfP+8oLXH4KI19bVQkqPFLwWWb+P1My9VIB5Y6dYU70awEg9zHJzkbHYD6nDck4IgQLK6yEl0i3OU8tgtnA0Pf5xuuB0E/+Or3dGunsdM8tY4IxBkmLkMDw62Wa4lWHkB2svnpxbvDH92Uo3kIcQpL7vKA29FLwsVsyUZ/Z8g2Z28QPSe/v0SUf6yGYk0Qd7jY6DkqDIH8RPlip80HPiwrBRUUVIQURNnsxFHWnf/RNlb9mi3dGVesosk3H4KsKaZ7wxGhpvcQAz51oZ7iKVtaJf+HVf09O6uWXerYFSCmOdgzUjfcUDwU5xBdhiJV6gOLylh5HEBpeTmgREVfWgzn9B2B5tiQGRp8kDNeLILBYIZcQnhgF53ygTH373/QMwm8d13dBKR79gfgnd+Za8umAkazZXmPjllF9Mqo1DPJp5GVC8BGyWGnybugPEn2XdOYZ5Xvv/nhu1v0puqoW8+lGOv7RfkrU0ahBT9Q9UBVYh0Rio4UY/Omxeaay8ZBRkSsOSEGh1IMVlQqRPOJ6x0czecxJV+OSCXWsNLbPQq/oacD89JQ+CIBNVJQPM5tjDOabWSVqZ7EEZ5ycassYZ1ghFgl7rDSuJ7gc0Al+bE1Kc1Ome6miipgYcoF0VBhjFBJ6iZPD+nc6ujAvWLVwJ1s4tFWnjALwbjH061aba6G0JhvVsNJZbMLLSZ8wxUpHCaAkffOpR82/gD2Q7ye9Avco/E5YvlISmUsqeCW9al763NPkOjm/PUbTvd2LXQbVo/RyrwcdHPLiGReWGAxcu0TrpAH3/4QC8Kymw1JSyU1J8HxeMQrsijV4L/jbvRrUZL/t0tRyYdkzXHPhipWUe5p+BnALJmZLT6VkzCSfqa5cL2oMJW7pxjH5YmMdxzua3Z7FF6rsclxsIYhRuSWke5+G0548f8E8ZJIVRgvKF2UB/YI3jPadHDZz/H0scINLcLSjO+JUrPIucG6EMkYhu+/SXFZ1ckrLeQBAHaZQydkLmr1C8kaYfoVS2CngXbDAbGhQw/2iljam6G8UQxe7aU3JA4uIH8wfPTqcXoRquawTLrPm86/1lJ6xN0ff/5zN5pPLi8nlYz2dY4FvLu6r2beJscDZvX0HQBVV0C+Td+sFhK67Jm+o8OUvpmRNA+PQs7p6pAcCmATA1e+ar8vV4r8S+gdOqy+UUyyaO/ovJ4fXVADTpr25/v6fBcWkZ3YZtdbRfQ0tpWD+VnDaiPqpJZDRUrQEGsrwKJ+ao431lZLTb5PTOZUNdCyeaEHV9/+pE+sm+O1LZCCTj9f4JHcLbM4b5GO0N/S3Ncy9givCnDzC2+XNMbIis9k7egde3U1FqfnRYwU3kJo3qKDdWtAvPP88Oac10u++oVL4zedm8aWBeNmcnzUbXkoWc4EyCDctnwxLKVOpGv6AKTLARpdr+kZ0bGQ1iTH4NHTEl1ANpSUd/koRocbQq1oBjoPFlNY4F1wMZRVZxEmBw41rF5Olvfz6KQC/MA5/wCu0WrYnmLJo/nrTh/AaUsjcBNfAVNRQyFN4IltKGa2hmw1gQ67p5y+f15z1Hcz5Gw2uE/O+kKEvsVF18Cpp42jHgyvS0RUh0LT8MkWvd5YNdqWhZCH5WD3Q/T7+/p/56ls9k82hIAmNjTmKvNBSlM4iHyiNKbly76illtFSnMThj9MnnhbQgaHDSb/8rnp+xoZAi4Tegk+UgN0+0p688UFokhpYiYR+uTMxU6EC97gllKkUDt4iM3LL6NYFjie3fPEf/isp9eQK2/PARjH+L96cTymWxO6yyo3W4ehiY4ZUDmdWFf2qZAg6pXWcNfVX9AVq+i4wannzC9WYCaWlV/X8nmIPx7TDwwkXw/5CT1Io14a/Sx5DDygEFCXRtjDiIgpnuSnlB79N6wcc2uN1vXFE6Cye0nkFt4ADTviYuPCX0iykF2wWoaJUEnFV5squFcFCmDgmr6IKBVucFrrAd6vwbTZ0McMriYr1GL47EmlVkacbB/a0Wt/Tj5oPHyJTTC6pMqDtePPuAb83tBZXmDIc9S236kNzTy4plYRapfpnmnvk8zp5P63vZvwsfv8fLGt51yzuh3uUWzg848r/Vj/OkHwt7ieH79q/OckUxhCx19GGkwe+VRJzle2tga+msyeoia+fH+i7YUo8OKZTHBrcs8SZyWX9GD0+Gty04EXPY1gl7pWosltSZx9DV7tJ6Afh2aZrz2/1IABT6D6r1twiCR+fSOhV0YNscZC1hF7l0u5B+vX3hkpc5A7f/889P5LYELlXFHp+qxc3y1Ug9CFXjjxKaQywxHFXS9zVqqdm+vHKYT19ottez+5kBbTyX3h3nulVoNpyLGtAry6WwNgw/QFfSEso1k6g0x/QHfpIJwZ3ivbmuVr8u9sUW1JNVVMEolqbA+J25MNjGzkrKvYicRauJfxqqn03Ix+jle7XC5yKYeZA/95HSu3aCBzMHVQeDcNIxENNI94WCcOmM0P3Q0r+o4IHSgkld476RNxhkDGdV+twsMnhAxb8QtyvD1pk8YmREGxUj7I4mK/+TQU9XVOAQVeUj0+/fJs+DJYDGVgq2WoOP+E3oYikDi0bPMQ446skkdiUzvULmoNmUNMt4cSKaxS0zXGhDxZoek5jOaaimxF7m8KlJL9NWqKw1QKH4VKSngQq4uutPUEpQMs/mNE2PE9O3qH5ifyuMHkW/y46WIz47yLhFsCtzQcJSQxaHKtv/jEyE6BA6yX/6vDzHCmIjIr5D2qkuFriqzN5e3tZ+VFKs8vmccFZHP3JT6iAbOAFQF9q7h/oSAZFD6wKB1nDcIfgclC+GgmyzmVln3EfzxpaDt4eRNo/51/pnYalJ20JJ7hn1U1DL9UJ/mvapa/ThzcnzcyfmFzlRSRf4NMRpDRpFuJs11IamWJQaJ3+kVwieVwg4XyqZSlyaUo7eTe78/XQx5/oe0LleulDPzBfOnxSlLd0DsR8h5NiJMaWVny5PjXPeAMpcE3eLb/M0CzgRO58Oqel3AK2G74sBVx1Y8WQDtJEufRoOWYKI9pezHVVf3lEpy55N0fxcbpe3K/9y8ynhq7ucbWIr8ZpW4QPSunDa2iig/DaEs4Uhk1l3+eYr+tZlRxT4jab1jes8UrZJT7QR3pyYOpAZVBC7yblKpEwS1VyGs6eFDcxg9kTfyQtS7K9sDcenm/TJcwI8Xvoa31o8HWe6Xe2sf+/sEunzcI3YMLlYh4pop2JWWpymdTSz+CknPZN8ObLI32wgzWWM5uuvg0P0ORzM3tCEhcOtEUeWUcUZ6B4a9pAmyHg9BOvBQviSyZ3ME/8pWY5XEe3+vt/br6NpSxl4cLXyeUxgSdOKlvOGS1ngIDHQJBVkumkcOJCL9Cb86a6ma4QYt5+//OmnlE683GwL21ON6HsNAvHGMtYmeBoCS2XlndGKzF9E/68mcO98i1AOo9crt3UczkrlGdfff/z9pF2LJYnFFnkTlkmxwd5ATgtLekMi5EO7y+zBS5M/yzjGiWXzff/8d065HJTWhr/b34A3lzQkz2NtD0URcDAyhiEEWE7MQijpaHRyqyE4k/NzZReSh92fpnjyKC3Cl4Y/R8DlPrDXc0JZSj5Rn8/spDIwN8vJO8WYoeZCyuwL77/CRTcoPVA9/r7nzOMLbvrHesfptaE80zFDd/QFbd4Ma2E5cz1w8nDxfQBjxR9qdXj0ruzDo4zZELwX/PjFcs1bXg5TkVn28gnrMTlPO0Vuj5QojS9rVpi9kk1X745hh3IG/+AlymuOqWj6I3EXnFKkcI5nmEXuhC8V6PSthKVc9cj9hmsAo8sJDnzm2qNYnF9V33xUtyS7TlkwGh+Lp+m4LHPh1mftqUKv1y+9A+9XLwg0y2o7BZ0OKVamuIJRzZEwre0jF/o7aCqLjOT3opttkY1Gd4ibYK3y1P7w2V/wYdaonOhlN6cGaCtWtMTsZmQ0/t/Sn9zrOtbpJESTseSjAI5l5XIDO+WoQk0hrY8F6CgAgCEz7Yg4kGFLX+a4DGmVCU8svBxObQlHJetxGWVird89w2Sq0d+OidoYNVd5pfvWgvVgeG8oiyjqQ5uuJOwrLJeIZ0fcezJ0rdkvv8HTfiuIY5C8j6edGkTPijKT/rDKky0EgnDaqCL/qEBWL1KLio0OijLmeNc9BkxhbUrutkj2Q38LMIfqYyW2LwxEoxVB6j0pm88O3mERsbr7hD9qp9W3x4WcAwP94dyG77XuohRGQwin5NArMq0u9dg4VTJzzgujxVuy/D06l2H10YanY4FKUIgQc76nMRgneb5oA193Mzu6DF4nlLhhoODvH1ecQimSHdV1/d19DnI8/CmFKwZH4Sv4z47ib46U137brV6xNMNEAZXL9iBr/VD4srJwRdaYzX5dDJYw6fH7/9Z+FhT0iMQybPK2Jb4oyJBV+fKdOvgZXxcL5f0AjzjrKy+NV8pFV59S2w+uZghqv6tkUnrKh8ShuLdkECrdW8ddlbTw1ghb1jU828zvEVtYUtrGBmcxIIJ21IFp+d8SiXAaqc3FLKS9wCMonVXDW4L+mjf/8Mdj6Ty+fnl+jGaxSitbOSVZvmFUB6s+chKvDWqF4fyl4dCG4vzHi/WSHmHtXZK9ebdXf31+59U4o0kDpGPFbXh48SqlLBr8LUHTSrIfdLhXFUPUrJcMQ2TjujNAj2BhALIRwbeT58jA2zoIIYvlPLScyFcOzaqlBBsXKEGPlY8E904wSbzQ9HFN8pwYIUzyF2UiVQI3lgy+Dwjdykl4lKFK32Ix3Z+fg1nebrOKKN4InHPJQId4oNnDJtmsa1wuY005F3M0Ytfo1ICru3wb1er+p6O6eeGx1xndYNwImN8I0MTAHW+DecleRHpVlkb0/XnGXWpuhU4PbzP72sqhyqq2u6CwxLalXTybjFrx22hiEsfKFih+L5zGGzBfedSIq41ohklUFdfK4UWJFP032q0KoIdRmMLG+0bZWFxWO4blRJ6bZluoj9gWjWjL3Ewu0F92Y/zqWyqn7+sYu+iVWlkFMnuYhHmPa1Ewm9J/789vRTXzoF48QmuFEh02C/hcT9vvr6eTtg0nFJ6MfQoVryUyFtqubmnklEe3KxeD2W54UqvHr/KwbwJxLRYphKRZrDs++pXgmp3g1NWDZtCnN3O6XbPh6NHD7WY19Nvs0W9XoUPL4WmSMzVjtOFsJtvlrVsN1oZJd6bh5f+zPf3kUW5/IcWVaqYQRGezKylvqkcSCbRBqr/qFpLCsxFaz8Yfc8XinKVJS2YspnPQCID8FXR2x1/NDOrIgsrYiAIv7BcFjagpnyu4BpYJddrj/bqqzbKBa8pJ+aJ8qKJ7BKFvDICHuLxaHC6rrGYoluMqKZTCXnbJAc8fqzmocYnXQzuy3J5HTnjMeiOjanIo5eVtVw4Wg4Frh4oswJ+6D1Vtnfc4qPjdLfEK9TlXHmxKylHbyyWgYZlrhQfbS0LcmX+un1+uvj+H2Q5r5oj9LAcLxp6v+Zfp/VjfMhRRtpHxgvohJxSeVltdM5znWfD+frJ+ia0nExTahNfBniq0ZBUBh8Jw0fZdstQbrttdMBoiM3RuiknR4vp7whUYw3iNIKEw65E/GN5V9pYnReDkvuiXiEdHbSMLmhfFjc+GHVtAO75zacPS0oG55FDZIo8/IIVZexrAd2ZpRK5iyLttulTNf93BV2fNaNpAJ2p74YAT0rBrqcevLg9kVKlU5EdymPKlIDXZ1narUTsQnFu1kt+y66+/4kv4DsS/wVMzykUZ5EVvvmICA2mZQTaaUwEaGTzmKOG4hVJmC6MmBS8W0xvk9P143ww4aCqaf3wIJiZ4IAsc+FntXAxX2iHQ5NJOFZFunVoDjiebKbnmnbpP7PINDcWZDS3p2PkySyTIKxU8foa0Yfxw5WnxfrhVh5PKgOp0IrsRZaVEcivxylGuClZJuFXWZ0Oy8xHJOfo4kG9lRYgrRC7E4eRRZCCJo/pufqDKnFX57prhjAQ45R2ITbGpUfngL5WNXt9ZQp6riPFk5exi4DEs0xCrdZ6UC00CP/tax3NzemhfMdpBdhFkfQ8jaRcbEQTxECUWJVEXu16cDYTYWo4hsoklf7i21Pzdfn0IuUU3dLT6vnLm7PpfHXfLKJYAGXpDIwML4PmoXjCMwnCJi23e8FvKfwuQqeZqmqueqOFVYmELvpEhTkqhg9S2S/HbfXtz/GzN4FF9h9Ckn2XMQinxEOZS9w1uXhqn62fqiW91/XNq540BYGL9erbyEukjQo3bKCD5IJJMRp7WS7R1qj+gvsa4rx6xHv9CkZPTx56fTvmYHkeq8WjMi7o2Ge5RF/Y9vTFnc8d6FM09bz2md6yh2ocL+rf6+Xz9/8ARTmSSORFWUTmCKxrHbrznPXlEpBt2nf8WJeoThCGMF19nWQpdIaZhhFNsyJXSkezYtDHs1zCsgUQZTi+3Khjtp5veqquq4fH7/9nvaxjXRzj4IgTmymYMDitwJIkPkOLtn+y5vMpIK+nFB0ftobOpaJztGwR3b81s5l/NMLZRGGLaOM68pL68y3BGgKGQ8Wz94t1/bg1kk9xkKaz6LwFfkHRzxWeNhveG4nNLhctBMB+0JHFqX49a8kVeloPM66S41hcG8GKuTJme1Eg+8wlIDstQzE8X/SR6ke/Ht8EpcuzqO/wnOLZms1G5oSZjeQXhZfHDc0VeGckGJeUYkijADBlD66M9GXp5FCqc/s40pil4xKpYCClkYXzDKReRdqtSGwTYfL5nn7dMoZDwiyKcrOHOBIpAvvRWbSaQiwsJDhTnWOGnKLzhvVyWyYCPtRg2l1QOdrgvwgB13SkK2BsFF6IgNMS4xT0EdpduaBvMv3ypedbhV7xYnJUUSnzjVtukQdUR8Z0nrYSauvzdypkRTqzg0Ps09LXiE96Zf8G4lNxXRkUhcNzIEy4gtLqwfSFmf/n1RQtpGpZMbqyzUMLeh2esQsJfRFgYO/QFj05kb89ySkfCX+aguGNQcE+RDihwhX0E7bzGenZzAP8JqoXDmarWfUSITZFmmzg/BdhYzX+LEZWo003KJw+UfVWA1d/XAPh0zWIgT+grxXrhaR5+Ov4NQTFVXgNVtZQ6oG7HAUOibgbDT7TN/gOcTippI13+CK4z8LFYAhcUgoJriic2Hf5V8ifVvrDV9WC/9SYnyIh/P4n4zKi0M9I4zyLunNwPGmjbaFSnfa3mK+x98UY9hnpwT3F6HQBKNYiOlOmej6SApcsxB3EYKGOEg5cQaV+Z5LrZ1BtEryRenK3KEOSxU3P4KEtskjMZ3uOUIUCfmAmFLhCDyoUz1G8uK+RwGxyNy2VTGtkVrwpMRSPKiO5HYMagzcI9YlQ4rAY1c3mIMNAT45Ha7+frR8eq/s+vFB98GnqiV/xxnlJ+xOJMGwyEIp2/EgLHa6gpD4d1ipdS7ht7nP716eaijmlP9cvc8+eBGt5GafqFdiu6HTXhCMxchlhydHa+nas50Twp7vvsOa8jm4kb+jOtxnNGwZHzed9Iop/NJZXuDQyrCmLmK5qzp9VwjTFRTUAc5w0956z1hMt+ZTH8AJZrD1QxF4JrjeFNEe/XmZmH77/Sc/RSc1c6bbkxUtZZr5T3bAjKPfXwmBZypAixVQaA1hr/mYSn/VAK9rrRAww369xAvQPv20eF0FEPmssx6nBobaJv/quW4vZBu6e0jUDlbAb5dGHO10vVrhn0/Aoz+gIkrl1MA9VT4jMQo9DRjJ8Nk/4VuN74PdfLKp6CdcK5rnz4EVDUoTpAw/hdpIZSfjC+gNQiciEJVcYI3rabbvtfYXQ/L7BIqqXhOd79DtrXJlg1CkilZs35w51JrjNJvy4wjjXppzXzez+gWoiXG2+2QfPy+l88DxYjjqUFt8AibS4q16xcVUZySn89Dt4Tng1EpXp+tiNjiwdlHcPaEBeNKsl3V/aogvuDBc8YoWi5kOknnRZsPz36OUyLIOFBEcIcoVNrVxmUPUOKwwyvEbaoIG1A85oIlN4w6i9EGiCb7EQ4woWy+wi77+rFg/wCHZ9f06cVxnoJq6UjkbyCco9I60sE1WuRiIs9LiCG5Udlzz55Fdy6qG/HidHz/zJen4XzbCUjhF8PDQhVJXwMTHdGlynvftM5f0THsD5K+C9KsAYma+q2Rv/ldoaO/z2pC6PgMVULL/hTEv4cbSqsj+8Ld5T+J3HrESz6Lp8mcBswC2E58grejIylChyOpyfAzmdCV+usEZng2DXKWVsoZSLv4BSjvSsvDV3SGqcF1J2C+mD/2m1+ne1QTTdhkJBiaEB7D5a1TpKsKPT5Sz8NALfmAlvDg4k+eBm9/xONIvqp1o+E+XxB7P7VqEiMBbUMXB7yTsTUmXkdUjwZVAXr4O++hxegtx5CIDs6RddTmv6RtH0XKsIY9ub2wc79vhMwporXFFuDaHeTemP+chD3K1JFLJBXJ5ILpzaIAPKh94wS5pDr7DnCmdUtn2VWrgyfaVgo5rC5GW1RHMkklC5IsKN8CoEIbgNd6eFRFeUuZgdomf+7pHuKVjb8/kAnUvLBjg0is1NrY00IrRlA7BYl1N4c0U5SKV+WSzXM8Tgyrd3TxswTuffhjx/TcHupm4LhQhUzEbY2zg64fGGPzqmW5O8lB1YqydDyRFGX489Mblj/fy8nnOb5G2zWD0DZx5rC6g8gsN33DMP8td4x9q4rNJUDbEk9wwl4QS4ZmrP5tSOn/WsLWvqWOpHCW0RAcxGC1CLSkVYdSrNemLLObtS+d7W67Yjf8YSVcvvIyMhpyOjhTL6sINmngm5TqXFkGb+UM+QfHmAy0DxQ1OWXj/GBhyWMuJIPupxxCHjTIREYdXRKkqz1WdrIfAH64flVjMWKiirlS8tfWyuZ/cL9pnzITsGwNYRZAnIqyZeSAjDTtGjb7dBbn18OmuWb3gH6+Ubn63mPOikAzfW08hdROuiKGPYUca7CN1OZdb24jG39awdDG304zJ0vzhhfZhVd8gHkp8fR0aNCvTLcAab8WwopMDBe9XGckV/LNsfdPZ0PKRf+wz073ZeRBlbmxd9qhmGu5EPxSUmyjCkDBdOuHYqL/OhlRTENVGD1q8yEFruu4c+YoYPep5HYqbLuPQLjYd4W9o4DvhL+5pc1TeVD5pMROTDEBa+OKTjH8vTHNWi4RWVDHQNsQoYIClUO+7yvOJG+urrl/t5te4HaHTKatiSR9dCb1QoDEgCEHRZRgIgJDvUpcUgeb2up9//x/dtGN+WsDZUruixbe9/7DthzjOCzQm1vjirF46dUqb3dOx2pZ3ebxHKitIjuOIMIW0j/S3jnZ8DV5yhdsK0U1RitIPWq690gKlExzzm+38GnBzGdjR3MXIxhYlw6CtioGOLUyL8OqXpiLdv/YxuiOdUBAYgBd2r2UgzR5mY+AWjn4MOftgKodcpbcoeU3fbUKLHLNTYWJMO7fG0HjkqrtSRDq4qYi6kfmsk/mpbBDQfIIT3bXLe9J0urbnxtb4fTMuMLmIiWTo2xoQYdSaMOvrtesh18Z2krRNK5ccJcprVciwf1HmE+GJcTLHGfxoJtZoW3XnmTW+bWd3d4MWdV3LrIr6ZnDQUfpleEFHUtCqcUxgVe63BJciEVqdMlndXWJDfHaHt6sui7uoapNa0jKVnxMdAxBEVg4I51kEwEO+OxFmje5djkXfwF/ncY2wG2JtL1HzL+IC3SGPFeRHLGfyZlUBrBwx0kUjkB3FGJ+WuZhz8SiQSobjDlc60jrbwcx3h3BlOjEOYLYYgCudO2QEwtJ36ejjJodfQ/AZoFDpeJ/XsoWFt2MiXslEpKAbJhlAKPCQSnp2yKtX9UrwfNYIrncotCiCdd9ocH3Ai5NAsEvqRGUf0upDdCcNOWe3yQeOtRSn8BCTbIf0yZh+2vVG8CHMv6xBo5hdFpM9lY6IFOVAKQqpTLtVqmNBVG1qfJzzUpLQSf1G3K6K87qqe3j9C6nx06pHbCKxYqRjUhVuDwrlTLs/VZrMJWB8KiUsUe0/DlkoJJDiaYmPIKBPbLLYgCdLRebOKbj29SGAnnnJerRcMhdygF2eKahzGur3+aMbE7rnm7k6I/ofXQch3ypW5GP75l2mrAma0eRcGIaZQP89BHIhMp7JIEDQ8lg4CgPg7SUwuezl+VtJqi7z3s2bVVpqF4T6l7wNG+ylKx0R/WAYvBPLjAYgQ7jQFUL1Ji+eM96f579UM06DBFPikWUTxYsZAKzkyHkczMNReQiQWvp1O4XPRtZferheLQGdCp2j6gJ6+WkWgJZEutvGj3tjAQYh3OjWvH0uKNR5Ltz2JLo03twgBwGM6G15BKwgX4A9TdgtRW+LTIg611aWlPJK1A25Z+iLYqM0i5FmXRVtIVKzlwrTTeao38DYtKP5Vne90h0Sia0b572PTfNkEImWRKs16VnyIqqWxlEyWkvXdLApwj8BRdCJIUTFUZbuF0aVq5tUbFPubK7OxBnIZE+iDj00uBDvgF7r6sdUBAeEZpFWmAb2KwFQj+JjL/ztKddExcKqLeWQh38qFa6dzJRaJQpPsQMWvwaEU1j7W67lI2cTiDowGI3Enes8K3i0lqzJWvKm8SJNXkYGIzHtwbwZLMjL9fE2zVZFVeJBAaMapeRVaVmFFg/kCtL/bp14VZGsVlIOfNjeUnX5D6xvCpPV05ROtYBxKI/DZkuW1gh0AhZWZbmXGDMqZ9sk8eE4OKRpMH1brxTPaapdAlAB0PYvMGrNI26j0PKXIGC0Xrp3OO9mUD3Rtlp5CFuK0UT5xXU3vHpv7aIdGxzj8rJgd7BkXWIvE5rzMO0+82b2fz/Al8Tkv+DkzqJQcoNas70KKuikKzeg7VQQ3BO9ULqw6XaS6g9h4rVbuW7+CkyEl9oM86CqEzkgRUwgp82hyRfl5LrQ6+gG9oZhEHJZjqnFWzhnO0hVTk59v8HvoXoOxFOsBmAhmFTjRPDjsxHg8F16dLmBQ274UrYTLASQOeG4X16rOJ8czIAFjpUwWmwY7L50XogfhBRO6nS6s68B30Kt7goL2/InKvA/VY7dJWQlNJHryY6J5MYUb8HojhwfxRih3FDTFhw5JzhUnMt8mP8/u6uWsGbaPrB9Se2+HqNKN0zHqqvedDqWhvC0ShYvS9g4BdLPvph0PcYMjRDEYx+fN+fc/oUgxbNxksW5JyRjrUOIHQbFcOHdaWYEi/vK1pkt97w1Itjtq9DJdVr9zvbJeRB0TIjPOUsXsldHtzIV1pymJdQNa6M9ewutVnlPyrIBBh3dRpbXcZeHFgNIWtkYFpS0Xsh0tRuuNeYZX3/kZnaJHAa/9F16mq9V6tYLyenKFpjm9jhijR3AeJsKRLFyMI8n5hDDttM7KfllPTesoMbxG+c5bFNH/8SOVUI7Op1ZCsM77U9vypD5QzoJG/SuNR7ND4zELyoiLxmPwxOD0CsUOKmlm6C1xsWggcRZWdDH5hCUqoE4RfSbLPNYDYCRt0B8Lz6Sw7bCkckPq5tjDY0PihZb/mXt5RwfKhbFr7Uk3QaAUr0IirrYme/04tf5Cr1lAdIp/my5GGNdpdABmGDkQ0t/BdRJ2ndZl1ql6sbeE6OIhwXrzEaZmeJzaKw7qNWekcwB1YpLiQbyfVyoNKyT5wyPh13RDsF440EvkvabSwn+D6ocqPqq0OuIuBC/mIvhIwos5F3YdwmcPCm8Nj1qrlO0hYZFPTqff/2fG8MOIZFMRmchr/8FCiBz+YBKMrS57jv6TCFlvQXEo3dtFCs9jQmx5DNUMqEIuxDpwngediVN6p1fTLexuxs/jt+ky6uVjIpi/kqm8QT9oXoTrF2FewRMOGQ8wxLPRkacrNqYqVloXK8GL2DHhDFjodNopUQG5go+cj//g2mwaHH1aNMtV9LO4IoarcyxvEa0ghUNHqU/RW1uAU3wNudbnCu/A686RmVwLJj/8gVSsR5LG5C6hWp0LjU47J681FSdfHr1zzVa9RqXJE/3+UFlSRIK+xyAFcVF4lYU7p8u01078ozUTCuSVhj7LesmiBSNZZQTEYrwuayCj0ygChDWnqVroIA+tdoHoZ223Wp3BrxkDjOks0oGOCq1zeiksOgOfmm2syvvKxxIo0cO0TZToy7/Q+I3gHlTUkJCDiRDpTA7izash6ZXnuwdfIgouVFg/UF45MrQsbaxNVDLbMNQI5kJfOHWGXm6zPQl7zzL920h4imQXNavjbeHfiyxGUPDOBaHRIH8rK2so7Gt56t7XYSvGKOCf1t/qkdiv0kjYdS6m4wDwXC6cOpNTNiUr+olKfAF8B97nfHLVUNUCQ8LoC20inkvKxECqEHTMhVtnKGgOmcxsQNKh4E2BmeBzq5MXwb/nMVoNDy6Csu+okIRPRysQmlYr++6VNDZRVp5hzo9QVMo8i2DQUDaGgwzfa6HTGaVEqqVtIApv44jiy4w7L109Qpu+qJdosoZvdZZHbNR21NFCpzNKSyLnu2QtI3UQX5Z9xg3N2Lt6thxjmKvCRdgbvuERmFJmvKI2BhudD1CfXgf6sAJiOSBnlu2SM4vGFhX19ORDIzFYF1LiC2HhtHmchdlz+yfPgdEb9soyfKIkNDM3ZJMLNGSbbCAlQHXYjZQINq34G6K/F/2GEpJ1Z2c25MO3UusDEoF8R+3xV4v1I8/E2zzwzeF6VsX7ASYLnjRprUWHQ0K0A5zj1dyj70JSbrpJ8kiKyU/Pz+INFIbPxQScta+AQ8AFRG3h2xkzcBBpATj4LX2fpONRoX8Gha0RtKyLic2UDBkLvqzIl4V2Zzi13GIiemjqFjwJ5Vt1G/OccTqPNUE1l5uhL4W8TOh2xpiyHCo99A10VjtEOO/yMmwL/cWMPxmE4tt/MrZLaUzUWZsYF5CrPWHiGasG2oI9encLE5Qpttkd0XywaQThZ7gKDn0ujuVCwjN2QL7ozzOn81twXacErntd33m+1wCwW0TeFAio5OEHDvWN8O+A+8o3g2abjr2azVN9K/RtEeC+rqfdk3OMv46ZgtG+RyAehiFUQUwmjrdQ9AzVk247cex0O8S6c0CzpaU+It0dsxGNyYumMawQZDtyIekZV4oSQtfIaLv9J83gjNvJNbfDxkBUuoykbJpp/6GJiOHtkSBe0knYkHFfM+l2W8hXTc64exD30Yv1+lUehcCg8hB+ninp4dmyd/qFEaMRvdySitRqFm3IuULFBBEscx0igti5MPQM5ZydLe+TnwCvX0PLAPlAV/f++58Y9kctwmKGDFx6BAXc+chIkKa8Mx9y0VhtP2AkB31ctF+WUdktayLtWwa6BfmCuPfCzqOVFL2x9vR5Cr/X3gdBJCqVTwAARpz7RDLcoYwod2pvGBSiNvFi2qhsKVdxG/XPe/ooNyzaseEhSv/UNSgLIEeH+uzORew6/a4EKx9eSC4LoeJpS99PYDkA0M7W96tOGahMJwc3M4p9USOuCNSj4KowlJhxb1v4eTbtdA4EUuGlpe844dkSKv+HOuXK81xDQ3HU7ULLs6nrnfZaXsdJvfjWPLyKMRQ/0DBcfmnmUYtIrSPCIl70P2jpxMvRspxSUn0vQtPhKa5WzeJp4y0wjo7J/f00zDCjtCluQeiC7xJXqsLKsxAYjWfPm+gOmBW16I7LCttWzWKwjgjuT8dU5bk1JmQ8m6eCQ/TjonZu9Sr/gurNIAEb/PcjjprQNIq8EVF2JY+OhJVnc9ga9szgDxXeh+2BoxkOHH+rvu3iCqUqZiSRxfw+uMIQap4t7NCGxOOQRTun637kDuEYI6Qo8V7biOGU9aqeIYwinlBh59F7ZcSyvfp92j4S23aW1k4OlqIfF5bTjEDEvYlyEGnGy8i6ZZRSF3qRWs6U6Qma+fbmwFXpeI3d8S6tQVGjSNZuXWwpIAXnwrmz7IO5WWl1dNzX1RZ9o7Pv/7nbyVDUZRFTu/cNmZCkMI6MsO6ssb3vdW+v2faqXi8MnbM63rdzsZm5Y2pMCLkEo7tcyHf03ogAtq+8DplVvqH7lBdQ3h9VGM1srMFasCFMqFaHZ3surDvo9ouBbwMejDSetyClWc56GsdeTMP38V4hXG1MjsbypoTAdzxHEsKddVmvT+5tfM7r1bcHhkFvaVDR5lyg+1LFKPe5iWXH3nAjEGMAnM+FcWddLkjAfkwwsOTaJJHq3rWhYQOXv+3aYFlMLZRpKD7HrltW8Wp60ZftfTpYFogFqx2S5eHoV9iYtYU/PBKGHcBhm3edyQXbvESTAjLDpyaOHzWRcZsPPDGHhFx4eLSYXt6jy5K5yzuPavkqJuPPlqDbj9wyF6uI2zl6qOGDjybUPCqI84GUxA3aB6et1tI208DtHDilEciZ5sUE609ejERo57JiAGny8wsMQev6S724qR7ngG3/Np3Pm687nBKsislpRoVYuS8nHD1ay0Yt/EjVBKwqX11312IrO0vN8J3XKkIpirJJOVcVnp6ln1AOZnFeGeHU+zYGVdZbHEbb9DmvFt9o+zZMosK+BF3bPrBD3PIVrp4ti/xV80nwO+9m6FMu6kGaaCaHVIvNojrmWkU49X7gFNoiHjgJWY/WU3RHuePvtDKoQYFYOiHXFfzWeEIYEIlVEZ6nyWNPKfhxuTD2sCK3vUOV4KO3nekcVKDHlBCyIoLRy6IgBP5eEp9L24sencrnGlgobsdGcNgAFIlTeig0RgQ/DBtvhrJUzbdM4nTpjNvopHL//SFUZtDWHlOBWHeA/+hGuSCj3cMjTLB+Z3iE0PcslYUbzZ6z+nERpcvpfqWLZtTq0UWkoRV7RQdtOHhZbah2adE3xX5ZVW1HbGPUU9AlW9TPyAQAd/n+v6EkVdccsCYnh7yyOoE+e+1bsPxPg3+pTUTm1sUM9PhDCpnPpUpqaa7G+kH4prK4mlDNejOiLJ6a+CE3YQgFDrmQ+FxaSmv+/axZcDvK2+xuyp4Zz0OKQxspGYukIK2GakRIMRcaH63EbAitnzaMxXo907Q7LZrSiDp+4WIzTYbBCoOPXlWX9m1vT0QSlMshN51njHVn01J65pv4yMKoLAIr97bVsWRISHwuG0ivtQnH8ff/0+r0r+gst0RmyMsqtHUXs2Y9XY7EIBvBzBnmWwaJNehzCJvPZVqMBRkjJlDL13OmfKPPwfPLXS2OSHTk2UAIwMBTXiH40cr6R18g79fr2X1IcwTV/noxjw3kchUxji6iJB/OqIXj5/Ksdztsud7Xfk7xGnNuepXvXzAR48HGJoMtjRRkJc8Hg3krfbRCaH4w8M22H7PTxbp+CLQ1DZTg6UPxrYs6WEVAbR4BH2z8KCwo6xakBiY4sB7suCSv+Tb5P5RvdjGxOtyxQuh99LF7Xv45C1YNXb62e60UQNnIfszROtZqNf5zhXzH+HNJWKacqNM7ark2vbLQZfO44HYUJTio5a/od67vZyhZOW2LiyXHRICV51CE5hcGy5IYXYCUN2hxnnHwCUAywRSijzifRsaAxkV4AsqLh0Zkmwth9zmVdu6mfY0ohqKvuuPp5Bod/PhY0hQRGWDDeofB1JXXI2Eaiodd6tqxbVqZ61dWOJSboY+69KZsMSecLIL59kT0IJeCv5YEap0KuLnXYWr5vVfNfN6R89NOgCxy12Nj90LH4PmOT7PEZY1W5HmPkaATO3+CGVlnuKrtBtzwtedqDIHA88jQ1BjVaSHMPqcxLNu44adrng/HJZLpXzhruLIfMKKAd4pCIUzYkQNhT3h9Tus02z4n70RscTh1M3Zy8Fy3ghIBckus8e7HAUH/aOyIsPkABN7E2P3CMrOvEXZ0pi6rOzq+61kMYjcCM0ABEcooLNYi8dcAebslGzMU3tiQR7GTd52++Osa1Ma0xQtuPsVyv0JofM6o3obovJ5x3+CwqR+fN3SO6HE9RO+guV9ueYMYrSLj6oJ1NUKvkeMjoroVuGyjk9LaWW9le5T6fHpsZvXLWLlZRIQK0uhIK8VSJMoamwYKcskbwl0CR9kDxEciGi2ljijceshVqNrM+bCYbk3CyvJgfCFtBOxl6YRf4iFfCLw6rC4bS61sNLXimyRh1oKH7aObl9l4z0Yt9NNYuGCbXoOn8fuffKBHNPiykZQmIjHBx1hCrlO91oU3CGVV+oDSJOWFGNzQmlnRjC7e4xgaTLsYWctksV6z4UNVdkvrRaLYe6ztF7yS4KDn72q9emTNuYAKh4492Ux/D90zSHkVQu9zFALLjXvW8vteYbAoQHyetlDVsEBKGcGD6TI69UP2ILQ+54yMAvwMqd2RCy7F/Stt4YyBdHS0iRsx8wO9MI+y8Ash9jk3GK1B/4ixlfXjRrpA5ej1dLXiyil8eMs8YjNTej2okDAhb4fEYOjVbcfggTP5phMwrecTYDsLCny0huBjqSPU97YVGKGYF0Lmc6UdFgjYF7a/aB6Gqj7Qj/3+J1zjwyygWOaQM0ssYjtZCIPPlWj299emXq16BZJgq40q3BO40j2MAXIBYYsSOyIGKmizFULlc2Wp3Csshm+0D4nDlCJ8WiO7GpPnK1A0jzAHQlUlL8Z2i5HxuX8cTtDE33o48/KH2iTKd/hCqDO+S20ELtNOWIdFj/CpgtDukmVZ3vwyi0AKyjzCkjLRyYzmhZSyECdufX0ZcDKlkAG7s+ZL43NOfgq0zD8O4ZIHMbgh6jXGj8JQWIX1ffByC6sPzqvdLfJWoTgpj+07+aqxNvHQ5K8AB8YmwxH4tnYxBykw6woh9pWZDkEJPIxz66ls3djeApi3qCKdiEJHylprYiaYIDIXQvUrM9Nj8q6ms6e6v+MbSXCGL/W7qBQE0mATyR8MLyQU8TQvpJCFWOEcehPtCuLmW2K5dHJ3i+VG5DZ1DJXDJaww/Mp82NEDO5gBQu/m3kJ63sMacse8pISu2nGsp2d0EL7pJ3hhPB4meIUQ/Gg1Tg2jCx1f+iU3odyF/sXP9DQ8NvFmjEnjHcYymHdynSCsPnpl9fYourWRDlBvFMt0z+BGWdfRh9vpCHjS8OQ+NHLhJbXhFwZzdqig25KYh8hsIAjou8Qx2bETU8bklbkR8//R9i7LbSTZluiv4APAY4iHh3sMQRKiKPFVIClVpt1JkAiRkQIQrACgTGnYk9t1B/0FbdZmbT2o7B70pK3sDmpGu/9199ruHhEAfENZlVlmZedk5cmT2Ap338+11vasPowQPaPb48Pd2Hf6XHWgk9H3ttvEApALzk4HwZLW2Xmvm6iOKNtRDaaVfcz71BkQt0u3lUKMj1QaCgfEGj5BdCs6MZ7ilyfar4m6a8HzK3jfHS4GlVjf5WIIU0PFejlBVjMOypP8sLh1tFUG9HZyhDQtM1tx/8wtUInwlArSXDqXutIpG+X9b4riZqtschtlA9Ul9hy+/mPxMONbFC4thQ2HkLbMwiN6Pq6kNSfqNinY5WKXwZ40Re6PGFDJqmXqAPECg6iQLUitPNEvB795u1xyMM0Akp6ewtXrPx4eDuzoNBI2OpNE5oBeSDyjj0qayC+UZU1o3rPb30JJwalikU2JQZwlRkK52aVrwqrxxJP4yAbVyY58rp2mUL9qS8GBx254aROesGw4Zx2N0Iw75q/gHW5qMk8kwlJAy835tguBzpLfC4G2XfBQSc3PxztflXrlP+Yw05cgT283HkNMnW4LeZiKkrs8ZW2A9UEUkIEckAQVFVhxUNZIPD0vh5zQFiYSMs+rcmttIbALTitRFkXMTTYSqGc8NQ0uQFa82qE1pUs179etRFlox2TE4A6R1ZlLPO8kF4MATsnT8Sg7Vl2S2ekUcBEd0o00v1U3UliRlysJC84zSs/Oy8nBeD1CXjHpdKADq38SjAvqBcY5uMv9Br20ht5oUYsa1ZKn5pEROupdYd9d3BvhQKBgIXPgc3G3iGKCsCC0kXjqHf1z3Q5ix/04LjYLQJuXoWopHY4XsiILy/8cMCfEBGRzvLvV2u9Z5+0Md/SOnur1enixKbsFDYp1jQ9uRkgEqhDLRIYKNi6qPeMuN3E3x0ETxJMs6M/JC2x7d4Tu1pT+lqWbDz7UPP0/bjboZIltVy3441SsELA9J/FEvNyYfC9g21t8Uz/32TrnS8+o2u9smrBAgIWkR8ExbQK359l3ea47uVpOrT5WIGxuLzd3604+ViVWKm4L/1HaIbwiI617t6/IOl41GqVRf9624Q2tRenubr+ShapfOf8iykMqLahga14DGhzuo4R0nDsyBeTKPW8HUGEozaSk7hipsIi5DN8PpUUdAnwXx7mDMXm/nm0snxaNlgAqJGtRIXf10qK/tzEhqbCt4SDoO3G8O+ghtmJybWOoJ52+u5dU40Gx6qmISU0iYS1BxsTo4MAJocmx76Ac0t2bViXG3t7A6ILuzgnZTb9xRHnNLxROxU1rkj6tHWGHnhQXTo6KR4Zl+T5Y1jEHtvTCEjWcrNfNgaZIZqSOayxSoPG8HQ9PjeIekPi4aB6KpnBbNvZ3H+cYG6zWFlEkKbCmkp65JS2FhGPYoKw1SHU7DdHXc0vhL7CexlcGPT7eGzo9O2PaHw8mwf2KFvQQjhDcMXIsPLKFisDtfd6OGbTL5aKLdoY06wDvWFCghs5zWH+fk3PHuCNTcrMjcocfDytBRdFv0iQUMFcxf5sQVYnPyXvlpJX3hBzreBZk2ALrUWLOgiaoQLKVJHvt0uxQcsPNEe19chInLptotyq+r8Gh2BZhAFfpkAiDUL0pkUSKJ629L0566yaZ/2zna2HYcooVCZ8LabdHLpyMHknitDxo194VJyYKdItaMffj+hfmKvnWg+4075GFls1DU9eft6LDaCQt8M545hXSysDt1d4VUxrRacFW9eA91SkV1o2gzuQWZ4x513w5OEdKdRDaqUZZJswOLCwj1JhBp157/6vIx/gPdAaeKAVKHzr7qRVKkMIFy0BmJXEmMi4oJYZCor3rzTq1I9TbzJHa0V51W0beli9ic17CluaJhAlmCIT2/pZqg17rbNaUdu/gFn4IZVBT/hzWchOuhmHxohBeh8eh2vtYPepqE7eoFbJKgc0iaPYuZweaMMZEwpGYVJpUYMtJor2XJS/dTpWc/Lfr2YXxKVQXMN6hCUYfSR6RcWZBwRTkdzrvjGl9ylUxwyNmKGkT3GZE1+SswT4j+kTh5TiRFgZKiZIoNZy3GO9pNR1qb45zQZ6+rzRhhhfoVjk4lZDTCeguu9Y3ZALnvMb7WYM9XH5q8qlFQfcLx0yhAT2XVWPCdaOVcg7rvEKNNzHevxqzVRFR6jYvXfLme0Fu+TPEXouVnOKOlMAXVnaVaYgohxTXeL9qct1rGm4gKPe8BL55dXTW0KEc2S4QxYMpfZrykApZkgsjJCvKGMIU8OzeeK+a92i6rlw9m9eWiLI/osiAA1kfkLcWBT1TXhQUVAKBYzPevWJqsls2tgynnU2d2fDmU8MKAZL6htDySJW0qZub78Z7WUqrtG9H/QNIeL8bc79FpuPf2SKzC59D82Guqx1DTkWjuJsqvdsA9FwiMLNZbjvkeM6hSMVUG82/vP69mYUxMZKGVZ6KitLwu44eR6akcQsgXdhLvDWRVTm+yQFUcSZ8C8VwgqBf4cuSewOMx931lKB4mBXMsKlMAFjeq2kJKXYmAFBsih1sfCBBcFw4FWECuLtj6haz4U994H6UDCezcmHZugFoWyyIMipRtw/E4MRR3xT2VY76zWYWE91+PyBzUszGwkxWYpyJ70gL9HuTSbktMz0c1U1hU1v7qN/RB2F1a35RO62ghLf7PTwcgDVoQVlURVJSaz+Mc74QMjY7888uENTlS8nuzmfZw2nJ4E0xLiktzRsjcWkI25N6e5R2+eRV9XkOdVGLzw8InToUNqKBgMKWJOITqcXLWaVjvYEum/p1cRal7zKXvcULioXeHnop5SFM4khYlZFlkm4N1yGOAKcAtO1jEi0Cmbdb96c45Bouq/WaCuiZVKSBiydyNvPgNIkBo7l3vkncCQYzZQk5Cy8YD0y4mA28fPosR8s0kjaa2OgdeliI3rn3wEmPS7AthOKUkEICnXfP5BIPYwyEUi2NxUExu0Dvl8kZjvZKNe63bnFKKfIDLvltLWr6JOIKsEQiW6DNkI68M06Vl0TxK8BaiQRxX5HJ29Ia1V2513aNRgfWbgsSEgpWeecMgYttpaHNYnhGZUB/oI/1CJWs5ZVK+6W0ljSEIdCXjrxTTvNOCfOWG4gtWakmJ9jqlurcUszopn8rDyprJIJrtozokEWKLfKumfLqDh3opFRZmXNrWKv4nhxwg7kywjpRLY3UNdvhXbKKdR+eDpztJY/wdxuI5D3v5wv6NH//ND8E5je5RN7MRFgVXGA68p6Zjlvtfpo3FBkDi9oZNbl6wJhLUqgSUJwmlZaTo95OR94fq7Z+gf5/Vy9M61UXrdL0e/gqaVeb3YYbiuSazfDOmFky/dE+/cZmFmL6mpQb9E8y1VeAqzshxRC5LIIp3g9neadf2Plh3uxcbD4F+0OI5pUjdwkAmVhKjyOOmyGrcljl3bCmAqZNu2p4/ZaBjOVkRfNTC6EHeLJoNhBg2iHe0WMSbgtT6UMDURQsaeRdsE5GO4Az3jK4s3aA3PS4WUpkN0gyCr3MkbRND5STNPIuV/ek/nkg2pN/2vZ3WTa8wwXZHISDJDoRmBa5RXYFiksOApH3v1p3NDOIU3ixQC95xyuEm8/rOU+1Bc0FunrhKJBwuyqoGcan432uznW8W61wJ69HzUwS3stTLuufA7xMoVxyooShURYb4J2tUWn7GTwtnVfnzXw5C+E/u1CqwTQJLSvbiSYnVy9ln6uE6YCFhgevTAzLVGuZ30FgKTlOjmK7yE7UEIow/JMHYmOeSMI4VkA7BGpFAIi8zzW52SPEXNQ/l3sz2ijmhqvk6oy0mcG14UMiHfxVvNfNRx0JxTWMpH00UBHYPB/cRSNtCUp4gBNqhiC9i7zfJa+wLTnFasdD6zX6MsNwb195O9ns6HZNhyRT9zNtBI68wxuEPhECU+T9bp74TVs9ZrpFrHezk3hbA/SmXLyU8090xQ6JsAs9AVHOkS907D1xbnS0Vb3c0L9g1XbJKfc9YhmjHBJvzwt5oYZJhSafkajymg3xzpgK0qwHJbI6YTdN4RpqWEcO5kcjCQKaOJagx4mEEUfnKHU0NxWzQIxvwT7WW6pOezJuCSMVD20xEq6xXQccguNhTJw6thtZ01O98gUuZH6HP1aQXbZ0AuQ6G0khkbyWPAoNU4QwCk0dv03F6NH0Z30XpSWk7z0lyuvGG16wFFq5OAL5WeS/JGG1G/4WyhuSdu0zy39p5eb3paANlybH1QMdT8iYTDAms4pEoYqEr0nmjVGeOOUaIj+CNdVX6qZUmmp/7NGTNZryZCQU1IndLBuK0sgvHalNxbHu9kp5zQDEvN2WLzmVCa8IxemE21WC4H0uJt3o+aaO04YZbLcsnuXHPA1yf9Tmll6w0gMZ+FCEqbPaCJo/mkcG0s7m1LHbyKQs0X3ZqOOCv8xlvf5mWZlIcKr5Azfs14INgg6jyaUXjHUOqSO2YQt7/wVDXaKtinr8UKyIY0A6UifRnWDdmxh/wsB4jj+O00bGZF1OxyMCK6VAtxbVGLCiFNIhpnfApWmhRrQLQsW8MvEONs07rsDbEiNZL+gOOS+/45EhHtnQ8j6YvOuJqntViIBLN3axd6hmxSVJvIulh7yl0Wtzlh3OC73S73JetLDJbyTxiMGhSBPvZ5Xu3JtvhZMDsXXPnnIxFT1v60ZqPmeZEbx+xorBEsIjTVRrTSdz1gpSvrVdoN1+VMzTnAUqpqPTZ8q8w22pRPL/tvkitaUS73J1lLUpHWNy+qF5S6hXx61Q7+3rP8CqmssSh5EWiKLGap6FXjgCZOJdsE7VaHeba1+hZG92QMHplpG14uRACAqGJfCDG6T5mXtXrFV3lW7KdcPPHGsRKD4WDTcR8NaxnggI16VtKwgTfsEWpSQxpoy/jvfBGlTGnTLyTcFrGHeRS0natlcn1WL1wM9+vo1rlVY4AGSbBjuJABykqXfHUJ/2Uarq9XzfljPHArdNh2x48fr3pimA9SqKA2ocuRImB8ri3UIzf/if1PtktLvatGazwjLTnr5hbxm7sk7JqroL1qhU6IYY5lkESfI4rtR7Z5N4tQm3RHry3MxbDaI9grrKervH6JI9u0rqlJFnqzVFOnkBmdBaS7lXHkIsajbVO26Txfnuh+tZGBLGj4Yf62cqHrZ4IQn9qwRcac6zhBB/BzE+9a7bGE+AYCDNWbNhYOtuJwtqj69/f8C72oddSZsIbZoRagOzCd5fm7zjz/uOXqtDseUbgdReOD2XCzyx5ru6/JTkSW1QhpOH4K6QtkpT77tz8ggeG8e7C7gDutug1pzIL+eVPHHGfhCpwozCTUeuMFPvreHRunYfAGE2KexPEuJ4eAdhemmMIG2GsxEj1LO3j8z7Zwp5Hlz/hfknT+tdKX6jhxPw9BY2D9mfd6f07eUro8NanXxlvGfOjWdJdnHLATn7pOwU6wUXL6j5JVq2cDmM1XQP9WpwJMr75Dwf9dRL3I7rW6cTstvFSg3DsOhINgewRgJGwspshR402A+pY8GpZBR5lflxtzy4Kb8GfAoWIKEMthp54RXTAnP+sFpI6rhvKol7G7x6s9yitGu89pg7WHAWZu4I3CFltdhCbSKEKsd/I0P0aHsw6LbiBniBKZmAyXJTz4KFFSU5Sh7gJiJOPHUkODIm7/Bp7a1xIgd90gWVVpfkCOe1JO8osQIt2zi4Qowvi2oNSbcaJMeM19yO3VRi/KZtZgKFyG7MCYL4MddxFDgF2Psu+LbDsOyUNOSCvlvSCM/IWHWZENeML67uzMm6+8I6t7571Pcw+XAyX8zJ8wfFSoT6G6o/QnaOssrx3ciGzLdc33UC1pd4EVYm9XsrgkfCqA36heF4A1R46tluSTrK9xdWvG9e//HEfILGJp0+w7uGEJxA06eLKjCocmZIBkknOA9Pd0tSEGi2qRU8cevK/1xZPS9O0cTufKaE0U5m9+KEZhZ4u57qRpWgX07Bc+JLVyjtavtGubsOFlf6T+n7KhHswAWBJ7olauSJxXapklcuCOF2Ii50P0Hg9wDSNZW29RiR6MYtI090S4AU3JWqtqimrUyO/p/addsCilJqjTCjIbjUDXfXE9sSYKt7eUoLAwktyfgPoG65IYtYJJZLqcgetaE5lDvB23qCW5IlXaLrCW7vi/nDDmg8oSu+QeUvNCMyQQY+zyXOX8xn5D2tJr+yI7DDPRBUSX5OCxEQPKZmxfxjYU6bCasMjE1opezJk9oSneot3WWrULW/do9eNucBB+QuydfKFAdB/p0fk3e2Wo+6UIiOUXtl3hRUoPWEY01b6k9rDJDxlyvMuLZ4ShIQxFiJKKkR4kltZI/fv+XkN6/oSu5jHEASgUI9L4Q9iFcUpLs1B6MQhh1UrtRz2xLsDWmlu1m523+g5dA26j83m6d2iqHi4RVA2vNDjFEtLBGw+pJBB4ib7KluiRl1G+M79pRLLvdJo+Ruz1itP6yxCyaSSGANt2OBEEw92y0xSRT3/XGXv+y2hynX+Fhz6i3o/eapwGVyQIzQ9A11mqe5JVRT90CdNcv9MiBvT9NcaafWUcoDllR4XLkVRZJcjqe5JXm0PZnsRN/37UkwT4Dsu6SwLuTeSqIy8UzQU92SHFQm+67cjuq+KH6XTlAc+7GkiunosnyeH1BUzCHCLdZI4aXCXNZ72luSa39t3NjWCwp4ahdqthvKz1e8OHGx2CxZN9WyFC14ef1ciCxxNZL0zzC+DMtP8vjSE+PIwjjr+2lKND598muYu9otHt5s1t8OgvQEnQzQvsIZj+HDc046HUXeSVt+4Krjku7sUcrS7+1RSgSyr0ol7lUGB+05cWSL2Vvx6VbHom7sq7J9b7GLFtBocDx5WD4Ejsez4dJRqwfkt91YYMahC5Qod4Gm9ey7y5TUSGqUZJnEreShjGfLpVEUm6362yrwYtk61qeUs/44nuqm46aceYLlvnvMjOAedSzqSPMXi705cNVbyrfwgf/jU2B3bN7fHYuPIy6NjSSFdCW2bnC7PYUujbJuLX2rEuFgY8CquQDCFbCCElkjE1BzKv7kvW5J8MSgCpl6El0a0193Qz1KQYCi8dIQewiKLOXFYTzok0biwj7tPJfKYQ4inkWXxvSX26Mhq+3ff2pQ/viOAKKAMLck4eBeVP4szlunyai7OWMo6VmJioCsKhWwx7XfziGU6JmA6nbLNgWiTeqJc1QVRzuyEB/5XHZ7snQ8ZziMYLaR5uG74gmXIUYCCJepJ82RGdmedAe38MNLCR1W+C3LrsyxwmQuUQKoDhSINgAOhMmgDBzwfLo06U0Ub+uXZ6ty/Y2rZHJ+r//Nrh1AuHCiSoj70iOn6kng/SipEQnFqdQz6dIUjP9+Xu+luHqwqGR4+8L/TQA8QkBRqAFjibwGBbnUM+nSNIm2SVFMSbgip7fc3kDKKHPk03X9FGpyZeJ+tJwbbaEbjFftaXQpF5E9oItjiu2jHeGGefrLE5YwckBQwzGsIBdsuLEt3gGrnsiKW++7YZWr/d453Sn8E24IJiyqFhKMRPoy3LL2JLpUGZVsSbY9z7+TFGaQVFsvNgfW+6aRjB4TqH3c//J0ujQbab3TGKXnZOvA3al4HHd6HjXd261xeCT4nJxbTUERRD4s74izpBMRbZvo5HzWzwHMSQZY77p4AF0rPOiQ6HyxqGvHD8o74kx3MNR2MWtPdep4U84ZMOWnhZUUKFUmsUEzDpQhYB1PxzyBLs3ybNSHBfm+4O5mT6qHr+qlX/h1YN2KtLTDMmWl8sEz51I98ssY3U4et411R6XdeFUci+ywqU1PGGckzZl5whDqNlHZEylPm8OW0U6kzMGCPpa/zENwDrMlRHPEad/O4johZR9Jc3dETOXZcpQY+956289mF4yE+Oc+cS/SzB6mGlWUS5DkRE0iRUjAyJQnzWEtW9u0tdA6DpD7DRTKOm8htLqqsHl+v4GSCZWwXVgXwtcBhq88V44y4SzeCtXFztKZHQFYkCacAOx7PHwWQRUUYIXxoQXwCiAA5dlzlHB1e9CsqFK9CsYFusVnVcPTQ2mtnzRTdV0vgZelPG0Oe0S2F/N8LOkAGPjfLcimJONjzc9oI3IKTSI1mUacfYb6t3yJvQumIiNpe4JunR9reS4ZldDMjy6q8uHzElL/R+0X0oCSNF/KZhYCB8SU8AlDzYjXZYbIN/zOdWtVj3xTrClFP2No9/beGdWjZgm8akHBJ2e5SkmlV3n+HMCinb9x67+s59vTbPuebPtIYpu77VIhDVp+4t4FUw062rozxwXuy3ZRSVHjXysqvRBiEJuJu+t5cxSj2qUda/SQlk5MeWt9MRDwvfXFF+Vz+Z2txULjNuOVAxIPSXkeHeub9kS37RS895jy4aR6gCMCJkx6S5KwPvMaQ/NvVHLKM+fo+sZ6u7/u95Ftrc+jZ00ngTlVHRZAMUYA9WnLcxdaI8qT5+j++kfdyWceF+jVBnUPKe35WNolyvOQtkUmLUbLMolXbg/HuV4IbfSJuRwld9GgxolXrpbl5pD0dp5IOJJM8wUWNpUrz5+jv+jU9Xyly/nVp71mNjf6eSMmhOQOLR9LEmnumnI/K4iz4UNznljFI90hsB2Gzh7azhun2vh7bzwS/G+iJQFCw59Ie1uU9zV8YO83LBLTx7XQAz8tD+0tTVKhVwRxliic0ijY4HyvSnqCyhap/6awQWBfgoQ+/aRheUp5aftI2LSVptLSdtC6lWfPYVbvPQzzPl9eWIbBTs8WA8heV0/rTbMYAL1VYs0g5Rd82Vfro/dAhB/g0GUCX9aSzoMTCFjnOXQqybNtBZA39GEeMFhodoGgWGYOMI69PztQUCxvEXHyo3AzAFfHc+hgSKev7OVoPZxuF7uPRl6x+DSvV+E5eSpRcFhIM7i0CHfI0+nQcEy3ZTb83GNL8APcMfJ9C5EolaqDtJNQe58flGfSkSEdzNoumOKl0kNbAsAhzwGcG0NiKBwVwGAT2HyRtMY+RnjyVDr0rfKe2l1/ZhaSAIxR4a79qu1AqaAEZdFERFAbPhzvh9Ed8d/khy+AKrQKfHugQsjqYzQMeDdwf+WBuVCSaGFwn7MrDjX4OO3z9Drk570GMcAV5H0fePa6V1CRN/5YY5lcED+WSsmWyGqGsI/yBDtEhWQ7pWhHi/0Vdtrrhx/eWpGMJISf1eUOVQsI5J5lp3hZsZsifiZj2u8SWEITxVxbyYu2Jdy7fVMhh2zflHfI2ShLe8217+CDUruRfIoC4sCGyBRI9uCJpXxiwl4w5Rl3Kou65tpFyaqFpSXcBTYBQEqx2wQwff07aAuUGC2/iZuwYn0gUiRBGB5HCs/Co+8e9zkorpGzYegbXa3a6sDR57oFSn/JO07D+xlTwSEpqxYegBRgb6TyXDysNt8HJ9pVEu6Od0hJoM0PQyUjaa2FFjkMuE6eiUdJ4ah1R72NewzP7+NFsbL9q9jJUbmwAiWzK1AEnXnliXhKR9me0CTmCvN2n9H29rTR8INVoaCbwgJbbh3soP3b0k0yo1SYozHxJAiwZVO9A6e76LzBcbn8qVigGWcR0NuoApagfKhkpV38qyRoqZCvxnx23mHrNGmRTQBB92lDYa3dZDR8+/r3Z27KhbA7+pCIVVDfiw3SrUEeu2jhFm1/J6ACpBi5WC9FGaAsF7qDdrdDqP7K2BrTWmOyXZzD+zKMeqXS8xa95nBjW2fShp+D3AblCXnKRNHWokK/ffTQTETZiTBnkFICbSKBFeMk9EI1KiKtp+aRYf7EIBrq1gQEFohBhY3KZOwqlPSRkkyUXUCBGhLRxjfynDxl4i6EtGP7VqozHN4irK3ilQHygweIS2zLhfeJc8fb0/OUAYhoe3D0sXhabrct6bPeNdUDfaDVQVW2NLjy0i/cCXWawUtRnoCnqKBry/gWOdhpBIc1eugdTIuDi0EzI2zGs8ljcPEZopqn4ymKi23yaBf1jQtOyW6acvltzsmjE1I2PtOVthlIVK+UWx1Bcgh/I++hKRi5WA/9capGrdjh/toqygutCzy4lC4VQpuRlKXsE/NeOtee/epxe6z+OpnPF+WyQceuzWdTVi/63NRLUe5aifhKyw8JgVH44zgfTemjn7Exvtz36vpDfPKF1UJU3DaptDnbjpBCEdQ+KONN0P58HNyUFWf2e1Dp8GNZUk4oeT+dCRECR5OGd5Py0eTeENPRmbyQB4OS96dZqdVdfG/1caUunRZb7+IGQ65WPS0vi3pbSh3m49ax7YNNzEyjAw2LQrLFRsrKUnEei6TZU/LAFu4EnapVYVWlO6hphmJ5PoOXcwj3A/v6hLQZYFMV9MIAmypPyMviUZeoui/DFIRQYOD1IMV8daBRl8UCgCmPpX4qOzzPy8vidDTq1WF2fn+2eehmsVQ0vmMfKGU4SRZs/7OKvrAuGyr6ytPxyIY+NpEJET0hvW2cUGS3rD80r//APRqw9MlnVBrz4e2F3cAOKD7dYKGFmUVcC4ZkevgOq9aqrsPB24fsYpt9RTKK/zdN/bBCb2x+SP5E2kGk7SLDkIoFei6eopclWBa5y7qtUHP2dy/kv2n3goAn53wr1CeM+RZ775vk3dZJFhx5ZKgOJ6X7cggUMBd2oSADHQRpPZMIFQ3wiSYseIWKxtP1MparakWeFvWSr3PrA9uBCfotaCceWigobbXFMw/XNPaZe5dM/wKPcrekdarU51Y62W+SBYIMALy5vBRCJ8KODB1J9GgeannWXpZqvyqqBSazKg4+CA9xGnKADx3aDMPB1783c+dxegTgXNrNpDh/CGrH44t43l6Wwou7erNZzgZWH3O4s5opUx6EcteUL2toQ3YIFErOhQWGtiEn8J2UJ+xlCtvWthpyLgMOoiQZFJN5exilbAGcfZMiaexoq7vQtA95jCfsZarbtMuqzU4LTKDJDa/LmSXtCQ2wWBgApAzWDIroIVR62l6WqaiFClldVYfAE1LyJPGU+llVy6qZmTAKULEEdOBmk2fuway0G99getOu79hLzNPvJubimaUSdijlj+T9MMW7eCufaEdJIZng2GZc9fP8qdk8SFrBiZBWxNJOOJ4/eiYfmWS24P3PnU0AO9BptGAHiinT2o7SD1V4I6GJYmJOukITQLbIe2ODkereZkP6k2/5wJHltr/MUUDI3UodCwuUoHUXVt82/PK9PzY6N7uNueO6fF50im4YFVcwZiVjoVUQdWb3BZmwPC8yHU/iy0y7GtOFBYeM2SLnUpl7DJL30wH2TCSBWy2NOlS8AEaqPHUvyyPvd6bl8vVXplLaFoiEG1Kj4Rhj2wOQnVjqv+d2zUmoFYhL4xl8WR5329ftyA+/si4CuLw4+42VjJBUMI85FLasRd4/57onYflYt4JzO0g4Ksic2si+mHF6IHKGUdEcOT15T4/S1Eu+OMzOtYXk0ePe+yx0yseFvLE5z0ZCsZlkEluOe4Cev6ep2PSkd2B1LI0owL4AxRJvSaTfJ0rYQZZlohIAW+LcsI7QNexwZhDHaPsA+whkQCNf/4FmhdyGlDo2iifWwRwHCbrn6WnUnLvQndPGd4/3wYo6pQPkoWlfakUJvs7kkvo2d/Y9SU9HpqcKZktMC/LqieFSyXvWQFBq+Y3JFsGgFAVrS8tgMsGLCwaT8gw9jX9yZ58gA1uPyds/rBtsTGpVAK/J94stEpUnwkYyi5QMxUdGSnqGnqY8LdoZV8MUKFx1SpqpsruLlsEJvpZWncdSuwgIdeUZeDpOOjU7p3Ftk70QCEUPx58FO6Qt3m6tQGi/Fa6Hp95pyjXaT8FQ2o4mfYN52UPdLPtTTpYXrRe+hx+eAcXCzhkrrRWq3bhe8tQ7FDoBnSI35pByT8VaRVaD8EkkDCXCo064lAuuOmTT0tY00xcc4T9yTwcWstbNdq0ktuqNqOvHgHDPtNMpJXQ9H2uZXJd1B+DCwyyahZU17f1yGksrl5kqIAklK8+u06lW7Syuneq204v+TsMs/i6QLRGQHI72HJrDsTG6NUaPdl7McYVyNSDanH1XtFmQOrAcitCrYVtMa0veq0C4BLlk9E0f1JymbMWE5RgkoGomNOJtxRjqlnHF6Pl0WoEOsm1JR3vGLLK0SgJdVQ91ZsjB93L9A6l/DLq5CP5Lg1kuA008xY4sVK2nw5q4S34yb+abp+fiU4f7o1TvY1GxLkP4AsVaaOFh5JWFuzAIQJ5lp7NIpXtZv9XsCytLUyCavv59xRrxUiyKRGF2uwAy1O5EluAZdzpL/eT9Xb0EzrhGIyTA/Uspl6tru7ROGlcqQVYE0sHhs4J0sPKUO52ZVoGswjOf9xd99aYpcUwJg1eeDcPsRgITMREXWDGkzPPttEYR3N+7MOGy0X6T8NwA/fFmtVmEpgZRUOPQZi9ZcKbM2Ysn2mlt8n0ZY8BYlkGkcYrG9Jz+JmqkA0BjIYvAeeXh+QGfl3fQedzNMlyv3Cm+hoI3vUOOmmsZvpFJ4H23mELg9yrPvCO/mI62W0andJUb+8PzHpM15+yK3tbq0J64WEl8Y8u+EwityrPvNBme7Z1bqy5wWT83PReUgkVKzppvtbTyRVgLYUaSjJs1yDlsM4pMT21wWbjvs7WjMsuRIXNTMQzY1NIaRm25tcJnyTz7zkSjbshiJ9stbH5XHYfizd3P9N3KHbHbNBVkQ60MTXDQwzZErQ3pPry4N+rZx4ZTInxCf+MBIWJX93Y0EviISsKEgwmeedqdiaIOWOPrx226m7jNj95ku81vju20AOJsExTDraJ2r0oo4YhhXeKti7cXBfH8dL3ens2B9d+AEbgsN88smyW88kQJw27kYuFaDrlY5gl4Jk66V2Wvz7uC26/t7hmqdm5rB4b8CMqtqAisJeZxJm4UZVucZzZ90QWPiOL+FTzObGdgmIBx9dl37oPFiyTNlUccQ0PXOoc9mbfHdJsXmczvuQ47NBnK227K5nMR1tzL00ToVbnNxYHvAsJ65ol3BsuXtyTU0AOmsF3jJVHxX64qSBVB2p7LXShAzGY+pge9X0ZWyaNCHT4svsemNcrrPbU4w6vCqt2dldxu7VAs7ytMcYVMZySgAKxURzBv5lPybjgd+cXSTp68vwNgr6Ag5/keSZCEWE1TQYM2USIBGd7HM/HIHL23oowVwqjaPaX3fU++ripA1TknMyirF0GroqJmLm2PAGg18+w7Qw+ytaTFYrl55d6SXvS3nmZufXxg/XckLbq2IvLCRoTM0/BMmuU7IjO2etja1pkPJ9DVZNEtGaqhBflXaBSFvwuIeJkn4pk0H+1SxK3WjA8D7F8orZmWHqwrHZG0M94qE4Xm7fayeO+rkmS3fWX7EGE0zWg4QVskKBcicGE0i1eG2r+cRnj+HVWvqjf59/BGxxDa4boh4YAzEQqGkZYVwBDAQxD0DLZ4v5u1slt2hmHXrwRgnzk8XTWvniAs9Uk+p0RLy9CUSA3nc/IeOMtU25K+KmbIzv3AIGBWYiAtsP7Jk8QlRGpQMckOncL7nDF0yjwXz+jI69u986q9OyEy6YnvTF//MTsgwppoYVJphRGDyjf8ibwb1qp3gSB2sOwYZr31kDwEKzcHGeKpko4rkS4zB0zPuzMaDaatcRMdwBJ6SM2Qi0/OGj7ZRp+2Qp9AYq74Fg3G1eEFvWYkra2xiUWotELI8nQ8o3Pf6pk01WeIfzm0NWWbFmKD04vpqi1Q+B2SEBFacVZ/IZQro/2VeS4e/ihqp5VPn2ozX1sVd/+6nvDjDpsFId1jutdYoyt3DSKpgQEvHcZ0sJf25DxjwCje7Vg6cXkx9YGwT30o79GpEcS7LZIi2HuCc/KEPWOUMjsu+2OFPu4W1Z6S6o4ad75c+lVVYv8gE4iwUO0ZBdMOLsE8b8/kxiOC7n8unTT0lGpiq4NgsQroY2ygADogL/Tt8H4PysWFMkynklxEyh/Ke/E8T7rlb583qy1u4x66Qyf8j32mvz6wrTtNpc9kk8UQLJJfnnPk+WikOzUACxVyrIKA1i5VYaxefeCWwwUHbxMfW8iHaz424+2Jzdass+Ci54p+cC1XqfQN4MU45uwvP9MSqlbzBQ+hahOYlHuTkpFL7N+U8+oXCwffGu0xgEoNX//rs6M4HbMyi63EhJVIWmgxKEZYBHe0INh5Bl8+Srt17zZ/vCgtk2inyaGy4UXFKt+SyBvGIlKdOhJwrXCanqyXj/JOCMWvZTvlfAy0yt3+WGplSldiDR+LktaxJP4BSE7mKXuUm+e7Gv43n4pPn3aVoNJWCeotVAw9krQvBSV9mRFn+SFxbT6lxFuiOxoj773gx7616h0bIQ8rSWYCnFYpaeM8J2uerEdGZLsjnfcbkDe3aFX4IHTFXv8rXvfRTTE/LCXBSwHFUXUYzW+/jWrN8gWz76fy+pjd+xtza/fnMrDXkD6MMBSwTOEQjoEvrnPJeRylequR8Jb+F7+iENec/AijsBd13YgVkIRktXuPgiNRPizvkNEA3V1VfW3HbDvvCPsw0UI8SKampySkHbbXEuomIHX0rLw8NnHPIaN1CJ1qhj9XS2br8Nvxn0ijEFt9/ioDpwTNj0wC+ho+sbw1R5ud/OyUKcH7CKEk/h5CSFDY1DwwD5aI6Ph4Mh6F8w7o0IHDuQgScbYYdbU422/F83ILZJsJ99mqbYYqeYC4Ms/Jy5O8r5nIQxNp1QEP3pF/CHSLA1tUw+u7ONnxJLw8HXUY9S6OY+Xjt+ExP3cPlX/iF74m91zODyxZpERY2CFoecChpiEyC8/Ey1O0j7qLXLMAIAodC0sRoBf58B5v7DuIfiV0YNJUqn84h/ZsvBzUKOuHMDl6KpZPfsvYDmwcIFt7ffi6b12eRFhnanJpTJDwB1KtEV1G4YfIrXRCD8uap3afoV01GwQjaqEetFTgoOQ5Ui7Pwcux5Nw1PFBVrVt05K7asAa+9OWZsveQ2nAiKEIBgaDDswA2w/tiFbd7FgBPuSzX3zAK6AGN28CphuSkP1Xzwy2FLPSoeFIrLL3BpDbzRDwqYv0aE/dZxtxOmFdUbXUsL/pDf3c9sZARc74naaxnnodHb1G7hOKOgqOD9t7Uzy1shy7Wx6IJa6onJhEQ16ltnYZ09xAiPekuz0zkdQktuJj5ogHZAcrj79HatnhrIRHPBBRiKso+89v1pDuypns2Tkvy/YaFygIKMSi2bH/5FNO05mDBmcbyau843DFA6PYUvFxHPg122c2bavmZyvD5/GdoIjYDu7/pFJB0LOJhYBHdX6gNCGTW4AW2BM5YJHBmnoSXk5tLdhJzty9ZsErHXgNpXixwzcN2SWAD23YO2cVtZ0/My02UdHGzmn9GKwVEtz6SNOUodQAHZuJEQIUDvJ8HrxIW1WeeiZebJN2D1fabBOP5A0C2LTSNUjvKU+sDrtiokSBDD8X1sMAZyL+Z5+LlJo13j6zVsNnTY8GaHX6SK3HjVo4lXlKLAA8uJHHGX8l7ZaO6JMfjHqj63XwKTLfoab7/2vDF2a0dzAHqrwp3m5FreRZebkw3nL2qHut5OXhbf/pk0eq+0URZ1AQ7Huf1c0jAIxNQjBYkE7q33ATw7DsqtUyykw37SLm1ntTglx9e/zEXp47KCG4QcvMq3CXBE/IMvJwuuV+xBfSoW18ttmxUt8faNpq49N5abBUL+UyiJSlSKC9lnoiX06s2uwmf65eixlxZvJULFfH39jkAjxz2xiOJT4C9Vpnj5GWjURx3Z7Whh/wVNVO1KpaP5TAdDcZfSsq5yO+hMphX9bfh+Oru/HhyO7wZg3dL/+fH8mg8fylXRyev/2ddDmb/14b+rXr8jaIfZAJGwzdTBhXTv3BLZyAc02P+Wok3L0lbQOx8XjdfB8dkG3nlGiSpQbOB6sngAhXocDy9nJBtr/95StZd3XD4OropVkez8uikwB9qiEotaI+xnZPAzCvlz5U6e+LI928xPKGAedbUyxld1cwZs4EOwUONG3RVrKt6iRnpeIoPdnlDUXZWHd18bV5/Xb7+Wq6GlD2Ev48jDIbwgwjzjplHtWLihRUhGzSn0zvdLOoNUu8s8Rad0J+9GRzXjxQ1hhfjwYfxxeTubnJ0en/0YTz9TQdpkigLGpqxww7uSucPlzlD05FpD/Lrc/nz4APdX0p8ig1lxGzoTd10P3rdQAVpeDK5ur4ajv80HP9lQ/6DsmzG8AUt0bm0WY7rY8fVgyVeUHXcNNWsWNaDs83rr7Pm6zCL2ZLbYlYNTiio1uvh+P7Pk+mU6plrKhAxTnkiG0wuHJviVyd6SMfOIxvijtzOqCvUxPgLMJKpUmEzTopv9IGGp9PJ/Z/pSwxPyuW6ocTcRMKdETVcNb+p3P+48umOZXPSn3cJ4bQnysSpauTfflcWrIJVUHU5Gd9/GJ/Tgz8f3lSPRTOrSjpc4QugzRaFQcoIl46KR0ZAz8R9gWm9+WVwSW+C/t1DZQaF9Tn0wkCPKBs6mdvX//wBz/r8Yvj6X+YlnrT75TzGhpTQzWQtnKBoGD6Ho+JlI5V7TeazYkkhcTm4cBefHhScBqX+5BIf2dPMAYiYlXMUfPRwpuchk7SCXmroerAgaRBJhFetvVPORpF7LFfFoqbz+VLTN0/dg6aPQldlXj2Rv786p4vx2zxxHD4wMBaFneNI23XS2tQh0N5sEKbW4HAul+UaZwbL7H8rG7L18vrq9mhydXQ8nl5P7i9Ed5xoyf1F4VqC3Z/27libDuo+odNarrm51FR8kyIfH+buQ1RPc3aANxfj86vJ0S39z7uj08lV8BRzzNak2CWM2vlied+s804A/ZQH7Zf16/9ekfvoDvK2LjfNavjufnx1dDG5Pbo5v/qtkVVwyIqnEaFEEZzuTHuHbJK4h5UAlqcqBx8oyFJgGpqMLTyr6YOWDZ0afdfh+PzPONKb6fWHydVvvHYRxaeAmRYGroKJPjcXtPfWJvW6R2fw1O8KANLX+IT2TV4VLzWZTOnAxWRwfDG+Ojm6nNxe0XGGTlQIHVbGOFji83XzbtuoTp8Xig7kCKrV4E+bqgH7XqnBdFNapQJOmCioVI90zU/GF/fn08nR5O7oBH8xnI6H0+fX/7N0321ICbfwOLVE0Lc+zLt0Y7w8LUcTLGjYNIitJh1QMjIvv9CDGIwXVVPMBycUxx7o1V5PL34IfaVUcKiHl3JlZtQa0z3Kk3rxieP6MX0MPMpMt3efLtxmjengekUZcL0ovtXD0+uLyfDNif3tZ3IU9WL9+isD28TbHlZ159tuvJPPTZ60eRJdm4tyUdpZms09ynVFRr7hxhW5r2OKNXf0PwGCWUOjgqoiwX/qXHJVjNxyvL8sGiVdYXZRP6E+nM8BRodyzPrrUJveSfknNKeirBx+GF99EGJfFPafhxUFM8f7I6NU4l4X5f5o3R1DQRn5nnUAPoU9GOtGSfC7GKadhEA2SMccv49MyEb7t4VC7+uv82o+zCKfDcwQXzZfyL+vhreT0/EVPSvc9sULzudojHUadE4jI5xTyosIgnU8317ntSMmrtjsiFzfC3lAnBElIXQNo7xN8uvV6vVXvsZ3BeDf5MTvrqcnoQeltWQSzwiC6ul8dTJvEgiNvYkOULy2HsMTT7pocrrhr8Nh7XJ8P6XYdnv05vqWsqfg9UmlTNLCIkLVBxvmnHMUm7iTmacDe/0bRm5AbiGNjUYHfc/rX8cU67q8NlWCNZCXCYtMJHCCjiMIazLnBN9SmQ+2EL0tDMBWwzRl10wHd1EtitXweHx7d07X55quXLNCPBLqVOwQysJyG8iOHCuQfjtvaflN+XO1XqNlCeDKZoEL0PsQx81XMuB0fHlJL2rCQf/ih+DL0lEodHLGJtSqnLE5HmAWJbFy/fCPmB4VC9Rc6/JTueFymerDk+cSgHBcneNiTQ6RETbvxicn59f9jyPk+oklLwiaBZnjAcKQjkg6pn/DvKYnfrr5QqVxZg+GKoDV6ivdivHV2W9LLIx0dx1vShoF5t4fp6prfJy8/voIGhBFBYpJvwxjn/BvqBDawPXc8EF+OJ9Mf6SCtLu0kVSMqVjSdeTAlHsPnGbdRHK8maH/8naDQJnkLgXb8AnRfSo22F1GPs4+8JtzLkzPfhiebb5SdkllYyoEbtvCDIUouOLcu+KOZPuWXMx8TaXQbdXUKIGsj4ENX8jl0NW9nZxfXPzmHHAULIfSSFIPYJece5escj93Gy9h1IxT/FXxFSWRDw8bOMSSytg5FY4U2e1Huri+p7A1vhi6UEqhL3ReLZo3tEkIlXvufbFGCmi/0eZpQw/nrKm+4BOltmZ36Re9nAc45zcXk/vpD/zKx/fHU5TRfY+XCpcn4YcVgoXyC/f+14wi1bs8BTcyKUTC4/yeUlHIRpWWVufynDT3jtj03vvFhpP0hvJgciVp1A8Jc+uk0f8enrwdXx6//nX6w15yrBPB/WQMUQ1dH8VPzPtmykddw+eq/ALXxzKLdDq/oMG5U4lRdXhyfT+9mtx/mIRLiDj8bZQktgjUkx55p5z3kLwfqsd13SCW34HwNqdqw1/nN2Uzq5b0Ybi9CQLgy2p4Nh3f3v7WM6RXJ/Vnwvw3wKD0yPtsirrOF1Al8Zni+eDi9dcXKmfKBmnQ1GZkPEDkbufw5GIypar/7ugNuaXx1elwfE938kvZIHvOpDNELZ2GxxoJzHHeOsZ+s06LoHmCICR9I6s1GQ+aerO2neCvVIid395Nf2ODOkqMEPjFqW/C55l4w3I/9b0oyfWcFVx26fhA6nN/cYEqf78IFHKQRBTxgFKGdhzBLI7jrjo9wb1aoo+3Rv2UJeE0+gOdVLj1Ea4rVCYVO0CwaUcRJFPSvC12TuZYU8aNmTfzYoETU/bZWQ4qkv0S1fv76eSSvszR8fnJ6/9DJxjMWAXDcsZOyPfIue84iUc+xJVLTBMo76hBzOEb/dYWQTxRWOFIr/lK7zaghfrYSv+HZlJofWrHE8ziNIrVlnu8rTePX327ClVHg1YRUmgOYpOj8dUVBfpTCvToPdZUjlCwj8KTDM0yRcGtpGyF8VboTmr7GH0MuqpFW6a7hNn1935AphGsIKIk7Gbs6jkJs6EdKxAir6ZLDb9h5fCb+YZ+0WT93/9wfT6ltGv3xSRGCTmgJaCEOhUUP7UjAdKv91aiXjeL4hs91oZSiMVDsZmv4ViCr4Ye7844QKqjGEETVIHIYYdztrEycbrr3dAUH+bZP996hhh14HKy4FpwL4WBKXFrimrbvONmWWz4j14hqaA7JQbv1/904Tq+COQ34zO6sMH6HEWeGJjCyE8OTJF3uBRKXTS/JP/PjSWMt4auY8nD9H4j535M6df46JjywFueDQq9aP43B68yl5/BSSpcS+TdL5kY9Wc6C3iLgd2SvUYG/Uc3BxNLAQ4JpSMuRN4Z68TLGb0pHjCznNYP8GUoAlPrdMgsSmabao0ah8LTZHp7dHs/PaJE/yrshxNhCJeKPMYMNnk/rNtlSFPontCFokLnqSRXrKM/LLnQYn2R8vPzDlm3TFPK6bF1DiTcoYl8fYwUek5u6IJLHmmUIRiRaAkWZfj6mNYIP5Y8K+tPn1AI8mV+QeMpjwYv3g9cVk25qOlK4aQmnKjigp+Np5Or8GHFSbiRoFhQKJjPw0lH3kmbyDcSTrBasVo+DW6pziC7VFdn2FbhanC/rNoe1AmVX6dBi4TRSmak9WdAbOnYO26Qpv00edNUaOhSDQamJ+7PI92Z1QD/7s+MSa3rVfmlJIf+z9etmeBLBTVf9qWxd+u5Sh1+9qz69InuDJz6+hkptNGhwuP8+m589xttE5JFi1YMKr4hb3WMQnoyeeLze/oyGE59KBtEWwzk1b+jesx5+BkUaYVrcIzCLGGyVrtKnoqOZ7tMvmBUA304+xLcwR/B1TYlxlRcQ94cdlyasp6wdTY+Cvgq7XiFZF3iNayBjAHw+HQzuGu4beScaRgD8vb8+opfLGWV0KxrOJlUWkgd8kjc3gTf5QiFGSZabQJ1jexgvbZ9mp/qOTkxiqd/cH8ttUiZUNMPcdrxCrOEqhHlYRYPkNqt7OROJx5TxM3zDRDs8Bhn0/Ozq+A8KhJijeJ9UhJsWzsyIVmSdBcKLDl8oYvCZgr0Kv4dQ3Wryh7kQLEfM960NHXwtD9tquXjV4z6kX7HaWdVvZk13Ji9vL94e31/O9lqZwkGJOLGNGA7tWMRkgG6m9W9mZMjwgIpBJyd0fnkpdwgWN+T+6THdXE5Pr+9DpaO8YGcPA7Kp3JO7hiEWcKOzQ18Vy8FYNRA2q5WJefkiRn8aVNUtmddzp+wKGkyvv9zuIF+aBQUKtQ4LXcEQjKFnlmLVAYwZLx5wBRoH+1wPh1fjn9rxyFKxesctoqjcRK3Vnkl/9u6gUI86FgoYiMZRjWe3kyu7qa/0USKrvI42gSdJKfCiXfh6Shr66rz5evfMJl6QJM0Ue29KrhfOyg2vwBqVaxXwIxQ1X8vje5loFxYBweje514v63SbkM0+g6vf0Pt0NSPzxtU/+Hbfn0/uRq+pf9ApOyIsj50L+i8dSZ5bSUtuoQKg06810aPtJuIPDm3fVljENwhCf+duD37BoKbwvgNeDeeZaptVlxRxLLO84vDiaStqc+v/x0tVZf4vbu+ndy83Z5NhNsVqZGUUaFQqxPvxDPTdZQmq5cSIrUF3ZzFZkUW74NBxvSxhjf0n+Lryt03ShEbirhitcCtyFA5lfLl9j6b8rvETyOgF39RrnA+9mbfvv4KiDz4vO5TvP717pwT8926Do8s3EcSHUHCjsD7bqrUPaSyJk+ADgWA1QWVf37aeWOLE1s8hZ6VEtrZxtZxoRQXzyr13jrPuo2bFhHLQMaSoXxZMvgLfPUtpTrrI0ajFxiTo73+Bg+dkqEAKBbakWH3k0ubdrk6SL3fzk1XiTu+4R2ERlFdpv/0U5dOaSSRxoAY1o5ymKUjpbcb/lXTItTyfwFZGG7vaN71FLq8iq1JvDWZ34DwnhHjLrqu0XMcmsEtnVfjzUHDGjGOrLmgcE9RNpidCTcojSWpB6zO0I5kSBblXsfg1PmtBnlQteAP5OaOBRpReNY/BNqAGZXeuTSTiYK1EM9kHMUwS6O0SzlsfTbwYmlD+gO4dPUSQ3TKOi6Kx6aufhme3l+9n0z/dC93BLJwoM+YdyNts9WObkhm5R1I2CrbzRHeF6XFfURtjnZTLsnq1799oTO8mZ5/GO8PF0ZauMZ2YUWQSIG0zJEOszQme7qlKzYO3BR/2TDSI/YQlMFZuaRSmzJUIE4urhiVviln9eMRlyAWuJ+MDiA9wnI4CT9w54LTJMqjvvKUh8LkLVroQ01FLdy9TVRvj8YXb66nIReYpULfL2VJriAEn4/JueE0TXYmwnfPr7/S++ZsVf3r99fOpMMXBTNp7ZiHWaqyTkGO+418OPN68+lT6Uec5yt0telJvZlObq/CzXw65PAdYQXhUK4Aso12nMMszWJPjqD/UTxVTxWC4wt9Dc8m+X0ggiDOLMkkNC5AKFp5P0z3N9mqdGwqUz5y91hlvuv4oWxmmyUnoyfk8nh0f3mOL7bV89eR1ObIJNh5zPYkrT2dvvH4C7ewyie+NCb+o2JUaimZoeQKj1t5H5yZDoN3w32WcvCGUorV2o/qOIQPmGVETvFsPD0Dboke+dHZNX2b22DfUQmVTm6feWijHJ65Up1Zbc73tpx9qZ4ot6iWfznCnqdh7gEyWxjls8kOXySVPJ9hjHlQEhEpjvJuWMdxVyIX1vP1OujR70+2LIQ0iM+Bp1HeB+s86gHKf6mQiS/Q/fJgoT+6JLUaUtJGLq28PzYjvx8MJUzxBMJYvXks1xRofYVwxukdBuFj1gtBP31yO7g4vxgL6HuhcBHlZbjBqbxbNpHfG3H3XD3Qi6GP9cnd6fhApfxhTD6JvPRvK5QlpEwiSRQBwKMz77nzUaeBfALEKyZG5UM5x7Dt97ptFUsLawBB05l323nSUXGOy/mssO1CBNRYBQhRx5OpHxIFD07Cslt+ltTb94zENNdxbzc1QzwG0Af8iomMv0ygBxWPSD94+HhHnprcY58mlYSdol+fE5oKgXmgPfVQdYnpD1+ArXXnXmPLcD7iR/+uXpUvz0xlI0OOMfUMdqJyciES6kySjUOE97RDFUW+EzWleuaRCdjkp4vHYfIHAqqUPaEQZpFPSHlrVGR2R1XFjBs8UfYHsbcwuwungjy787xChZFG2xeYYwg0uC2/YBoQew7L1wLokhtgGO7//J2xplR52mImtLSWn5L21vSm51bTsioAnFpT3sCjFR39xoQwWOXhCalge4krB08vVHHkD2i8aSqLebtk1iwwBW0JfNbgnRdfKbO4HF/Zwcn5Lc/Id02KpY6XZWoFFycgRHjSoYpb0ZITrEKd0RUG3ZkuRxJvsx3HHybhFmAkDQ1HUlcSFFTtKYcqTjqsyWU536xA0KdTKhnlQInC1n25fP3rhL8IJanCKEmaS4ykNUNcOHjioUrSzu+eVjPHEHOe1/n/fTTQ0en5j+R6BXRFLBxTZpOdUM2JY/LMQ5XQw+tYKhs8p5Pm9X/Sj2d9EPuY9Q0sDhfOhr7XyVsKUOMg/8EItCsrnCdiUjz1UKWpBzAc86z3GHkEQEpt33ZafF3UiBN1tXzkAcYV95d+OHiC5JblSz0SRVa0px+qVPWU8O2qN9/7MpnUN3j968nb8+k1FT77ZXpiBNhmxt2moHQc33HvmrFkt43lGBsNpkzZBWGuRy+y/dDqoWx4PvnP12KYqIU7l3nwJYKpoT33UCnTIeAmc8qkB5dl87hBd7YbfK2qXwafmte/UcqGYIvlffQCyImf3U9sQbY9SJX4WUZy3QC/a080JJuy0U7TcEy/acHUUoydTM+CI0sloUq1lANxa9cTDVWWdbt4JrNFiXsFIgD3xMw/Qc1SUl/ByH0FzyrE9s3UC7/Q+9pAceYIzPCujcrXejV8e83d3KAVRvgWOQMVQ4QsSOZqTydUdETtZWmJx5QkNxto7OzXPRcX4/tQWzmSfOPhJXnacwiVifyw9tKKnR3XmzVlxLb5NaEAv1oPpvT8l5R8XZ/fAvZ/NL0+DyYaQfiG1dyKgl1TTk49m1AZ1cHI39utLUjbv/p63b7w45pSD/oo4Dvfnk+DpyMwsHO+qaEsmdsYnkGoTN4Bsz+UMws/KL6hQ4hBqcVq3K6LZbVC8/9qfHXHtJWQN5YS9oPUDO2ZhCrPot6SYboMsGW8ebKI9fCg6oyqdfoLABcvxj8Ew7zE8LFdsRBCg7tinlAIwSsPv6NYXmwoZlp0qf7jOysm4tFRKGvmT+UcMF2+qIOK07tZ9TkaQdGX6cVvRtQLjjhn9kgQ/4Pg4EmFZJvf4gqIhk1eKdNZMzRDJ/+E80ulgWMeHv7za3cuOMMKyp0ZdrkADEObf3f3II0lCDePTjzpkK57p+U/npe/IJ+mFGRaevTyH43hSDNpd5uGU/DMQ9A1XEv8rniom8FpQYdnrSKP/QfC87RA3RBgjUzd8LTELM6Mu2gfKGss6RXekNf4xoMECRHwr387ZXU2QsU1WkKel5jFuuMl2oHgEi8RAi2LF8oQR3tc0hOKMX8Wh1BSRWvn7lI3xvMTs5hST0+cZyQz5A6WSyRqIZju+clbyrmv72+Pjq8FVQ0BjOpKbIHNpT1JMUuTTti/BXJcFA8M/qQC55/C7MqpCdoyoYiD1MQTE6mk7HLG9qy6TE391ull8BorUdMWYjHaMxIpQVKtL0dDiiOwDTF5/J1sGiHw9vWvTIemEIhi6eIaIPQQciJNY4FTpkSsIj8479mzSLfL3K2Y10mxemo4x45+ZxNApRLH1T4v79b1qCvZPO2XyZrVHPXkH4H4tu476CX5Jnv3bdqx8wXvJWdAG/OOEglX83YyPYZ2jXA+wnXOIkm8X1FSaTw3MTOqa0i8rZfAlFgWio14F72E8oDyUfAyO9qhwO4wnnaYmbxbnt6GNKq3avD6ksyTFSqLhPccCvomZ1fjkzCYRKJ12FgWaumlsMj74zzyOFbP7L/AIBsY7vh3fxYdSaKBKZ+M88K4tC6HvG5sRnSFOwshnFEYd4Rs7WR4U1fresMYaaxfWTH5SXrDAjwTb9h4kqEexT4enNE3fmI0C6h05Ilr6BH+C1JZovCCCTasILxgPNNQUw7UsW4gj7gqlhj1LOr1M8UDQeLgYnL1+n+TMz6m/0CLsjdMzSSCkuuOh8S7Yljk/LGO4eU6wep6i29Dfu6434bpfvfN9dUdVKkCPSGJF8p5TugTGT4y7Q1KOzq2Y7ldFE/FI3ih5o+vi8COCOvUo6tnPAFRx0Z7yYx5Qc9q/NCQD1bR78f35rGkrxuzBc4B60T7lYRUOXsOCX2CzIgwkovxmzfnd3fhDyKck6PZhC5OygISzpyU2zVueMDKfC8sCzgnY9D+dYKO/zZRLivIICgIwh15vqJOc68gCMmK6hGsyRqzuPx36icmtkYM6RYlMMA5ZZ2lfm22p4+9qxcvz6Xnjf4R7DEVSSEr44/hfTOFCLP1MRxTJU3+wEpHUKKzyjhCSW08NVHr1JfU7RJQnzG3CgjbKLL7O+HRQUBV7LOasIIq3KNnI2py3T6eUpb8AAAtNmeDwD+M2rznBu6Swly5HPxlU24oRbLf4+34//t/GZIzvb6VpuHh1oiVxAr1pg2/Qu++jeqg2Xb6srbyZQ7S5RANsi7L1fjHsZxCCz4iSyUdM8xnjKcqwrpWvBSBY/ABH4r+XXTT1r2ByB8Fr8/FbknMp+qduck6dNWJVTUuBwBe1CzxGgu9CPvF6ElOX/+6jRYXACmRFIMBczWesaix6MNJH31FbLEQEDo9bQ7F38nV+DvVaypBeKznDE0Z4Cw8cZFiT+zO7wIKDU9tJP6nBNeCjS3LGAtKKrMJznkbmxD3uAfVX+x9bnotoxbHCdSkpa0Fv4ZwTFYLO7SaHX7JkxBNnES7u+LtEPQvUPDptN8kNZSr08nR7Q9Xd2/lGk2HsTHuIoWWlOMieTKiYdThpVNqKFZeLyLt62l4/w2dvtPryf2H6cQ2cK/GVyc/bE+pUqmzFjHUU1CnM55+aCgZ7Fp+mwaQITfa/0pZn1AKHLHU2PDyT27jAg6c3phQzatYgv4DNmQ889B1L92MajNn/VA4QyYsZ23mfbqdeV+dATYU9I1iPa24tA9lJfCNnnFo0qxTNB2v1k01g9DPT3RQc0pM/kgFNp1Le9YA2DOeeWhU3CEOLvhqH9fLFRr+jjnzL8ssm0xUS0A08wRDo5LMpbj0OuiBeUGYaPRvb8OiKxSWAOGK0lMQMcnS2yNrgMb5voDtx8F2s1rTMQ4mFdYxDaeMPrv/M2jvN+ObkGSCNB/JJckoSLQYT0KkTN4rob2pm/Vmic4wCmgr7RN8+hecfu/jZqRM12rnhUR9cICeg2iy2GsTtNz7Sypt6QhT0zb3vsMRlQZYFk0UErbgj+G9tE7i1N+h1/81H9x+nX9Bz6414HeOGrUWN5UiWnhWodG60zfCwAVRqvlSWHnHoPja619Pxpc3Yci0IL2W8QK6ECYFfAjj+YSGUozW2TAJoWyLIP2HiYoZyxQJwQX4hLwrzuNu0+VNsVx9pdfM/Zh0S1PoFK1Oljii07u7vr/gmxJsmkmTqVRSgGNX7FmDZFC7Fbic24bVKXfKpjVI13Tt/0CmURZLFEJMDoynEOYjlcadjCALhVdzm5/mbR/khj6CXVbc84i34+nd9PpeyoO0pENkh+ghcmrMj9x4y6i0dp/LSmK9q2dkIPgs3GEEpRGrS5tfKJu4Ont3fyl+IgE0nYsb3/giOV8Mt5luF2fjjSVIHOhbTY/HJ293wDjS2o9EmjrFeF2eVphHIM2xHe8KgDkHp2RGvcJmhL3uEJ3N+dn9b55Oy+M5YRNwxpY5f5zHquMRT+tVAdhZb3WD6Vdg09dfXzYPnF/Ls5+gMqYltYTSU44OnlyYxz1sjMPGdbKz5o8M8UGCHUjEo6B/4qzVsw7zRHlN5cliwTv8eE0JpUHJIcmM8YfJNCwJoQREIYas4f6RPUXnvXM10gEy0Gl5xpuJhnHQg0/oex0dFrbRRhi3wrD8gGHKG5ZkHvzgch5U00vo5IYSoetxmFksuG9tkU7CihTjmYe5SpPeDLr5ChToc8MBnqcwW0hCLCOjC351fTbBNPEwvDpccmhRb5rzaU9CzJVuV3VArWq5pdbmxjFObhDtj3o9vJlMb87PqBATyYiZhGw29shCKTYfmXfemcr6ul/LdTH3iml/uNqBJUiOQpwlPsC8tanb/mdl9VA180w6DZ/eOdiRoYmRPrTrxoT5kSihPTsxzzLvy6+fLLW29VCJbtkCKIQAqaEycjW8u2ednHMqmhY1cNr0c4IVQGGFdQy4cea5iZTsxlnnt60229iJMcWdBkxIxep4cnX9+l/utvtTQirLDdlQnOVU1pMRc53kO3Lhdyj4Pg2j6A+XiTOJtEOC77EnJAIL0cUStBSfy6335dL98ayev9D/6eOmWX9jYsX9D5hAfJhcjH/Y6qynUmfdiK1ivsWelkjZwsgXr7wGx8pkpoEU4PZWUA6Su5rCBnk2wHvjPG3jFhwvb0ymmwMsh4p7b/vtZl6vUYg4Ze7pOdZH7J+Q0DpPOfcIapjwCXm3TNHBvaQ35IxnaEk/Dl7/OqtZMFxnv58dZYWD0mCzBcJBxnER9ShSnfDDBRXr0OKYFw9NuQKcijcDFZ/o7Q7G80/0oOoB1BFPisVLTU73qaa/PbirQV2kOHo/s7qqIyraz++G5+QE+pvsIgk3yK1VR0Iki7IOVE4XYw52B97RsqZktvj2rQRE9qRePtb00gpenAn9A064EWa/FMtieDOlAopSW1zeKAlYkxkJtaz46uTOmqTlu4znZMRjPYDXWVasqV9bVzfAymn6Pw4iFCaUDtGTrwaX5bdv9fDuangHD1wPEzNSoe9iOwih8p1zRMcxJEtUxxq94J0jY2DtK/tVLPS+WNeDLB2OV4/lckUOsOTLC99M3yQ1I3oW+wZA0C0K5jSczDt+IRmQezTgZFbTTa0Hb2pQIpqab8pVPYfeqUqGx0AxP9Ev3t5QrB7cvpTfqgKuPw79+VNxiyqIoMbxCfUojb1YwATXlDViKgCS+ddnrKow/lZg3WKSDs+KL3Rz14VtoCBoAoFCVii6YHtWWDZhHpx2A7BtHJuQrMiTdqx0Mi82uIJ8wLifbAh0UpZMhaDvQX+DPuIN8lIqT49/pG8z/1bQP5rk2A8fPIwoHHmgrmcck1CPVE8MzRFA0IB9eqI/I8zAo6Q6gs4HX+OkXDw0xfZlpL+9//PGtrMEYoNx1EH6+bxDzJ0vmGxK+cBTsShWj8/2RN7UDZ4nXYcCIv/0Z35zjTbcvAKXOwo+BS219Xhs7MiCegTIrCcSbZY12sPwV+4hnNHvPRRzOgg6ult6ItCrgQtZgXFZgFbuN8P9ZVPNC0rdR5kJfQwtNSQSNsf7UKrjej60XlYz/Ny3b/gO9Ns1XOpj1fBhRFGKac1z+VAuK7gJrOoi03YOJwt6ULujMYT/QqaWeQ9KdVoHvnrCxTjBut+nOXsEnA0586bEA96syKNF5CHpvmAvF/5BsrMentySh1uVy2/F0AinZYy09obDv6MM6ijqbZeZQKD6gV34LeVD9JDd+70tHuniDJIM7AOqlOh3+xElaIC2w9bQeApvxREGdZS0nuOmeKzwx7ypHh8r+14/VC/00/WA/l/uyG9tME4nz3VLUQWZdgXJlWA8y5W0+J774o4eSL+OUY+N9owFqQdnFRYaO38BaApWAy4HmEwNwE2ZVo9z7kNQ+LjGycC3UCURvBSZhekIeFvjGIE6UnFHFbhlBac1PYfBTTGrvzhb5kyzoAyphtO6LRsq7UtyoRd35MaxBnlIXyIYTW1BGjwJXAXHAIQRHjl6Vm1W5cuL7SfSH9ydBR0E2BX1F+s6zjD0odv4ZmJJF+TD0jR8GYBpD6fHwLQbx/XTUZZ2nuvNZo4fOiuX9ZdyVfXyi9vHwmq5TTcPuKODKE6Ht3ga9JiqOT/T2zHdkXnZ0MczKd2S0HdRkjdP+Y44dwoocutB6LuX0A3HhNK7tBn/8LR82Tw8zHFsUYQsbPlo3+qP9NdrCKlRKDZGuK5cBgdpWojyjtOnI9P2MN+UM86oakqZ+XjImC/lAJbCoVNKSnVyVa4rqr3DwczJAEtJluPs6XgUeTHW25KKDsqxKuRO9jfppS75AMinFRW8RM14P55s4xxMHP51K8QYeqCM13IkPR33Z7F0FTm1wlTfZjWVjR7V07KmlGagIzr0L3Aiw6vx0CYhQ6oHkjzkIe18UdgkYBxBT8cpjqxVNqVT5RR3Kw8f/8fZf5DbLqiQHqCpcEJetAE6vRqOr7CNibIdjBnTUIZnYokHy1A+R9EjO3qBwzorehxNWT5CwbcpEOTJILhQyvOyIYIFaMvwU5PhHeUgi5q+bfj+2TQvuCQIT8Gx83RMLsJLQm++VfXggj4G5ZhtdL8hb/hYkeO4X2ACQl8D+0wpui6/beikhm/O3X8rh1hpF/JWwJiHl4EBkW8cOY9MybSvWzdL2NI9BbofJX57lJG3fino40C6k8LtDZJvuhO4uORWAEtLQrlFbumsIe4mnobj5ek4620YPm9K3su28rFjK+mlMEf+Ev8geYFA1ht8oJH4QJH1OkqejrXycyC7oLwenNIjmJU9Kz5U2FGPeS3SX4cOovi+cz2DD5UXbgQFTvBMHAMPQNMeMGBu51/buc3eLYXXOGu4HoGM8t14eEdBBF5Dp6M4dDlyO6sLVUNsjPOTSYQ/qi0MH3DxB2+q2tYBLv3lghzpFfmLsuHbQ3VyQy4kJ+fVrKgoItd9NTzZLEsK77EQ1hgcHGzN87NxLhRkPS+/V6AA7WU4KFgH6NiSR6kRVMZ3wFVUQ/oTKMFdCByMhG+Fc5xJGnW1EA+SBscb8s81ZQvNEwL5Qz1INLcqQKYnt307dEdDOVL4z5unYkuH36ZzmUnW43+gO4A/5qpXFdo/Ou8Zo09/XD4+AsOOakiRrY+4MJCHoHz9Ef3+JFybAouVBfu0jMVyRDmdmDhLvPIglg5A4K/CLXPvgy4pVWb1gCr6afW04WJxPB2OG+4RqFhwmYkEdeR+kiPA0a/rTn3oesbBY/c2dmGUIs8NBW3Kpeqfit8WSFOrayVNHR3lTSd5u5RnTOVmjT/wm2JJV8J3S3qVGRVwZMasWnC7BOmv8yy2mVWv6BNhDWgilMmMZwzSHOA+He1Np2A497fjLGfQS6DalD6NrTvWQK3yo7wE2rFGTXZMVSw9npuz4U3ZbFBSZ1GobWAxD+EcI0WO4dhuOk2SbrKPj4OC58nH9llZ9c5rYPLhTb3gGwpzWjXN3YwjFFaySFpyAn0J4xhvZE/WSf80OBH6pYuaJarcd6koNSbf9YLmEV2oiI6Wu3Er/N16/qlASN76QJTSBvsqwjyW+yqO/QaD/CKRE7QL6EdOS3rRpfNgqwW+DH3DC3bzW5k35avhdGMULgfgNx2XDb9r3O9e23Riu6l2XOMrwNWf1rzO52pD5dLw8ppuy6xEUy0SmmoZc39DnoMvhml/3yOUQGCpuW9Q+Mt5x38LXvamJkeBq3m2odNCnb7GsoetOzoSsnAdSW+FG6+Ox0ammLhdJbHEOIe+xg0+88JVqgUVZpSARFRI2k4FpVtoRRcraDkMTSSkOqkk0w6Xnjvamk7TZGS6N1Kgyfdj5Xxor51Fvo9uarHa6a0G6y+TSjiFhH/aedBUKw/npZQFOjRcfOGHL0sgVgeUKqA0WFI1MDy7Hp5ReYCeZiLUfWijReHJiOJGqP1dNTJ+swiiF31odBx6qUQdrEFStDexCXaNcS2Fsy8ojGMzykIuyoJXgjEshS3OVao4jboNphw3kVzV8N4feCzwBcohyLZ9k4gu6xo+bPsOBP0kuJRRWMEyhxHOT6pE+1ER6n2EDgreFYZY2x13foaU3cUUT5FhUVGMMgTztJIj7WSB5hLEucNp1YhlyUIsAngmR0vTKs38gpqTco5fr5raNvYow7NlBn0SzVh54KOqIcDe9C2W9OMmG5lgt4TRV8FV43wrnU9U+HX745z5b7WtEDFP60WJG0nfIXOKjeSRtpvM4VEMd+6CSL0YBjjniKUmTmX6/KnGSgm6gGv3Iu0l+FTNeBKAfxBTIfRXt1uHWcg721WIQdQkG2C8AalXov1Q0Q83VPWhI7hyV7LGY6EHeMY9VDyHD+PhB9sMjqNwq8pYBmuoJcMnn/ufVl7u47JY2m5p5xXOqs0cGX2x2JBfRlpFb8fdPFu30/FPT/b+FuVscahRoFIJYAZYSe6oZajCkq5VwcnMJQpRFy7oOa7pltw+gnRGt3SgyFGgt1UMzyb+r6IsSkMmZLkksg0Uae44Y2SC7sbL4+Wy7Z71a0HUyeVAGbiFl5K/xTk9VLSKKM9OQzfCMmiD6w7hICLvMelGtzUgbzraamL6HkGF9iYP5DLO8KkkOXF/gSVaQiksLNZTbIB3k7npaKHT8gGFBaaRa38l5wBAwjlaF015FTBO9CGKOaJpDRuml4xdGFJ2FIemMGBZ5+F+De6o44PpLEk9iXe8KGflTgp3SpYUkLkiR6kwDqW7iipsK2SE/cOIC0DxLjjvmKm0k/gcP5RzbhHSj7iSY1Us7cyDnAP8FX2EFXfOdoaSQoIvtM6Q4OeO5gUTPJHqfMV9fNx+zP7KYBqboW1KaR5Hi+30NZg7YTdfuPxmR+X4XGQGYMQ2jVx94QxtK5E7q+A3B4mdVXP9xdncRUke+4l8ytReZsoo9CgNJVEOWhVaU8BH4jwm1AjaI7mlM6Y7R09k46L4vHQvFFhBiKVQckkJCKtOdW2b8RKNPTqq4cmd7SzTX+YqfFcswTvkNxJ2Xc6fYsamd4dkW7a5mG6r5cULfS0qEeuGhyPzL/XwcjK8LFcQexySE1chJ5JxsRHUHsEbduwtnZm4Xfa7sQOh3eFl8B3nVLAjAhe9ByxU7lpCF+gEhjhnyjDUdm8Uxjy7qd9WmL/FRJUdyvC0+lLhCm9/lPBjZsGakOQgFjbljsRFtiRJW4EtZ9Ws3uk0Dj5UVIThCuWKrzGnpbW9Mqjem7JiNUu02nhwJmUfFnweAoIg/XGcLRik2xtzVay5ILCe9uCcxuFBigYp0OB2M6ePiEpxp2gMWsaNjqBONh5Z7P1unvrXTg97tXn929YUq+v8ndJXo1RhBkAv2QWYCBziTo86ONYzkbT/L+Gr7F1wnketVj/lAYdxETYiI01EPUH+h0xFZ3a+DnWMwzPoRJLpt7fJ+WXyYt3uFgzpcZXn32mMYmHgLU/8quH76fCkqdfw48ZAZjLw2q0qfihKsSm6NSXra5HYP2LhcTwuZcDqPyQtpUVz1CgzofcwRzd1u02rQwmcXfMnkMxzx+PSOkl7a3lnjLfa7qRTfcOyAOSZ6Q991yCh3hoC58HbovhUBKms3HG0sDQx60vrPfJY0S1mc30fcoTo19LfReoKOxLkUE/oOjGlbVUMldBpyHNxqIH342hZZEbadeXGzbpcVKuq6KXX/YkC5bAT6CfMh+Pr4ZgHMFEE3dLAIVgynbDVPHdcLPp545/vJd2/J4wbfQy6KWp2+RA2rgeU3VzQ/321wZS1D0cIpixWLzjIb+Q/vXO0lLbpUes9GtQHd/XqsXcX29l4r19KKRSlDc7ZAjZSbYPcwvPXXFKh5DzOMbLIotQPuiwwvddf4i463Y8KCSaZYXj8SQ7EKkHM0ZCgyinsLFz7XKAy546ERb+fd9Dr07KeWSyTm8Be2O4Hy3AXA0q7EAMw5hmOP2B5JxCC5NKlQxH3qGJ3UO6oV9qMItWbLaHeQ97sMSqu6qM8xMYYYEUacu/uPE7KJd2fflYfSuFS/hohH8E9EEe6IlOMauW1KLQv6sFk9VKvfCvMd6wpAVp5N861z3Y2G6wrYmmzPQRVcsew0iZpaffOSTjU404fkFtBNjvcShSjOOQitd22Gyq8+cedizRpnPdOAt24rVT6dFPTFyEveGHrPAsv/1IjrisdfgdGSR1Ido+ONgX6V+T+3KcVebz2XbpMbG2BqIzx41Ew52kItwOjGQux4uOY1itOnC5v+QTJX6YC9NGqMoSgjzE3qbxdPWrthCErV23W3GaH6ASg2qJ7fEPhFfXdzaT9y0xFwSCKDmmY0MIdUseXIhPadZ5WsKLfgrZey1Z4zRztKDsEfgYW8QSVDXduwT8K1jZ2BVsoW+aWjGNJaUPFd+K3JyyBOx3Y61kHwbgUaK9xDjvIujg4xjmoR5E7AhRZYLotBZe293DWlI+te6jmpQUwYYaBPPms/oKMk0y8vEIjaY0WDFXtwecJzLYJWgHMdu5YT5rSocSdxWT5rUbC8qXL1LfdZa4sIPkRJdTgvHnhyeO230yCCc1Iggtp/iLeb+aqmwHbCV6BtHfe3k8/gsXgzVa7A02FXcEdqnCPTBhjJJIoOWeejvSk8xFItA6xXXJzsr2pDA+A6/JGoQyu+cbGWN7kRm73mF4O76aAtCyojlHh16tjke9suAb0BikPeqPysQa6zqWeFl/PIZRxf8flsnh4qOrhxT0KUyonlApPl7QSiVaK/Yr/6bxrYjbfeBDeOxqL0OUttFQ6UGbz5mz4xkKItdQ9tcJOgjJ87ohLOseEq4voD3VTPO8APveblYbCJ/2hGaoB5Ha/Zyn0HkbiZxjxHXbWJICBtPwPm3NtxdQPxfylYJSCgxOVGLxZhJHtWF2TcXRBavrAo2DPLkskqABXI47ApOladHrEb7BAYxtF38u16GduKnS1kX1uZ1tBJ5Zm0upzICBzR1rSZMrIixBiIN6OPANgZbob5F14XA6+1N0NKpIXHGEejeLg3JX7dsEkGCHNsZTIiKTlVTSzTdWBuDnWYr68RveKYhZuCNbEbuFWwj1D+ypCenGKm+zutzOsHWwVT6jcwTsEkNwNNhDDLL6oZtzMRfVCOcfw5JriDP3vWIBJaEvkELAAuWMjaXq82pUf5F9KjLpdcnUg/Wfg07x84qjeS/vDeCojrfvK+Ay8p6S8aAsEUHdJt2tYphE3U9zwZbtNGYaKRNKWBs5xHe3IjEbaZ1qTOWWz68HHsqSMHqtnC3Qfm6JYD9TwbVF8xqYAOqPh9YfhNfoFP61WVAxSERcNTsfDq4vhVbl+Lnm/1Gp7wBeFJ0wRfyJvSB739yKTm7qEotVyVv30GYiQcumMMcMfa+wmWJRlM/zx7fDHTTU7eltDtGs2jHUUD/48Fa1BZRoHQwVXpo5tREXIqKvPp8Wn6nMx+FgsVkAeY/YaAxs+o+/1mf6zb0RussHte9GILJN62VBKzR3RiIxAKWRhj/VmPfhQsvT+KfRBqSz/uaw+lyumOl1XzerxuV4Pr47pH4Vq03FTPJAfHarMmMHJW9kUJYHsoLmfO8oR1tH5cTCTSAa4AOVqeIwtwHQQKzoPCiTzx7p5Wc0LuqmRyejS4Iasdy6MGWXRYCx/HYDekrCkDKK5oyGhTtTu0dw+Q7vkFJnl8/BtuR78WNfzQaaHF2W5+bnAKh8LZqJowgdkKMMbHF+IJiChyMObq9mExJkQxx1n76p8Zrzj53JOPmSFYzougVjJKf1ZIDfeeTcZ5Zdj+aaaVBrUczvf8Y/IBjpip+xAwX3BCnoNFL0aynk3sy9lNR9Eo+Hdzxy9OJTjofN3AFJsML6SbYgk1Dz3Ph0JCTZ07H8Qj+kIbivyR5RIFZ/tE2awyn2Dh/uyfRh5nOrBR9mIlAsjaSFW7rhIqCfaGRyyqRu6A3QQw498OX8un+goUrodZfO8eap/LsDwvHrrnot/uZEm13BzLNoCMFESXkfPL9d71dj4zSZjSuioCvhCT2Jalku6iW/r8tPDvJgxJPZi8/Xz6gkmzgOPV2k1OH0nP16rgB96vHxFvGtNRl2x9K54rB9+Ktmga4ptSwqlJRXts9a/6oju7nJAHv9p37OpNBrcXcoWiX0NxQ/Hu9ckTbs6+jM5NLqp8POW9YKNC8u6ouKNO12YFL5wIxKOhQwdnL/jJ7Tvd6lMupSflDYSnBZk8lx7v5so34V7VxQvLJFX158+DU8YgrVczze44aycwNPtx+e149Y/QB01i+No8E6+RJauFUzdYy6BvRn5qH1V58snyK+d1rg/G7rQK77R9A9cbZarl7Jc7zxtQ4F9cHorv6pcfFURe0BnhEJHsg/vxT7698vq4QEgb7CBn9y9sc8LQmN7rk6peHAhpwgZ4+LEaaH27lbpUda5mUW1KOiWfkM2Rs7uFroxzZfqkVwNecQP9CmeIcMSelaUmV6dHXQ44Q3e7HC097zKdJoQ758LusAFe9/N42cKffSimp/pTlvfgxUu42b5XC52zikzFGY+yN9GMyRfHDdr74KzpFtd8o7c/+CsptqFIxEdE2sw+GQO+NFVOFdIBseyKU7rTSDR5No7Yj3qGLG3z6gYsEOlXjzQxR1+rObPFCHR0z1t6EtRGtF8GlBs+EC2cBJDR7b3sGOVDKYfvxMighhTPjHvlnUPMjOhYhcESOSZDf3oCrl3U63ZM9LpULpVLjmVoYL6gEOkq/3xQKpnV6eErrZij9Ra1gl3XlTlF0oj2BAqs4bvMSlc/lTavEpl0PwpmyMqgV6K1eNmsxieToc2++HtYNHg7Z1oUGKk0oxxFNp7aCqOW4PoVUMNDzlw9dPyqbAZVXeSPQcwXiDazopFILaSLxl8nMifSvOzC6nnJdxiaC3THdaFvNFThcMpFxTLqF7DjBNOhz8WXt2EKmvJEWTkhO/kCw+mXlgKhbv0xjtqTTV4W76xePa0+vwZywSOS4gIlu4DZel3qoZkcHsiX3KDZCxkDRf0xnts0+Owva2xFxQXaVL9hENzKlRvUVqxn7yqKEX2Fk2GP5YlG0MVbTaYyDFM24GW5JaMd9nG+Jbbu5qKFf+23hYoJG/qlw0aLu6dRUYNp9jLyfdn9/MkI/IBV3LqYRKGzUn5svFeO4+8/uO7DcLZ4P2mAjTuJ/Q3Nus2tOYRnV65mu346zQiM/4sZ6vKspZCuH/+MN5fk89qneRp3dTr569QWN9Un1eLgipcutgP87pc2C+T0/veLIN3OFKUkckH5VoQoXk43xrvtPN81HNAS7ow89UzHPZlzTiEwqeHdLkotKy2LkuaxYPbAy/bqmSFnCDb4NwzZTPdvrkfPjkPeA0I4xLcsXL5XJefBwk+RlXaZsTRlHKMcsl51/3d8H7dlMjGEkpgBicHvgpL8oYKvIxftvEW9fZqAuRaLtsrfEb1Cw6E3w/fGFYFKZY/v/73+V7FmcdZfihxRsUZnphzxenYVgZct25jPbR4eWcifAyz67ynoXxWKjr14EQ2I7U+WIJLOZoVmaG7wncyo1h+XMwpDNWf0QdADlSTSS4qUGCzH2lA/2tWU/kXKLuSZPBBjqI2WQ0hpzhZdfwrEzH3rrds9xu6NnMYwmWgt4j8ML11KkQpYkCdrn+VszwdnMglRM47CkPxnDubjoFFliSqUwX5uoKbaUrKmRukY5tN84A/P+Xvmu5yQTd2/76Q/z2Wv4iKRBj/iCOXtwPI9Xa517LesAjV8jPgPO/Lp6fyuf4EjYEJ1RDz4ZsLiPd9qW0FkdB1HH+QX7UdkoVwunwozuFGySjuexYGiV1TeFyiRXK9wWYJSvvqOfl/jVvLoWjPz6UxFgDLLRurnROyBqz63PGtYE0n8HhZVsv1T9gn1qyf63m9KCl/wM+u4VyeFzVCE+fLDXqi+zYZqr3fyRE7s1Dq0A6CnJsJ3qYodSHp7rmsZ8D68D7c4QSvCJ2t6tN6cPtSzVdzutRkU6Lw3CFeDNc47f2XHA3Qd6eyo0mkCTwP73LvixPlkQe3FdrC7+nh1vR85sMrTNI3NjMeDa9Lqv+e0AHcT2iSNBpMZFPwllT4yPgtmdaUHiJkVpDPe1dv+FG/pTS5eSgpKNCVoyBNGftWIR5l2eCtXDY44RBhRVOee6ebjpIeSqpiUQgU/bYZe7fhW7QCG+jHDYrQQG2VIi2fHOj2WdxYaMeP4o2t3pTUA2bfbWbF4EODEg9WvK+LxQpObQDQx8F2fTx4K/eUQFJKpQ0oYBF6Q1SXL6Av8Pq/8K6xmps+T9EMP+A7zbBk0TeEYySbPy1KvrLbNbBK08H4QGpnnV1oFwRW1o7i1qbI9ct/AEqPvgmsGb5lo+juksNNzPBHqsoppeE8YS/JTJL0kNtNrfRfyO1qWJK0luh23Evf5fUfq8EdyMh8WA3SSh+pUaJMwWDsF3F5QhHmTE6oDqIk6N/oXW+ae4DbxaZaf2uzl8F73En8yfFOBvFwSg54v+xOIuR18l1JbDAM1ZL/P23vstXIsm0J/op9gPYd8rd7U0JCAoSkLYkgiFEdBzzAA0lO6hFs4nuqkTmqUb1sVe/8WK25zMxdDzNj3xqjbuYNBOfcwMIe6znnXHxptc2lGFVVQ6/+QNsPaJ231g0PMhNderVvB57Zma+RR/rmMCixVTEgxHq0vY3ICaj1rCsuKA4xxuHlldtvlEXKqDuNW9+WJTDduLJHyRG9ocV3Z95oHmTs8y3RNjZKNPzxGvU0MVljX8YoURcrAOy+5cs3FdwemjXcjgt7/SpmMJOxAe7j12u7GkdpI0SE0LZLnpfiAZnEL5/L4p0ilAhqhJ/bXf58Eh74PiVCbods1pKgcIBWoY1rHDerYCB7CW2NLmSVeYhj63a/Xpe0DRxj+wgR3lawMOdRZEor6tndscxZTcV7cr+gJ9Yraup6FwXLHnaRfXBPhyxrHdlmCSwaxyvHBi1IY7qo9hPK2jalvQDv19NGNuHJNKoLSZnhDq2d/RtuyVHnTcbX69c9J0P1Y8KNWeEhne0UJXviwm7mssw6+YZ3ShvcxNMtl3lJN1VM9ztYuG/0eMgr/pJFGEzvA3SAjK6xyYH+8cxRhJGAbtM9yrAWbXKT0G/roj3eLJu6Eb2l7X7V+kF/52uelwKzJ50WJkpdJdBINsRM2T0vRhteTENQ45JyBE8FRoKrOtrTG5CjxY7LDBS3uEoecSK+/bBvDef2RiUpPHRP21+0EuppBvvN86e42GD0Nt2PX7g/j8W2vjMpn9U7xZnPpkJsHMSuxmkQ2eZZs0fwtAXO2g0poZfz/Bxxs67e8dgpSuE7063+MXfF/DQQC3sFRkq9m+CtKbyAl9RrSOsLw2kAAyBeK3re9Nh+6WdErmP71xAQFPICjxVFNKYlUeTWd7e0U/N7T7EkbZEzvyk3XG1X+Std4WH5/EweqDaD7xTGRG3nrQl98d2et0WhbS50DMPsacNMfkRPRtu+fqgH1aNnvcYVySmu48aGT9d686a35tBNUaYlunboAR62GQXBD1txxFL6pDFjPLUKUy+2iHRb18X7604Xn7MvSs+RuLY3w9LAhnsN4LAVS4yWkmhJNrL7W9HL9wiqEGfDVRdcJhNZ+m8uDL2jL8pBZhpJiAujqGLkgtuNmskMjlMVzVCguiczhzbY7k2Wp4LWnKGm5yljFHkujEjIYZ5x/DIetaKJ0WL8pqQ53//JwUm+rnjyEO5vJTODLfo9iexiro9rdpknbuwpUsxAWFPJOYbTVJywFKtoxJByujCv3Mp55cyZfRPFOcuqLk+1W/cFcrajq0sRVt/RIZSq5KYdQZKkOGG0lDiphwtiZLkYlYX8DTKyY2NDcUTWutij/W1wjgHtiQMbIcXLzCA/Woiytz4FvFkT6i1/c0PuEU/5Ov+dSzfd/sIzBs5qkGQqm6wupySKA0Yr8RuNv1lVLKufv3gkQ7HctlAM+qDrsa07XXSHdU/w+MrGZFv6jsw+s8kacACjWGC0mjjQwg7LMt8CuL6FxObrqqlfhhHFfSAtk8M8zaUpJ72w5wEh4xBNgXgCT6TIYKkfBY2YwH1ZbGlHbihMkBWyPj3diIErZksSpKEY2ivdQWSr/7NHDtr1GsIGDbtfs4xw+ato3WyqFYXiz6j19Ir9Zl0Y1pBEvug46mCRrXMV8Rq0eY0iX9PBwBvooaY8Kt7R+KxPI4CMcb45thwJPJ49XU5962w3WNNAW9Mo0eJ6D9X2dZ8L2oiX1gRKmEv6mzYC4e6wql7eaD2ol547mtiLXYm71OE01kzxXgNtSuMgirVQ/6Z4E513+nVIzWC+BtXLq25M31f0ZqQHOjKl9FgdIZLUsjMBh9iiB9qUxrFfX4puudnJKekU0eJiXrzmK4xJVL27jJ5n/tt0OwJKhkYOFJO1chvB+QbamCZtXSWlbL3ar8W3av0EaCiqXHD9NVj1B63IHAcA+bFwFBCsLVYOqANtTpOD6j6QsmrYjfJ01xUGyxVbMSOj3qwLZv4W7V86roJjFcMC0Un7bn9JeM0WXW4+OG1lk6h5zbNXCdoZFYANbAFZRpYPCbzxvjA3FeOE/J/dsMkHZToy+aC0eW3o/UOel9alaAQJIj3vF+gqv+b4i+nyOCFnmbPoEyU2fa8YvjjQRjaNtTIgz2gVt8V234KG6VbwgQlo6VbyMtPD6mCvfqIXcPSwKJkSPxzdXymhb+q24nxCbW0zz2+A78stygrr5xe6Jj9bzBf69Uf3xJNWjxbxgREvZ8ihOBLf7T4Q8VJglrKBRw611SU35R1UcpfVnuKlzSMt5q3VLfbscTdwAYgis7T+2VllOUpTMbO/84Qfl0XriNajrXCWNeKivB48rlG53qHdilbRDovh5CNANchc5o5pcxZftqTNgsiIquvFxCcDdT8bEENVcKgE0jzA6A3K4x4KYnRE5x0RMoU3jpqZlSbMYYvinaVBO9ZK5g+M+aUj4cIdflljb9IUSSPFTrCFJ+FTmIkL+zrCyEqgRoqoOGcpemkqfHrYk8Pu7GijWjOwyAsOWuj43tbAIp91FmPRd3vJxFze5sOJ9a/PmnbvoEAWKhY6g5eDefdbWDq6JqYQPxQj+xqy2Jaycy1ZccroX9JuVMyZJ7+DWgesPkcKmL3cUBfw6ntVQQt7OkcCJkg6Og7EZmrTy+JYX1HN0LXVpMQfr6VQoBbugtDzkFUnII/l9yiOnXshnzLPa/v1kM1nI7eELUumVxKm6UEhlWUd7svlc92z4qHmIKl2y90LreS4BU73XYztSLaEFYGMECTsh+Kc4a41JKOLvNy9YnJft3hTNJIPiqC2BRf5l4Lu16gwV74CcjF9B3KCXaIxJYRLVKyzNKCc0D/q9VJItWO65pblYNmuLFd1zOC76xppLH44IGOhDcDG7SpFREuDwIsO6syylTdfV8XbFsH2G/gDd5u300dM/9ieAymRWR8QbJnin9HvjqIGs5GTGYGydrX/aN2AekW+UQKdQ+fDyUTHAS+OrYJieMqRtqpB3PDdxxCiEPRGpDUZ/Nf8vxDOyWjuV4kNyuJW5+1tc3pt05DilYHDLbMKpFEKglejbWuQaCXMazC9RAe9QbjAS8rI4PNqMx9bkcWh6DvbIbAn5hmBtJC4XkgzbQgTAtdI0l+gnP3CR8Nu9zwDiehMHBh4j3+5CQOP4p+mpwVQddWqsI9kPSjzA+uLMsO3akkXMwxaPz7Q5j0jGZFvnzpYaZGV8I9ATbPSoKytPO3iF/A7qgi6VpEjbcIagE9NTaN4BF2O7SkaP46TQHy3V/IjVuU0Dkvl7dAWNfQbhBxQwIjUVuWv1tXqfb/cijBDfX8F2IqJ3BKGsQuAG2Y2BGWCC6FpaUEY6irozScK1PvyhQtrqGgB7ooKtUeWdFb9QeyRL88uZuasN6ZtK0QF7kVz0oIwSmrDPs1XUL2jMHqLN3Jdrd/Iks5KStNr4Du45VfXOJzjBxtHnuja49aU6+WmuDVAMKT5aEGYNMnyxafkSWz25R/yLLmq3utrkjIPCdq+Z8y0MPTFhZtNaZHB470J6sUE8aGARp2cdnMyqavyIL1wQNsjMXM0Mdl+GGcx83XRZjXytZxZDyJOlfi2zLcI4ZH5yPwviFr35S8y7WfIkJgS9htH1ZELsaZ8OETypwlqtIaojtJu8s8lxFvIVDyWW4oCqhVfDWubm1Zg3wWl0GjCePGBaCsapZrL2UOosVJMpu6mKH7h9ZR0SwuYU7ocNnPu5NnimlqKXHxNtUWNU41/vqdwA4NoKwmOafVyiZVE08s3w6kop7I/lIiL0SY4VYyATNPRaAWNEbssOAac49/+uoXc8X4lGTNkhwdk7SnzfT5JXtIscKH2U+lgTTQVOFjNQQswK1oNo+K4669uvoXCDT3MgsKP++IFcp1B62aZ/wHmix/rEdMChQlHlCy1bmy1AM02C3iol/Sv76+bvFyJm3z1ziFh66bSOCHajhvege15o42iI9FxFNja/FBtjRPNN6OFZFrBTg1NJ2sK4P7rWlyWuz8vzE0XNwWZtufPd8B3KECNv3A5gSsGkUgZC2qTlqZtK+RFmzS8XP3nf8K6woZ+gAU+hKgoS4Nbg3df3Dr6OQwHN4VCHLxrAlpAdjXTodCeFU0UnqqLpskbHRvrTy/3qoMRwtAvuRRgKJq3EycHObVdH05vNAstSMPGC0J7cZ2jQbpTTBNFIqIXXj0XGkEzqwpZnzjKh8nxOKq0Evlg5DUgQtE8tCCNm87+t3Inma99Ss2RBYPxgY5tvis4pLdh1FPxfeq6MxZ9ZHlntMXNEj0mGark9Kpo4x9lMWRK5h2Tk5mkTqdigsiAG9S3ZZ9yTKV52pvH+6HMbdimULcuFVdbae5G5G/5twy40EYusJuDMKQDFfpb7PDeQAwdaWhsE3rjlpPmm4WsDi+RgTnKwjtxU3KPdMuZ+gEihB64ldUZi7kDHx7aBN8iOCTNNAu9uKG5kh94f2W+IAW2z3wyI56arWgNCQdOyxO/RPGoGNiLsllgG3jFF0YTy0JAEmr0xbJaP0HV9DVXVeABChd10zhxN+QcAOw4sM1K5HaHJpWF/oGe002+AdmVVvOf//Mn7F1V/Xyu1xKysXvFZp04SrqeXXtBKbYObeYymyaUhago1UkHt/MVkJTsiDwB2dZg8xJjZ4C3lad32jANEzG3p0HQH0vNJR14Ks0qC4Mw0NIPJZQAyW1Dm4Yi21Z3T6fy0gT96K1Xu9dTqEEYOdltDNw3yhnhcWsuWRiFOpZaVI/oH3cLINK3dGBMWK/fs0ex7rL4qPa7LfPcj8hTQeIEP8syvlmTjdYS6bWkjQT41fo//2srAGICML3VL5hUUa8GhDMrAJo85djhsOXOmCBnvDPK+EJYraGP7h8B3FkzOxop0U9awIpn1S45UXQgvXxx71hMwoUdU7kc0b/mlYWx34yooshe1pjmoJCt6eCWHHirAqCX+PUtObwwtLuuQwrbtgCC0zLNJwvjA0p/t3zkyWg3//nfr0t61JcldzE/KrIzMoVObJlAELkiB4jWWwJgJGaaS0b5qq81XSgxW4od8hEOLs+ovZSgfcnstZ9TavXYDK/SrLIwSZvB2mD2rnKwjpEdVMt3MmyMDB5BX4TVoOa7EjHFSdGYkvmR3SkFjI02ggNh8zSTLEzrpJWcH2M2R7SccgNAxL78KHFV0IMpfz2fX5aYQqmuvXsayCleJtg8zkdzyMI01Pp5czIVq1KMq0d6JnN1Uyk1uYewymP5S/T2ezCFnotjKxe76yxcgzNRPbgGpzlktI7Iq3X8pGizRM13mS6rF5S2JnRF6am/VdX7aVXD98TUeUEAYzKthC+ItrfpQVv79j//z5/i85OWQekSP+HWfL/CAMg3yIb4XxTO6QrM7KWwKLV1OLiprNlkYQq9XHVEtJZnMS3fih3FVIp1fUvRClQpEEFgsLG72HJhrxoHckEmGA8vKK4X1IjaXTB9rQsWMTLXKt+8b+vqNWZhjMoKzbmzN+QFoRjb0KwgI9n0H338f00hC8nW1RGMTLHXLHM9VGRiBrNcITBY7Rv5GSRNdKN2nH2+Latqew68SgO6Kxf2yAYkbHNZiCsRmlkWZuRg6hViEjgOjH8faMWcaNMzBxF8vy0NnQcKPCmvdDioyMaS91EC0ASzMIt8dY36TJS9/c//XsIlkDUGu22pYKWQcn3HVfqLb/P5tUYy75LWyiRIz1QjQtVOs8xAG4rrGhGTym6W5TtnB7Qxbyq2kppKX0gapK7SqhRrN55TxnOD5HoiysBrjohUIp4V/3yUa8oqIXIFZVfy6opm1rZ2BJzQ9SS1ab4i3/Y0yYzWEqu08p7eS/FJVrBkPhNF5x8FWFYNcjBo9T/JNG4lpvIoZUmcQABZwzJ6zBCLUaY58toaGz0sWSdxLzUWBhVZ4RXZIUotYXzEfY7OIsTIbitr8OdTDiTGDi2axJbRodrqadpZBJJtzePfv0FvtnWZ/2FdTxRJ+E3dAtS3PYnLmZ5uvy+hZEmaGs8RFhDpBcR6/uUh763/wkWPDgaQrkolp+J5rQVtHQ7lPP5sU2juQJkmbVtHPuLnpIxyBFC93o8F0DPA+6C52ZouIWb9UjATnLnQ97CTKNSf17CSxNVRiwLbyAxKyWgxiV5M0Mwx7mzy51fQm/Zvb8Vqi/FdkFgouO98Q4ameC2YQXTCTad829EYkMGwZaQbrSTVKwnDWl4dYA3RX4rOnz/V/k+pFQNuJCON52c5+M5TR8CX2AR4Ur6zWb2YpE5Y5kzxvcSIpptNle8a30Tb8q1avpl5TOQRZ/ZoC7bOHPWxrdPss8iP6qh8k6/fKii3bGFfLpey/3zAPvvGinLcYjuK++if23OwUZh4YWQ78Eq01fUTX2XZEvlERrf8pSz9NP9dLn+BFIfjAVTbVXrt2N8QXLUZN0E2ghajze7hvI5evvnMUSGCai79zhvAx6XdU76ZW1tei7VDmCB+jp2j0NgRkMrhx+ZxlbQqbX+DqFFJmpU/3zAGruLKvYRivVGysGUolPWofPHdnirE0mGbp8jSOrTFDRKv7m+Nc6C3sRuKYzGiGEpa/C0q1dwmdqN/AvHjK5CscYwlHKVmndGaGhrldcmjm6DRiSvUmjNzUdWvPGwOu4ITCTsyJWLkbr1FZp4rb442v0HaaLkO0Zd9Ayprvyl3W6m1tdm+lksRx19Rw33X+07lfHDzaANaTVKvJmvrmlHVyMmQO9zRPkj0gAofXI8qtpfI5axfMz/F4xeuLXCQNUYPl6BeTed3VVKACRg+RRTdfN8ENfQAnDSRMBLX9gstI3NjU4wvjzbHoR82oFQMLbqHYpRsGw/JRVEGvoE4JCNA0X067tp6QRSIqYMiHdpU9VCb9jT3jF534OlBB59rMctRJjpUwAgppVv+NIRSaB8MHBVG31ZHA8HL04yzKGrrBPNqtYFO5m+ka5N/DlCeiYOeEtuNi2xhZMbyGd8STTKLIi/TjZRX2n91aUfMWBoWdF/3q4PGhSPFTQIxdkhgZLZOOspWnqaZRRTBRBpBR1FCnSxJUVBO/gPPsSe+6DrIo5m1PsSL0AaX/JGqDyEegDI3P92WAtlrNm1guZ1+KLr2Dlcc2bT9wZfxNL0siuNEXY7er2q91t1ICqTYLTLVTdp9xYyI7Eo2QSQGDkBwyLh2U1uJ34s2tUlbv5du9SGR0t9g2snNVLviERNSVGfUWmV1ERElaMwEZ+QLq01s4nkNj6igfZnQEljxt2BZ5l8lCKtcQxzm9HGVnwa4oKQ7PGBoM/U+70ZaryNoSvK/mQ+JqjOH9z0oZTd54mCT71BfOOrIZrHoOHZDapGYtLoQ8Gt2WZTEWov4HnVMBChbKacm4xGJsMzS1oyys/15sZkyVZdiWGhDEfLN0PyyKEljXVLINXVnAZ5OE9FmgZPVTE7DEedLLWSTMUNbzdMcsyhtNxFbD7PMtxAJWAMaDORJXW7xAkurMRZ9ewMLKGALRhtmXTPN6J4fxI3VFpXuz/r1Ig9cs8bd9h0tHMzflOi6rZKtPle0jTzxwwFmZ9FP01XhkE3TziJyeKl+OG9bMX8nG/9IN+WtxQS0P/kbXxWv1SX3+xu8p6NOVkQRhyNCilnK1ijwCduqWWcYbJzoxj1qqjd5uZVSS99Q8lnTI0Wj0QcEdVk+IYY97xxFqbhxEJjkfFKzsDktRVvYNAvqZPkhpyRjXkkSFVn9mnoOzj6lauj8nZS90YCdOkpOzOYyZRkh7Lymm0WUBpzyHIb75Z9CIoFu+M1oE5+4m3uJ+OEoxyU2TAOq3p5ml8XtqGGd/8j/8MyOF8a00e/bvb/SEbFNoTv+jZXFTwHbPl3Xnv2iJHJGvWlf+HRSvY6kYQrNd8VPBg7I+RVcQcZUbNa3C1tzAKmOOaNx6FT6kErvpswLFThNK4u9pBFknL/m5YYS0xKMpN+oImNmDD1ZjAJjUNdpQzz140hcfNmuMpVS8HA1n4xWkapSyrTYkCnh5uYG0sGbdbGiqKguGtgjAM8TNw5dMI7OjNpKeLyaTRajF6FZmuuyTiimmOgrMRQKOBYn9IswP0/OiDjFfSJMs+fnciiWqXUFBoqnuWRxU2LqbHav+w1UGN9ANccz2m32n7sDjVHaI1Yps0X0tCRH1SuIbF0RUDM9zSiL8ffUuP5NsfoUU7KsdFm43qYifB1Op1zClcTAIwWoJBQD+1oipsYYa018bcJ6LfWErsdnQGKr/ZbM+Q6VfjU0gZW7QHI4rroFcSC+OQdH2F4wn0+kF5BmKl7kRFwKcTGO7qYoUUl/YXXMr6Wl6aFe2KlCigBuKufALWtCGSV7DVDgAVlnt1oz65r8ju6EJ3aosie6jg5eZpOc5cxCE8poDQ2e7+KVB7cXYpHrkFFfjeRfCPU4QmklkmPqQcO8aTJZHITNjiwqzGcaAmmPeuRk+aK07R1ilH7gIvol7IaNPRcYek0ko6grbIws+s8AcPQwCAsiJysw7HhHuMuh5NrOJOShLuKIH3nqjFGrE1dEs8niyE/bzfGgLKr0INEDZxaMVLWgq1Md5F19872lSENcOIQLWC7AWIbEO9aksjgKdBtoUW0F9OIVo2xWUXD5m+xrnWpwl8EmSxMmkBtxVPYTW48Muiee5pPFUdhIvl5utCgN0vUlxJZkqTY/4EVS1GWdSRC6MGyhFWTI6YdmmdGKmr74EJyZgnG7tD88MIiH3z1LdQkXGMkB9055CLYRdYMERLPM4ijSyJJblnYYVNw9JAe50xU4mHkmNUtXmYRwnH9M0gqUeYuhQ9cotM7v4d2J6jWFDQeP7O4OG4NwFnsDsCoHuU4DTGH9wOGvJUnfVPbji6MNcBRnweFDfyWDMyG/+MGqU+Xj035ZKUZgmugfnOGHs0BM7TlawJMTTVLb3BHS1DMKQbzo5KUDrVVVS6U9u6nbmV671eEC9qGGZ0j+YOgoqWQ2Cwy2lacZaIiEan/QoT/FPRAAiLWv/6vzX5S8Pj7S/VFVlcStJue7ZCPltAOjtDVbQG2M40APf73a5v/5v0XnvVozdwQceAaugcLiEGlMxMBJ1rQUiLn2pUlocZzoVPWAvaGjTVwdZPRI55dchpN1uZP0jMymSyQ+zWwqKVyQ1Dy0OM6aYUZTuidP3OwQQzzr60qZOyhIvkkNKoVT8JTOJvNKDK4hTVzqaWFmk5Dk66NZaTFZ9KzJHdfF7/I///N3IeaUQz9W1aoFbB2MTS18Ya7LBeK7IzeQgENb5VaT0uIk0aHvbUX3ZMLDNqs36HAzWlgyfb9QciMP5Yh8lVaZyUPx9dFWOIEybq0Uv9nkiCh0AaaLjbkE5G79UuuEUaA6qnis2pnshSfm9mJuIvtCJtAEr0jb4DTU+iTX+U9KGRfwj1hFHXpSDDxXl5xO8qT0Qhm1Y5hclljJ2LB5mqUWA4pUTxx8JeOCuaz4x+7AkkuDL1GgQ0fVlME+pggL0GFPE9TQmm1GL1awbmJSoLrQuv0vwfDums+Zca72p/y1PIk7s4DyI/c8AQvTFXGVZqrFKcWvte19p38nR8DdkmGPk4pSlkNlqrZTeCi2i+VKSJ1nNDWcXWvOWpx5taBz8Rsd8ErWCiXd9WK/2ZV7nSJAWZolKP5gBSf6H23fVZjKpF6lzRVo6lqMKQX1/pTbJeaGgBK9hbiznNAhJ+9RInsLINlpORWFdsdMl4zHChsNC3ZGM9fiLNLarw0MqgfG7RqTuA56qrFD38F3al7g9pqnCvPt1Uy1OKO3pmWGnjf/+V/0YIfVZsuKF+WvN07l69Zd2LrZlCgLfT3Zznf6bjlR1NjPRDqnCWwJ/aQRZFopt4TVtTCaG4KEFYC8lIzL4S8G1lqQiKGTu23RiuTEX7PWkrbfzurhnZ81jx4j9lb5rqzvMAtDFVwRODW7lM85NN/itpVRjxel+WpJ+4A+19k/b4pPqab8WGGAJuPP1/irpeBa8sXsWe+r0MbSouBDivWSKKBQaNBN+URX+QYtNNQRpepAsRV1PCqFyhMQRd9yettHBNUsSl1jaCXi0WSQGfGoKWxJu77S11Vd0kNa8Na6Wm/3DMJREpJZ674CGfR3YVQ3Q4PAUdXL2rZ5lvK5K7OceO2kDrgwJBq+G1gXPe6ztsh2/fZYdJxqDJ5lSDCbv0yvI0jqdskadUwJMySjs9hUnwVDo+j/wj2BLBFjJxHJUpXgAommrkGHW7PLK4payichx57m5LAfq3p0k5JZ46KVaQpP6pT7VxNpTYN5keFq5lpCN6eZBltBzRKzWLFH5TKnF1b8ZCFFY6AZuweqyKK8SUgSL0gz1hI/arhQnWX59CsXU0y3f38vZY1IlYyigIzy5m37aqaAJqETMQyuZWTECiS8Gm10/TgINM9m/fQmgMRlwjurOz7Vxi5OvgTnOoB0MYd4RgFWhHias0arSZrVvLA+1BIoMV2A6GzW1fIZja7P/IAUBSYDp7hH7jtwzhoMfVv3gruhmrqW+EkD9hnRY9mSR7gFk27JAxk2mHu6r+FiWatPyd1J8ZUunbh1zu3AizYJmvHV1aY3DDWsY/Er37xJRMVkKzsV9JwVstA9EDJwYYbVoBcbTExz1pIwaXTFWBLwiSLP/ua5WLUosiHb0UjO9OG5/xoV+42prZM4sxMpEmjEY8HkauJaEkcaatLd7J8K8cAdNwQMmB64lTMhReTcmNB34ZcTLsAa5QH5smijS0d9OP4xFx1c37w1LaCZsK83xgYMg7SZo4sjLYypSo83ralqCcUNjdY1Yym5BEwO+Zlsbr7criTzB9FvGrfklDoZUp1rJASR6Nt7KUlsy645gdSUtSTxa3Hyar/RIULOGg59SPQ8Vys52TAytkMjl+J14tukjPl0NGeNLH9S11l7v/YbtFC2HL51S434TFtX13/kVPDj2WsRujgO5nRoE7tm0J7mqyVJlKhH3BZIgJ735Zri3ZeC55D7IcUI5fO52hzFHQ5KcCqDN1MqwhdDm9f0YIbhdQW5+vm6eP+N2AywAckFq+veHtQzWJ7vdIAIueLvDuo2r8ZYJePVaMuKOlmjpYkpdBU0ADH89qXJFMHvpjt7vozEo4DNtSkcQRrry4ggNSuNlqHTVjlCuhKXgDF+W+YSNnAgZeaqXwapGDnmCcghwCY2MF8PbVnhb3Sh5al6pPgaTSRmfQ3A+uIHIXUzuD53cFZu1HTkwklJxInJPTPiRFPSkjRt9CLI+e0Q27LAyHoHrYY/+Wa/ZYwj2Z8BZf+5IVAgP+cYNSY1qG20f0+T0pLMC2rx+nxVF8eGdDTMVGERAlZhjVrgaXwg7D8Os32n70E3ycLmoYjF12y0JAu0yihgNk9VeVDo3aGHTsFL+WsJrP0z158lNIjCB1FApOAZDN5fW0OPNPLErWO8SmyDtkEC1NfstCSjOFGtD/N2xHwFXSV67doP6GTWgQZ1cFEVVslC1/A1MS2h9Kmu9PZZ4h+xXQlNVrRyOvnLL4hUv+kCtGeHLofOsjjPCDXShvnYlBlO22HTtO0s8w3rV1QY/KggZfSflJKoMIfiZTO2qLMEhqiuiUDH4HyVIYUKDtGPxKraRoeFuRl6lVFDAZW3SUwplsF9+VahFVc8lqxzmH6BQMgSMXMCaC1Np4SXE+nlJFHddLrOtys8f7KZ/5wrVYvpBnBa5jqHHGB8sLLEWd87DUXPcbsSW8FcnqYy4SklEP5B2FX8hdYyLBOQicDm/WBOfEZLgYDBcScM80XsdkAKZZn2BkJZvmau0RL0NI1evir/839RZM7It3cUGy6lxMT6WQz3n09vPNcONMMLqcTxdYGtnYiho5yU8SM0cU08rFGZ79RHi6MZFZmvD+exLGWbt9hri0ABXOcdkHrWbjl2vaB4DByQltgmkhiwcVJWPIXXbKrpH8AqcOCuVLMRqn+ggIO1UIQgg6HzQYBRJO7tJiFk2XNjLxMnqLltWIyu+uXlzwKDjQpwtvZvFJP9bgwlaAT78vc5xiYjjzJwNMU4Bzbq3eIya2YbXeW4Nk3X++dy94qiyJKVGudQp0IVXxauFYfMXA0Ns3YkOg7cbWAbRQXlIV+T29IwCOqQpNzQWpBo35TF71LpNZMlpevzfnZH6G07FJgkhtFYx8Id0Sy2NEz1GLdevgGV7qZaty5QNqJ/JBlfPKkXxt0qxuwd7ZhFNiuk+NxRH46YSGAcvA07qAltYDY3A8LKFbwHa5lvcD7P21cp+JYCl4waxbnCOYh1LrXEtq1agjq1r0lsAERHTTmfQzGMfqI1ITTkka8fyHYN+mFpJhZuhmForI0EvBHa7lIuU7/fOSgErxwO0lHsP1qdnxILDTkqOK31PyBI/YXR5FupdHYOAU588c0JvQUiy/CSkef5msUGTPIBYmQLIAQoMOsS3Vx0XDb1UKGDIZbnzolS356jZdi22ZWAT0lb3STM6jTnR4GigOL1UXa13q0qHVsE3hdihc5BJUnAObBpb/g1a5Ob+s3Uxmv6BRD50U5ghrx7hGxQthl00EMp8Uoi+QzDGhNnyxtY2NBsfLFJmsuWpnGDXJvSJknCQZdOp1q17vOXdW1/Gd+RbzFs+jxqDmibXEDhWEo6mjwldkkz21Iy1vVyrraIGTCHW2lck7HbKUExD6opAko3J70Np2pW7NsyU8D5fM1tSym6Vpb3B4hCwO5tKfGq0PIWl0vUa3U9KXHHf0kgug6h2szGyITYtq/JbWmWNZL51/SKxRQil4zUkPKf5Vpg4kxeSzPRfx8DkZbVYWU2wiw1x2wQKaRgoh+yw9Y0t6zdzuqr3F+tSiBRl7waOpPBBq1UOqGvWhxORTH04gMztYx3JtJLAbnwVr+pAto6+w9ylK0JJYW/ajYxHVOf8r0zwHIWU3zuIFoHtomn8tbGehVZg5SbV+v8U9zkWt9Dw1gY2+PS/Uxd5AeZXhln6yCG0SS3DA0XVVOhO4syG4//PhVo8TO4hnNgbuSLC/eAqMQ68s/XDLfMjxo9M5R2yAugZ/objff+RymDF2xGuawqExiMlmEdqiAZqmZFYTBUfU1xyyjqVl56QNn+z035DNYyVE5zxptO82Vew0b8r5xAIr7bDYuc8GMyb5ysaL4bLSk9gMCWz7m4pxvBkr57VLxkgtKO6XT2y2LDuduJZlYQiKmjGxcz3tTUjUNwpwlvGaADNfHuRbUQ5rvq6e2Vsm6eJup/NYPWJSaR2RJbYL98zXijZTSJ7S1YMgtgDdjUo3u8ZkImCyHDsJWoSR91mrzENbA4YpCVCZgsT0WZ1yzwtdYypOsr0d1/biXxQvUl3VuRRK6SQ8r616YmE6qhvua30SKiul60LN/JkC3Jz86XkIShvUCU4hjrdusQOuWCmlFuKuBYSy8AEkZHOnvNUKNbFqYHPIRhtvRfpGjFyEAJxDdHFMDSjMZOLS9F29PwgI55BaD4PXAorOr0vH2EdFmNXPdb/bVCUJrCbQhp2GsMQcJwSQvo19fUtozSFy1WX6yh7sQ6p+UaNV9ZDuaY1s1F8cTUnrhHgS1cg06Pr8ltKJrUpnXyZ1msxDf6Da1ZTumP6stSMs25PBuPU4W0wHeVD5ApWwjdvB/askbt9oGIByVgYrJkehAgDqt3tu9RiHG4v7Efy3PBV08MHU+GS6ymhJ2NmOa30abpMK2DXRYTMpMt8rxv+Kez7IAd6Ra40jA01zLjPoS8AG1FY193zWf56lP08hVAU1OynjwQpIR6M2ZI9/J3FA1YrUIRYk4a1eT9HeuRLDJTjwssMl8T27I48xRGaIwGyq+ccnSIvTLn41mWfneSPhBlrQ7Fh8Vfs9LCbEgoC+s4oG+yRGiqg+OuaGYb5UfZATWmJDfGWtcqE+vxooolGVkK/B1yOK5Z8IENIMT1FM1ryyC0WjsbsmRc4VGU99Y3+sfL+oHnHr7uR2Lovjm+mbvEN0cb27QWaZjTpanWZOHoH33xiiPBiMjPuvDddiizO0hCctCSkdwND6zJbXSMJ1xI5uPQhnRL8gHruqTyBUTIc4roWZUrQr6+0sxmbcyQUc+phJWdvxbv78D/pW2x2OTlcgk2YlHsWrPJ3WBIiR7wTJg++rNavh1/M+2TqQsuF61Bt3W3LndgjNPTe6Yk5cjoplZxNF/R3TBxLD3QUdrl21coz+zo2FpZAET/82tBb35W5c+tfme+EBfDq9HiajxYTMatEXnMrfqzO05EOuq71pR6NjIV93UU+Q2CtvrcLjcomXL1hyvc61biC2hvL4sPipjksi6v5sP+7HIy67U60F0uoNEF4dqT77qRJ8LF+Ktdy8wqqNg1xYmjFUbNkMT+sqDAaiNuiuXys5Wk6Be+7+jaX1R7Sqlnz6355G4xvO+PRnSMn6tqT08TZfspRWF8x8w/7S0i4d3/cC0X2i2ReTgqHoJiy2Xt2Gv4GkNZUuuhdLVteb54qDZvciNpmaPOuDdHxfHnT3oFcuNOvlv4bZHe3bvWFVqHuQMG5SvWHK0r8KOmpvb4+CnGxQdtHXlR0SdzsRUdeqJ7CuSHV4v+5VV/1GsNS8wCpv2pP1x980T0w3nzEpais6EKfUWaowWljSA+Q23mbzAVG/qviQnlD/nq4CXMF5MbMlLFR04biKOjN735qKpn888GdK2SvvPRJolt4DvkJn3FqMva5FKbnPwJdnW3o+uGev62FYgxjL+yJgM60b969w+XnYtha/DxuaYHVX8djehJxPOh80l4tjsW89ZFek0YOKQYmXSQFKZ9YGwyOdHBsnp+zotmTV1pQVqd7SvuVf11MSYHFH5/cNoQOW/WVEWGvVW8OlpO3Fz5a9z1nxTqj4slDBtFX3RC71Ao5tO86MxGZM7Exd1sdNW/A2D+SYriHnzqp8IfOXcqtcrTBGw8tCtIvaYB+6182lWbEpiu4h+uItNfJBbV+pEy6JcC1uNiMrmZ316NRmIw6/fH5Egprd7yhh1+vPVEdu98lUlgna/C69MuAdKQ9av8fKbFXeQrTOQjqyIulhXsLhm1hbi9uhh2+iNx0enPhp2HeesClKacYvb6w3REFyAZOh9nxMxsU4zDTZtIuwX0GpU6o1wPuPNoDK9blIBcUBa9LJ6rTSEPdXE3uxpj8vF+q/7s9VKRjJz2HzpU5loHAFd+rO1/ljSGa/paQUd5vsJ1pxzxYg+L8Yk9uu1cjedicim6d7PBVR+Tu/NP9efVtyAWce+7046mNlFczlkUHy+jbCCuK4XbV1mxBFcWte5tbbSGndli1H9Ay2vHd6b50KFfMZ45L09obZPwzvh6KX6D4uzl5fYTNW426Sn699v34mmHKZPLVm/WebiYLBateQWBGbIL9YfunG5p0Js6X1ub8znTgtAEUBQ8WlCksTTXdGu24r5av7RCcQlR0y0tTF2X+6vxzZx2R3Q79IGCNHA85Z/DQSii2dy5O1IA1qKL6ysSXuZ5flLfm1nOI6eG+Ew5Y3+zotRFWcj72WRBMaCYdmY3rS4kTXb6S38ciajrNERZZkNecXysCHiZ56dNqttfrXIxKB/xmtL4ZDV0TrddVF1xkOrLqE/+PelfO192asM7QZXDVyS8zAvStBkSAYJkQefDZpfyws7m5bOJjucLelOIPftoHfEfFx16/sG907OGVloBhNR8RcWjFLPd4OiRApAj+6R70qLEiMINvYjOfIiHNH+lv11FRc3HxWUmomHHeUCJjWfL+aVi42Ve5GuxEE1ymPwf+3bbT8YFXhAFYmPK5uhv5zdOjrTbn9EFng0m/R4ZyWLz/loiKD38eElGPPnhfOuohUdmUAF8rOLm0fLSRMXp14DHjSl5aGWZmL1W2+fnPRZ02Z+Nye5dkveVf9w8eJGIr3tfPCZYPRNcD9dXcfEyL243yFucVYlRGQWuTHe/XCLoYj81JT8wmF8BEUQe/1V0PjfyoE6+vcE1al87r1GQ2bIqDrgTbZDjMG34OuQMtqJXfdJnimvvyRyuc4qQ2Ox0epwXzDn8n/xzEGaf/WTyPRPByJkPRFKu3FRpw71KtJGOAQg9GBCN+dCXFERyOyVO2K9WL5hZxouc3c2HHWQv5E3g1Ouv81AEs67zpvs2KB94lX6irXSGeZTHFuA2hy1KPDHbo7K25MMcXk3p8f91N+71Z3+RXRreTyY9mR0f7tXZDybfExE9OG99xHV9Y0Wdz1bbcNrlwzbuI9nwC2hR0s5lFOtSvrL5H/vXXAeX3c5o8GDMWqJQpO6XkEr7aauvK0pf5rfbTUR5tc0fGf9Fvv/5o1wjdaED+4CYK4WJFARYQu9EZG63kjJJzAjg4LOM9Wq8ZmaJCo5u9xvEPLRB3WpT7V9edXjLO0T5+2QqDdtcDO/4PZyGBIsOJXnXfzuDt9h2gICI+4rdBwZv7J0cIIrKcMPxcfQ2GvXHDwKx95xHQaM69Xr4aUimIOgOvojhPDMtM+EzTPWqEi0XOchpdxAZFB/lDiMCgM9806nwqHM3vuhjRefRdv875Zjh2GkjFEzUNOkLgZyi+mXQ2mkfFqymRb55Kuji0RHm0EhZ03/S791PZoshrWC/3n2K3n5Dnujku94oEpnbSiSRTbWFi2iK9JeBdOAfrun+lTaIsb5Zm+zF8qlash8cUdJ72bma/fUw/mvcv+ve05WiHGBZbIvP5sNoFFO6OXW+wJjVcY1dchgFxQDMgGdtUJCP5UteLsViz6O7Y99+6cXtZHyDaKa2uIefBoHwx+6Cgc8pp0l1DE9S0QMz1ETbp/nKK2b8pW26+/kKNkA5pPF8MbtbDPuzY6/dFukPZwgcWWcrcZ1AcQMzP4j0bKXb6hWTGJ/J3XxSiEVLuYTcBFkrOqRZb9wZ9pHBcfinSngHn6cdip/SrvP0ZJPUVCzgJqliCGa0O41kyLx6hwbPKF+xnmCaiD5lljryu6XgqjPrU+7bmc2HKoToVWyijr6ZTxOR/O2OAjNbmMVheqqte1hLxuFucGkFinnkHMmeXpT00si+L7ds3kcoHrdmlKM/S/yr+jAZUVQQ3DtLKxkXP21jRv1U2/coixQJ+Ef+djAnl8ysmBf5T7Jdz3q/RqOr3hUdZufOUHwiYxW48yrZ8jcp9HC1J9UmPU4PSFjFqqQU+HNJHjlIxOWmWuP4WsP+GPndxWQ2pstt9DBtET8435ts8powyNzkTbUxT4J2E84Uu9d8/anNAXmMC8yF2ZALUUHg3ZSrT7PJ3FxCof+TsON2zZFtGp/cJ23Uk1QPPB6VlDLQ3mz5in8rn/KNtOnzzrj3MOgs+uQbKYqofpJhXR9/vqXkvON2em0bnxwDUfxMG/QkazAjoC1T1FK87FetRMyfqt1WxvBcu5iM6ewWD33alyWFqZwcHn6cxBQeuJNRK36cy0yZNuVp1PDZ5m+fhVhUdFspKC7+qUspV7M+KuWtyeZtTX7oaiu7Qiff3tzDHHa+LGOYdfm5jJFpC07/Tz26ybIE+VLX5EKUvjY5ROq5rHJ3NaBoncJjc8yJeq/TCgSJrbnGuIFM23EKB9v1jDxZujyogS3z7TsYAGTKh1eDISWltF+Lh1nfYc8ptEi+uxtIMWMcbc1zRUnMgvbBOKbbvNxSZnhb5q8Up1Jq+PTWJMyLIVkD7mSVawoe1s3XcSbi3p3zjssmsQmFyhsV6cXEGureKZ8l/pTj8kAlKFjH/awzHoz6GH+G2or6Mu1TLutP3QmWZx1NB7erCIm0iqTdPqr1yCwLlY3efr2qPuTFHsyubqcTS2Vj/oBCyw93RCItkUXh2VecxCwAQFRZogoTY3rlP7jMkbgunjm4luuBxZZlb+ldoUHXZHWmn80HoUjGl19fIxuz1VfMRECnGrVlbl+jdU7reizZ+4Yg2+7X2yU2j3JT5Auzq96g3+r/A8mU+st38qn+wrltGY/nMObJiMoVRZGW5DdKE9Pq/f1Te7rwtGS3GHbvZmOgRximUhx8uuggYvzubBakvg3OhGQvUFTFLIjaWW0tb7lgdg9e7WbbynCWW/T2sTs3V6Px1fgbmSWHAQhFNHX6lYCZ4SYUL8AigSIo0n8NgxgVv2ONXGqavxStNAOzbPkoo3FxsSm2T5AV4/6soLi331+c9LRTEfbdld/QVmtFOhUormIWpH54ONAPbXbypUDMoIx3XuKgzOBhDG8HL0ixVf1hMEpF9LfbJjDUyRjIBVhToNcU6pHos6KA762q3SvOjZkw5W6/KyvcKM6nKAue9CE+Axv/qpvqp9/PO0EgMnd5MbBCbeUhaisOHmyNI9nTIXb3O4w9iZLTBtl43qfn5UxCh3SSc2c4Hvq2IWEJ33hlz0PPj87KB+XqHRaBAmCLfznKpSgdHn3VBrZISvH5xXolQcOUQVRQYtrGijOpabH7YONIi+jd0RIukNFxWYzM/SO3ZA5KZSc/G6SeSHtO+5ClnO5ZZmIFisKYhWQgQj2LAyKRKy5m0Ckmnvsxorfe2ezX8o/u2EspA3XnLyzYbgSDeliSMuuAGSrXc5OXQrZVdq0s5HLC0+t+o4Elffjkeb/zV3c2mdyg28rbwwnh6Xf3vsgunTG6bOaZlpfwqSoTH8YHg6BkB20KLNIGZEDYjEqGVeQVOwsx7CwkLIKT1Fn5DIUbitMB6pBnaf0Phnfkzb2uM8aRTBuTNwfTJlA0R1pz1OQ7o/3TJ6USH5yg+mIAosta3KyrD6C/GLAz64+vLoaty3z5Vm7emq83tDkP7kpRaIM9s7lVfMcMWjKRHg8A9aHe/ufPzxZd10sKFNTjHHauRnOJtMqxxvprF8XQ5N5pLVLPVnkBlilQREc69LSB+3JpgWW8SyjyY/ToEVKj3xmPxWA463RpaTOyYCuMV6DEaw2RrRI4I3rg+cbxnwznvggG7pyaR6MYhyHi5SqOZBZmodabZdUI0V8CtB0GHDmuZJY/XeZPgARN7mawciAxKatSf+oi2Ii+O1MzSc4xwa7QcAoUR5JuYxw3Y+2hjEA2I2fElOxbjIsP7lqoHb3pd3rdBxUlqsj+MHA8+lFvTDmP13EcumRnmB2rx4euXETk+U05YlQuD9B+9H/beXwskPh/tMYUpPWNlQg6HLetk/r+Ri5EysPy9EqC+FD6gy7c0ytZE4rOTDi16dVieDebG4KghbvyzmBDI1c8wWqUM8AkQL8JqJdVwRRxgJboLVzSmf1hHQ1ZiLigd4CQrAU1Sfhw/XU2RDw9d2eKUn3ANE6Y7YTyBVEYHDxPSqNfBY8TQT35ev9ID4ue64bybYqZF3NpaWfVjllH8stiTAFP5G6Du6H4gaJR0mKSuH5xFEcXj1zayGLk+oe14zFq7qIzx5OTOWT95Xss4pnzrCQf2QhxxM4o8mQWxX7sN4vBa3tbImv1TPjG+/6sfmiNhzn9/mGSiGz8JXIwMg88w71WXEpaXXYoVQ4KI2YuI51NyDuyWCPXskaLznxoLqqRobx2I99Y394U2gMMGihGJdqBsXeeJs7pE/01iX+G7PjeX6AAaUmxI8qwnbcpblvrWPDJilhJy4I6Vl0EeS4AjS9QK3qXYwKjlAL+3bJQRcjbq15v1MeVmn9AQStvvnYC4f340huap4CxN/S1AU8oSFMhffGYQ6xrXadBseCed0HvX7W6uqPOxc3obtwZzzHlRwFk6Zdvqu3W9JMhV3Cdh5qE1voEX39twtN2mGnd2PVGtpoKQKGBz+tu8qe3eucWV7fdUZ+sZfG81X/OfaAsnFgzWfkzNetDPkdtwtODWvJtviuXzxTm73coEKV0uShx/IdTjc5spmDZoDKxOWg+zCjUG7qvFdsFE84z4o3RNjyLvXqagwT379iCk/c0Xyd6iU+5xFA2n+hKZUOnh0tYStHGyw8UIZNew0Hnrf8bFpJuQob+zftrfULDvria0xnZHh2Z8Gu3XZJjqUz0UARNipdJq4kbZC43IrjwQNdyQb9pq3DWdK+vZuPOLZ3Y5P4IFNjpBomI77+q9dumhiJYUnxMTFBr1JFu8k96/pRmDfPPD8QhYaJxVbLMhmp2b0LGafbvUvz2whljZlL0wjIvMlBUTawxrstZGojSy39TnOmjrP0P3IyMMbv3o4vhXw9/zTu0c3etEeKETbl+rp5BM6WAxvCTXiyir92Nbz5WXDLF5aR1ZlkDVM/3G0poehRzt0JK+Def65dlrlIbCuLH98Or0eiBQuCXXfWhs+zDb3oDOryhm76R2GDOQPIEit5Jb7GtmZUU373x3j1z4hWKy2JZ/qP84PBqBsCaVEiUKe7xN90hRQRex22wfOu8Z96ssF5TEzm8kcfBwAOM1DzsCfRnk978cjJZ2Bpe6cIN6I/5gpmwzCHWEum1+JpD31nvXqu1xvPFbdP9HwA2V60/PtWfI6CX/b/dra7IJrvFuYvifcJqHugkobcseugrRRkXEB4rdAdlNWvUp1jqvCERCd+d2MW8EqMCBK8k0SvJdF2NFV4+xLBim5BEYrJ9rDYaEntD7kSjcuaT2y4ap60FykXf6JiKz6PP8wlFJMGlO03wbEqwEaKEQJt0KM0pKgEXt8Utjy1F7jl/L8Epr3PmWWe8mNyP/5qM/5pP+yac1XTowww4MTHA6Pjmka6w7oG27n4UBIftyhLk/+KlooQhyEyoudv+jJzO/ifTtvTX8Qw10ju3kZfSjqYkHQY01EY+iML4PCOmJ/eOEltCUShKVJNceZ75RQepAwyULPxJl9wUAg++H4V0UL2vEFde24hgAOIqCLWZD9Lm7itUbf4bjtFPZNE5XzUFQG6hMCqyiyElOOiDTxeTTHh3zmA04FWZQAMpr0ob9chPs6NGr+w906ZPl6hJcixDwcNkNL1DMKNKpRLfcfi5t/CFN3DWrVSGbBnWG4Tankdhg+TrM6T1FvEMPU19POQ+KKuZdcxEKF8kXWfvSwYyps3hqx5qIx5F7fZRUsNL4WeYgFcgS8v3ExgJrqFVqux98Gk2wBX629n/lhNhjTK7MFqhNuRRrPuFt3vAhjBNRxqnXrXGyEBkWpZa48Ww80BXiLxdtWy+zj3Klb+7m5mSMWPiSvOpadseJY3o7Lgke03p355O7zeFCDi59bIi57woNhuEMtP++EdnfNE3uL7FDGVuNwMqzGygJsjHBJpxGsdZUOd/lzkKjVMMZwURo0u/q/xZ5BqA0u2MKBydTE75MpkI3N2vqG3TX+D6sGaZxkm7medbx6Kqc5kF4n6jsBXPrf73cac7QaReSBpk/eFHPxDpwN3Y5TG6RiUZOBhNMI2TVGucMVetu9+8FMga4rP6+m1nPtc33M3H9R4cO8WlQ/QJDe+OS4eaWRqnSaM/pKC8ucJV+BG9/3LzoopA/fuu7DafloBSEdw7Ty209ug5ZNG00Tg7UAq9KSBhIgNrOu9RtQbiRNLnJmDPUfy+XuOak7XYFiff3c8pz806zsMLrGA9bgtq0igGqYRamD3nVVxXr2sGTHhiumFSD8N3J1yT/m/wMyMRuyHioVXjg1s4mkaatMNG+qU+RMTBRStNxRyFT1XinMwXw/vO1aKvAP8jipjltT/+dtRJRXztJItFiW0iDqcMmjyatKMoO+7IdTiDR9YQibt/DiuN89HVoEPmc2z0NpREel135ZPZ8bYRwYEmjybtTAuYdygbIVczhOzxE+cNx+D1UX+OgK/mdh986HrCv3RescyzjR/3+REqe554XlOIBWfshtv0ibiqlnmpCYb0AMXVeEyZ+5rZ2zJqOv5uPA1F1nOuSU2EtVCTA80aTbw0OSDWoYNEXuZq/bIhR0smnT5tVXjQ7884wDsuLESR8IYOtKwcyWXWafFhzDU/NIHZV9CdPQWbzK6l397yjUzfEdg3drbU/Tdyw/7IHSNkfGymUhA8niaIJmE9ExyhU7XTAQuZdegryCc3mc0mdzMxH1LySUsTowlsqClvoLjWHzjjXzm23aR/xGen6aJJ5DUiCyPWha7VHyL+W7cf+afgcnlrSNaU8j9UzdUfE49C3u9foVAssmFc1dRE0SQ+oB7LdgflL/R26Ekccwy+9Wf0zi5ujC29+dQTmcvdyUlLkbHJiElLgaaLJvGBgkr/N2Ytb142Rf6bUvQkMGzNbffOUs+n65GMnTlVwMo3pt4x5wWaMZrEWSMCMH+FH1szePdxjygzkTslq1KUTs37DPJo9farn5AyUyWe4+8GvojcCN4staFPGE+hGaRJAtciK+jvBaV7mMKB/DvzzxnQ9w/jvx5mf016D/DOm+fy58/m6yU9pztnjSVObdOPAHYONI00wfiJRl2CXhzGC8v8LvTOQRWjWzSu+oKMee/+6uIGNYWPtwL6cJ+HHwcQJ/DcZTLMzzIPL4FcaqAJpkmS+XqYSy6ZI9L1bWHVz5QmRv3FYq48cE0PPPn2hhxycO2uKrRtejkpL05b9vRgsooK1Kvdbote8aJYvS8lI+L+arzoz7pku2jvJDzG/DaDr6KYjK2WbSxloNmnSeoFpzwuxcolXyJ7e7Uie6s3GQ0mRomJgOy7W5hAwXpNWAp+mNq+Z3EzZHtesurtARciOikYP5AJ05IJTnoSXZ/k2q1Q4NvqHBiPFmhGKgYSqMd5g87WBQ9k5FQ41qnFBcY5t3pXC2D8prOryWzu8ozRpTO+SgPbgD1ITgSaj5q2gzp7zv85iPdCMayW0NWRznHYR3lovuiMm3h0nev27flPbtFFjf7+UtPEPFmTwWyak4o5ADqsL9a/MIlW9CoKznef4BTfl+i2b9X2MTGPFtubyx7zHvVU9tqn3/YpRo0vnc805SnQRiQPH65yC6nn+WqBuh7CX3cStnzsPhf0VDk3k3tGcdFe6ogc7uTpT7vzgMz/2BnkB5ktggUBOdAk1dTPovCsaUhBB20iJi3T1vIAJ1Y+oIha5rf/HW2iULQfnCvNEltLmEGomruaBl58WlPlu5iEYlYoKEifwhGVhf/3cW4Pk9AX4dT5wLPAljv5/I6UH0nDSPevr6t1Dl6PGBb0FcjPcyr+uDNe/Lt2WvTdmSNEEtlomlnGp648SRpBT1QX7YDOABtl8gGNvNQ7xoTePwweLh9GF8Ork45HEArPDZSNrKxkqDoEmtGaRv5Rb+oSiDGIkgBdDMEiQPAoP+jPe2SiO59L6ffRUChOv729IXeVzZ2oWZX/mjaJL1ymlxU0bm2AgpzgAYnbVuaJRf4sO9RY3PRq2hf0jO87s/7X1Y34u8PpcnKFNM+El8WL0BTXlGIpzYvYvx2UEeMA3DrVreoAcPAgJ4Ful8XBh04s4oGzX4VA08zi5kBTM1rTtH1AzSpXBeDhH9CX9E+ZUF3zSii+nLsNL0+TNMrBwe1r8mqaek2HX0+ImVVgZ7KY05k0B5TgZIsDM5GUba0/zQaUTiXzf1NHNKHrECFpLmuaJoHmspZPr3mxFLNPdIUDcQtwyqeysCBiwHZJs98gxA+dwdlPR55Iem68sxwvbmqy8yq1L0gz/Q4pOv/NwS8DgMjb3Fbrty2Hl4vJ7Hbe64xMqfDNPT12v+NcTMSOyXSt2ERpMisEtBs3T9HH8wsrziQown5KVO7oHmd3yqGhCCLou/Fa3BM2GgC+T8qIZ21ovNT1iy0UeFhL5eQaWbSJGJzpFiKQop1Wb6JZq7QQTWdHVHaQ+mbROYD/X5nI4MHd/LEi5Ll1oOmrtDBdSLna5vkTQGO7jw1tzw7aaUOpEyyv9uRbf9xHd4VMPNowAFhj4sPZD+ajUITuKkaU2sqr7Fg0izVre94JCWqUf2yQDbeiNkJFuD1c6psxShh2wlFCd6rjBkt7jIoyYR7wxjSRFXpzqR5Fmb+K4SelRF6CCOZpSem2SuEmkxFyX5WVLIryRdFYjj//TZlfcO+Orjyu1ZmiK16Xp9eVNVputVLgfS6FCTKFuNuseXkSVtof91q6sVF/naFX5gbER3K8i0nFDSUoTWTNvHoiknx99O5RFm8fVzCnKM3NF+K6c9vXqfgNYmWMHTj5dtqP6eK7dd1Cm5Aat181qRUqNKoOPSoq8FO2r3Vw30e3gC72bHIrgcA2MBkKA27wNofGJsgIBh4EmsYKVRzvuNYzZ54td6o0r13S7Du9B8EaPRf0K9ho1x9GHTIM3thdYeVynVF4Ei9Pc1kzP264KTJ2UpysxBfTUmcUlEBOR31xebX4Me1cjc0Q90B4PWfLPMhsVA9OdTSzNQtgDQ65fmRwuEpBQZkZkUi26Zl7ZfWH0ZwSFa/n7ppJxpMJ6MN7pK06JWF1OUyKmsqaEyD3h3e8M54PO+MBGJFfGXYKWKZu7yuDOtPj47Vpwx7Uc69Un0W+vsg3+BtlpeZ4Y1X+tj34tBjDQl06w/HYijNnPKlmtGZR4Df5H0o6Sr2QQg75zrXyhlRKEZT2L4bqARqulS/SS6cpyFKbhBfi31ATW+FlNM5V65R09lvK8IC6HVTLn+qu33fGtxNKEPpcfS2hClR/mk8CX8RDt8ZjZNNXRTM21JRWOrumSnhbvDDhZ/2EfhTCBSjQMya/3Dzh3l0tFvQC53cLSW6n//S9qpaHn+JYtN2ZQuLZwhh0N0JNbc2yemLY7PVzW/MWPM+8KqkYhKuNPJ1x1QLUjsL4s/k4EaGbEybrmUbeH69TG3eMgGnm6UBTXwJIupuy4PrhUWP27uIQJ2VE/ZBpcmspSCKnye2g9hUqpqvXRiDYlGuklF1draHI1Hy697POgnV05zvRf17t18+ycH7y7UU3E/6107RJHWkjYsrDOqNmncGpsILu1NJL7/+DcqcWGx3f9K2ksPkYtU03Sz+xzg+W1y+uF5U2s9CUdtxwvyPzpmucJ1n94mIIPUtyS7tNtX/WXwYjehH3TjokMtbQLLMSYkmJXpLnR83I9lWBqa/KhbcSQVZOsudoNd3+xc391WJIruDeKgKVjZ2WNpMDekzTCtiCpPWqQm3SWCZWAac+kX0dgYXvLm7GiCXMPILkhxvpJiuUJkwg36VMr8any3RS+NNAHIiMSRSCxnlORr0+PBKlG/+mouaL9hfKpJl1UCRWqYitWGWkc7TLcr3MARaSMomhoL8bnUGF6hpRqtFnlLUdIouGVsfNjY9tgSoAE6Hit2JdySFLWMEB7qvqmaG86TFccHA1kPXRun11/N0NQsTk+5f0eHOICKh8qAivWFl6SNIsPnOpvZIkDcyLgXn9kZ2r4t+5b7yUYTIhFWBXFYWV1hJ4jVyGbK3JmD5rW4j6I/SweHNG1e61zNcn3wxRSx46jQQeQGw0pvwAvNroBxjxolFCq+qlWNeq6RBKrCGWk56saHVG8z6dNQVhOzLr9YfxmDxt4kZ8BZGtgSWvVW3ggyiNj6DXl8vPNSsCM7Lqo3xSSGLyOyCvnIfS3R6oT3O3jGpsG8aHTnzo1aadY+kj+D7HBq0wPe1gCCCGaUHOxxcu3Cw2WfYzDd+Ex/Fq8x76DbVGip6Mq0f6jPllG4rKCq5x1/ryN/0xqL6T2bT/LziSAH2Pv5RVTs3joxHCerXBDzGJ5QhNfJGv3h8ZLRsc6Tj2eyhQGvL+QMRuWa2QZbVMLciEz7K292F6oKtV/SwxZwRMpNSgxc2m4V9sVSoy95Emsa1OAp5I6Leb1aW6s5Nj6mNTYGaw40udhlyN5uBIGnP/3gM0se/caWTA3toU7ydYUW3hI9+vZf8+AWvubvbkZ8jqDTYFndw9tw4VBnM0GQ9kUmRWd0+/u5Oi0DpmKMaiauMeB0GjNE8BKpmpNSWESUgX6ZMbydx3ai1mjOI1Ipx9Mg/udxjZxmCA4Rb6QbOaGolWfJTbWtMK77Ba7ySZHDVlqCIieSQ7Mbga9eeGm+6DD+xutic2zhHKNqFfG/Y4DmMtkPj0CpT1Be0JA9Gufv5saqczCvz6t6I7oSzt9lw0KhL+F5r83FMyCmW3saDaqpMZ0PAXMtNYwEW1LCShZkcucb/BmAu5KnAd9HgfY94YiqDvXFbKM8CNZGC+4bV1T9KobldSjKAG++yk6vOEAuRaaVdtGGRv/77rzGYPiq2i3PLRN/0hhJXdbXSpT2haYso7V1v6LPLi46xba+R7Z1ykyd2s15l/NYMoENGD85olmU0MA2jH0NfW3aPYtJkBDVThdb4BdTJIGKctTTsmCAiWc7QUKDyR/XCicyLfRkUC+CX0s3o9YRQ0hS94xWHB5UogswNVzdFWS4fNt1fjPpAc5mJOKHy3IFgYcuHLJMIHwxVo++55XlS77FEFOHbT0wiz80J9jxf15Vlm3a/U8FAAMMEgYckCr17ega6bLBrelltEdxlt3NOmlF1BeaQs/CAuR53bb2ifW8bVeG0R9tzmI7XJ4KHoGwba6Ht+GB3QW/LVK9BfDKxNDqkbUFy+78ymZ3o6izEZ18wNF4m4GWyUD4ajDrTN9wLceiUhQI/vXh4hWSVNINMonN7sDpfLNjDKTUYIPRuXEvMiwkDbei+sW2XoGMx3FWU8IHUeBle3k/HDvxUwiOg5OvPrlBWpjZBHmIcgqleWpKouLuXpFfOV7sXBxK9v/Vn3ag49ii+lTJMvkV6BdXoV7H6g7b4XHZQy+6BGAB/CvXzvoA087s07F/8GeZEtnCYiiq1DEfhmJfWywkZTZFBUmxdUJDB4Dt0NilU3nKDJbANdsm6hMh719faG/Kl/5w7dfVt1hI1pUBv3KGpmsyjGeRdxexydjQucz29QqDfe81TEX6T1sU26h8P3oLbuURwFhwEyy7htoYbRecqfi9Vnjfnvz25QBrkl41++6y9dTNfw3GjGJLb1fzlYD2tjHh1Q2/rLcluI26ebYv2n5Okfp/U1wcM/vuLX0P32B25OvnSEJj0YnF1YG/O4ndYXSaLsJX5dymPXUusXd0BC341bN+Xm7Wn//MimTFVkzn/UG0BM2FFvlgwOy9xShDVhbdGTdqMHv5DNBFWehIb3LmfhR9q+v+/6t2q0oqWM64nYjTlIEpuuCNfAw9qwpwdKoip8wN8QxUf0wPuHzhzQ8QlGARpqR76I+u5Wp1SGsgyyCsPasqdZw4DliGEG9ReK+2ovqxTz78Y3puA9mjnRKrGckWiwlhHfptqOU4xTW4JZhZK7nvSXZDwDd9k8PYjSA2h0zjgPvxKiRq/CwnXlx1dbb1p5+ygvVTxOqb92NFVvJr51WNV4uUW1pvkwD0MRj5w1vjCx1doTvje12Sazods6mEJ0m7+uwOdLzHQkFs2iRGOpsoWDj/0h5dZB391r4ttj1Ozn21Pb7yzLGvsNRUSWNVE49UMA5GB433mwvy7vzl1tZ0kFU8kKkgphqM233w70+GQ9XgQFxE8yXWdCGN3JVe/h31TZQxG5O4hxwKBHkyIFIoJIG3TfazfkNtUvgX8p16jUnrjey85s3KNDtFRCsx9O0GHs2Sb8QT0rjLx6SWFTYH9g1CE0+wqJ5Gc9rxaYkXC/E+eUCrKQ351nGErFdVOsiZse+fWSkkDVFfpc5hcDyBSge384w3UkCx0sZmwo6NGV+u5Er6US52DRhwwjbbDBmKoL/TeQvZD6CSHdqO2rjH0f+mN+cKqT36R4R992waUZunFYoU3TAXoYYaStNppw/nFo0HldFc8Yu3C5oZBxo+oJGDg9GC6Q6Smgg/Iy9dfRCCCHa6fRzHzbvGCMtwojbcT9iLZVZS1P1U4W79SAxmeEs8pAjfv3zG61gwnu3BI9CcyBEa7GV0kbcT+OU7+Z4aFUGCsmAp4MYfnmkuwnM+513UmmVOy26DuEkTbjftJuZrBIzrTGzRw8OIotH+YXV7N/qf+E1qmbTpHY1PNT3q+0Xl2oBcX7H6zhv3lELzem3VJt0+4IQ8nkbT93vxSWXLuHwbCptA16D6PakKfBQdv08ZHCgc5mDWHw0KfMoHilAPNZw44veLaQDadGCd3C3WYLbew6dnaxNt+BH2rQum7T6IyOrOUpo7zfGTC+93B+I11Y9ySfkEdaGqMBZL2xNttBEDdKE2qiCGibdAAnYw7GCgw2Kx8fl4VuEx1/1+1SUjByCxLL+QumcVpwwLE23pTy+u3jhUHwdAVo/+n4ldm/UZyIhP/DrafAmDDTylLY8Vjb8SBKGh0cgPoAC+NphUFqlmRkcR6pYaU26uibXj+i6PfOmUvJVpspK+BWW6ztOR1n44aldJBUqY8OnQyTzFuIhfn3qAjl9Ptpn3xs9rfT+YWRLYNiNFasDXqQRGF9nLPisXh6yjWuljz+LF9tpRIUd7O6Uh65v/zk/73oxiL9282f9rk2Z8qa4FhibciD1NNqGMB07HPRo5hgmW9RETsTf5pQME4Br9CG6fDz1RQcPfdc98jKHeHGY6yNeZCFuo18rDGBcFMLPukK8KzzgHDu6n37UT69Nl+niYi/GJcul2Oi3/NytPUOvXajz3MNdVgtO0ybdIrtnUMw1pRZZiK8dteiZaHJBBBFBhVn9XJ8rcAxp32Y77hnRa70EH9GzgSp0w0UsfiPiwWlTO5pWZJ3YASFw4ck7XoBQXygwyzJjDdrLqT6GcJc3TibzC46vatOq7vfFYpIU3+i0LYt/K5bpo+TSqNcO7Yk8eoVhfExRwWTXGkrTzgZ3QfR64tRR/wAPs9S+aYMK3BDy2KGOxvHNePiJH6zrEy/Lpnr9j5BnEnFRfFTdV4pHRmpftmhZkMswvuvhjzAo5m2BqFHos2znKBwKCY+2JQ/f0op5oNMZNy57NyNev+yzpwKf+yG80c2iA1G54WJNtAhxd619+j/BnBRztKk4PZMQRus2IubLxv7vRGaFwN3jzi0FVI5IUi0oaYDbNfFOJYq+UmealhSeNJKQ3ELLvhrVfHwYPa/d+OxHAL+ZcUQ9nLk1tSV3GdTg5bPOK4XmRxELbL7A5QZBS50ScfFh0a7kDfp3PRlo/+eVsJ1zsNPl8BOfcVosUzCZs+bJPWa0lgPn5a4egz2QSsvpov/RqupkW+d2eALtdPMvaYsteGBGJOdaEMetbMwOHoLZL3eEUPRRT0oW07vZg9copdnxvdLipYfUu7Ofjweky/JZm4JqNRGukt4qdrIR77XFICU5qHGa9PDPWzBkPUAGwDKdGSB6V+ApzqkAzf9ZOxTqvwVWdgzz0hiaGParlcYNgxP9orSuEXmhqMSvGddv/rTnP4pgXuQlJz2Y0oiGHqWahcQBV6ioWcUfG6ErHFsAbc/GfcznbIK1FgGM73id7U++NT/7iUinToXBalUyxxHWI9UOwCybV50DOW4zTcgAYTe+YjC2/5srt8mhcsUCuuvFFx9bdF4CIbJKTHqJdUOIYrqwRxqFOCgBJgDymI7MUFNRS0I7dj+uNUr1o90j9RrPPxmhKmgqZtaEsuxSKYJuIhE07BeVqxppjdlsZFJMk/WiT1TJXaA2Tr3nV6/dYHqXU7pvuyjqXKj6YfzAdmtr0iEVscQ89FqxxCdayFp+FAaK3kTpfM1XjAY4DBjpURi4BZZkfIHlhlcYRrXy4gb/8kbx9gc3Hol/SslmlRcOLybXUiO3t1uR6fOsenBx4sumaZs8RViD9bWhthLk3ppqa4Zs5xJtyK3F/vH0OJx59uVpceeiHDorMVmcoiJKV7lq1Wb/dRvKo1aJlzWPumXHOuqjDuYFbB63OAkZZx69N28E/rCc1vQ2GpBGcGb1jYeyp8ngjTQHmVoVSQmT2812rlPNlQsJrVE/7m8cyAytwR9xJ1aU8rMfexM23XKmNPkCMOrNL4iTAcDyPK+KJe13lG/hnuN9+sih7dhWkyMp8eUMPt/cPENgdqDm6VuBfMxOyHz6mWnuroth3vPX3MeRjc+npHUm131TLXaUKQ/nLW+hLXejZMfcNsybfNj3ztAzf7OBahykk44LcgJqN4yt7j6vauBfVgmpUdu3emY+7WmEg03I7KgXlKcNVLF+xXZeJ7HrkZsjQv443ckss0YtUXnW59l977UlfTu3EO0GZljzAT43mkHEAd+GByoqnL31seYoU3xWmJAtYTD3PVm6C7RgvHTz6OPl2kgfPfgObeScphF9XJCDRRaVCsxyJfL/AXnmEYncoDksVna3DmYMnPPiU/aHBYaTpKhmFlcrwqTqhv9g3yFAZkgO9IjXQNEXmNDe4O+jGENSri+SL+eyGTWCZK7pG18TLGOenW9gqLTteApOr4pCJSjoU6nMXXHYSiS7peTxUOzM+TVpM1qEhXaMACnkC03sqWOUYG3d/MbBq//e6HXQERuzFCW2DCY7L0z7QHiqN1M+vpWPu2qDUBDJUYqxOeqVQykcGMKg0DEU7fXzGwPAGMOo3btByK/0U1ZlHI0K3TlwswCKbzojBfgUJCxzRk6/le1/quDmPr8J12yzOF3N3+0bQ2yeaG15acIyDuUm7hhVhq5dmms6vO+pWgRA4GZAvzFOEZyp27BRVnptc1tiNq1O4jjRpH5oth8bjEijKuHkW8RpzWFQ74nsp6zeRdmNmxBwvtVe4M48U/0L4ZQdVy/tIKYMmLWWkT/rjvr9G87s69uHMplbuBTaJPyQkgd1TTWOPEa4JPkxUjxqQCDK5cU6DCGBxiD/mi0eOgZ1AJDCig6bsgDQ35NJfqIV1M7gMSP08MMnEs8aFTsoPpoJuqMFuP+Aw9O/td4zS+YhjFvnsnHy+XWniGJtCwNNk3nmUl0OOIJFTQ2Igb+gieintuRh7bBQfIYk4OVNA0hBYQc7lfvr1A0baXHEfekJ5Px5gVyfoBtwk9QknX8J0PyrAt3i90qAyHNXe07kqSZk8WTNllNdgf1QMnw5YJ2He1ewB+gwlZ/mHzHYX4pIG0eTweqeVSTW+PMT72m6rjdlk858mHUz2g9NpIY+KNXg6G9rBF9IZTMRVtj1wg2rWa10k5pFVmWZlPTWqO2GiCkSqELZCsKv3EOzo+E9wWRQeqMmsDv2Kyay0qryZJmIO8hdzuBVtXTa6HJ27d9luNvUYr8pK/QwccJNK7dfTUpw2aqZiAhqVmsSTtMggbXzTOMvHNBCj0ehwkWxjmV6dQ9YJcBCEbJowDr0TY/ASBRV2PpiBj4B879cadxoFiG3ZxsBgWLHwef5ilZiL/dtJjENpk74dWE9WqiBqupdGCYnKYaxowZV3SiyeiHdNcmzeZEhPdu4R6ZPZriWL7RUb2iOG0fTwVoyCdZfJJEduad4eRq1DdOIPbd7CbIElhAynyrtT1PAMdVoIjnfIUqPgrVUSx6+6e391KVUO6m0/5MzCdjKbPvyEEoGHUTdZLARgcD/j6q6auJXydqDP4BFAlaOSdDDXvTqwUCaC3gdfBxPoVUwlcMK1hKU9mXH1raLKbpRSqVYdDYeUTXaZ9hPLNeJXpqoWt4r5z86JsHCsL91UTVBLShEzEOSZeje3SOvcfkeUM5wheRu/gVSbC7SYGA0uqoZqYmQawVCHqfSNDmr/SYs0Dqo/zMS13fmYxGUnDDXI9YXEKuxD3TnQuDxpnuvKTaYIde0MR59WjRKQpytLCjpuTF6IFOzKQpGIvUNSBTSmZalCpxhWpSKrBZB/5Dtkd3r2ok5f53sSk1sZgiFSD+mBDQq/Q0yoNP40x4fzujqEwiREzxCCxSTU5NwkTHc3Oeer/bSWsUGMpIV72ribjs3M8MEXEUCN89SVTq5hnFLHwsqTbbUdCUvHk6JbrsaXrCmOiRZ+u1Fq90q8QMDYbt8Tf3PXpDP5wZAwpbZiRCzEdXm+0UjkQayXxToWewK7jlczjE93Y46vx/nIYeC89dKAGB0NyiBYEwqsmqSRrrgGAshX81fCvxz61CZ3bVP5+/4Xe/UuGx6FdChSeqSalJ5ummlKyUDvMPmY8eYzcXE8tcC3p5M7fyf2yDtUZsCLTxTtuxX8/zZS3k9eYTgkX/vxSak+5XI1oR2pmooPwKsnrRWVrL4LAOlByDuIW0rUEVpK6huvQH07lbE0Qic0ylELjmmqaaApp7PCFdsvYoND/sbz/MGlS1nPt02Ng+/MnkO1k0N4dWNkNNnogz1Jqkmnq1GoccIw/1Rlzu0DfNUHgQUiCkWjGUUn1djOkXBZdfkXoBjbG9yZqYmvpeU5pRyk8Mr4ZaMvni7VaXU6cTVnwSamLWmbYseUZnNBMnNkFgfhA1OzX1fd0yawZcK/J/4pmC426f3KOTzB6J4Lt7dEjb1kbDEIyo5qpKqXXJCM0pjYO53ZQvL1tubWyq7XvxtBNDsmSU1d+P2UkapuZhGI2b8cVim0YCO1/2qF5P5B9pbqA6A/rAdfF8wPof9e97jIk/PTS0V9xeKLRhz+Wr07Y9ZebnoY4Ehrh/tJL2STWtx919gbt0N198obt2NQ3atFXOIDDxrWNyUqwwaVYY6GbGa7XCw0PY68WwCquGUthBNnEzntyPtNIMyxf1is3j5yHW6vSnc2gHDxy3TEdjpq4xR2M1czVN44Y3px5ld8+VD7ITGIlRSIbvbedqLFEctgZaJnw3ZCJuc+BjCzNq8mrW9kNPm/w6V/UPNJZG35RYXb6BxlH99eIbgNb37oJ8wAqOpqIQrllNW82AcTlu9SMz3CJmlhWhbRNCXF5imu15RcEX8Q+nqwky255wFFGzVLP2AX1Haw7WU1kvltUTbw00eCfDsS50oLwtJpei1xnNHv41cTX5anadPEgjRxsHWRNXadHaBdVjQGeQXgdQKI4oAFkydFWqx6nkGjmjDaPgicTdxIhi2+B3TtNq+mpGeaPXCMUVwKkzXI7CGIteD6b/zZWkJJf4Dj5SQpuJZOG2s6zmbZw+hoCnZrJmXqQll5USAAO9ouQcoDmXsvnkq7cvG4lAaz7GnoiH7nF/Ca/IFMGiflRzWjPw8E5HbEnSTpIpGRiAwBXCdtS/uEEYZob7piJx91XS2DalDcCqqGa20vKDuqolleuaqncYicGSopvN/6B7p2u23c5oMLnvjJ3oRzpHt3R+llph23yO2glk5AOSYxkfuvsFyhLnY55HtrEMsQgvncF/KEUuTW0ovvHa0GdRqBUBbvMtBxP0r0Z7fViun1+L/FlDX2ZjBiLs0cfQX6aYF565oYPA+JrJrcD4RjW5FWOTtSpVji4nXGNaK2S3hmAhomzcFJMPP84nPqQJ3N1Cz9bL5HJWzWSly5bUIrMFUkLKg6D8fDqjgmGCjmqfJ/y5uw5pZY0CPx7VRFZKdrIGMyuVJCCCCBwbGfoFOZ5y99k0WSl4H3fvZhQMGMijmcjcKvAJ6+UbRzWiOlpzWbM0PWWIsKDEBrjxE8bYjRwi2WQ8tkRsPAgCkbonxkUBc0ZM6Fg465rdCmHqusY1zcsXHn/NUYx3rJBAYfLYIp4XC8/VHJSCDeYqKQdVmtbK4iTHIueK+hsoTWCOUOnV90Al32zypf5yMaSA+qvKqJw9aDo0GCDNYgXRL4jP0A63uRSI7DxKspUGsHRuuVPZzR8LiuXrr1fTlLLlr8atWaQIYr5EsV5PGDeNSoW/G5LXReB+VmMbdqb9kejoIumoKChkUH/O6Tq4VSOyzJaSchdS01hRY/OOVXeGZO/ycg0ULFONZpRY9LkSUgtCnnZyUzKM7nFVbdtMNc74NG0VKZZ/IvwmY98wMMolzcB9+m8EdT/cSFyOi03XSj61rF7lwWhbHQ3cPvWqdb58xlmOEOjVsbEczE3nuWAe4qJYcv6DsI9yfmgGmH50CTTezA0skPKfJtgnrp2mu9J6D7Qd1Lh3CVkJ6TXupAPaMiZkNFnwVAaZdQ0Lqch4mIkd/2xOuxpcOndVystaUTWaCSuDrFOGYjMnJUNUWguH3PShKK5upCxAkA/fAsF++M1oTFfODWlMuKFpsmjsljQflpbnZ9FJMYne7gqDX5Oj6i+lsotN/pPhzfWHW+Ck3GlZyomQUceANyqoVxI2zPPZ/vFTXFefTyz0cKr08u2qz1r/pqqq74vEpdUhM2jzoEE29prz6qEG2NaTBrnuAiF4MaAkh+mloVbsUIeHeY2sEHw1FothX2qu/Hf09b8orIItZ0ZKgS0XaU6sxy/jeF785SZfP5WU4RrUsod3s9kXU3MxBsctx8K6xkYsHEJ7TZOl/wkbgpWayF5oiT2yMKeLY4GRIU8w+qy/QlIvHblV7AJuDpk8F0yeJsjS/6S+f8ThIJuxfcUYnJOKEyLnplAjejkakec/6McicsPHJfLBaDf49mmf4QVeM/6Ym0RDijQgqqrohlD6m0wWc3Exo7ymW/zD5V35pdfxQhH/7VYV8Gw4dsbQaJosjiw5ZqlWux0s7erdMGFtNOUp9Tm6feuX5sPsFvXmubOlF3s2SV42/Jo3SyvywuhkRmm33L5W75jAfgr2xNzAm1NOqFthFrDTyGwhYDw1XdbzwAE6IzXWfJtMaA/Y6ELdzP9V/ExGbPZlih8Zbzin+Jo6C7lNPRWEZZnkNQee305rkeAnCq5/HnjE4+8WzAz9knNsngPJHkgTaz0vhiTSscKPjNm4g8Vy1BeoJ6k7dqNykPtqo2oPB59mmFLibDRGDD8wFpKQzCZhs6wgPKJvNOosEIypaqWI/uJi2H9wq0QEFNh+OaIkNReReLdq4554ke7UAs6iU0h6x6dSy+M51+st0JFJSFa060Zfy8DRooUSJbVVT5KG119b9QduIScnOoCdB1ZGMxP7g6FbmyW1DpjhPaqteuqH2cEkHM1gxGDR8zSbolgGM1/x1C7HgOphLJKBe9YSz9ww6mwh1Elq256GBxrsx4Qlsjp0pk81Y6k7YUz4fNo5yZV8SML/i4aUkckIV5NkzXI0DFGrEHS2r5xjh9xe/yOvlZJE6IBr88VQdvqlqXuWt1t/PUprY5+FvlocpPOrHcJTjvXCTCl9KFNPmciwP+vxRD/L8IGph+mVzrp9IslwJvVUtILS2vZnUV1204OplLJrdD6/dqYL99PJZGQGTkX/b2tfutw2j6V9K7wAdRVXAPwpW94Sb20p8aSrvh+0xdj8LIseLUkrVz/nOSBAUgJlzKSrpt5OMt0xXhI867ME0bejn6NKh+bMCT8xG/JzYVGl8JdpWk7Yy3QgQbCxPwsu79zQhCSIj3PzNBx4sBE3LNkIe0Y79mrcNR5rKghA/6m3GM03UkDfxr7ivHkQjY9PmpMhTUf9pFJ7ONl6pGgxoLEBK9K/GrZzT/V2tWz4zjzL1e1ikzy7HWTvjya3eJ/H3TdkMuQmzftZw5CNYtExKPlSz+nz/FKgQUtAOftVIdg2BcbVhFqj6Wi8fNmum39OJipQ98e1k0LWTnKFLcQJw5GNYpkZ/XpW37oHW6z6WLMpck+s9Ox2en4GefjzRbVsJhWdX55eZkF8fHmdqCHNK6gmZYYcS2cSUdSrTiGbpEeYMuvzh6fHxPTT26NhIdX1lgsLgdxjOLJ0nly2EhLFotoVrx33aMpAr/TN49ZjIM4Gfw0YYlI816vtu/3PxwmV8MfhLFnIMwgXd4ZfnAnwcR638vlni5rl+BYVlimuufMMar1uB0sK6+PjglJMpXSVVymOZNixdKQ0NMDuelsBiPGki6tcHEoTnU0ocGLTz3nmEsvazi9n/8w/c/sQ0ZD4NLcWhv2KY0V79n50sLW2QoBqMBcyxu3zenz77fH08voarqcHgEFK0NH5cYcG1p5xxilcKkOFxalapW6N8z6t/40ifi/RNFPV4P7h6u5h6t6iy0+mhRqe6xLmRH1suLBREmatoMtDAZUUYKvZNQLSqlQxF/Oa++eHSzfFiALT8RWY7sOcG31+PiaOJ1HcQoUNU6aZKtBVnFGr2ozmH6+mdLkvYUByM0Z8Kpd8tc1/3p9FlPoujnM6NfHJ1abyEzJxO4mjtFVS3s3BSESul8H9AlsDXhzOR7O78YmP6vsnGjdK45hds2isCw3zNUqSqFU015rTDBjOrMjTaAaGKQYekM1YLRr9DP7FxTdFl/o4aDEfspPnIthQXukgabgnhXvKCZ6u9cH49vv4Mw+IlDrm40VBOuQ1wltMQ36lc2Em1B8r39fQgpCCmUxNOoF9JiSDZ+XvN+0T2/3lxTU1r9mXzyAiA1bNDBEx7NYoyVLVDuatkDkaUdk3y5xOcSI/Uc6IOuWjL1LFjGBxORxQJSwMwZWOJ4061ylsFxilD0RuFJxvFy0o6v7sdvZwpgvhh3+cn11f3Xrrh8rJ8U41G+LGY/4oDMU1SkQWm6IdcBWImzY+gkrtdxPfLi5Zb6Qp74pl0RfK6v7JBcTOxkebw8ZszWUpmOKMsT2j6CwMdGA9oRYRIyW6U3uShl+vdFlzDLqIquV4wSzVkHUlNo8itEFfSDN506/aqmk33D6zK6aDaUwZhmFrnkyaX0wpPeT/PM5Yy3gYMiBWK0Ib94XKbPHH2vBfyiVDsBV8ZSimGA2e04eri4urIyKs9Pqmx7uvaPBryHEmG/VlHJnhw67QO/5XhH1qKffH2+NbvmCDMujhZ1ZYWcZdmKMlFPygbNCXedgxjJ1XjBOhzmYNXNR+E3HDgtrNLbdN/d5vv47pWuWT43bY6ZCfLWoJEdpUoLKWM8q641iLwIHxlanUPbAiNfi8cDyUboHq+OVx0xZNO3RdLIjphjYH5GEkTAv9ah8VZe5yOf+9o9dpkEffHjRA160OkZ8dHXgjwrorCR1hbQLI43Yze0OPZ702mMUo75c3D5QB6OEMaqFQTTo7PjRiT08XOxk1l4hs1M9lvOeuZkJpkgZXv3Y6KdFpuOvyivMqSGbHd4nhkDMkPLlEFHUOl/fsYr9Wq6fdyAFqu5o2/sj/C90KChbHVaSF1rhxtRt4sYbeGqVh1II/taVTg9GVyZ5JPW4a5yStXF5hsEOpFUSggz+YTXJ6kkdqjzgMB/WSI4QzQ3ilE+atJXYzqmQLUjCo0gMcMTCp4wfob19RR7tdUolm/2NCcf+/ji+AxFBBBHi/MLxX+CeL1MzeVuwGB/5DfgDDvr3VSPH7azrcJ9PdPEgnx12njy6yheHARmksZVdBgJVccrEnfH13cU8V5DHDAhXIT3S7wkFcHqKZocBGaWIxsY0k3D0lxx10M7r2WBNqZyc+nUj8eDTqp2oIvyE5gpionzJ9sQ2yxjZP8WL8We9fNXTx+jtW63czK5vUVl6Hf3ID1mf4z6PjJaW5Gq5Awo9O2TMmZkzR6PI8oDgUoiN7Pbuc0mU/3p6kURAdd4ETnCtd/Ru6SmFosVGaxUafcbz42RnVq+ABkE44iFAWutRgPYZ++fGTojAQX44/tKOGksJQZaOUCo/miPfFZgWvYk0lpi/bZbRwG0zvrgEsZjpEf9Po+KPJWZRRQ3D000i4oHWuR1HQGgptlEqZ9hMFN8MUSLYbDOkeK5ZlgK/Aw5W2FXetrkQgjyPF1GAlhOZTxDYhSIRbOxYDj+q11pqyqdxThfh2O+OHZtXM6Yv7cjx9ah3cgXmTiG3Qp3qseSrX2zeuxThDp2kzUL1f1e/lsphDd+T72cNUO3c8lBV3Joz8L5brDYya3H94GQfJceV1pdEuruDGb9DkgiyMVJug2M+YFeuoKO6bvN6dfp1cTS+PC69nxwvalHElTmlx/gJMCsjgpNMzZ5o0U8R9vMTt2XQ6JC12fGqvodDO6QFCmGHM4iyiFc9m+FIF/FKWdPVYKEmiI/8vCCedGccM6uTp8ysWB7+/wiItPs7o1Qg0Z63Gl03a82UtmrVlsRuJoDzsy8Td8qL2k2hLlcbtccASeyS6Hl7ML9LE/yyBJsoeJpJKtFX9ew2UUH+SRwUQVsn6azhdFb/R75r/fLgEZuIYOMhUZa6GgKsyw52lYyXtxv1fdWm4jVF+INTyAPbnxbZaaHPEzq++0Ss8rhmZauC/67ojahm6LB2HLtl+f3JTPM81dDTLDxgu3yaPmJ/9GFqFxscNEAQTXJyFIu6WocpGWYpxfhfEYbRlpQout8Zt634MEdzL8U0AfVCI/T3Uz6/8lOwvplPgl45LyYuhbhwq+8LwZaMsi9p1DDs2sZZExJIN864B+9n19DI4uTr9entHxQPdtQbn0v3lNSrS4/y8LOdQ4dK3RCw1tFk6WNK6fOCrwy6N2SRUKjpcLNA2HRd5FXEQHcdM5FxVOw13UFUnNs5nmWnJG49EsOw3mqngxjJd32gk04EpoArSSw9zQqeAE198G+eFiLoWqvS8ZsXiqd5Aag2yUptqs+W96FSviy4ef1xf311gN7soAeW1v7i+ljKI/nX0e2wQAI5nxVVEYkO+UG1byWYAN8/wWX7djVQeWMaAZsBd3baItC4Vdf8PqOxCTjq6/YP1RuyWzuMvQLYHTC3lmCqsS8qRoyw7pF9eUHHBc8TG0rD9z3uKXifHP8dsqObiKWJiY7zI4335YGiELRjEFzpGBt/PgjF9mAyvcpssXxxXmGC4rKvWV3yyvD1Zu0zuCLyw4iyLEfQVVH/cfDv9Ckivt/tE8nic+SWHKHFcMKY2AeQdGx4KYuyO+coZfAHShYr2wR5XU1bF3YM00cX58Vm0GGBccLQwrNpIhDLrODlQCVotgpsdhiNC9idmMzQgF1fXJ2fUjh8fEchAHEPMUf7WUmuODMD52/BnI0FVz54rwQNeCSTae3SAHwDLufmW1Icfg9NyMTHg1aUPY6I+1f1C9RXjt6u3Enu4CxSjNXxgzNB69nBGkR9aS/XiF5sPwLdh/3ffRZBcHFfi0IJiri0hPgFDmo1ElkR7u68LKCNAA2pv922tupycicktHCAvjlaF2eDGC87CwvBmI0ENm71dhtMxKSErpoLbcrlpWZcwFg1Ovl1fnx0qJyQqyM+OXHfNSxODvDRhKLORkGm7aj5bVH+KJ4j8z0B/LamdyLJ9UOG3i8vpPXQFHZGVXt5x6LjgubWz/OLnJO2xpLHrO1+VcyCXLur5T1izK3GwDTmfXd59m571YsLjOXZ/x1Xi9UzfxSTkZ6TsYfJ0D6UKPcEPehUHzm9aYnm2NXYXekB98PtbqiSi40wEwAndNi8pPyoT6oWKlMXJnb2/F139RSom/rktbFi4xRh4NuDVpY47U0pG/bvoByCGCkOgjUSemQx9iWGhjk/0vWpKvyWB3d1MmVnl7jCo+Rt/pruImaoLbYIEY9izkQyTtqEGEoFnISWr0+07h51NeT/k0cNSnP6E5MVjQlc9w3fL0GgjGXU29FoKmMk9r2xVLQFZNTvbh/Ht7R0QKKeXgIJ6ytvS7f7xqY117rY4wj0zfNqIPsh21aZllPmkGGnyXB9AIR0kGvdDB5+Wnttxi/Y0GQJC85zE8mllGil5GLcuqqcnFDhUrO7NcMaTi4e772fTIduNG/BvjhuCaJE2l7geeiFLspWpMvJUDCK/2a4+qH6mHv6gSXs4+9QlMgnUPz/14HAvF2L+Lk2kl1lsEAy8XJi+ImbncU+W6vH6+h8//nFxPX6kqA5tlXvI2zadyeGfTMf0RpPr4/hH7WI5IEkqLPFWCoqzfUjtQ/HKipu5S3Hzhuuv0eOOesuGmtD99YQ3lo/HNQHUEBRZ8hs1CUDK0FAnMAhj5VZ56Itw98ibBh50bnUFSxl0/7dnaR5Et8cpOvnQHoRn0JZ3K+njja3G7ZKCxdMTBbb+tPfb5OTbgy8FLT45PpmLh3gdnJMswVZSH2zu2hr5EapsGGUl2UEauD2/ephdDkwyP7GmyrIhbC2jGCyVVoVR2JVLZn8qsDpcHOrx6eX13Y9hx8Tk8jgNQAyCs9HJWv6sAsG3pzejv0hK2Q5o7Zgv+8N2/fq8qH7+LLu/vL2Adurjp0paiXM5z0siy6VVESRUWgrAJaaUvCWCCDc2kZQCl7bPfhzPqM8GjEELUlGdwxrZnRXM3p9dULr45PpnLKfirMtw/S3NFupxYVe8cDtHzZMKF3rsHydj+E16osY+8z1KhhzWeQhsCbWKlWg135D9G7APwWCaS52Vrcsuxg+zkwdgp484dSefoQLzwdkmAzAsl1alMrQ1BxNEvxf0lgt2BkTl30QONuc4fTgbf3WKHAAzc7wG0jJyrsUkPyVpz5NnYn9U3oiJUJW2D+n8MTljd0ctttS/Xa4/u1AALB79OhLt3DZgcScstVZlMmmn5wX40YtdI56xz+g+P79y+0hkURBdHPf3YndTF8GW85Il2Kosz/cpMvSCVhV7yeklrVZHe6BsSWHmZ6n/MZlQp3ZcNkvwvMQ1kAAYV1hKraKmzXLdGxIFIBXUop6sdks4GTeTm+u7b6dn09nZkXl0IM6Pfncpu7q4ohjXEpZeq+iViT1tgusK1R6McQ5mhTe3Zz+CKQWJsxlFEsoEdfG27vxqBk6OPO5GkokhdT3IjAlLq1Uya9nZzK6nGF/gzj7rUqdndUD3aAggdns0U6tkKBbwxt1yaJWUaWcOzQSPeyrkNfdaxSxvQb/EVQI8GefSq/cG1tdyj/d+e5GkgToesBQrYbi4FYyLsYxaJZXKu4y5Ym7oyPRDuk6cZw8sE0pxYPfOY2h0Q6hY+bm5/3QyS4L4/jhnLeFm0zVN4adp473KUhvJxhgHX4OxloVd1u/l+OSkYSKPqQ5h1Dd4huX+by/vqfvPfhz/TNOhZTfndcu07ajbcVPCNSx1Sag5yjZq3X3ljsTlPjDh8fTZUbACuOWJ84Uq/j5NxM+j2GyWtWp0C1agK+t2Q7uyT43LigPvV/ef3p8BGnhcupD9TV17JB4oWu5tniR5R84A6Hk2ekzCwcoouHtov5SGS9PFxPb+6IQKl/j6s8HswGKCB7OWl5unsbHiMwyJiXYISR2cFjeXJTvOwgUOSjkDDOOgLAsXmne22LiunthXrqkps8YgCqKKOA8zIwK+hf8L6NHxIgRQN7fZEEPdLCsX50z6eYylZHmB2YbkEzaa02VGzT4q3V9PZtAKPH4enq0PiWcIy8WlBNxqGU6Kar0L7tZP9WrJA5cDZ8q7i0ZLqXppRo39X/8TuqOfcAJZdsFVrvFEzRJzcxkZjE8jv93Ax0AyuKphkFwa4uRjcHZ7wejAe4yuGwO4vd+cUX12XChTDErl8dbe0nKp7RR5Xy4LAvmJcN6zSyA8p82nONsty3U1Lw9+e0bfdzw7OtVONWvYNUdAerB83FzmWavmxR4nOOYaO3xB9dJ2sdBuxKzaOubR/yHMYfZAH91xo6gkG+r2uJ61vNxcRcZ4YQwX9SU+TkYRJ4pTxMK2KCfjh0bs91gTehxEn+ZDOtfwRxOWmptTh6J64AuWqEvVnlLFN0r1zZbXvenK6M0db5k0u9Ol18Jfo439ShmBypOCJftPF+U7JSZW1XEqHUwuvznL/zRIHo6Lp4shCDivwi03l8qn1v77pISlwS7gL4vtH6m2XNNHpmW4sa2/5rXpFm+3+ee3OJDHHQ10S+nEWODDs6TcHNLIB3o2uvamlhqU/QOACowKr9aNoBV06fZ+d3+XUiz613ENzXBI2YC7FMPOReZMWqVyqv3P4U5drtejSOWQYKRa7b02jS87CtBZq3pEpcOs/HcB2E8cydG3qTkKpqbl2nBb3qlefi6aaeyAFCwmZIaYi4VvvCefedE04Wmi0uBsS9/V3IylLrbFvFhsPwCAGp0W9MVRMKiKUZ6kSfr5odJ4aBko+VCJPZRVrgHJrYR7d61X31mmeVHFC6qe7Wozuq7X9JReSowO905FT9vjUaH6z50rEq7+DUcXp2q7XDrEuwbqzH+Xr6j/FQveP2Po20hpjk5XO17U3NzSv8eSAnm9KaiYS+mb/vRUUg0JWTOhzXB06VSZIbTpEvYUCtzL9SgVVI3fllusRJtwUP+ie4svDmArTTKm6iMOPY5DFXXkniZyRW3IuXEYxXEXEn9dQM4wUUpQS4YtHPLe1YoOcnCHQI/wuNgclVwbblgLCUPOpYOIUHW074IZU/Ixy4G2ZnBTLup5Q/G5LF7ocn8fj75X9ExwmDhGAve50ANCfPpCq/YwrTLgTUE/pGyWVqlgsfGCvq81Pxtea1iRstH59eicEk81pzeV5JnHA1Js/unaXOnrnJszxR1VNFYrPKEPbRRRVRVMty/F6ic9MP18rpbzqlgWH/WC6tCrW/N7OnzsdaJ4iJAPkpg0NFw6kTQr0smWfm4wrcoXyPjN6X1gb0sBe7615iD3xXz7XLyOvv4YfaUoun1+g4EIffOfn+h4qyVDG66TKIz60fGxfi+e30apTENoN1QMCdKg0MtiWWMWcDPBO95xngWMWnx+IE2DdOIb+UBxe6B2yT6m11G0EIlU5hG9r907nEI4NI4xWKCQ0/vcE/osPr/YvCxzbbUx+ZOhjdRJbDY/2k1w9kpNHmVEKqIoZj89F5vm4ZwUi031XoPd0ns8sc/7UnrJPgD7l6EN0dQ7NeXRA/31u6B5a1QdZVkS3IFkZgrJs0XwUC7r0d3X0d3booAZxoj+14nH28rjIVmqmC+0jc1pbuRm6IueY3j8xrMiisx3xdvvook/YObhrd1hyLcXFSOojPg8IAw8XDmMH5BoTxSHnRzGhmagKlUUinIKi18ZfI2Zgt5ZmOHU3cPoblW+QN1ExmHm8Ylp5yfXoifFiWTnRM1HP+WJHV3o90WDGonpFj2WJZsJmtnQUFEUe9xqvZgYWAHL0IbrLJTmvdGbwBf+dYGMFaUqDs6L1XLb9LzQo2NNxtsf7a+j0OsW6ZTqutSKT2MDtchkR2iGp2TV0wJz0ThXjGduIiL/hCfgw0dX16Mr1AF1BYRJKDyeDiDDbrl+QIZlZMM0XafEDrXfeKhthZSiPEzBEAG62Uygiu262I4eryiGrp/r5bpa0h3wutaJGsIU8COKbJyW2MCZFqSmsr80NpkUwWGKUmzsblVfMzpzQWX345jOt35t6iJKVbFHdBTahd7lxCxxLButZdamWFNbBDcV4pESKe+Wfhe9qz2hlFcfVEaR18NiQKKrGEGzLSMbs+lj6UBdF1Re0BF49U4lfx6cbhmSaN/eagVrsvHontqp9W7xq8CZIpEIjyiQMgfKRSjgPBLZwK1y6+BEP76ib5zyyKIe0beYYFVfrPREAh/dDa+WmPE/urmj0nq9pnRXUaqkS+pxz7UPtAtVHeFMNnrnadgR5oJ+MrDLTRCn+jCjX2/Wn3VrPsFSZUOCPAm/Ohu+c6rfuvntO/Y2ozQTEb3BarkBSHJnK6TpblU8b9dlPzpRyvYo2rRdjVNFCfE7svE7V6Zoo8CN3fcLjNrpiolgVj5v39dFQzO9LZBnNjXIPaPrsVbN4jqS7kHm0ahlesfg2sdzgFLtkQwt92tJnQYYRW9YuYUqQUUw56tkLDTeqGka3V2O7l7pxYEt61MepUPu3ZJPYkN3nrcb7h81MikUPqCDotDJ1kXD3zldgPxLdff7e7U5/NhUGvlUSTyVd9K7czqVYbfGyE9NAGeNirkxpIilyoBZ0OJuetxWls+g5W829fvo8bueMrdtkqBy0uPF8Q7X6RePL85wWelgabvZGGvyTvlCTVGcpFlwT18fvzljDkihC7jbmyueElQvmFlQ++MRKoGbcCMCuIAzZNaYPR5b7w4wgcHOpHf0SsEiDDGlrP6g6dYN/2z79rtaFAdZhcphvxpODhe5htsaR/RptapcGhze+IqnieT+v3wpLZKo2G26FzxLQ49aIMuH/Jf0G0vtWaAQphc89WrOfOOm4obkL9wcl/DUMl0AuOcWMN1r3MJI+sxqFE+QXK0k8omhr9K5VMt6pDIfnrNAskb0Xwmut8+m8OZF7KrX08pY+oyyxBA3FMNRabirVJZCI6zt/P9/vWIX6PqN4lFKHehJ9RLon80h+m4F09n9+BhGPuVtwuK9rmSr+KVJe6Ys7FnA32/pNZQYQQZf63q5+aOfzl9P1jQk1Cm/zAHJBOwosea99xVdjrKxyyp3KJTSnGLkdkm9ZfG0MARM1AD17861jjKPJ4QlnFuiAEs4adipdCARxn2VLfCUgNVLouCCgtBvlCgmDq2YPrX3zSuflKYGDZf5Pic2ZCcys9cIFiEVE+IoAI6iWAaX1RwU7Ru64vXv5gl9L+lp0aUy8l+T4o3HjxJKPJ8XSWIo1wI9KBMbspMO9ey6XBSYXVH9CE+oSKboTjTOkrqm90ZQBykE0X291xNQYeLxyKT+8gZPZmN3ClBSZ9ROX99/b8s/IyALkUyWFRx68U8rh1m8lVoh5wt/jF/KFYg3oZI+Q4qEhxSujpeblcTG70z0QgK9pVLjM5JICTrZW/kf6ghEPOjwgjSXpO2JZNINCJfFDuq89O9NGe7DHmdgGuAxKBVySLM+42dj4zYljazdJXHGLYPvVASMoN0btF+YOdNpvfrYroOmPu9W3qnP4C0d5CdBg0AmNo6LUBgwfT2HoMyE0UkstBxS3CxWhrf7f5+Z6A7OeYPQBiSyPUzeYVUio5xQKfT2DmBFRjX7Sb1op4DTD2CoNKqlNxbAHM9vaSPda0C+RDaIC+tY/Fjs6Epfzef0c9f0vWUhxaQCmg12kQSaM39q3QF3TOW3x4GyQQkLfko2iAvR1kkTyLlSENDemTAQo6KY6ojx6mm7ofvTZBWqXKr+zD30ybxCDjG4+HqnNoxTKxc58wrd7iS4LRfv5l5fliss5ve2EqFX7GbXA6ehH8YTqY3dUqqoMwysF8GU7sYb5TP6F0+jYFbPLXz/L7pb6HzEzmstUQakNmJLLDh0GaAFuowlK70ramuoAN+Ybrs7A7g9bX4k/ztUS44VwvNBufkNoBHI1EZslRrexQzYhAnluZdi9Qf3I2HiCtRQthuMCehvpnDdH78Jr6ekRXldoAT0lKkN1nloFn735XwFgeIXarkxV6aLSBecMgQVbXaAc1lvWeusfWsy/DzPGvKtqx2J+K1l7XkyWwEAL8jhiJM+BkppTLntectdY2lqk2t0ux9UeO+ltZRqWI/mJBkyOBB8v23MziMzLbkpFxgEMPGWokwMaQOGBujzTCnTAJR70Ld55TaNUHblNpTdqY3aeUfj+WuxeV1A6uEL1rU/f8JkOqRmYPnC+3/Acqx22OItuIBxLj1BKvheyv3hbuhTaPLa1AlxRdVkOKYUYTD6Nhj47Z9iCfOamv6JJycU1Hc6ZogTLC6ZdNs9UxYrr8yip7uuKQpHzdyeCZAHnXt1PKBajlqDLIsBtHwvzBZlVf4q+TB706Xo8/LNMt8HMI3S8ErpNJh88mnO6H8KSNAz3gvd9VwFGMvViw/r0HpeQKKFIcI3Y9jbrovn1+263GzWoxAzGY+mRaOTBxQDpGGY0sEooLXYl/Wa/ucUEoEvoXcHocYOQuE/cO8bssLQvNlQS2OO6+28AuMBDJk46yUJTK3p/le2oLt/rctl9e/R+F+jMQ9XCvr7wtTjQaVqSJ+Yh82GQRrTiTqD3QJ4PAx2rXEGJLkCdiKm19s8qOK5Ht3MKO0sN7hTGdAKvl2U6xFxr2JYpHQi0drKLwsg4IJpoX8UCKb9gm6mW+QCCaf72aVS+YSCBsTowirhszP8UTpUHtn3Nl5rjSjsCqkliiV1KtPq/QPj76am49GzzsgHA0wJWJZXoZA6gxQXCoZCGscJxuCGLPBR/cQPoay8XILeKnIEqRV9YpZuziKoh6fKpQ/iJFNDGreCr5W0p1JZtyrfsegux80sdzXCVPr9LrZvZXnQB/uM6HJWqXeyrDgs2Iie5K3NwReorUwpAi021AgnaR6csbYbX/J7uvIwi3DMe5I09WimBF9313SOd76ZDegp2ldzs+YF3eXlAo1UBInA7c+fdrRCF7tev1Wj77PR93JFmXEzCjNM1LwgHm6fbRASpLDhPMMwvAuD0WhB3j/TOe1Akyp1Axo8pezMbO29OYGE5vLnJ4uGpN7h5CGFjedZJFuT5HKFcFQEsxUjURnajh9cv9tR780zTKK6lR4FTo8opfcqrkfFexVhA3kWW3SF7oEB8eKIqWQanJbzwlad4wWF1Yp+xGj8QHH8rViucSBKsx7vTqbcTrmwb3wgG8hFHO2N6c63qwWPUnIJS/KypCbmo+nLC2oOHEEg8XlGUjMeXPry/NLSzpH2kMyziiVlUuolITv5ArgnQ730xu50UW/nexBBkfhux90wZtCzpcjaM7UJmGoWFvSlDKSyYPa6oryPS1UtqGb4jdVUe30y5fPVZ9rR2hUfEYiEjdqisw3XkkHgyvCoJAnDfH+uer1zVCYi8Wml4qFvn/OIsBFb5O0k7gw/u9DMWFCiKXqX9Pvv1XpTmCOd1qtfBU8uu7OdxGcHljDK1ElJ4qek2jO1VRwU65B2qdstoKgdB4+7os1rPEm9LxbvgO49v/YHF6nPhF7IIUVorgWEjdgU2lprY7rWFLS/rorfz392b6idJbLvr3a+c1+/o3DbxzLRZfCrT3Jn48lnkmF7prSHPWuyPv7dU6rfwC7SwrfV6p0uEBSwxtcUmYqn4p2eUBZ//pFpqpN7RgABIymj9jStEAiKSggbcziGuCSLz7Q7VfpzXSj153GxTzuQZjxmds0JUHZLG60pAKQGK7Cj1zEPzlE4ci+Qh3qLaW+SNSA97FIwbfQrvqUTwcjFt7QxW+UGIDyr39mrG8QgqnI7eznQ0jnB9hZzYexRFqlsaBTHUAppA3UeGtvwm2LLY+bx6n29WSF7gOpJrVyF3qmk4ntL7wtjKLSfeJL9cyVeC8NwkNrN98gG67wrb2NRJ9Om4qDCm2vJpmQ7B1p4vFz2ER6x8lmkNHWIq1pDlJQ2bueZtFFyUgGYaxzEU5VGwaT+VW6gTne21j/olD6v1cs+slJ9Hrmts7nLu4Gfko3ceafWvgYM9rqkG/tCPyhPgoeS3lthAV+D33/ukfjRkwhnduNcYliZVBmFmQWdILd9r5bP1UhS183S4dW8NCiBH/V2+bLeQJW0P/32iY5gwrhFk4F/l4Z6SeexKusPtRZzgvAJ2rqWhNY8oLPtqnwr+lgq+t97ZNqMT+OUl8JpDPsyBq7WLOOYBsAsBcwsVSbpAXGWa06DBklHx97sG44mHpc6HdIvZGieoVnSgToKCkDfoKEGjpqKfnpCDM7nS63tK7BpXXcro9in05ba0cw1iOenE9vDKNU8nZP6Kbgp5zzEzqj7YLXfX+3kFHP4LxDw30utWeRFwFFDVBcO0oZaGSdRGIpeKzulRlYLuYXMTuggBL9ybc/yGKOv0+a39P4jLySV1tlytfz8vtL2RIanZGaAlNVGEeYQlNZetut2Jfj3ySzhZObcCvJzMsE6Ad+lJceuKH/dFiDA0ddMd/Cc3lhwX8M19C/x79yhOZFdgmcx7XlSq5qzgsyffRTUc0fGPVe/tmr1ptUI9kps9MOfhyFtQu4SIeMTSXuiuPVtOtsukWcfmxHyKI8VFt8lRcs2pd3UENruRWohfEBdMh5Cd/M2R9lIDQ/IVmoC2/bXUv87BaZna0LRv8qP192q890Llfvg3Vg3xanBwdfHxugksiZNxQ4CbRUwr0kkM0r4qzmTFQqd5B/xM7oniT4fEmn5TTckiPNpbuNzkohWN1tPGh7K4oV+l8o04bqiasrpM4qFbJDe45VEXsBJTeRwAl1R21ueZJLIxD4ZgDdYGQiDBpguQyrCJIuBZ+MDJ+UJkasj47GHpUgmKf6sqRRXb8F9uWKHthT7Y71c7wwagTnvVfVoR30TqbOJRqqw1MgkpXtuqNsQDKPTlH9GsVKRTlqdL+lksS2bA677yT0NI4/kLsMhlGvGl8fG5iyKbXN491FAfYg+HMCAMV2hCv8loJrwo6GU4Bk24nj9RZZX/SO0s56rY0V9b3mRSZYYWTPzUUObbhSlMebDAE83L+1u/RsImz5GOop9Jh/ag8fFt+cHZMOyiFqLZy0peF0/F+vnqgYaKWmw26zCU+nZ0PBs2Otg+aAeHYYNliRJRZnV51iV9JbqNdRkkzAPzgtmURiUTfHeKDg4tlhR6jNkFEN+FfqC2+gsOnZdqI2hlA96QvGnWGmOEiATzHJpBJ3KJXVne3fJqxzKkkHtVhQflipJZ8qaRfIdo5LvtAeodcVOYsod19RGlgsz2p9ru6k2KNHJffBI+ZCTOaQJlOVKUnxUYYeZiC71aYTpEQ9ll7v/xIoBkAS31C4gCcrSJBNqO5SJkBtqLX437FYl8PW3kO2/xbNlQ2hJfPvKsiTp0RiIxEMND5bgFMB6hEH6f2XBCX2BaL+Oc7h8JtXQwXeDNhSfyIZtpQwYkRUymcv6u0BhJtJcS6af1//WyyEtu+2AbSSeayH3nDHmC2SDtsrbRT+9tWpBDeoFRaANpT+5j9fSabZgZsDhrDHOUh/WFMOAnLPGHCezkTtPjfkWFWYLrU5CXzt6igYqXbTY2+qjeN2OLq9Gl8XvoqpGuaAvzeMwGgnswpCABGAJkwll5twMGRf8hKoXakYzFZwt3ptEe8EIYN0B9S5RlPmMhlU4VBSlfBYbrXNr9dVc68st/ZPqPOBGH+vFT7h7NuYmbNy6KQ++ND/incqG2uiEv3wTrdMwbxugy2qBVnGsR3dU3OY+CDKfUVUih7T2Aa1VlipJPaf5zE6LFbg15euCmmMZmF1LUdm+tcUg3ky5NsL/fXxUFCNyz9c2UD+CR6YsXzKNqDozr20NHPLzG4u1dYFj49WiwbVellAdWR+AfHxoZHJICwvVtbJ0yTRSpk3UBKnT+v0dctXoAoKLGjYgwQNvq7UL5eKpWO5d7dhrAtI4zQ9sYZVlStKJhKmxm00efVTPla4cAvZw083HsprvDRa9wNmZLhsdNwgEe2XJkRAQsDtOXQFpto8EkM0eY7yacy/fP0nqqz2QOIsOTmKWEpnGkbEe0C0Q3egP8DS4l0+xSvxVzbk6+s+wNZQY0iACRExZXmQap8L2zzfUJMK9tGJzOGb97cFCIL5fQU3wxQELiXx4bZlGILt6I4WDCXuwrJV4AzbrrQgeq/d3hkYqFVzv2lN9MiPyaWW1RIJLZyPGqUzUTpNIZC3AADuXD6o/VkDzRpBrBbiu00DSe14DkzU5GwFn+rtYacVrH6SYXg25tFH4atmonSaZbdhOd0vYdWMIQj3bClYbSaNqWdVNghuv6H53ZntC+qCipTYEdXlV88dvo3aq2vbotOLW7LUCw04wePSV/sbmknOtSy32pnoencLshp7dM/1muxmFwqs1Elzzu0agGBArS5LEetwaaJf8UvRwJs1FgE25sV4Dpa3hIvdlfqLIB/IkhpIItnnKUiPTLG6NzcbvO8w81thoJFgInWqrWbNfRLW02O0thQBA9auLInecRFKzzEioC9krpF1tgNleY8cQU4mN+r9FrOlUC4OPg5emfFKt5JfmGqil/NJs9BZCxL259WVZaBm5kP1uO1A1lkkzyXZv9eFDQ1AMVnPBeMC2VZYimQop7fcPoc7NBu1/vaQnRf+/OJhu0a5dLAx/RDPuNZLOHrAlKEc+CgWKNw/OwyGWW55kKqna3seMYj89iqimxyNjxm1jelhvAXUNThbbnz+pQBmPrurfdNMjL5SRkkMLUC5OLGMylaBHGXkJVpc4remJgAZN0famXq5qGy+dXZJP7ZYNSbsn/NnZ4C3h091Kgq+Y4Yrl2Zpl+YKv6Jssjvx0VdHbXYIO2N9dJT5Aw5SLbqdDBeKlpUtC6GePRXK6hR22AEAEy8YNDIpbxu07qK8HWAwfKonQ28YBRJ+yhMm07bh1sdKMJSORB5fMvg8mZYlKARy7xrev/Hezc+gTgTOfwjId9GYFdlxZ4iSllpZSPgPAZ1JTGAfgnhohI5Zm0dqYNr0OYFgpVHnkF02+cWLXEKoscTJVFihGT0z7JFOu3/xBUAiDabnY6F3JbnQCNezR1WR0NadKk1oe6TO80ZwkpwAPPyEbyemXreo9TAgQAZaaREKHFoKa7peVUXl9pMfF9Dv6r787qTceaHudZ9z6hNx/W7IknS63Iq9L3q1RCFivR0oq+qHM6jCQw6WRKOtv2Lyq8kwNYiBRzVmqZKqECLtgCCMHEEmUBY3gpJbboyO4uUmfFwaWfjNgxKcsYzLNrX0HC+5xqh2lVFjs4+mmH/Ub55f+vi/32kBm4VAg4Lmk5UmmedRRTChfXnbB1Rqae3ES0d9Nva55XVyVHHYEXm9Lasc015afb48N33nWSt1g8Aao8eqDNQCByf5dvCzN6xrmQnh89UqvSlzwUD6QDd109J4QYL0JGA4CEqkMQPUxXOS/Jt8PVt/Y0CpLkwRN2ixKgFfj/p8JknEsDzaiYyxn6Wl15trC63tnVUKnWyLuj2VIZmFohIBmr4wPu9hCLgKgJirhti9gkjUMQBQC+1Qfr8uM5OFej3DysARJOoyZRGI380bfb3D2C/IMcYwM1xqn6J+gWVt9QRKV+hBFEsHjCZefKvKspUjCKz5qL/QCJtWA0eRpFHypEKLXRTtuaxZKz4dDQK8FqdAwWpfMFUKQpUdmlLLzFozFGjfF8xvLvMsD2cYT/sygqF++7Wm3+N1rzmtD7jLK8iQz6i3sqTQ9W0fqJEmyHhJi+Mv3KWvVkBQ+jwEsTTKDKEkPrUo/m/JrnGeg21MDZy72pNys6mqzd428ujepa2wX4IifjWgP01IRZ9XPn5BsPn1t3KWgRHyy2hUtswddrcmte1q2PovkJBua/POWxDIkM+humGPd0t2FXOyGtREhNZQHjyt0Q01TOQjri30ySDK0RuYyzfIhs6gz4J7UsNJgy4oozUJ9DtuKVBC41jvSvW7Sp+9m6r+LX8BTQcuFpPOYaI11DDzEmdhOeQYIgGrxxtKJNi7Rfdbl7OyWAjbe2xpC9DL2EpQSg2gNRCVLicziTOQd9EjBKlsQ3UzSVrZJ9yPGi4u6pv2VbeTR2GYsce9aAWJlqywZMoutzwi9tWIByigMZKgpScM8bzZKAGXqY30pePPdu0heba3ukFw+CtwhWRZkRn9fD9FyuX1mGAmM4YMv2yV+Y7OsHrj38DXSC3EYD+lsAAOlLAUyS4RMe4OSr9XynZ23VArXFzYo1ayw12oB4+6PVzrGIXvGK5Molv9wNUXc+1seZJaK9iLNqMsuKZ3dLQDLBH3sIJc8FHOW/d+XkvIh/8NLJ3YbYPBNsoE7C43A3TX7yDbnCelun/2iOgkFd5Nz2RnkotgcwDNDql68a/4BCrKy/MeMRbz2Ve6/l0sIJOGdJME1AiV6OI0/NNi68p1e43ovGKjIRzYxl0ODU867lgaZwY2+qeBWlOnWZoMi0jg4L5luC9R//as0shuPoLq+vO7NK6UP6A9SnG61ax7nWhZklinRS7+n1Em+QAxR67a/gshu1AmOld4+HIQ0YlzJAGZcWR5kJkKjnszfOaqirTYgp+oK+9tNJ5YP6Mr4oRSgeDmgK6MsFxLCRC1VlK0Mq5cXBClqMoEk2z6beHlf0dnY1OEwJmSxz0MSWqVkaBBvaZCZwMZa8+2x050+v67q5ze0igpsVZ3vmsqp2ZfYaWniBXTL8iH1ZB56WwJkJmVkjkLBcgu0D3vzsC5wO9m6X5TFmu50I0+yJyYV+8A4mmnpgKCzshTIrFUE1fL/Z8saiTfKAHeziOi2rD18W178R8VcQ1c3yWWu5T9mOUjjTcukDdPvnlBLMvg+YOV/iklauBHdUrOE61dMPsVJoqXTBtiPyrIf6USxvdRfwadBJ1CslvUI4mVg/Tyt+BRm2bUsQF09gCgp/z7X1Q5wn2vJkCKUwuKiJ8VvHOmFtcFFCLofRqXNy5uBfPBacKzuoW68SKJZPNQu8fLUciHpQDLrD5WnxZYlEagzi+ID7Yj77Y4Otv04aJsS6fmclJt8xM/JxG0B9F0XOlkV9LdUUOCnwil4KH7a9ffWagLvwTnyJPHUlZPOeSnIGspSIQU1KbKXSSBEyAO3S8hhrzp6zouNjgqMDPo2G33bFK8U2v0aS15ROBVbELQtDVJEmXGfNUlEL5BTJePAdJC2+2YU58CEO/NqwCVTbFzZhFsny4gUgLbu+aWc8I+mKlOqAPOdj05++xtXEDB+lVtnGrfckiIFqqh97b2bCpk+oQcZ0E+pPeYCHg2d4oGy01UG4cnyIXEi2ZnfXoKjuSpY2v28s+kC6LUZmjhkeD3KS01BzJ1LQVAQlaVG0t+WW9c+cLVeiw+WUpchipKdTSt/tcHJBs1leeJuCZEizo0J13XNa3f6SUstCka9Stvy6qnA6e6DCfb47vbcU3xOlTN91Fnl8lcn2lMpY1FUAChh28uY+5SeYHlXga8vWK58hsuYwyXOzWnGl9uG8CQys4GHLbxtQGWF+g7+rs3rzoZuCphr2Af0J0y5F+9HRyUXoYSfj43bSWoIf7BQYAfgR562U59DZcQVJeJGF3xS24jUBynHIvMh1jMOxzUR4DrAEiJFovI+NkDTs1NoEM1eOdW12Mkjo1MfcIBgTQRnW4L0ZnmRIk0Nh93Q2i7Lj9/MZ4lSCpJToE5gVbDZrmy/pMfgxWh62rScdrmUhF4sySwcemYgLijLkhRp1qq76hmqzjH0d9C/HG8u9DM73FH6SKEAYJ4Pb7wtQVJkkakqp8/1ZtMxucrz4Esx706Yruv3J4SHvemSD0Q5kUNsRMaZWn6kyJJW8+7LFopWjeFdpOg2sVktvTYzPoVWIaDTe1ooXm13xkHb2Uvyq7JBO8sMONgiKDGZKNnAWBvSWjaHsZdaO3i2ce4FEFZDfr+c3ixDUmQiaeen5dMTNt1UcOiyP6L+m+epfzrSwPdl8VTPdy5gXu6l6iiG1Oa5kLNkSQGKqsm86/LjNeDBHApL5QVc9ugsc5bkdsUBRuNbmiSdRVo++3i5ZJDQJbqmTCHnrpoxrnHl2b68vpXlx7ra83aIvZZxGpjn3FbyZ2eDuJC5aJ8QtwBA+0eACAU9tem75QZp0KH44cNS5LmXS7iN516WLymEamnJZ7ysfKhqjFCp+J7C1PGj3jTxiFdxjDrd77+VT5Gb5UO6wDwSsMRJIfKW32otXh4KJLGRknunaoTI9mWjPKicFpLvsgfAS7PUSSFDmXcKSphLrRg0nMjgse3f/kKqVA/dnMtcdACWOUmBp622vxSYNxZNc0stABURJ7vV3Ixu1vT/oQKKftoNP50bKi4p/VKE89kLpNnQ4JtxlJY/iRvU1CV36+diZfR3qUVUXs5OPqOtXAO5XBwcflVpexjzfTVLCk2bomcTYZWrs5o2T/m74wyZy/KzsbFaZZENQvevOwgdgam5ZjRABD7HeA4lNEu+sU1RD3ETepEVQL4L3YgbvkQ2TNMv073EVi//0O//UEDPQ8q2L7872fYCxKUmsfXGJcKHpih0wHbV2vzdy/ZUss80ASFnjTJawdeJy1vLekNtdqip51eq6YGSi6PIB1KdA6VtIKoXRfC9oC9tpPXAYXh7irkpZBGbgcnyBcISDoktH75yOvj2+IrbkJ2HxmuCAnbFjpJA5NOTitPQOHYH/zGaqZ4KulaW6AZyy58UedRKqJ9qpsekWOzA6k51n83bQX5U39Harg7krBIvimAeD0Fx4TWTWxKlyKGO1eKEF3qtRD9ss11imkMPu1FSNguBRYEauL8l9BkENA7rruSW4Ehxe6SWIzgpVoX1UEDeo05luQJtcTVvcdXL12KzKZb7YcqnGch18e3S/uFTJe2pDPVVLwRYtS6l6ER//y+6WtVmx6VKI1FcPhcbeLz0pLYyn8AJp7AB2DlfJhPHgTm2WjtMzLugi1xjokaPySjJtkf6q4ZAaa35AZJXbmmUMoraXSr77oznK9jMUEgJJuWqNAjh/5MDFvATbj9gDAFzy5+UUdKGpWmBfcAM+1EsKMbrV6vnfrKqXl66dGnlRcJpIEoDGla5pU5SN9oqjlojp0dqHEcqojDEgdGaAAyjXbyUR9Mhqhk4b7nlTkq4pxpOMO8fbqqXbYkeMuOHw0OT5h0t6Kks8f72mrXEh4iTa2FkVxfC9zi3J4qMJlrzA+lQ4JVSX0Bt7Z43KH3fXBh015GRT+7QZulOjSY4A1nipEQv2jAB8D5AnIae9ZbqlhCKEtBEbcr97c+fxWJPxCH1siZR+dDcH37JuSVNyjjNupCJCsXs/Xbxi1FcGVycf5frbs7HfnS3B0ZW0qc0irnnd8k3IGVY1qSkds/ipVh4Y/z0VMNdjvq2wMh/bA1DcFb/Xu/XapHXAjkLh0Bukt9Y0h6opUvM9Ec2Lao/cOEMgWVvc+pdodWH9sdrwsvIVcvaOKoPxeexgTkJ87SHJm3MP8FRiqjB/v22Dm5Lg7yb0n9ijbS3jvRa1zTWUi72JB/JhuV2SHs2Z/O9S3YA4OEwRRhrCqo//Nn2Ge+sJ4buRXgVg14yAJTlljQpk1S1PLedKWRB5hT0lTVkYLuEOPBHiaTPN5YNmdknfBgbphMpjZnkqt7pdgPkzUQPsxhbYzzb/89m27pGdM34uUa0NEkJbt3eDLuZqUWxioLJ9vlNl4rt3BjNQH1Q6qd+vi1iyCcFg6vc8iWhpSg7Yxk2jxyBq7S/eNCX3uWUmiY+YDsW+XLKyCCTWa6kpFo+79SJ1Htc1x9UJoo0Ce7r30gjYGo1ieM/IFubDlpu8MFszKZnknSJ7rCQaUT+6QcFjVqJ5f+gDtC6oz0FJz9GS854hIGNdm6ZkzJLLaNli9Ei3/E4kaIxsxg2bvQhIiSDHqCoyyxTUmbCeKQ2+jFmHQsmS7gHSYYsiZEnoP/2em9zRFHAb2jknq1nfDIbs+GtYHOIBtnTz16U7FAqqGm83W468oN3FAWYy4nAuSdz5VVQAx3lnoYCHZVbriR4ibIVS3oJ7n+WFBVYxDLXMvE2/Tf4d8cUIvZphRoYp6vxQAFguZJSxK1FA2qSxW4Jeb0S1PrwAJ4ImF1w3mqC7YPbhE+UypMhMFnM79HGcpG2Vm6aIXyyguYpfU3CupQa7haQbVRid3vZ0AvfkmvTW1d7jQ7Akiapue7MjufQPj6Fqy1EC+I4uFt9gFpmDXd4fbxnWS59zIlSViF0lkp8HhvEhexYlherJ7TX395RUbMdmFBUOfXwEVphe3R6h+lAvSrmNVU7fmB33oi45rWcWCxZUoo864EkAah7Xm3/QHy5GSI1h/lePW9qtCMd5q3XPEuXbs4VG16Z5UdS4DYAbq7J8PctlsUbPxxqZe5hv7fUYmmTevuyKNZNouuotHq5gGUAs7iBCHhjlihJpUmcGOXq+Ty4AdEXM8g07e+PGT9K7b2jDPACRoohH/eM35YN4ZLqqt7UQduRjyhRZD0bjftVjbfVd9GQPv7t2aCSvn42NmgrDBvapPalXGpwewab61ct+WslAAdWD6kP7zcd1ItF5re0SKkywyPjbSdbSa9RS6a5NkvkC7WoTAf5d1gWOahQgpmMZUdSPRmlHUhUjXh9WRTwSxcpvb+3UptrWS+kT7D3HgFS5kOiQJApyi1XUuZxxwxhUf0pnkpGs78/8dpP5KnG35vD/YWsPoVtwfs110iCL7lqTxVb4bQ1XuNjs7ChOpkLSasEsOAeoM8oS32s7kU+aDjATyhvz5LaXZ8xRCpLAJFkBH77qhFNMkK2y80zBKcObWJ8FEFVNKTEAUppbhmTkg5ozJOL7YJORIFolIWh8UTVJHKA3Z7qVe1aq0sf72TJX56TWoIvz5ImYeIoe3NHQLTKUYaBMVD2XfMTjZrgNW2vC/BCj4hsaBLKLa4lTcpcig6Ha1VvXnf0Mn7+fK6LDXUMsGWpftnO0k2+8VnVaFd5l6AsqjbLl1Rh1FpF4LJCTDcwdvYgw3q03cKHDqgVkV0CPHyg1B4ojVs64Kp4ppL7A8pSMImRAfTseTIAsc3t4idb7B2AbL0CJUjJ7s1DzJcosyfqUErtWr3B2K1h8hUH5wvcoL8uIIUcqmehk5BbzqSirGo3Rl+r9wY+ltD9eyiqRTcyHqn+fTRApe5uB+yPckuXVKEyjrusYA8psEn5q0b1T7noYoVDfi686YkXS/4RD+DFcsuWVBGsRe0AGdGRUm4sEysKZhJbycVjv80WXjAIDcxwzdkQrC1RErx9u7ti2S2QR1meKJLaCbEjq/9X08icuaSup8MdkaVJqshykLhIOqdw/cY2GlSN9RFas7ou6b/WQsZDL48RPa9xebDg4lhypAJ6r6nzt/DzuGYuVJ4jeTTOOfq5sE0TayQeEqE8Bkh5zN+WyzwHAciSIxUAZabxAMqQLTSA3M+AfVwX1bODyjI57fwuoN/FYRz6dLBiSGsbeIjckiRVbEXIjQoCoMflfAfmSKRhWUW7vf6i/YfMvK0/mFR+q+Khg6HatjRJlaTWRhpq2joujlKMjxuw9trmsu1/b0ssbfdxNTLyAdYLFgUcorfmliVJR2otGU2OxYyPsTVUc9+/ak5UV1fyP1He5uGQPTnP3S1nUiVCNc9M7wC0DeIfSrihkMxStrh/1nlyCABFPnZoGEgkzqpEv0QbvROpOpC/F4g50F9JYRLAdhVcwz3PAiPqxfb9abu37PdzRJFD4DrOb5YhSTGmHZDcFhv6sgprfggJtSiRwd2/eQquofbaw80KzO+LqHpZtaH2Tt01HMK5pUoCBdaOS0rqzfCX0KfF9gSHSITZdrEu+vKcaeQDcAcEOHY+LkCAc8uVVGmWtspprOB0wsU+/RQJ/cJlS8IfwG75TE1TNXSb4NiSW5akSkXcCoPUVG0E12XxSh1sTI1Sd1jCG4yDSYmH7I72QnOrpgGnmVuepGJ7HP7WsJBYBjNqprlno7INNrsGCDEsT+B1s9mY1blo52dj43iW2K3SbsVDLJZOgHZi1tdx4Zpyb7rtJW6tt8iuKpLLEsuPpL/NKhRze1ZDdDOYLndzViXKE22q2cqTn2xXTV+5Vy7FmU/BnQy5WnMGtixJBYJ0X1u6XC0LnnDFlG6oeizYQ+60/sXNLxLwwxW8AOdlcLXmNBeCCOoH2lBuXRk+kw3ZWa4sPXoFeCRG/Eh0MPi7LFa/7Ob2r9WSxCB+lHtJS5NUQiZpp9s+4YlklGGbXM3Rs/Vg0adAAupq5XbS/NBJ8cZoEpX6zG6xrHQnYF5WWqKkElYYpKFIYCtYorZMjKCDLgm2cJTrIm28mlvMkAfEXFEIWHqkkrEMuzPkG1g41JDTyCB1+cRbpY573c0OaMA9ppZPZIyHAjUof7nlR+JArQPRHIJtbzxCWmKLmwR3m01lF4Bnc7b368s6hz5eEiockgPmcY1lRSopYlPq1jsofG43FGqWWi2J8v5J446k31ajd6/RSF3XQeHl4KBJ9q5uiZdulhepqALviCUZS3RwkigWgNIv6Twba7tjno5mtvYflxeaPcuHXCwBa80tPVKp2EDrmlpENytp7ETWXVcsLoU97h62zk/whlnuQ2JOuWVIKmVpW+cVpTMNHUXeC07K8rkrmnpcKMELWaKp0q4Qzo/KhnCK4c2hzlaUSTc1VRrP23fOTvj0TBhoOIl/oQYoxZAKDwcDy5JUKm+b8AtKd3Svi9Y1OpOyQZSbbvNQzdVn6Z3p1zbgt5VbgiSO04FENoM36pEg24oDUQ1FCa5YGBkHxtcevDYKmJ6iV+6dAK+VLVNS5VHcd3XA0HTJ0LoUEBxKtu+1ESeYbBfbzes+LlF5XW52B3AyklHZWqKkyuMkMRF8WUFdlgEmdJRZ8Qu2kVq+Abyt5tocMpIz6YUrS4foUQwwsTRJOpLozt7o4SyQzhjJkcbU9u5Zft7UdLjgOx0WgLezEeTeylGYJj6T7lwrO7v4EihzLTtS5YldeoE5Rg3I7wVrXVJD2Usrfx0uoSjjhgbxVtkSJfMwMWQJXnsBkAibMIweZTCpyz3W1gIq1H3yhvJymZGDoHbcJUuUzENlnJNYQVFXbTsuAgNWTGvOY9yKFgPSBNLLijxXg/axCASWLJlHkYx6mgkXxeqJna9UMGHRooZN/lpsl8904D563MupLGXlDefQFAWKpUjmURaG3QBQUwSgch8S00loQf/6QGBtNLCp/TpX+QCmM4YCD+nc5pYcSYeK5N504pp/LNxRgunuuYABRkf0ZvzxoTE6e6iJ3Cc25Vx+u4o5HslZnmQed7yIqc1dazdAKJTI3jrwhDLNIcVd+TpZu3cU3HJbdmSehG1h+aNYv6NiavCBVJ4I3HUWbLKqbn/zhBp5EtdSACnXciQp77TLrnsKS/TitCawTIAPLP69acfwUBBd7jOlMy80lxzUuUEAsPzInMVOmv1tDdkGrQ06Eun/VnjL49Xl2uXFNXvDNbIcSfo2k6QfJulyQzkpDWUUnNSL1rugK97QjwLSd/2eufsCVG+WK0lHaitwwN6qRhiY5Qp1JrMDyr+zUdP0RJeeBC/hLFcypz67VU8GZ6UMbrdIvmumUvGsubTjb2f15pPbsiHpbckvzQbtNOt4lBYrxiyW88YWndqBRp+/I8V3Uu55tQvhQyYV2aCmIz8gG7nTvJVRBb8VUxOATKhTwHi53P6iA22sasOqBomsh98KfYbweT4kn8gdgKVKwqi9w2jnALRGK0R1SmLViboYhetiTXfrhV7W7ffRbfmroP6bClKv/jsaUt7jksQyJSnlyK7kdVUEtxSR6wV9bFneuF9MLOD9ryc5Gs7lgk8xZMkSJnORtvvcy5KVgC5r7CtSWE6Nl+/sM3uUDOyjyZvGQ4xELgPAlPx//wM=\", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t]),", - " Source2 = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(\"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\", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t]),", - " Source = Table.Combine({Source1, Source2}),", - " #\"Split Column by Delimiter1\" = Table.SplitColumn(Source, \"Column1\", Splitter.SplitTextByDelimiter(\",\", QuoteStyle.Csv), {\"Column1.1\", \"Column1.2\", \"Column1.3\", \"Column1.4\", \"Column1.5\", \"Column1.6\", \"Column1.7\", \"Column1.8\", \"Column1.9\", \"Column1.10\", \"Column1.11\", \"Column1.12\", \"Column1.13\"}),", - " #\"Promoted Headers\" = Table.PromoteHeaders(#\"Split Column by Delimiter1\", [PromoteAllScalars=true]),", - " #\"Replaced Value\" = Table.ReplaceValue(#\"Promoted Headers\",\"NULL\",null,Replacer.ReplaceValue,{\"CustomerKey\", \"Gender\", \"Name\", \"Address\", \"City\", \"State Code\", \"State\", \"Zip Code\", \"Country Code\", \"Country\", \"Continent\", \"Birthday\", \"Age\"})", - "in", - " #\"Replaced Value\"" - ], - "kind": "m", - "lineageTag": "cd034b2b-6d5e-4420-9158-90f5ead53ec3", - "queryGroup": "Raw Data" - }, - { - "name": "RAW-Store", - "annotations": [ - { - "name": "PBI_NavigationStepName", - "value": "Navigation" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "expression": [ - "let", - " Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(\"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\", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t]),", - " #\"Split Column by Delimiter\" = Table.SplitColumn(Source, \"Column1\", Splitter.SplitTextByDelimiter(\",\", QuoteStyle.Csv), {\"Column1.1\", \"Column1.2\", \"Column1.3\", \"Column1.4\", \"Column1.5\", \"Column1.6\", \"Column1.7\", \"Column1.8\", \"Column1.9\"}),", - " #\"Promoted Headers\" = Table.PromoteHeaders(#\"Split Column by Delimiter\", [PromoteAllScalars=true]),", - " #\"Replaced Value\" = Table.ReplaceValue(#\"Promoted Headers\",\"NULL\",null,Replacer.ReplaceValue,{\"StoreKey\", \"Store Code\", \"Country\", \"State\", \"Name\", \"Square Meters\", \"Open Date\", \"Close Date\", \"Status\"})", - "in", - " #\"Replaced Value\"" - ], - "kind": "m", - "lineageTag": "524dec4a-0544-44a7-8d45-cd6794082fe6", - "queryGroup": "Raw Data" - }, - { - "name": "RAW-CurrencyExchange", - "annotations": [ - { - "name": "PBI_NavigationStepName", - "value": "Navigation" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "expression": [ - "let", - " Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(\"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\", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t]),", - " #\"Split Column by Delimiter1\" = Table.SplitColumn(Source, \"Column1\", Splitter.SplitTextByDelimiter(\",\", QuoteStyle.Csv), {\"Column1.1\", \"Column1.2\", \"Column1.3\", \"Column1.4\"}),", - " #\"Promoted Headers1\" = Table.PromoteHeaders(#\"Split Column by Delimiter1\", [PromoteAllScalars=true])", - "in", - " #\"Promoted Headers1\"" - ], - "kind": "m", - "lineageTag": "78c5038c-6adf-4b1f-85fa-879cb022b266", - "queryGroup": "Raw Data" - }, - { - "name": "RangeStart", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "DateTime" - } - ], - "expression": "#datetime(2020, 1, 1, 0, 0, 0) meta [IsParameterQuery=true, List={#datetime(2020, 1, 1, 0, 0, 0)}, DefaultValue=#datetime(2020, 1, 1, 0, 0, 0), Type=\"DateTime\", IsParameterQueryRequired=true]", - "kind": "m", - "lineageTag": "a1a6bc94-b59e-4ec1-9b6d-cd637ebafff5" - }, - { - "name": "RangeEnd", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "DateTime" - } - ], - "expression": "#datetime(2023, 2, 1, 0, 0, 0) meta [IsParameterQuery=true, List={#datetime(2023, 12, 31, 0, 0, 0)}, DefaultValue=#datetime(2023, 12, 31, 0, 0, 0), Type=\"DateTime\", IsParameterQueryRequired=true]", - "kind": "m", - "lineageTag": "3230a904-a045-4d7e-9424-64fc69c93735" - }, - { - "name": "RAW-SalesDateAdjustedAndSalesRandomized", - "annotations": [ - { - "name": "PBI_NavigationStepName", - "value": "Navigation" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "expression": [ - "let", - " Source = #\"RAW-Sales\",", - " #\"Changed Type\" = Table.TransformColumnTypes(Source,{{\"Order Date\", type datetime}, {\"Delivery Date\", type datetime}}),", - " minDate = List.Min(#\"Changed Type\"[Order Date]),", - " numYearsOnDummyData = 3,", - " yearsDiff = (Date.Year(DateTime.LocalNow()) - numYearsOnDummyData) - Date.Year(minDate),", - " #\"AdjustDates\" = Table.TransformColumns(#\"Changed Type\",{{\"Order Date\", each Date.AddYears(_, yearsDiff)}", - " , {\"Delivery Date\", each Date.AddYears(_, yearsDiff)}", - " }),", - " #\"Filtered Rows\" = Table.SelectRows(AdjustDates, each true),", - " #\"Changed Type2\" = Table.TransformColumnTypes(#\"Filtered Rows\",{{\"Quantity\", Int64.Type}, {\"Unit Price\", type number}, {\"Net Price\", type number}, {\"Unit Cost\", type number}}),", - " #\"Added Custom\" = Table.AddColumn(#\"Changed Type2\", \"NewQuantity\", each Number.RoundDown(Number.RandomBetween(", - "List.Max({1, [Quantity] - ([Quantity] * Randomizer)})", - ",", - "List.Min({[Quantity], [Quantity] + ([Quantity] * Randomizer)", - "})", - ")", - ")),", - " #\"Added Custom1\" = Table.AddColumn(#\"Added Custom\", \"NewNetPrice\", each Number.Round(", - "Number.RandomBetween(", - "List.Max({1, [Net Price] - ([Net Price] * Randomizer)})", - ",", - "List.Min({[Net Price], [Net Price] + ([Net Price] * Randomizer)", - "})", - "), 3)),", - " #\"Removed Columns\" = Table.RemoveColumns(#\"Added Custom1\",{\"Quantity\", \"Net Price\"}),", - " #\"Renamed Columns\" = Table.RenameColumns(#\"Removed Columns\",{{\"NewQuantity\", \"Quantity\"}, {\"NewNetPrice\", \"Net Price\"}})", - "in", - " #\"Renamed Columns\"" - ], - "kind": "m", - "lineageTag": "0bd410b3-1a0d-49c7-8952-7a7465778b08", - "queryGroup": "Raw Data" - }, - { - "name": "Environment", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "Text" - } - ], - "description": "Dummy parameter to simulate data from different servers", - "expression": "\"DEV\" meta [IsParameterQuery=true, List={\"DEV\", \"QUAL\", \"PRD\"}, DefaultValue=\"DEV\", Type=\"Text\", IsParameterQueryRequired=true]", - "kind": "m", - "lineageTag": "64edd943-1a90-4438-b62f-bb95a9da1510" - }, - { - "name": "Randomizer", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "Number" - } - ], - "expression": "0.7 meta [IsParameterQuery=true, Type=\"Number\", IsParameterQueryRequired=true]", - "kind": "m", - "lineageTag": "c92d2f56-fbbe-4162-b5ce-373932bf5cf2" - } - ], - "perspectives": [ - { - "name": "Sales", - "tables": [ - { - "name": "Sales", - "columns": [ - { - "name": "Quantity" - }, - { - "name": "Order Number" - }, - { - "name": "Line Number" - }, - { - "name": "Order Date" - }, - { - "name": "Delivery Date" - }, - { - "name": "CustomerKey" - }, - { - "name": "StoreKey" - }, - { - "name": "ProductKey" - }, - { - "name": "Net Price" - }, - { - "name": "Unit Cost" - }, - { - "name": "Currency Code" - }, - { - "name": "Exchange Rate" - }, - { - "name": "Environment" - } - ], - "measures": [ - { - "name": "# Customers (with Sales)" - }, - { - "name": "# Products (with Sales)" - }, - { - "name": "Sales Qty" - }, - { - "name": "Sales Amount" - }, - { - "name": "Sales Amount (Δ LY)" - }, - { - "name": "Sales Amount (LY)" - }, - { - "name": "Sales Amount (YTD, LY)" - }, - { - "name": "Sales Amount (YTD)" - }, - { - "name": "Sales Amount Avg per Day" - }, - { - "name": "Sales Amount (% Δ LY)" - }, - { - "name": "Margin" - }, - { - "name": "Margin (ly)" - }, - { - "name": "# Sales" - }, - { - "name": "Sales Qty by Delivery Date" - }, - { - "name": "Sales Amount (12M average)" - }, - { - "name": "Sales Amount (6M average)" - } - ] - }, - { - "name": "Product", - "columns": [ - { - "name": "Product" - }, - { - "name": "ProductKey" - }, - { - "name": "Product Code" - }, - { - "name": "Manufacturer" - }, - { - "name": "Brand" - }, - { - "name": "Color" - }, - { - "name": "Weight Unit Measure" - }, - { - "name": "Weight" - }, - { - "name": "Unit Cost" - }, - { - "name": "Unit Price" - }, - { - "name": "Subcategory Code" - }, - { - "name": "Subcategory" - }, - { - "name": "Category Code" - }, - { - "name": "Category" - } - ], - "measures": [ - { - "name": "# Products" - } - ] - } - ] - } - ], - "queryGroups": [ - { - "annotations": [ - { - "name": "PBI_QueryGroupOrder", - "value": "0" - } - ], - "folder": "Raw Data" - } - ], - "relationships": [ - { - "name": "d4e6dc5a-6f46-443d-ab94-4cc0e10323c6", - "fromColumn": "CustomerKey", - "fromTable": "Sales", - "toColumn": "CustomerKey", - "toTable": "Customer" - }, - { - "name": "434e79a9-f527-481f-accd-8bc60ed1370e", - "fromColumn": "Order Date", - "fromTable": "Sales", - "toColumn": "Date", - "toTable": "Calendar" - }, - { - "name": "21bd108e-527d-4566-be7d-9e474c858ee0", - "fromColumn": "Delivery Date", - "fromTable": "Sales", - "isActive": false, - "toColumn": "Date", - "toTable": "Calendar" - }, - { - "name": "bb5c5591-a0ff-4ce4-a62e-6c5f56006368", - "fromColumn": "ProductKey", - "fromTable": "Sales", - "toColumn": "ProductKey", - "toTable": "Product" - }, - { - "name": "55a6f513-c6f2-4d1c-b8aa-46edeaeb23f2", - "fromColumn": "StoreKey", - "fromTable": "Sales", - "toColumn": "StoreKey", - "toTable": "Store" - } - ], - "roles": [ - { - "name": "Stores Cluster 1", - "annotations": [ - { - "name": "PBI_Id", - "value": "3c40ccf098eb4253ad31f8c679e140d3" - } - ], - "modelPermission": "read", - "tablePermissions": [ - { - "name": "Store", - "filterExpression": "'Store'[Store Code] IN {\"1\",\"2\",\"4\"}" - } - ] - }, - { - "name": "Stores Cluster 2", - "annotations": [ - { - "name": "PBI_Id", - "value": "160d7a327dfd4ca2996841bfdde3567d" - } - ], - "modelPermission": "read", - "tablePermissions": [ - { - "name": "Store", - "filterExpression": "'Store'[Store Code] IN {\"10\",\"11\",\"15\",\"8\"}" - } - ] - } - ], - "sourceQueryCulture": "en-US", - "tables": [ - { - "name": "Calendar", - "annotations": [ - { - "name": "PBI_Id", - "value": "9eaa3654-c4d6-42e5-a057-348df1b3f460" - }, - { - "name": "LinkedQueryName", - "value": "Calendar" - }, - { - "name": "PBI_QueryRelationships", - "value": "{\"columnCount\":25,\"keyColumnNames\":[],\"queryRelationships\":[],\"columnIdentities\":[\"Section1/Calendar/ChangedType.{Column1,0}\",\"Section1/Calendar/Changed Type.{DateId,1}\",\"Section1/Calendar/Changed Type.{Day,2}\",\"Section1/Calendar/Changed Type.{Week Day (#),3}\",\"Section1/Calendar/Capitalized Each Word.{Week Day,15}\",\"Section1/Calendar/Changed Type.{Week,5}\",\"Section1/Calendar/Capitalized Each Word.{Month (Long),8}\",\"Section1/Calendar/Capitalized Each Word.{Month,9}\",\"Section1/Calendar/Changed Type.{Month (#),8}\",\"Section1/Calendar/Changed Type.{Quarter,9}\",\"Section1/Calendar/Changed Type.{Semester,10}\",\"Section1/Calendar/Changed Type.{Year,11}\",\"Section1/Calendar/Changed Type.{Week (Year),12}\",\"Section1/Calendar/Capitalized Each Word.{Month (Year),11}\",\"Section1/Calendar/Changed Type.{Quarter (Year),14}\",\"Section1/Calendar/Changed Type.{Semester (Year),15}\",\"Section1/Calendar/Changed Type.{WeekYearId,16}\",\"Section1/Calendar/Changed Type.{MonthYearId,17}\",\"Section1/Calendar/Changed Type.{QuarterYearId,18}\",\"Section1/Calendar/Changed Type.{SemesterYearId,19}\",\"Section1/Calendar/Changed Type.{Day (Relative),20}\",\"Section1/Calendar/Changed Type.{Month (Relative),21}\",\"Section1/Calendar/Changed Type.{Year (Relative),22}\",\"Section1/Calendar/Changed Type.{Holiday,23}\",\"Section1/Calendar/Changed Type.{Work Day,24}\"],\"ColumnCount\":25,\"KeyColumnNames\":[],\"ColumnIdentities\":[\"Section1/Calendar/ChangedType.{Column1,0}\",\"Section1/Calendar/Changed Type.{DateId,1}\",\"Section1/Calendar/Changed Type.{Day,2}\",\"Section1/Calendar/Changed Type.{Week Day (#),3}\",\"Section1/Calendar/Capitalized Each Word.{Week Day,15}\",\"Section1/Calendar/Changed Type.{Week,5}\",\"Section1/Calendar/Capitalized Each Word.{Month (Long),8}\",\"Section1/Calendar/Capitalized Each Word.{Month,9}\",\"Section1/Calendar/Changed Type.{Month (#),8}\",\"Section1/Calendar/Changed Type.{Quarter,9}\",\"Section1/Calendar/Changed Type.{Semester,10}\",\"Section1/Calendar/Changed Type.{Year,11}\",\"Section1/Calendar/Changed Type.{Week (Year),12}\",\"Section1/Calendar/Capitalized Each Word.{Month (Year),11}\",\"Section1/Calendar/Changed Type.{Quarter (Year),14}\",\"Section1/Calendar/Changed Type.{Semester (Year),15}\",\"Section1/Calendar/Changed Type.{WeekYearId,16}\",\"Section1/Calendar/Changed Type.{MonthYearId,17}\",\"Section1/Calendar/Changed Type.{QuarterYearId,18}\",\"Section1/Calendar/Changed Type.{SemesterYearId,19}\",\"Section1/Calendar/Changed Type.{Day (Relative),20}\",\"Section1/Calendar/Changed Type.{Month (Relative),21}\",\"Section1/Calendar/Changed Type.{Year (Relative),22}\",\"Section1/Calendar/Changed Type.{Holiday,23}\",\"Section1/Calendar/Changed Type.{Work Day,24}\"],\"RelationshipInfo\":[]}" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "columns": [ - { - "name": "Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - }, - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"RELATIONSHIP_COLUMNS_SHOULD_BE_OF_INTEGER_DATA_TYPE\"]}" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isDateTimeCustom\":true}" - } - ], - "changedProperties": [ - { - "property": "FormatString" - } - ], - "dataType": "dateTime", - "formatString": "yyyy-mm-dd", - "isKey": true, - "lineageTag": "ede68123-9903-412b-8747-d0cb8117ed41", - "sourceColumn": "Date", - "summarizeBy": "none" - }, - { - "name": "Day", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "15ee1357-a55a-4206-b87f-4537950cefd1", - "sourceColumn": "Day", - "summarizeBy": "none" - }, - { - "name": "Week Day", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "SortByColumn" - } - ], - "dataType": "string", - "lineageTag": "b249ac49-0964-4b34-ab62-786c1f6d7709", - "sortByColumn": "Week Day (#)", - "sourceColumn": "Week Day", - "summarizeBy": "none" - }, - { - "name": "Week", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "0b47fd7c-b526-4a87-8304-50f9da4c79ed", - "sourceColumn": "Week", - "summarizeBy": "none" - }, - { - "name": "Month", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "SortByColumn" - } - ], - "dataType": "string", - "lineageTag": "229b9d13-a91e-42b0-9d59-bf1aa3568da7", - "sortByColumn": "Month (#)", - "sourceColumn": "Month", - "summarizeBy": "none" - }, - { - "name": "Quarter", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "0d2804c4-111e-4544-aaf6-c6a6988e49a1", - "sourceColumn": "Quarter", - "summarizeBy": "none" - }, - { - "name": "Semester", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "1fa538c1-2ae2-4bbf-81ba-ef222e5944ed", - "sourceColumn": "Semester", - "summarizeBy": "none" - }, - { - "name": "Year", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "e2e54129-d9ea-422d-a07a-747ce9981020", - "sourceColumn": "Year", - "summarizeBy": "none" - }, - { - "name": "Week (Year)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "SortByColumn" - } - ], - "dataType": "string", - "lineageTag": "6977be25-6a48-4eac-846f-85a3c3c271fe", - "sortByColumn": "WeekYearId", - "sourceColumn": "Week (Year)", - "summarizeBy": "none" - }, - { - "name": "Month (Year)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "SortByColumn" - } - ], - "dataType": "string", - "lineageTag": "557c5e05-696f-4418-85cc-b086c08c3d61", - "sortByColumn": "MonthYearId", - "sourceColumn": "Month (Year)", - "summarizeBy": "none" - }, - { - "name": "Quarter (Year)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "SortByColumn" - } - ], - "dataType": "string", - "lineageTag": "c0e2abff-7224-4166-b0a9-2fef49976c82", - "sortByColumn": "QuarterYearId", - "sourceColumn": "Quarter (Year)", - "summarizeBy": "none" - }, - { - "name": "Semester (Year)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "SortByColumn" - } - ], - "dataType": "string", - "lineageTag": "b2b0b830-1b3e-4e9e-b87d-585f4a23deeb", - "sortByColumn": "SemesterYearId", - "sourceColumn": "Semester (Year)", - "summarizeBy": "none" - }, - { - "name": "WeekYearId", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isHidden": true, - "lineageTag": "90bba6d9-d308-40f6-826b-e4d5ca8fda78", - "sourceColumn": "WeekYearId", - "summarizeBy": "none" - }, - { - "name": "MonthYearId", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isHidden": true, - "lineageTag": "81e0ba4a-f927-4152-843a-dfc0e3bd858c", - "sourceColumn": "MonthYearId", - "summarizeBy": "none" - }, - { - "name": "QuarterYearId", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isHidden": true, - "lineageTag": "72f566a1-f253-4c96-ba97-47c76d5ce07c", - "sourceColumn": "QuarterYearId", - "summarizeBy": "none" - }, - { - "name": "SemesterYearId", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isHidden": true, - "lineageTag": "9c42bf51-c759-439c-b02e-1861c41f56c4", - "sourceColumn": "SemesterYearId", - "summarizeBy": "none" - }, - { - "name": "Week Day (#)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "5559c18c-3ef4-4f17-bce2-e0a7cd5e6dea", - "sourceColumn": "Week Day (#)", - "summarizeBy": "none" - }, - { - "name": "Month (#)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "337dbbc4-139c-47a8-bb39-85c6deba31c8", - "sourceColumn": "Month (#)", - "summarizeBy": "none" - }, - { - "name": "Day (Relative)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "6eee588e-1d25-4994-a6f6-1f94ef196fef", - "sourceColumn": "Day (Relative)", - "summarizeBy": "none" - }, - { - "name": "Month (Relative)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "f884824e-2aa5-4a3c-9b59-89e31181a6bb", - "sourceColumn": "Month (Relative)", - "summarizeBy": "none" - }, - { - "name": "Year (Relative)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "f9ccaf5a-3f08-42f5-bce9-2f67e78a4173", - "sourceColumn": "Year (Relative)", - "summarizeBy": "none" - }, - { - "name": "Work Day", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "f58c6828-fdf1-4c2a-9e2f-59454b2f43b5", - "sourceColumn": "Work Day", - "summarizeBy": "none" - }, - { - "name": "DateId", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - }, - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"ISAVAILABLEINMDX_FALSE_NONATTRIBUTE_COLUMNS\",\"UNNECESSARY_COLUMNS\"]}" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isHidden": true, - "lineageTag": "5e5b06be-4f4f-4624-a922-74de16645764", - "sourceColumn": "DateId", - "summarizeBy": "none" - }, - { - "name": "Month (Long)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "SortByColumn" - } - ], - "dataType": "string", - "lineageTag": "b27d8a5e-c4bd-4a82-a4c8-bb402aefa873", - "sortByColumn": "Month (#)", - "sourceColumn": "Month (Long)", - "summarizeBy": "none" - }, - { - "name": "Week (Relative)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "9e0d9793-9a33-451a-bc05-24d7878c8964", - "sourceColumn": "Week (Relative)", - "summarizeBy": "none" - }, - { - "name": "Week Start Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - } - ], - "dataType": "dateTime", - "formatString": "yyyy-mm-dd", - "lineageTag": "138a3a63-bb64-469c-a94e-1a65757bd1d1", - "sourceColumn": "Week Start Date", - "summarizeBy": "none" - }, - { - "name": "Week End Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - } - ], - "dataType": "dateTime", - "formatString": "yyyy-mm-dd", - "lineageTag": "c91844bb-d8e2-49b1-97e8-7ff79f35faf6", - "sourceColumn": "Week End Date", - "summarizeBy": "none" - }, - { - "name": "Month Start Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - } - ], - "dataType": "dateTime", - "formatString": "yyyy-mmm", - "lineageTag": "9398fb3c-c0de-4357-afc0-69df70186f6d", - "sourceColumn": "Month Start Date", - "summarizeBy": "none" - }, - { - "name": "Date (Year-Month)", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isCustom\":true}" - } - ], - "dataType": "dateTime", - "expression": "DATE([Year], [Month (#)],1)", - "formatString": "mmm yyyy", - "isDataTypeInferred": true, - "lineageTag": "0e65a7a5-6c15-437d-8333-10653d8d673f", - "summarizeBy": "none", - "type": "calculated" - } - ], - "dataCategory": "Time", - "description": "Calendar table", - "hierarchies": [ - { - "name": "Year-Month-Day", - "levels": [ - { - "name": "Year", - "column": "Year", - "lineageTag": "5d3a5413-d08c-47e5-866e-894c288c29b0", - "ordinal": 0 - }, - { - "name": "Month", - "column": "Month", - "lineageTag": "4009bc63-dec6-46e9-84eb-679578b38105", - "ordinal": 1 - }, - { - "name": "Day", - "column": "Day", - "lineageTag": "6c697b75-14d3-4471-b6ad-80d79cd35f23", - "ordinal": 2 - } - ], - "lineageTag": "d00d0618-e29f-486f-a4d4-dea56417f06b" - } - ], - "lineageTag": "bfa0074c-0a44-4a9c-8bba-c87af778b3d7", - "partitions": [ - { - "name": "Calendar-c9bc757b-0dad-4b99-8287-18a451a3c5c3", - "annotations": [ - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"MINIMIZE_POWER_QUERY_TRANSFORMATIONS\"]}" - } - ], - "mode": "import", - "source": { - "expression": [ - "let ", - "", - " P_Today = DateTime.LocalNow(),", - " P_StartDate = #date(Date.Year(List.Min(#\"RAW-SalesDateAdjustedAndSalesRandomized\"[Order Date])), 1, 1),", - " P_EndDate = #date(Date.Year(List.Max(#\"RAW-SalesDateAdjustedAndSalesRandomized\"[Order Date])), 12, 31), ", - " P_FirstDayOfWeek = 1,", - " P_IsCarnivalHoliday = true,", - " P_UseIsoWeek = true,", - " P_Culture = \"en-US\",", - " DayCount = Duration.Days(Duration.From(P_EndDate - P_StartDate)) + 1,", - " Source = List.Dates(P_StartDate,DayCount,#duration(1,0,0,0)),", - " TableFromList = Table.FromList(Source, Splitter.SplitByNothing()), ", - " ChangedType = Table.TransformColumnTypes(TableFromList,{{\"Column1\", type date}}),", - " RenamedColumns = Table.RenameColumns(ChangedType,{{\"Column1\", \"Date\"}}),", - " InsertId = Table.AddColumn(RenamedColumns, \"DateId\", each Date.Year([Date])*10000 + Date.Month([Date])*100 +Date.Day([Date])),", - " InsertYear = Table.AddColumn(InsertId, \"Year\", each Date.Year([Date])),", - " InsertQuarter = Table.AddColumn(InsertYear, \"Quarter\", each Date.QuarterOfYear([Date])),", - " InsertSemester = Table.AddColumn(InsertQuarter, \"Semester\", each if [Quarter] < 3 then 1 else 2),", - " InsertMonth = Table.AddColumn(InsertSemester, \"Month (#)\", each Date.Month([Date])),", - " // Simple week", - " InsertWeekYear = Table.AddColumn(InsertMonth, \"WeekYear\", each [Year]),", - " InsertWeek = Table.AddColumn(InsertWeekYear, \"Week\", each Date.WeekOfYear([Date], P_FirstDayOfWeek )),", - " // ISO Week", - " InsertIsoYear = Table.AddColumn(InsertMonth, \"WeekYear\", each Date.Year(Date.AddDays([Date], 4-(Date.DayOfWeek([Date], Day.Monday) + 1)))),", - " InsertIsoWeek = Table.AddColumn(InsertIsoYear, \"Week\", each Duration.Days(Date.AddDays( [Date], 4-(Date.DayOfWeek([Date], Day.Monday) + 1)) - #date([WeekYear], 1 , 7 - Date.DayOfWeek( #date([WeekYear],1,4), Day.Monday)) ) / 7 + 1),", - " // Choose beetween simple Week and Iso Week according to parameter", - " ChosenWeek = if P_UseIsoWeek = true then InsertIsoWeek else InsertWeek,", - " ", - " InsertDay = Table.AddColumn(ChosenWeek, \"Day\", each Date.Day([Date])), ", - " InsertMonthName = Table.AddColumn(InsertDay, \"Month (Long)\", each Date.ToText([Date], \"MMMM\", P_Culture), type text),", - " InsertShortMonthName = Table.AddColumn(InsertMonthName, \"Month\", each try(Text.Range([#\"Month (Long)\"],0,3)) otherwise [#\"Month (Long)\"]),", - " InsertCalendarWeek = Table.AddColumn(InsertShortMonthName, \"Week (Year)\", each \"W\" & Number.ToText([Week]) & \" \" & Number.ToText([WeekYear])),", - " InsertCalendarMonth = Table.AddColumn(InsertCalendarWeek, \"Month (Year)\", each [#\"Month\"] & \" \" & Number.ToText([Year])),", - " InsertCalendarQtr = Table.AddColumn(InsertCalendarMonth, \"Quarter (Year)\", each \"Q\" & Number.ToText([Quarter]) & \" \" & Number.ToText([Year])), ", - " InsertCalendarSem = Table.AddColumn(InsertCalendarQtr, \"Semester (Year)\", each \"S\" & Number.ToText([Semester]) & \" \" & Number.ToText([Year])), ", - " InsertDayWeek = Table.AddColumn(InsertCalendarSem , \"Week Day (#)\", each Date.DayOfWeek([Date], P_FirstDayOfWeek ) + 1),", - " InsertDayName = Table.AddColumn(InsertDayWeek, \"Week Day\", each Date.ToText([Date], \"dddd\", P_Culture), type text),", - " InsertWeekYearId = Table.AddColumn(InsertDayName, \"WeekYearId\", each [WeekYear] * 100 + [Week]),", - " InsertMonthYear = Table.AddColumn(InsertWeekYearId, \"MonthYearId\", each [Year] *100 + [#\"Month (#)\"]),", - " InsertWeekStartDate = Table.AddColumn(InsertMonthYear , \"Week Start Date\", each Date.StartOfWeek([Date], P_FirstDayOfWeek), type date),", - " InsertWeekEndDate = Table.AddColumn(InsertWeekStartDate , \"Week End Date\", each Date.EndOfWeek([Date], P_FirstDayOfWeek), type date),", - " InsertQuarterYear = Table.AddColumn(InsertWeekEndDate, \"QuarterYearId\", each [Year] * 100 + [Quarter]),", - " InsertSemesterYear = Table.AddColumn(InsertQuarterYear, \"SemesterYearId\", each [Year] * 100 + [Semester]),", - " #\"Capitalized Each Word\" = Table.TransformColumns(InsertSemesterYear,{{\"Month (Long)\", Text.Proper}, {\"Month\", Text.Proper}, {\"Month (Year)\", Text.Proper}, {\"Week Day\", Text.Proper}}),", - " #\"Relative (Year)\" = Table.AddColumn(#\"Capitalized Each Word\", \"Year (Relative)\", each [Year] - Date.Year(P_Today)),", - " #\"Relative (Month)\" = Table.AddColumn(#\"Relative (Year)\", \"Month (Relative)\", each [#\"Year (Relative)\"] * 12 + ([#\"Month (#)\"] - Date.Month(P_Today))),", - " #\"Relative (Week)\" = Table.AddColumn(#\"Relative (Month)\", \"Week (Relative)\", each Duration.TotalDays(DateTime.Date(Date.StartOfWeek([Date])) - DateTime.Date(Date.StartOfWeek(P_Today))) / 7),", - " #\"Relative (Day)\" = Table.AddColumn(#\"Relative (Week)\", \"Day (Relative)\", each Duration.TotalDays([Date] - DateTime.Date(P_Today))),", - " AddedWorkDay =Table.AddColumn(#\"Relative (Day)\", \"Work Day\", each if [#\"Week Day (#)\"] > 5 then \"Weekend\" else \"WorkDay\"),", - " #\"Reordered Columns\" = Table.ReorderColumns(AddedWorkDay,{\"Date\", \"Day\", \"Week Day (#)\", \"Week Day\", \"Week\", \"Month (Long)\", \"Month\", \"Month (#)\", \"Quarter\", \"Semester\", \"Year\", \"Week (Year)\", \"Month (Year)\", \"Quarter (Year)\", \"Semester (Year)\", \"WeekYearId\", \"MonthYearId\", \"QuarterYearId\", \"SemesterYearId\", \"Day (Relative)\", \"Week (Relative)\", \"Month (Relative)\", \"Year (Relative)\", \"Work Day\"}),", - " #\"Removed Columns\" = Table.RemoveColumns(#\"Reordered Columns\",{\"WeekYear\"}),", - " #\"Changed Type\" = Table.TransformColumnTypes(#\"Removed Columns\",{{\"Day\", Int64.Type}, {\"Week Day (#)\", Int64.Type}, {\"Week\", Int64.Type}, {\"Month (#)\", Int64.Type}, {\"Quarter\", Int64.Type}, {\"Semester\", Int64.Type}, {\"Year\", Int64.Type}, {\"Week (Year)\", type text}, {\"Quarter (Year)\", type text}, {\"Semester (Year)\", type text}, {\"WeekYearId\", Int64.Type}, {\"SemesterYearId\", Int64.Type}, {\"MonthYearId\", Int64.Type}, {\"QuarterYearId\", Int64.Type}, {\"Day (Relative)\", Int64.Type}, {\"Month (Relative)\", Int64.Type}, {\"Year (Relative)\", Int64.Type}, {\"Work Day\", type text}, {\"DateId\", Int64.Type}, {\"Week (Relative)\", Int64.Type}}),", - " #\"Added Custom\" = Table.AddColumn(#\"Changed Type\", \"Month Start Date\", each #date([Year],[#\"Month (#)\"],1)),", - " #\"Changed Type1\" = Table.TransformColumnTypes(#\"Added Custom\",{{\"Month Start Date\", type date}})", - "in", - " #\"Changed Type1\"" - ], - "type": "m" - } - } - ] - }, - { - "name": "Sales", - "annotations": [ - { - "name": "PBI_Id", - "value": "975ddcc4-65e2-4eb0-9c9c-2c5ef0586f0e" - }, - { - "name": "LinkedQueryName", - "value": "Sales" - }, - { - "name": "PBI_QueryRelationships", - "value": "{\"columnCount\":7,\"keyColumnNames\":[],\"queryRelationships\":[],\"columnIdentities\":[\"Section1/Sales/Changed Type.{FK_Customer,0}\",\"Section1/Sales/Changed Type.{FK_Product,1}\",\"Section1/Sales/Changed Type.{FK_SalesDate,2}\",\"Section1/Sales/Changed Type.{Quantity,3}\",\"Section1/Sales/Changed Type.{UnitPrice,4}\",\"Section1/Sales/Changed Type.{Discount,5}\",\"Section1/Sales/Changed Type.{TotalAmount,6}\"],\"ColumnCount\":7,\"KeyColumnNames\":[],\"ColumnIdentities\":[\"Section1/Sales/Changed Type.{FK_Customer,0}\",\"Section1/Sales/Changed Type.{FK_Product,1}\",\"Section1/Sales/Changed Type.{FK_SalesDate,2}\",\"Section1/Sales/Changed Type.{Quantity,3}\",\"Section1/Sales/Changed Type.{UnitPrice,4}\",\"Section1/Sales/Changed Type.{Discount,5}\",\"Section1/Sales/Changed Type.{TotalAmount,6}\"],\"RelationshipInfo\":[]}" - }, - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "columns": [ - { - "name": "Quantity", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isAvailableInMdx": false, - "isHidden": true, - "lineageTag": "0aaf711e-9b42-4cfb-9ab6-bee80e843a12", - "sourceColumn": "Quantity", - "summarizeBy": "none" - }, - { - "name": "Order Number", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "e2e629f1-f1fb-4444-a627-b121d77fdc06", - "sourceColumn": "Order Number", - "summarizeBy": "none" - }, - { - "name": "Line Number", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "d4046972-90a5-46b2-badb-709e834b99af", - "sourceColumn": "Line Number", - "summarizeBy": "none" - }, - { - "name": "Order Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - }, - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"HIDE_FOREIGN_KEYS\",\"RELATIONSHIP_COLUMNS_SHOULD_BE_OF_INTEGER_DATA_TYPE\"]}" - } - ], - "dataType": "dateTime", - "formatString": "Long Date", - "lineageTag": "d2d074d6-be1f-4dfd-b826-cd99fe83bc3a", - "sourceColumn": "Order Date", - "summarizeBy": "none" - }, - { - "name": "Delivery Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - }, - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"HIDE_FOREIGN_KEYS\",\"RELATIONSHIP_COLUMNS_SHOULD_BE_OF_INTEGER_DATA_TYPE\"]}" - } - ], - "dataType": "dateTime", - "formatString": "Long Date", - "lineageTag": "1056fc0f-d1e5-4872-a39b-25603dba5cf5", - "sourceColumn": "Delivery Date", - "summarizeBy": "none" - }, - { - "name": "CustomerKey", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isAvailableInMdx": false, - "isHidden": true, - "lineageTag": "4de77f33-318d-4006-85db-580cb119fc6a", - "sourceColumn": "CustomerKey", - "summarizeBy": "none" - }, - { - "name": "StoreKey", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isAvailableInMdx": false, - "isHidden": true, - "lineageTag": "cf26d73c-10d7-40ac-83a3-c91c04e948d8", - "sourceColumn": "StoreKey", - "summarizeBy": "none" - }, - { - "name": "ProductKey", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - } - ], - "dataType": "int64", - "formatString": "0", - "isAvailableInMdx": false, - "isHidden": true, - "lineageTag": "595525b1-f5ca-442e-a226-0cc478b823c0", - "sourceColumn": "ProductKey", - "summarizeBy": "none" - }, - { - "name": "Net Price", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "changedProperties": [ - { - "property": "DataType" - }, - { - "property": "IsHidden" - } - ], - "dataType": "decimal", - "isAvailableInMdx": false, - "isHidden": true, - "lineageTag": "d4df8046-8db3-4910-8759-33b904a0cac6", - "sourceColumn": "Net Price", - "summarizeBy": "sum" - }, - { - "name": "Unit Cost", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"REMOVE_REDUNDANT_COLUMNS_IN_RELATED_TABLES\"]}" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "changedProperties": [ - { - "property": "IsHidden" - }, - { - "property": "DataType" - } - ], - "dataType": "decimal", - "isAvailableInMdx": false, - "isHidden": true, - "lineageTag": "03a43676-2ce4-4678-a4a7-0ee8f4e00b17", - "sourceColumn": "Unit Cost", - "summarizeBy": "sum" - }, - { - "name": "Currency Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "a75ba79f-22a3-4bfd-9b52-80e5d73247a9", - "sourceColumn": "Currency Code", - "summarizeBy": "none" - }, - { - "name": "Exchange Rate", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "changedProperties": [ - { - "property": "DataType" - } - ], - "dataType": "decimal", - "lineageTag": "0b3f913f-1587-4280-a8bb-21ab7d661086", - "sourceColumn": "Exchange Rate", - "summarizeBy": "none" - }, - { - "name": "Environment", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "05fb6ae8-19b4-40c5-81b2-e40a6edd8692", - "sourceColumn": "Environment", - "summarizeBy": "none" - } - ], - "description": "Sales table for year over year analysis", - "lineageTag": "97143e5b-7736-4fcb-8042-26b92b1f5684", - "measures": [ - { - "name": "# Customers (with Sales)", - "expression": "DISTINCTCOUNT('Sales'[CustomerKey])", - "formatString": "#,##0", - "lineageTag": "4ec872f0-a42d-4aa9-bf6b-1ca7c4916336" - }, - { - "name": "# Products (with Sales)", - "changedProperties": [ - { - "property": "FormatString" - } - ], - "expression": "DISTINCTCOUNT('Sales'[ProductKey])", - "formatString": "#,##0", - "lineageTag": "b11c3386-30b8-4303-8ddb-ac0d2fa7660d" - }, - { - "name": "Sales Qty", - "expression": "sum('Sales'[Quantity])", - "formatString": "#,##0", - "lineageTag": "c2ff8d96-2f03-4005-84df-91458625b73b" - }, - { - "name": "Sales Amount", - "annotations": [ - { - "name": "PBI_FormatHint", - "value": "{\"isCustom\":true}" - } - ], - "changedProperties": [ - { - "property": "FormatString" - } - ], - "expression": "SUMX('Sales', 'Sales'[Quantity] * 'Sales'[Net Price])", - "formatString": "$ #,##0", - "lineageTag": "a8e95485-02a2-4525-b02a-b2418fbdbe4c" - }, - { - "name": "Sales Amount (Δ LY)", - "expression": "[Sales Amount] - [Sales Amount (LY)]", - "formatString": "$ #,##0", - "lineageTag": "d2724187-f1de-4a90-84bc-fc96aab3194b" - }, - { - "name": "Sales Amount (LY)", - "annotations": [ - { - "name": "PBI_FormatHint", - "value": "{\"currencyCulture\":\"en-US\"}" - } - ], - "changedProperties": [ - { - "property": "FormatString" - } - ], - "description": [ - "Sales Amount Last Year considering a full month", - "" - ], - "expression": "CALCULATE([Sales Amount], SAMEPERIODLASTYEAR('Calendar'[Date]))", - "formatString": "\\$#,0;(\\$#,0);\\$#,0", - "lineageTag": "3fa889ae-64b9-4a6e-b0e6-c81c90fd32bf" - }, - { - "name": "Sales Amount (YTD, LY)", - "expression": "CALCULATE([Sales Amount (YTD)], SAMEPERIODLASTYEAR('Calendar'[Date]))", - "formatString": "$ #,##0", - "lineageTag": "6868b958-3e8b-4e0e-ae86-8a88e4651834" - }, - { - "name": "Sales Amount (YTD)", - "expression": "TOTALYTD([Sales Amount],'Calendar'[Date])", - "formatString": "$ #,##0", - "lineageTag": "aafaacd7-ff25-4a69-b8e7-f29fb02c5351" - }, - { - "name": "Sales Amount Avg per Day", - "expression": "AVERAGEX(VALUES('Calendar'[Date]), [Sales Amount])", - "formatString": "$ #,##0", - "lineageTag": "65842ce7-7176-4106-868e-2e83aaa62b4c" - }, - { - "name": "Sales Amount (% Δ LY)", - "expression": [ - "var ly =[Sales Amount (LY)]", - "return", - "DIVIDE(", - " [Sales Amount]- ly,", - " ly", - " )" - ], - "formatString": "#,##0.00 %", - "lineageTag": "f1a3a032-8cea-4b06-b85a-f6f4e03e1d9f" - }, - { - "name": "Margin", - "annotations": [ - { - "name": "PBI_FormatHint", - "value": "{\"isCustom\":true}" - } - ], - "changedProperties": [ - { - "property": "FormatString" - } - ], - "expression": [ - "SUMX ( ", - " Sales, ", - " Sales[Quantity] ", - " * ( Sales[Net Price] - Sales[Unit Cost] )", - ")" - ], - "formatString": "$ #,##0", - "kpi": { - "statusExpression": [ - "VAR MarginPercentage = [Margin %]", - "VAR MarginTolerance = 0.02", - "VAR MarginGoal = 0.3", - "RETURN", - " IF (", - " NOT ISBLANK ( MarginPercentage ),", - " SWITCH (", - " TRUE,", - " MarginPercentage < MarginGoal - MarginTolerance, -1, -- Negative", - " MarginPercentage > MarginGoal + MarginTolerance, 1, -- Positive", - " 0", - " )", - " )" - ], - "targetExpression": [ - "[Margin % Overall]", - "" - ], - "trendExpression": [ - "-- DAX code for Trend Expression", - "VAR MarginPerc = [Margin %]", - "VAR PrevMarginPerc =", - " CALCULATE (", - " [Margin %],", - " PREVIOUSYEAR( 'Calendar'[Date] )", - " )", - "RETURN", - " IF (", - " NOT ISBLANK ( MarginPerc ) && NOT ISBLANK ( PrevMarginPerc ),", - " SWITCH (", - " TRUE,", - " MarginPerc > PrevMarginPerc, 1, -- Positive", - " MarginPerc < PrevMarginPerc, -1, -- Negative", - " 0", - " )", - " )" - ] - }, - "lineageTag": "d22a262b-b776-4ccd-b9bd-7ef9c90eba51" - }, - { - "name": "Margin (ly)", - "expression": [ - "CALCULATE([Margin], SAMEPERIODLASTYEAR('Calendar'[Date]))", - "" - ], - "formatString": "$ #,##0", - "lineageTag": "bdf081eb-1952-439f-96b1-a27e1a13f1a3" - }, - { - "name": "# Sales", - "changedProperties": [ - { - "property": "FormatString" - } - ], - "expression": "COUNTROWS('Sales')", - "formatString": "#,##0", - "lineageTag": "67791bd1-5f33-4a29-a9ac-be0ad2453fcf" - }, - { - "name": "Sales Qty by Delivery Date", - "expression": [ - "CALCULATE([Sales Qty], USERELATIONSHIP('Sales'[Delivery Date],'Calendar'[Date]))", - "", - "" - ], - "formatString": "#,##0", - "lineageTag": "c85c43d0-a2e1-4d90-a8a0-4009499b6eea" - }, - { - "name": "Sales Amount (12M average)", - "annotations": [ - { - "name": "PBI_FormatHint", - "value": "{\"isCustom\":true}" - } - ], - "description": [ - "12 Month moving average sales calculation", - "" - ], - "expression": [ - "", - "VAR v_selDate =", - " MAX ( 'Calendar'[Date] )", - "VAR v_period =", - " DATESINPERIOD ( 'Calendar'[Date], v_selDate, -12, MONTH )", - "VAR v_result =", - " CALCULATE ( AVERAGEX ( VALUES ( 'Calendar'[Date] ), [Sales Amount] ), v_period )", - "VAR v_firstDate =", - " MINX ( v_period, 'Calendar'[Date] )", - "VAR v_lastDateSales =", - " MAX ( Sales[Order Date] )", - "RETURN", - " IF ( v_firstDate <= v_lastDateSales, v_result )" - ], - "formatString": "$ #,##0", - "lineageTag": "7fd60f7e-287e-46c3-a745-24c4507bc77b" - }, - { - "name": "Sales Amount (6M average)", - "expression": [ - "", - "VAR v_selDate =", - " MAX ( 'Calendar'[Date] )", - "VAR v_period =", - " DATESINPERIOD ( 'Calendar'[Date], v_selDate, -6, MONTH )", - "VAR v_result =", - " CALCULATE ( AVERAGEX ( VALUES ( 'Calendar'[Date] ), [Sales Amount] ), v_period )", - "VAR v_firstDate =", - " MINX ( v_period, 'Calendar'[Date] )", - "VAR v_lastDateSales =", - " MAX ( Sales[Order Date] )", - "RETURN", - " IF ( v_firstDate <= v_lastDateSales, v_result )" - ], - "formatString": "$ #,##0", - "lineageTag": "9a48bea0-e5fb-40fa-9e81-f61288e31a02" - }, - { - "name": "Margin %", - "expression": "DIVIDE ( [Margin], [Sales Amount] )", - "formatString": "#,##0.00 %", - "lineageTag": "91a7a901-c646-41fe-a1ed-6e82b421d140" - }, - { - "name": "Cost", - "expression": "SUMX ( Sales, Sales[Quantity] * Sales[Unit Cost] )", - "formatString": "$ #,##0", - "lineageTag": "3e5cb67e-a411-476f-ad34-7ec10dfd084d" - }, - { - "name": "Margin % Overall", - "expression": [ - "ROUND ( CALCULATE( [Margin %], REMOVEFILTERS () ), 2 )", - "" - ], - "formatString": "#,##0.00 %", - "lineageTag": "b2155351-e104-4bae-941b-3e651804cee5" - } - ], - "partitions": [ - { - "name": "Sales-ddb4c40b-46fd-49ea-9a19-16e7e640a21a", - "mode": "import", - "source": { - "expression": [ - "let", - " Source = #\"RAW-SalesDateAdjustedAndSalesRandomized\",", - " #\"Changed Type\" = Table.TransformColumnTypes(Source,{{\"Order Number\", Int64.Type}, {\"Line Number\", Int64.Type}, {\"Order Date\", type date}, {\"Delivery Date\", type date}, {\"CustomerKey\", Int64.Type}, {\"StoreKey\", Int64.Type}, {\"ProductKey\", Int64.Type}, {\"Quantity\", Int64.Type}, {\"Unit Price\", type number}, {\"Net Price\", type number}, {\"Unit Cost\", type number}, {\"Currency Code\", type text}, {\"Exchange Rate\", type number}}),", - " #\"Removed Columns\" = Table.RemoveColumns(#\"Changed Type\",{\"Unit Price\"}),", - " ", - " #\"Changed Type1\" = Table.TransformColumnTypes(#\"Removed Columns\",{{\"Delivery Date\", type datetime}, {\"Order Date\", type datetime}}),", - " #\"Filtered Rows\" = Table.SelectRows(#\"Changed Type1\", each [Order Date] >= RangeStart and [Order Date] <= RangeEnd),", - " #\"Changed Type2\" = Table.TransformColumnTypes(#\"Filtered Rows\",{{\"Delivery Date\", type date}, {\"Order Date\", type date}}),", - " #\"Added Custom\" = Table.AddColumn(#\"Changed Type2\", \"Environment\", each Environment),", - " #\"Changed Type3\" = Table.TransformColumnTypes(#\"Added Custom\",{{\"Environment\", type text}})", - "in", - " #\"Changed Type3\"" - ], - "type": "m" - } - } - ] - }, - { - "name": "Product", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "columns": [ - { - "name": "Product", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDefaultLabel": true, - "lineageTag": "da435585-1f9a-44bd-ba2c-34c98f298cfc", - "sourceColumn": "Product", - "summarizeBy": "none" - }, - { - "name": "ProductKey", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "isAvailableInMdx": false, - "isKey": true, - "lineageTag": "4184d53e-cd2d-4cbe-b8cb-04c72a750bc4", - "sourceColumn": "ProductKey", - "summarizeBy": "none" - }, - { - "name": "Product Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "e9d204ad-76d8-4db9-9d1a-b9c07a4b50b2", - "sourceColumn": "Product Code", - "summarizeBy": "none" - }, - { - "name": "Manufacturer", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "59e45f50-f68d-44c3-becd-70ccd5a7eb7d", - "sourceColumn": "Manufacturer", - "summarizeBy": "none" - }, - { - "name": "Brand", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "a71b235d-8f7e-4678-85a3-96a78d64bf87", - "sourceColumn": "Brand", - "summarizeBy": "none" - }, - { - "name": "Color", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "7054b4d0-6d93-4c96-be74-800d02d96e43", - "sourceColumn": "Color", - "summarizeBy": "none" - }, - { - "name": "Weight Unit Measure", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "78fcf7c4-2b5d-45b0-abf9-6ee3b3aa255b", - "sourceColumn": "Weight Unit Measure", - "summarizeBy": "none" - }, - { - "name": "Weight", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "changedProperties": [ - { - "property": "DataType" - } - ], - "dataType": "decimal", - "lineageTag": "a6299b36-bd05-4b41-8493-e45359af237b", - "sourceColumn": "Weight", - "summarizeBy": "none" - }, - { - "name": "Unit Cost", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "changedProperties": [ - { - "property": "DataType" - } - ], - "dataType": "decimal", - "lineageTag": "f89fa3e3-061d-4269-8cd3-aa6ce2a464d2", - "sourceColumn": "Unit Cost", - "summarizeBy": "none" - }, - { - "name": "Unit Price", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "changedProperties": [ - { - "property": "DataType" - } - ], - "dataType": "decimal", - "lineageTag": "ef300027-e4eb-4c7d-9770-ab8f6dab6b15", - "sourceColumn": "Unit Price", - "summarizeBy": "none" - }, - { - "name": "Subcategory Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "7cd08eb9-2cad-4263-ae88-8c5121a68b7e", - "sourceColumn": "Subcategory Code", - "summarizeBy": "none" - }, - { - "name": "Subcategory", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "0a208c62-4bdd-4873-af18-ebc286c5b3bb", - "sourceColumn": "Subcategory", - "summarizeBy": "none" - }, - { - "name": "Category Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "c0fc218a-5a06-4757-9172-2d303a67f3ff", - "sourceColumn": "Category Code", - "summarizeBy": "none" - }, - { - "name": "Category", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "0f4b99cc-fdb6-4f04-b7d9-bbdcf7b2c601", - "sourceColumn": "Category", - "summarizeBy": "none" - } - ], - "description": "Product Catalog", - "hierarchies": [ - { - "name": "Product Hierarchy", - "levels": [ - { - "name": "Category", - "column": "Category", - "lineageTag": "9ff3052d-e0de-44e8-85c3-85b8cc978936", - "ordinal": 0 - }, - { - "name": "Subcategory", - "column": "Subcategory", - "lineageTag": "647503e7-1d2b-4e0a-bc36-1ce6bc3d81ca", - "ordinal": 1 - }, - { - "name": "Product", - "column": "Product", - "lineageTag": "85ba527d-a9e2-4f3d-85d9-447600445bc3", - "ordinal": 2 - } - ], - "lineageTag": "89345cc9-e735-4d62-8caf-e494a6314e93" - } - ], - "lineageTag": "e9374b9a-faee-4f9e-b2e7-d9aafb9d6a91", - "measures": [ - { - "name": "# Products", - "expression": "COUNTROWS('Product')", - "formatString": "#,##0", - "lineageTag": "1f8f1a2a-06b6-4989-8af7-212719cf3617" - } - ], - "partitions": [ - { - "name": "Product-171f48b3-e0ea-4ea3-b9a0-c8c673eb0648", - "mode": "import", - "source": { - "expression": [ - "let", - " Source = #\"RAW-Product\",", - " #\"Renamed Columns\" = Table.RenameColumns(Source,{{\"Product Name\", \"Product\"}}),", - " #\"Changed Type\" = Table.TransformColumnTypes(#\"Renamed Columns\",{{\"ProductKey\", Int64.Type}, {\"Product Code\", type text}, {\"Product\", type text}, {\"Manufacturer\", type text}, {\"Brand\", type text}, {\"Color\", type text}, {\"Weight Unit Measure\", type text}, {\"Weight\", type number}, {\"Unit Cost\", type number}, {\"Unit Price\", type number}, {\"Subcategory Code\", type text}, {\"Subcategory\", type text}, {\"Category Code\", type text}, {\"Category\", type text}})", - "in", - " #\"Changed Type\"" - ], - "type": "m" - } - } - ] - }, - { - "name": "Customer", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "columns": [ - { - "name": "CustomerKey", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "isAvailableInMdx": false, - "isKey": true, - "lineageTag": "901662ed-f0ae-41f6-96b3-4aa68bad1c7a", - "sourceColumn": "CustomerKey", - "summarizeBy": "none" - }, - { - "name": "Gender", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "7e0ce5eb-3e63-4870-b30a-032f5186375d", - "sourceColumn": "Gender", - "summarizeBy": "none" - }, - { - "name": "Customer", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDefaultLabel": true, - "lineageTag": "8845f8b5-b069-41a0-8af8-12e402b113e9", - "sourceColumn": "Customer", - "summarizeBy": "none" - }, - { - "name": "Address", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "adbeb074-7c67-40ee-be2d-d46775ce5e17", - "sourceColumn": "Address", - "summarizeBy": "none" - }, - { - "name": "City", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "City", - "dataType": "string", - "lineageTag": "a791eb8c-7fab-418d-844a-f8b094453037", - "sourceColumn": "City", - "summarizeBy": "none" - }, - { - "name": "State Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "StateOrProvince", - "dataType": "string", - "lineageTag": "29de3d16-fffa-4f61-8c39-c1a2a36d737a", - "sourceColumn": "State Code", - "summarizeBy": "none" - }, - { - "name": "State", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "StateOrProvince", - "dataType": "string", - "lineageTag": "923b4cc1-6137-4bb4-bcb2-3a90c5f76c5e", - "sourceColumn": "State", - "summarizeBy": "none" - }, - { - "name": "Zip Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "PostalCode", - "dataType": "string", - "lineageTag": "439ce0ee-437e-4770-90c4-c4d53ebfb35b", - "sourceColumn": "Zip Code", - "summarizeBy": "none" - }, - { - "name": "Country Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "Country", - "dataType": "string", - "lineageTag": "f3eea279-4255-446b-9f83-f40f66abf6d1", - "sourceColumn": "Country Code", - "summarizeBy": "none" - }, - { - "name": "Country", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "Country", - "dataType": "string", - "lineageTag": "99af44ae-86e9-4b75-98a3-e4b6e53fe806", - "sourceColumn": "Country", - "summarizeBy": "none" - }, - { - "name": "Continent", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "Continent", - "dataType": "string", - "lineageTag": "53b840d2-962c-44c4-8733-6cc003fb9a83", - "sourceColumn": "Continent", - "summarizeBy": "none" - }, - { - "name": "Birthday", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "dateTime", - "formatString": "General Date", - "lineageTag": "afa5e191-c690-45c1-a725-41c9d3ca9434", - "sourceColumn": "Birthday", - "summarizeBy": "none" - }, - { - "name": "Age", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "5c487309-c061-45ed-aa74-aba4d20bde3b", - "sourceColumn": "Age", - "summarizeBy": "none" - } - ], - "description": "Customer data", - "lineageTag": "04227f5a-0aeb-4448-b5c5-dbf3e276da85", - "measures": [ - { - "name": "# Customers", - "expression": "COUNTROWS('Customer')", - "formatString": "#,##0", - "lineageTag": "a8dc565a-aa9b-40dc-902c-1ba2596b0977" - } - ], - "partitions": [ - { - "name": "Customer-3757b886-e26c-4cec-a550-cdeea37b94d4", - "mode": "import", - "source": { - "expression": [ - "let", - " Source = #\"RAW-Customer\",", - " #\"Renamed Columns\" = Table.RenameColumns(Source,{{\"Name\", \"Customer\"}}),", - " #\"Changed Type\" = Table.TransformColumnTypes(#\"Renamed Columns\",{{\"CustomerKey\", Int64.Type}, {\"Gender\", type text}, {\"Customer\", type text}, {\"Address\", type text}, {\"City\", type text}, {\"State Code\", type text}, {\"State\", type text}, {\"Zip Code\", type text}, {\"Country Code\", type text}, {\"Country\", type text}, {\"Continent\", type text}, {\"Birthday\", type datetime}, {\"Age\", Int64.Type}})", - "in", - " #\"Changed Type\"" - ], - "type": "m" - } - } - ] - }, - { - "name": "Smart Calcs", - "calculationGroup": { - "calculationItems": [ - { - "name": "Normalize", - "expression": [ - "VAR DetailValue = SELECTEDMEASURE()", - "", - "return if (DetailValue, ", - "", - " VAR MinOfGroup = MINX(ALLSELECTED('Calendar'), SELECTEDMEASURE())", - " VAR MaxOfGroup = MAXX(ALLSELECTED('Calendar'), SELECTEDMEASURE())", - "", - " RETURN DIVIDE(DetailValue - MinOfGroup, MaxOfGroup - MinOfGroup)", - ")" - ], - "formatStringDefinition": { - "expression": "\"0.0\"" - } - }, - { - "name": "Randomize", - "expression": "IFERROR(SELECTEDMEASURE() * RAND(), SELECTEDMEASURE())" - }, - { - "name": "Label - ▲ LY", - "expression": [ - "var vValue = SELECTEDMEASURE()", - "var vValueLY = CALCULATE(SELECTEDMEASURE(), SAMEPERIODLASTYEAR('Calendar'[Date]))", - "var vGrowth = DIVIDE(vValue - vValueLY, vValueLY)", - "var vFormat = SELECTEDMEASUREFORMATSTRING()", - "", - "return", - " FORMAT(vValue, vFormat)", - " & IF (ISBLANK(vGrowth)", - " , BLANK()", - " , \" | \" ", - " & IF (vGrowth >= 0, \"▲\" , \"▼\") & FORMAT(vGrowth, \"0%\")", - " )", - "" - ], - "formatStringDefinition": { - "expression": "SELECTEDMEASUREFORMATSTRING()" - } - }, - { - "name": "Dynamic Measure - Apply Format", - "expression": "SELECTEDMEASURE()", - "formatStringDefinition": { - "expression": [ - "", - "IF (", - " // Only do this for the 'Dynamic Measure' Measures", - " ISSELECTEDMEASURE ( [Value], [Value (ly)], [Value (ytd)], [Value Avg per Month] ),", - " VAR measureCode =", - " SELECTEDVALUE ( 'Dynamic Measure'[Code] )", - " VAR measureFormat =", - " IF (", - " measureCode <> BLANK (),", - " LOOKUPVALUE ( 'Dynamic Measure'[Format], 'Dynamic Measure'[Code], measureCode )", - " )", - " RETURN", - " IF ( measureFormat <> BLANK (), measureFormat, SELECTEDMEASUREFORMATSTRING () )", - " ", - " , SELECTEDMEASUREFORMATSTRING ()", - ")" - ] - } - }, - { - "name": "Label - ▲ LM", - "expression": [ - "var vValue = SELECTEDMEASURE()", - "var vValueLM = CALCULATE(SELECTEDMEASURE(), PREVIOUSMONTH('Calendar'[Date]))", - "var vGrowth = DIVIDE(vValue - vValueLM, vValueLM)", - "var vFormat = SELECTEDMEASUREFORMATSTRING()", - "", - "return", - " FORMAT(vValue, vFormat)", - " & IF (ISBLANK(vGrowth)", - " , BLANK()", - " , \" | \" ", - " & IF (vGrowth >= 0, \"▲\" , \"▼\") & FORMAT(vGrowth, \"0%\")", - " )", - "" - ], - "formatStringDefinition": { - "expression": "SELECTEDMEASUREFORMATSTRING()" - } - } - ] - }, - "columns": [ - { - "name": "Smart Calc", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "005e8b9e-378a-421a-905c-f21dad946ceb", - "sortByColumn": "Ordinal", - "sourceColumn": "Name", - "summarizeBy": "none" - }, - { - "name": "Ordinal", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "isHidden": true, - "lineageTag": "2532b358-06e7-449d-a944-810f51fff7a5", - "sourceColumn": "Ordinal", - "summarizeBy": "none" - } - ], - "lineageTag": "fa79b28a-6a9c-43b1-a775-099dabcd4428", - "partitions": [ - { - "name": "Partition", - "mode": "import", - "source": { - "type": "calculationGroup" - } - } - ] - }, - { - "name": "Dynamic Measure", - "annotations": [ - { - "name": "PBI_Id", - "value": "4d2f308d1af14ea3accf3c458621a9d7" - }, - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"REDUCE_USAGE_OF_CALCULATED_TABLES\",\"ENSURE_TABLES_HAVE_RELATIONSHIPS\"]}" - } - ], - "columns": [ - { - "name": "Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "isDataTypeInferred": true, - "isNameInferred": true, - "lineageTag": "a5db8d3b-70af-483d-bcab-5c0fcc7478c2", - "sourceColumn": "[Code]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Order", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "isDataTypeInferred": true, - "isNameInferred": true, - "lineageTag": "c7824b9c-021c-4bff-b363-c04b3cb2c681", - "sourceColumn": "[Order]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Measure", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDataTypeInferred": true, - "isNameInferred": true, - "lineageTag": "d8e3ae78-fda2-49e6-b8cb-b6257e927261", - "sourceColumn": "[Measure]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Area", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDataTypeInferred": true, - "isNameInferred": true, - "lineageTag": "364ae39d-73bd-4527-91e4-0e649a23d8a7", - "sourceColumn": "[Area]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Format", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDataTypeInferred": true, - "isNameInferred": true, - "lineageTag": "ec17695c-dc59-4674-bdaa-bbdeb4131593", - "sourceColumn": "[Format]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - } - ], - "description": [ - "Dynamic Measure table", - "Useful to explore measure \"as a dimension\"" - ], - "lineageTag": "208f785d-03c1-41ca-b33a-c56c36916caa", - "measures": [ - { - "name": "Value", - "annotations": [ - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"PROVIDE_FORMAT_STRING_FOR_MEASURES\",\"INTEGER_FORMATTING\"]}" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "expression": [ - "", - "IF (", - " HASONEVALUE ( 'Dynamic Measure'[Code] ),", - " var measureCode = SELECTEDVALUE('Dynamic Measure'[Code])", - " return SWITCH (", - " measureCode", - " ,1, [Sales Amount]", - " ,2, [Sales Amount (% Δ LY)]", - " ,3, [# Customers (with Sales)] ", - " ,4, [Sales Qty]", - " ,5, [Margin]", - " ,BLANK ()", - " )", - ")" - ], - "lineageTag": "1f748dd6-758e-4445-bf6c-dab17d61d2ee" - }, - { - "name": "Value (ly)", - "annotations": [ - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"PROVIDE_FORMAT_STRING_FOR_MEASURES\",\"INTEGER_FORMATTING\"]}" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "expression": "CALCULATE ( [Value], SAMEPERIODLASTYEAR('Calendar'[Date]) )", - "lineageTag": "59781612-6890-4665-a1b2-aa25324fe896" - }, - { - "name": "Value (ytd)", - "annotations": [ - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"PROVIDE_FORMAT_STRING_FOR_MEASURES\",\"INTEGER_FORMATTING\"]}" - } - ], - "expression": "CALCULATE([Value], DATESYTD('Calendar'[Date]))", - "lineageTag": "b01909c9-718c-48d2-98af-482b39bf2272" - }, - { - "name": "Value Avg per Month", - "annotations": [ - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"PROVIDE_FORMAT_STRING_FOR_MEASURES\",\"INTEGER_FORMATTING\"]}" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "expression": [ - "", - " AVERAGEX(VALUES('Calendar'[Month (Year)]), [Value])" - ], - "lineageTag": "ba87b819-2147-443b-aaeb-8ae1729c6550" - }, - { - "name": "Value Daily Max", - "annotations": [ - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"PROVIDE_FORMAT_STRING_FOR_MEASURES\",\"INTEGER_FORMATTING\"]}" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "expression": "MAXX(VALUES('Calendar'[Date]), [Value])", - "lineageTag": "d8fbae5f-a0c0-4b2e-85e9-1e045d7510e0" - }, - { - "name": "Value % (Δ ly)", - "changedProperties": [ - { - "property": "FormatString" - } - ], - "expression": [ - "", - "var ly =[Value (ly)]", - "return", - "DIVIDE(", - " [Value]- ly,", - " ly", - " )" - ], - "formatString": "0.00%;-0.00%;0.00%", - "lineageTag": "897fe824-eb94-4ec9-996e-26238f4c2b23" - }, - { - "name": "Value Normalized (by date)", - "annotations": [ - { - "name": "BestPracticeAnalyzer_IgnoreRules", - "value": "{\"RuleIDs\":[\"PROVIDE_FORMAT_STRING_FOR_MEASURES\",\"INTEGER_FORMATTING\"]}" - }, - { - "name": "PBI_FormatHint", - "value": "{\"isGeneralNumber\":true}" - } - ], - "expression": [ - "", - "VAR DetailValue = [Value]", - "return if (DetailValue, ", - "", - " //VAR MinOfGroup = MINX(ALLSELECTED('Calendar'[Month (Year)], 'Calendar'[MonthYearId]), [Value])", - " //VAR MaxOfGroup = MAXX(ALLSELECTED('Calendar'[Month (Year)], 'Calendar'[MonthYearId]), [Value])", - " VAR MinOfGroup = MINX(ALLSELECTED('Calendar'), [Value])", - " VAR MaxOfGroup = MAXX(ALLSELECTED('Calendar'), [Value])", - " RETURN DIVIDE(DetailValue - MinOfGroup, MaxOfGroup - MinOfGroup)", - ")" - ], - "lineageTag": "15549406-fb29-46e8-9094-25add2c74995" - } - ], - "partitions": [ - { - "name": "Dynamic Measure-e40351ed-a523-4ff4-9185-7834b3fbb8e8", - "mode": "import", - "source": { - "expression": [ - "", - "DATATABLE (", - " \"Code\", INTEGER,", - " \"Order\", INTEGER,", - " \"Measure\", STRING,", - " \"Area\", STRING,", - " \"Format\", STRING,", - " { ", - " { 1, 1, \"Sales Amount\", \"Sales\", \"\\$#,0.00;(\\$#,0.00);\\$#,0.00\" },", - " { 2, 2, \"Sales Growth vs LY\", \"Sales\", \"0.000%\"},", - " { 3, 4, \"# Customers\", \"Marketing\", \"#,#\" }, ", - " { 4, 2, \"Sales Qty\", \"Sales\", \"#,#\" },", - " { 5, 2, \"Sales Margin\", \"Sales\", \"\\$#,0.00;(\\$#,0.00);\\$#,0.00\" }", - " } ", - ")", - "" - ], - "type": "calculated" - } - } - ] - }, - { - "name": "Store", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "columns": [ - { - "name": "StoreKey", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "isAvailableInMdx": false, - "isKey": true, - "lineageTag": "b63bc7b8-266a-4424-9676-c3e68501b2ec", - "sourceColumn": "StoreKey", - "summarizeBy": "none" - }, - { - "name": "Store Code", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "307e3348-2132-4db3-b76a-771ac4561ef5", - "sourceColumn": "Store Code", - "summarizeBy": "none" - }, - { - "name": "Country", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataCategory": "Country", - "dataType": "string", - "lineageTag": "7564fe29-ad01-43e0-ba93-2e1634e1a9b3", - "sourceColumn": "Country", - "summarizeBy": "none" - }, - { - "name": "State", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "f471c9c0-1924-46e4-99d8-59ccf0f64cba", - "sourceColumn": "State", - "summarizeBy": "none" - }, - { - "name": "Store", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDefaultLabel": true, - "lineageTag": "7bee915f-7eb7-4dbc-a16f-e796f410b3f5", - "sourceColumn": "Store", - "summarizeBy": "none" - }, - { - "name": "Square Meters", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "1596588a-acc4-41c4-a692-310fd28994bb", - "sourceColumn": "Square Meters", - "summarizeBy": "none" - }, - { - "name": "Open Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - } - ], - "dataType": "dateTime", - "formatString": "Long Date", - "lineageTag": "66cf5168-4add-4a05-b2c8-0380a85b0539", - "sourceColumn": "Open Date", - "summarizeBy": "none" - }, - { - "name": "Close Date", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - }, - { - "name": "UnderlyingDateTimeDataType", - "value": "Date" - } - ], - "dataType": "dateTime", - "formatString": "Long Date", - "lineageTag": "e627ea17-3b62-46f2-a2f4-549dc98beb06", - "sourceColumn": "Close Date", - "summarizeBy": "none" - }, - { - "name": "Status", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "93122991-3d6a-413c-ad26-3610fa90013b", - "sourceColumn": "Status", - "summarizeBy": "none" - } - ], - "description": "Store metadata", - "lineageTag": "54d5f884-12db-4e03-a120-08cd725db2c4", - "measures": [ - { - "name": "# Stores", - "changedProperties": [ - { - "property": "FormatString" - } - ], - "expression": "COUNTROWS('Store')", - "formatString": "#,##0", - "lineageTag": "868df9c8-f579-47d1-a776-3d29121df7c7" - } - ], - "partitions": [ - { - "name": "Store-c0e5ba98-f95a-4712-91ec-71c7dc35e177", - "mode": "import", - "source": { - "expression": [ - "let", - " Source = #\"RAW-Store\",", - " #\"Renamed Columns\" = Table.RenameColumns(Source,{{\"Name\", \"Store\"}}),", - " #\"Changed Type\" = Table.TransformColumnTypes(#\"Renamed Columns\",{{\"StoreKey\", Int64.Type}, {\"Store Code\", type text}, {\"Country\", type text}, {\"State\", type text}, {\"Store\", type text}, {\"Square Meters\", Int64.Type}, {\"Open Date\", type date}, {\"Close Date\", type date}, {\"Status\", type text}})", - "in", - " #\"Changed Type\"" - ], - "type": "m" - } - } - ] - }, - { - "name": "About", - "annotations": [ - { - "name": "PBI_ResultType", - "value": "Table" - } - ], - "columns": [ - { - "name": "Key", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "64fdcf75-33e0-4134-a5b8-677f8fefa7ed", - "sourceColumn": "Key", - "summarizeBy": "none" - }, - { - "name": "Value", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "lineageTag": "41af608f-f46f-4878-be03-184d0cad44cf", - "sourceColumn": "Value", - "summarizeBy": "none" - }, - { - "name": "Order", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "User" - } - ], - "dataType": "int64", - "formatString": "0", - "lineageTag": "93bae8d8-9794-480e-8753-d51693dfa9ad", - "sourceColumn": "Order", - "summarizeBy": "none" - } - ], - "description": [ - "Table with information about the model.", - "Key/Value representation, with properties like: last refresh; model creator;..." - ], - "lineageTag": "68907830-e8ac-4c12-98d9-f70711413080", - "partitions": [ - { - "name": "About-77c21240-7751-4575-bf40-8c068bfd01cd", - "mode": "import", - "source": { - "expression": [ - "let", - "", - "    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(\"NVA7bmMxDLzK4NXGQ3IHp9stk8ZwwUi0Q0ASFYk0ktsvlZftxCE1v8tlO/ODi3bOeP/eTttfSUOn3my7ni7bG48p2gJ/3p9+kDPPNKTbgf7RwRXSp1dkLTowxUCV7YSkbXIyNh+gLF1mknYHF4nlDL2sYPFZNcO49vgsLUmW7M3ghkLvQQ+2g5pR6d4IVOTTacergZvU4EaV9XjESPWET5eJptOGZ/AXjyRGyzC8FKpJD+Z1JFOW0g+l9DgGUxiv4UmPACFlO86LktwYMjycHFmlYXAf/MEt84jgATy0eA85DjuRFDwnI0kp/xuKQI6b34UMbRlCpxGDjx0vX4m7sa8aowNNiTjFXfIumWz9iBR9qGRuq8XVVIgmL51WbujtJkkImSePta1alg1aBUnUMX979bpv1+s/\", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Key = _t, Value = _t]),", - "", - "    DynamicTable = Table.FromRows({{\"Last Refresh\", DateTime.ToText(DateTime.LocalNow(), \"yyyy-MM-dd HH:mm:ss\")}}, {\"Key\", \"Value\"}),", - "", - "    FinalTable = Table.Combine({Source, DynamicTable}),", - "", - "    #\"Added Index\" = Table.AddIndexColumn(FinalTable, \"Order\", 1, 1),", - "", - "    #\"Changed Type\" = Table.TransformColumnTypes(#\"Added Index\",{{\"Key\", type text},  {\"Value\", type text},{\"Order\", Int64.Type}}),", - "", - "    #\"Reordered Columns\" = Table.ReorderColumns(#\"Changed Type\",{\"Key\", \"Value\", \"Order\"})", - "", - "in", - "", - "    #\"Reordered Columns\"" - ], - "type": "m" - } - } - ] - }, - { - "name": "Parameter - Dimension", - "annotations": [ - { - "name": "PBI_Id", - "value": "fb0b744d500c4fe187fc782efa6004bb" - } - ], - "columns": [ - { - "name": "Parameter - Dimension", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDataTypeInferred": true, - "isNameInferred": false, - "lineageTag": "f29057e2-1184-48f8-b6c5-08f85cfd5ec1", - "relatedColumnDetails": { - "groupByColumns": [ - { - "groupingColumn": "Parameter - Dimension Fields" - } - ] - }, - "sortByColumn": "Parameter - Dimension Order", - "sourceColumn": "[Value1]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Parameter - Dimension Fields", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "extendedProperties": [ - { - "name": "ParameterMetadata", - "type": "json", - "value": { - "kind": 2, - "version": 3 - } - } - ], - "isDataTypeInferred": true, - "isHidden": true, - "isNameInferred": false, - "lineageTag": "dc604c44-bcb1-44cd-acb7-55201a651d63", - "sortByColumn": "Parameter - Dimension Order", - "sourceColumn": "[Value2]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Parameter - Dimension Order", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "isDataTypeInferred": true, - "isHidden": true, - "isNameInferred": false, - "lineageTag": "2102f8c8-0503-42f5-ac07-b1a606810a4a", - "sourceColumn": "[Value3]", - "summarizeBy": "sum", - "type": "calculatedTableColumn" - } - ], - "lineageTag": "29294de5-e93a-47f4-bd78-26ec8efe7786", - "partitions": [ - { - "name": "Parameter - Dimension", - "mode": "import", - "source": { - "expression": [ - "{", - " (\"Customer\", NAMEOF('Customer'[Customer]), 0),", - " (\"Product\", NAMEOF('Product'[Product]), 1),", - " (\"Store\", NAMEOF('Store'[Store]), 2)", - "}" - ], - "type": "calculated" - } - } - ] - }, - { - "name": "Parameter - Measure", - "annotations": [ - { - "name": "PBI_Id", - "value": "1a6e47ba0137472192b990cc0ff130aa" - } - ], - "columns": [ - { - "name": "Parameter - Measure", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "isDataTypeInferred": true, - "isNameInferred": false, - "lineageTag": "f2f4b00e-fa13-46c5-9726-16717f325e26", - "relatedColumnDetails": { - "groupByColumns": [ - { - "groupingColumn": "Parameter - Measure Fields" - } - ] - }, - "sortByColumn": "Parameter - Measure Order", - "sourceColumn": "[Value1]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Parameter - Measure Fields", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "string", - "extendedProperties": [ - { - "name": "ParameterMetadata", - "type": "json", - "value": { - "kind": 2, - "version": 3 - } - } - ], - "isDataTypeInferred": true, - "isHidden": true, - "isNameInferred": false, - "lineageTag": "4787b049-3037-4007-9719-bfeed93a7cff", - "sortByColumn": "Parameter - Measure Order", - "sourceColumn": "[Value2]", - "summarizeBy": "none", - "type": "calculatedTableColumn" - }, - { - "name": "Parameter - Measure Order", - "annotations": [ - { - "name": "SummarizationSetBy", - "value": "Automatic" - } - ], - "dataType": "int64", - "formatString": "0", - "isDataTypeInferred": true, - "isHidden": true, - "isNameInferred": false, - "lineageTag": "5898d688-d0af-4285-9d33-3a41d2401e4b", - "sourceColumn": "[Value3]", - "summarizeBy": "sum", - "type": "calculatedTableColumn" - } - ], - "lineageTag": "eee26640-bfec-44ed-b1e7-d56562bc25ed", - "partitions": [ - { - "name": "Parameter - Measure", - "mode": "import", - "source": { - "expression": [ - "{", - " (\"# Sales\", NAMEOF('Sales'[# Sales]), 0),", - " (\"# Products (with Sales)\", NAMEOF('Sales'[# Products (with Sales)]), 1),", - " (\"# Customers (with Sales)\", NAMEOF('Sales'[# Customers (with Sales)]), 2),", - " (\"Margin\", NAMEOF('Sales'[Margin]), 3),", - " (\"Sales Amount\", NAMEOF('Sales'[Sales Amount]), 4),", - " (\"Sales Qty\", NAMEOF('Sales'[Sales Qty]), 5)", - "}" - ], - "type": "calculated" - } - } - ] - } - ] - } -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Report/StaticResources/RegisteredResources/Light4437032645752863.json b/Test/SamplePBIP/Sales.Report/StaticResources/RegisteredResources/Light4437032645752863.json deleted file mode 100644 index 55da67ed..00000000 --- a/Test/SamplePBIP/Sales.Report/StaticResources/RegisteredResources/Light4437032645752863.json +++ /dev/null @@ -1,499 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/microsoft/powerbi-desktop-samples/main/Report%20Theme%20JSON%20Schema/reportThemeSchema-2.116.json", - "name": "Light", - "dataColors": [ - "#364B59", - "#C480A7", - "#663466", - "#6E98B5", - "#67002E", - "#5D8099", - "#3F213F", - "#EC64A9", - "#3599B8", - "#DFBFBF", - "#4AC5BB", - "#5F6B6D", - "#FB8281", - "#F4D25A", - "#7F898A", - "#A4DDEE", - "#FDAB89", - "#B687AC", - "#28738A", - "#A78F8F", - "#168980", - "#293537", - "#BB4A4A", - "#B59525", - "#475052", - "#6A9FB0", - "#BD7150", - "#7B4F71", - "#1B4D5C", - "#706060", - "#0F5C55", - "#1C2325", - "#7D3231", - "#796419", - "#303637", - "#476A75", - "#7E4B36", - "#52354C", - "#0D262E", - "#544848", - "#016AB8", - "#373D49", - "#FDB15D", - "#AAF20F", - "#5F646D", - "#8AA3EB", - "#FEE266", - "#A6687A", - "#3557B8", - "#DFCFBF", - "#4A91C5", - "#5F646D", - "#FBBF81", - "#C9F459", - "#7F838A", - "#A4B8EE", - "#FDE489", - "#B68794", - "#28428A", - "#A79B8F", - "#165889", - "#292E37", - "#BB824A", - "#8DB525", - "#474A52", - "#6A7CB0", - "#BDA750", - "#7B4F5A", - "#1B2C5C", - "#706860", - "#0F3C5C", - "#1C1E25", - "#7D5731", - "#5D7918", - "#303237", - "#475375", - "#7E6F36", - "#52343D", - "#0D152E", - "#544E48", - "#010EB8", - "#393749", - "#F9FD5D", - "#38F20F", - "#615F6D", - "#A08AEB", - "#CEFE66", - "#A67668", - "#5435B8", - "#DFDFBF", - "#4A53C5", - "#615F6D", - "#FAFB81", - "#7CF459", - "#807F8A", - "#B5A4EE", - "#DBFD89", - "#B69087", - "#3F288A", - "#A7A78F", - "#161F89", - "#2A2937", - "#BBBB4A", - "#45B525", - "#494752", - "#7A6AB0", - "#9CBD50", - "#7B594F", - "#291B5C", - "#707060", - "#0F155C", - "#1E1C25", - "#7C7D31", - "#2D7918", - "#303037", - "#514775", - "#697E36", - "#523B34", - "#140D2E", - "#545448", - "#4E01B8", - "#423749", - "#A9FD5D", - "#0FF256", - "#675F6D", - "#D18AEB", - "#82FE66", - "#A69468", - "#9535B8", - "#CFDFBF", - "#7D4AC5", - "#675F6D", - "#BCFB81", - "#59F484", - "#857F8A", - "#DAA4EE", - "#A1FD89", - "#B6A887", - "#6F288A", - "#9BA78F", - "#461689", - "#322937", - "#82BB4A", - "#25B54C", - "#4E4752", - "#9E6AB0", - "#65BD50", - "#7B6E4F", - "#4A1B5C", - "#687060", - "#2E0F5C", - "#221C25", - "#567D31", - "#187934", - "#343037", - "#684775", - "#457E36", - "#524934", - "#250D2E", - "#4E5448", - "#AA01B8", - "#493746", - "#5DFD62", - "#0FF2C7", - "#6D5F6B", - "#EB8AD3", - "#66FE96", - "#99A668", - "#B83598", - "#BFDFBF", - "#BA4AC5", - "#6D5F6B", - "#81FB82", - "#59F4D1", - "#8A7F89", - "#EEA4DD", - "#89FDAA", - "#ACB687", - "#8A2873", - "#8FA78F", - "#801689", - "#372934", - "#4ABB4A", - "#25B594", - "#524750", - "#B06A9F", - "#50BD70", - "#717B4F", - "#5C1B4D", - "#607060", - "#540F5C", - "#251C22", - "#317D32", - "#187964", - "#373036", - "#75476A", - "#367E4A", - "#4C5234", - "#2E0D26", - "#485448", - "#B8016A", - "#49373D", - "#5DFDB1", - "#0FAAF2", - "#6D5F64", - "#EB8AA3", - "#66FEE2", - "#7AA668", - "#B83557", - "#BFDFCF", - "#C54A91", - "#6D5F64", - "#81FBBF", - "#59C9F4", - "#8A7F83", - "#EEA4B8", - "#89FDE5", - "#94B687", - "#8A2842", - "#8FA79B", - "#891658", - "#37292E", - "#4ABB82", - "#258DB5", - "#52474A", - "#B06A7C", - "#50BDA7", - "#5B7B4F", - "#5C1B2C", - "#607068", - "#5C0F3C", - "#251C1E", - "#317D58", - "#185D79", - "#373032", - "#754752", - "#367E6F", - "#3D5234", - "#2E0D15", - "#48544E", - "#B8010E", - "#493937", - "#5DF9FD", - "#0F38F2", - "#6D615F", - "#EBA08A", - "#66CEFE", - "#68A676", - "#B85435", - "#BFDFDF", - "#C54A53", - "#6D615F", - "#81FAFB", - "#597CF4", - "#8A807F", - "#EEB5A4", - "#89DBFD", - "#87B691", - "#8A3F28", - "#8FA7A7", - "#89161E", - "#372A29", - "#4ABBBB", - "#2545B5", - "#524947", - "#B07A6A", - "#509CBD", - "#4F7B58", - "#5C291B", - "#607070", - "#5C0F15", - "#251E1C", - "#317C7D", - "#182D79", - "#373030", - "#755147", - "#36687E", - "#34523B", - "#2E140D", - "#485454", - "#B84E01", - "#494337", - "#5DA9FD", - "#560FF2", - "#6D685F", - "#EBD18A", - "#6681FE", - "#68A694", - "#B89535", - "#BFCFDF", - "#C57D4A", - "#6D685F", - "#81BCFB", - "#8459F4", - "#8A857F", - "#EEDAA4", - "#89A1FD", - "#87B6A8", - "#8A7028", - "#8F9BA7", - "#894616", - "#373229", - "#4A82BB", - "#4D25B5", - "#524E47", - "#B09E6A", - "#5065BD", - "#4F7B6F", - "#5C4A1B", - "#606870", - "#5C2E0F", - "#25221C", - "#31567D", - "#341879", - "#373430", - "#756947", - "#36447E", - "#345249", - "#2E250D", - "#484E54", - "#B8A901", - "#464937", - "#615DFD", - "#C70FF2", - "#6B6D5F", - "#D4EB8A", - "#9666FE", - "#6898A6", - "#99B835", - "#BFBFDF", - "#C5BA4A", - "#6B6D5F", - "#8181FB", - "#D159F4", - "#898A7F", - "#DDEEA4", - "#AA89FD", - "#87ACB6", - "#738A28", - "#8F8FA7", - "#897F16", - "#353729", - "#4A4ABB", - "#9425B5", - "#505247", - "#9FB06A", - "#7050BD", - "#4F717B", - "#4D5C1B", - "#606070", - "#5C540F", - "#23251C", - "#31317D", - "#641879", - "#363730", - "#6A7547", - "#4B367E", - "#344C52", - "#262E0D", - "#484854", - "#6AB801", - "#3D4937", - "#B15DFD", - "#F20FAA", - "#646D5F", - "#A3EB8A", - "#E266FE", - "#687AA6", - "#57B835", - "#CFBFDF", - "#91C54A", - "#646D5F", - "#BF81FB", - "#F459C9", - "#838A7F", - "#B7EEA4", - "#E589FD", - "#8794B6", - "#428A28", - "#9B8FA7", - "#588916", - "#2E3729", - "#824ABB", - "#B5258D", - "#4A5247", - "#7CB06A", - "#A750BD", - "#4F5B7B", - "#2C5C1B", - "#686070", - "#3C5C0F", - "#1E251C", - "#57317D", - "#79185D", - "#323730", - "#537547", - "#6F367E", - "#343D52", - "#152E0D", - "#4E4854", - "#0FB801", - "#37493A", - "#FD5DF9", - "#F20F39", - "#5F6D61", - "#8AEBA1", - "#FE66CE", - "#7568A6", - "#35B854", - "#DFBFDF", - "#54C54A", - "#5F6D61", - "#FB81FA", - "#F4597C", - "#7F8A80", - "#A4EEB5", - "#FD89DB", - "#9187B6", - "#288A3F", - "#A78FA7", - "#1F8916", - "#29372B", - "#BB4ABB", - "#B52544", - "#475249", - "#6AB07B", - "#BD509B", - "#594F7B", - "#1B5C2A", - "#706070", - "#155C0F", - "#1C251E", - "#7D317B", - "#79182D", - "#303731", - "#477552", - "#7E3668", - "#3B3452", - "#0D2E14", - "#544854", - "#01B84E", - "#374942", - "#FD5DA9", - "#F2560F", - "#5F6D67", - "#8AEBD1", - "#FE6682", - "#9468A6", - "#35B895", - "#DFBFCF", - "#4AC57D", - "#5F6D67", - "#FB81BD", - "#F48459", - "#7F8A85", - "#A4EEDA", - "#FD89A1", - "#A887B6", - "#288A6F", - "#A78F9B", - "#168946", - "#293732", - "#BB4A82", - "#B54C25", - "#47524E", - "#6AB09D", - "#BD5065", - "#6E4F7B", - "#1B5C4A", - "#706068", - "#0F5C2E", - "#1C2522", - "#7D3156", - "#793418", - "#303734", - "#477568", - "#7E3644", - "#493452", - "#0D2E25", - "#54484E" - ], - "foreground": "#192229", - "background": "#FFFFFF", - "foregroundNeutralSecondary": "#716E76", - "backgroundLight": "#EBE8FA", - "foregroundNeutralTertiary": "#96939E", - "backgroundNeutral": "#DAD8E8", - "tableAccent": "#BD3978", - "maximum": "#3F213F", - "center": "#663466", - "minimum": "#6E98B5", - "bad": "#BD3978", - "neutral": "#C480A7", - "good": "#9C6584" -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Report/StaticResources/RegisteredResources/_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg b/Test/SamplePBIP/Sales.Report/StaticResources/RegisteredResources/_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg deleted file mode 100644 index 00e5de2a0e6c1ee204097684e76289140759209d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 366046 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≧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 b4H_qh{oYy+@AO+3_}sXrp`5RH|Ji>5pX=db diff --git a/Test/sample-resources/logo2.jpg b/Test/sample-resources/logo2.jpg deleted file mode 100644 index 9962c15182276c7172c5455e966c160aa7dfa60d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31754 zcmcG$2|QHa|35xLq$EX_ER{Wk&?1Z$dnJ@9S&FhINy$jG4J}?z#7z^ID#-=j(MY@f)!lwnIZjT?Ix(MFqPG z{=N=izCk}8_YiWOV7xFG6)m`1=!Pl(s5Vj4&~B!q-@>pJG|1Tj+eAf8y@`gJmX?MF zv~~l(!)TakckYuqv3ZyFHM;$FEQcNj#M1Mf%=*Bp(=f#^ZF1+)7KYty>>Qi|fziJ`VPXp1%l>i%&>QN`94+`sQtR&b!>a_xWYz6_r)hH6K4UHZ`}jwtf5F-qYLHKQK5n zJTf{xGdnlGu!vp4LF1wV<6Qkm9@rn_Vg}>dL_%$g4X`LxnlRFIzyZL2$1g4>(kw*644eZhXG_tjUt&gi6wvCz! zEFLv83=Ug3)7Z$MZ^EFVFQv3a`Jjj}mGEFF3Z^0QE$+e2z({4-Vbne0O7Pc#1=cRo ze4g4OSsMkj5f>ML35XvQj-g3NHt~Uungl-BNojfy>SgsXXv{JWodW(UF~&urgsDVC znoS<;QI;d0x87h>m?}Fvd;Jp770I`u0)ElWQichzWS`%ptx5V|0RdxKbDI%{9EO+p_qh|r3|ShUrZXj!z?Hlc)LXyXFGQQS@Vo=RO4oGdN?dePBVA{|kNzG#DC ziwm&8w3XLi+2j_wPFa9H~3%Y_BR3e zW8R@2DJMhgOj?AE_e5F_c}51YrGEAmz^f>vTZV_y6)D&%|Fl0mg)E zRiFtcP1@STK_7G)*GIQ@(~toM_K9SGgTW;H-s|cdZdDf8^kDa@LIwhVLeYz>U?6+f zOd-jpBtZ5N1tST)#s)Qp&JG5qX%mC~3CLs#C;TQmf02RJvHUMHMVg`2;i`uN)rXWE zx@VHc1LYS(V->HOafeU8=o(q!Ve|&8;CcsgV^^)vfHk?Ij zwdMp#eFB>f*`?Kvfw9_Pv;TTUXdb{si2%Q}aRI@?Q0yc{;vm5!3n!2aokpD|Fn*D> zsapN@xAzU^X!UOX?j2aC?0@ZO-OF#Zqkox;zm9Ef5`Md^01L@y{_Q0pxUy#6e;W*C z_#|sgHu>O4aQ36n=b&@oN;LFg!U=Kfx<{H-vR2U~#s&WE59F!;L-jVC_^-bQh6b)- zn>CFmoebT|zj$C^`(tQ;WW$`Z!H+G_-|NCwJ*1-udA*=cnye1|8ZZ&?l&Hvu^ zPYXj4&J6+zRALpNt)nXFG!jbt^ZX5hPBN=xlNJp7O#?PG`Zp&aKoUD0ve5!?MSsoG zlCaU5!CEz3q<4`AK(-!ioZtWEvj((qi5nISTFG@85dkZ@K_(c46Uje9Uf?gM{x{eA zzqHYSTng|PB$cBVv0#CaEP$M=5au+S}{U=lbUa^r3sQ+~C6leQi zuIRsB`akqRk_K^#4~A*4s|5*}|B3j?bM(j5{HM99i|%kii0G z>i>@y#ee((5=cO073By}P`3^&f#j6+DL#BnCH^;c_^WtAR(6B&gS3$>`Ee8#AS(+9 zk}dwhRIh8(@8<5mJpOk8L1P`t(28$Dk<7z7j#--nXik2g2N4lVVHiLi8&v!+H$@6& zz%u_UD)|jUZNz}2FkTo6<0xi|3JR~F7GzwtF?xpd`~Ts!8Yt4n0jC3n*pi?(|9_TA zfMQll5$AqH}?9c509!l$1+m83zhQPCI~4+SPZJaN-sp%UezViQBEpD@$x-6 zmT8BlW87s#Sg9(u<$0lcl7`)aulGt0-+U$;i%DV2^OoXw<}ov)HWuGUBn>{?E{F*~ zrkW~w)BU2j6Dm|?o6iUDliTU>%2wa8p@b%?@8&-Df-o!dZL_rwx?f`E!m~c=N`$u_ z9CA>;KH|PNr}>GGnFtX!6cL4_{+VN4|F}=qvEbz)50C7S=h@oIm-hi^p?vra)lwiFr$$+v*p zqp(zhmRm~s{b3v*K>+mTU?C#zvQKTQ{tEMA^hbMLOSy^YOjWV)yM#LxKiGfq(Q!-& z=kL4V;hHjP(MH$HjNW1BG7iTMXHHEU2tDz081op9#?fQoQUtayb2F2@C0aAmS>VEs zs@aDWk>>ir12V*mNbA0$gOk3kgD?1%St>|EbD_VEO zS#&N2t}fO8s^|Y~On!HK^dPi^xUp3L_9r$cxrdEbcm2SseP53?0kH;RO$sK{7pBD2 zeUm>D))opbll5t(tZR zly~~PEr7*O%hrrW@4Wl@kWSe#3FGDReyuM)glON}&RtJzbG|SB{G=gre7_MhoN4rq z+Zg}L)?039!FQ&Dzs_2Aj7$>tjfFjaas$jLc((+JcGOpR$Lh4_BDgR5~Nef~imB+ckIYK+@{Z%!oD@vk+l51@=Uk@mXL8 zGLBk|J6wgM{JPt=_VO-mubb0ap+VfW_MB#t>nFmLiLiR*JA?4DrRd9HNVWljii3do zJ`DZBBM`4V-wb@3h}0e<_JSGNeYqRZx7CKm~g@UKPecQLkN zDbBYfV*AW?Xuk>{g{-%;dDNkLO`~jg zIt(>#MCpdw6C9~yxM68>3vUnSrfS|l!m(s}rhmWR17y^>vu1vx92!G)z0(J?Ga8Tx z1`$g}^$Ho(DGf_T+I`7I&gim-L>No+Q0oX0=8qAtb&f7WFYOjo%+*0^+{U6+B?uxv z!h2j#5Mld15}rZ+VB+j;!am+<`RL&i9DSxiHP%>q^bQf$TXO0!O8=A*$FD7Fmd#!d zp8{G%{Xy)zkZa9cgq)GIMb*Z_(kM9RT>oLkRF>65J_6bl?V#fL*H zyP6+>*4t|}+9Jiq8f3Tnk06N>K+|Lj^`1+$nX9?UchovmT#)HFBDdFx2z$_GV+8N} zT<*|Ykl4Yock52mcQAfrmvZWjtdG7Co>URfN;gQPsP+odfX#AVLC?L85^npHHGzfAkqS z_lH|ngyyrySW8|-1KQ8^+fCPRBy0mk5sT2x; znfA3%h@`)eQ^z>fzASG&Oi(G@E%jJ?m~IB+`ko^<*g2MPu{!|m??!8`8{0pKG#?a6 z;`DD~JkbyAoh2hoHdR_jR;79OlRD>lG| zU-Zj^kG%SGrH&Yc$WLRwk6()G>gG6Prhim4e){wzUI1;?Kabo)zo?ufI5>R6t`x^K zjZaI-&-I(=?0IpY&xiRXBCzv7wUeCSJu#oOXeCEX@(1l??P8mou3r^%^eyjLA`9IH z=4>-)+~quliQ|Qq-O{#FZ?uQt>aBF8lLHe?yzr^{XDuNSz*F+uhW$ zcun?biZ}%Q_dg=ho&cr+aoNa_Q3z5f^9Mydz@d`}&&|L#Y=YLA1k0eTB|ve16+nw5g9}`gXcQ83K@-II-;)yV&3DN3<=WZntVQwXnE^Ky2M~+C>AluGiqr`b@(VA?|LG)lb*n!`Hatv-A{@)E-zw zn!{k>)ujHbE^V2X_cu>!War&_Q5!FI1tspiq1eLj*>2=Fb~0hxF3*A1mz1*l-#-vYuH8E8y&2pMc-Q{L-L!$PINat+%t( zXJz`%%7)9^i7<)_NOD?wMKzb7uz0eF(Zd30zdyZXeqLw}Bcd?Y;=T)6lOeIdN|wBM)*hagipqVU z5JmTdomUu3UgDUa*8CA^by9Iab(lyo%Q@AmsctsY-%nk+vorjvJ`91QQPf=2uC@Eb#`z@po zN(Lv4B701u886MgRLz~?N4zj8I#L!rBYye*k{02ybzL=nYxDe}Fy}XOvcqwxnL1aJ z29g;`N)nOm2v$ipl72#wUIHixpcn~ufFS9CcOY{tBxvwE=MH$4hd{VMvr^#1l*lnV z1yzt@1Hg!{g%}%95I6`}0NT~?0Xh%(Fn~Crqu>kaMNVkzfR^U4j`Ki}`%za$sn=*DWtCLT--z>9*KYv<|hp z6Y{QlCQWwni$qvJu1oJPy|H~}mv#_g+ceQ_n6XPib+VsBqbvnXJHKVESUzq#>nxQ> z?<&IgCGv8!`&`5}21zD>mo7d%6L8QxS7k;hR3Z#(fSJJb{K`Z&x9vW*{H%HY4!ZYq6W$ky z5+c-%R1`oK0tnfFI0UMHSWk42vR@E{h3F#++$F~?6gmtD7-Z2P@dG`sy|T5TC@79Z zO9O@}JNUsG_OhT*!DMb2C<3rrBnSooF`1JlX$7PnB>%f1c@OmgwIXHnMg6Cfat1I9 zH|Ykia+(~!PmLp7Z0a%;@O{~KXr(4Ni>J5?tyNVRJ~*hE_Eac%B2Y!Fx+DD82W{PG z>F+%EObyGp0y+t&uMhf%pJ_1q*zHMH2jvGUM3`RJDWjr9t`WWH`AVceW*>6ic20Jz z9f5!J3po}Dyyxs&#;`PnGiiNcaf10o7~0YW@4bY0=qELYaV`Yk)${5memtDRY^G+% zA$)aXGtC#=uQrzh#QsYDAsEJ)&Eb|TvuCXsP74-Y=~2a_-XKY0myVi;d-BeRY64n> z93pE<5cd5D?{nyhG%}6@VX|RBCfsOE>~5B0t($QMyF(OJZnYn7npQ_mr#t8FL7mOU zYom5<_4xtG9o4xHcGq}|(JRyVv0`q~$B#`crlHS;+@ejN729fN?S5dMhxCm@l4kV5 z=%ni8o>A+OJCMNnkOXcA!#xqD#7bxJDEiK9lSp75Z|^}#f%3YzZfVw$s69YW?o|`= zGMrLaN8GvDr{>WscE$q+mnJ=(quy!Z)txynXe&D<*OLTo)#^a+tI1VJbk46Q7Ae#j ziIs)`7WjqCLPKAmxM>~OkS(T$J}sqKZG&+ngY|Wh-Vng8iYp|$n*%ps_VtceFQReP z-d*m!p(91`w5f(?(Sc^4XY#_L zw}dOfyc^y>dP@-cWD+fD01h1lpFL~(dGnF67~{s0el{B-%phzD5pZdozvNS4M|qD?`uv?}KcgpZOO-xDpoKja@t9%J=2WC&zbWDJkr~Vu*1ij!d(4p6 zPo0t7m&EB&y$1!@02AgjGJR?moP2y4fNgn*X81^!rI}XN(P#!Q`2$4Q{H(xHSJlKY zB5cPm#yfJ|UojHPX?RbEs`qE@ki9nh<}VU1dL1*0{(>{ulcN<7`OZgoT1B)vYox=h z{mg~u36;lbtAG=J63=q!AT#=>AhHl1dEYegb_3JTBjj6`TL4#H$&NV@MgKe=`nW&+2n!Ii zGv{}6Dm!a=`?)HZf{6n0YDD-PdOHB1CD8ZKyy+5#d4-$t*uXiN0&P_*(OQM>zb+ui z)sbXq6{v$09R$+XY8$}vgS;IiVPrV~Xn%u*B|?cZ$n`P+ulxYW=>t&J`InT{hNK52 z)k3)nDeU{J)UFB_xVKZly59bIoPTt8%-qaV-e@8$^}S(Ei{mgdxgGc-Y5dspN|#H< zJ$wUMU6VTr`P(WFihPS_eP3;tm#{w?MW1ZfVFroV^W@v__nj9LFp4pZIOui=G z(@7K^P#iMmd>`*&QvzA8eMasI%dh5VkU+2uPZ8t~9;=>+2>5}T@<)EeI2O^gpP>^n zHJ!n1<}MB)Nmn!}617VT_o&Vb)>f90ViXjyP)tsg@)ZpSw(2vQ;q~|}eu6_C_EhPN zm8Zn~1rhUd+^2fR8Dr$W%@q>NvcoPD+s`0Ztz zbS4`6!n}p{Zc9kY$l3cs5nRu&!WdM!Tw0I5(zyGJHD%&__~VJME;f6t%sqSW1d8Z! zpGXS9@P%a5omE$Ove0Ofus?_;osI8@ib%hXNeC+MG~6|Ma(jvXFI9~`6W(I2Meo_n zn!A`&2b&`Lov^j_ykkZ>eT%+?X@bLOa&Bv3M)l;Cl|KB~@a^Mo2WNpmO2J4{=)7%e zoFs=*8Uy^K4+c7UjePryc+2q?yWzkyggAX?9nPo2KM*S!LjkYQR|R+ANxG1sW8?+A ziILL;#iy)8EnxJ1s|8#2nPhJYTrhM1^0VtMc!Ry%s=NtufFc&`?Eou6HQu0B_m6;L z{X3<86gmL#AE+wYSmv}wkZm}f+zJ>>E-k;3K0;g5Cpd0LrZH=a;}8L!0UwJ%N*NT3 zelJIE;T1&_VUA%nXS4xUHl*VQsGMaSOnzCPor=g0i%K>QE{{c(I~*e25(JD)xj;It@oC=NZxmdkfe+=rCv7b`GQ&Gy#E|^ZPet_S#M9kx0-E;$ zPQ-4=WGHj96NV>gxJ!1KuU4kXS3pDlx=j7qg9loplMF}O(kxAxLv>D=Hk?N~p|9bW z%^vl!30Y)lJQo{H-Fn`u=UX8ni?rECK?+_fT_MH{4}ff_+D}u@Wv0okqkB^Kr8J6JbfB_%t-zRD9E? z;$zO7Kwxh3bgxS0KfOZ|`4a)g1t5I^U!eN)b!@pxzpma|whn_nIrfFP=7(IyesnmSt zB1|J2J`SJN9{_%O4_0l-6}endZ0xU%y3L}ucr?8V6#e6Mn3XP(eKhs!YF<7!(Uxo? zY>Dm%Fv7Bgivwri_qsIZt(maX50J0nA-DV9sb&RXoQSg)E04>54^v~|PN~-irmMLSLAtT;tQXW7VY-?RYwx*pOmVe+xRvwRQGANw z#LMH0OC|XV`72Uiln;-i4~1~Fgo$aRd%CuEV!e=^gL{;1e;V-K`gAjqgF? z8=tNroUU-cq7`>CTf5i#%~2&s<9qPNI9+XoS@6iGgkY1*&+SnemI)sQ4L@f`=X-?O z#$+DS;xJTC75G$@!hkQ%rz7jA+v6~DNrF1=6Hjp44qraU zyfT;(g-N#3!zGqPTJ?m*ayR3c+cOb?mqyv-Y-*xf&!X!+YOW2d+8KGrVo^Wz(N=`f z9u<8-`4!bynfbCUXL*KR_>`XW81&1+`#w54_y%(~=6hhr)p^lxEAX}_kXA7-*JMDhPg2UHH?v&%cu4JyqAl2I{jFz<8_g}d(o|hpPCJi^xxM_O)9K9 z4R?;##*d|P)83ihR#T#Fs5C6~5*0AK2o;k8!VfW-)bbaFBD%4;%!?8Hfp^qD3Ki{>2^svFkb3-M zwCX9wbB~>_mVn?hm@_DbQS46Pq4ca{ey5KibRRM9OA2?nx%=$p$RTh1)i6(+oSj); z9WTp?5@8QBF&T~}Mmfz`mntUncWpX?YI0&l8ZNxrxHgAv8t&tZ@?!TcTQ|pLkD`@Q z7Y}E33bt`A@V!deA0p^`{b@^s*Tvv|=K18dD;gSKrgzzyv4rYeIrE<5q~ldl2WO_l zedo(*HYtp>gd8{!aqoM`vG<8~$J<_LNXvV##N)@BtiRNJ&YBl2lIIzpk=F7OZJXny zJ=lJm#iSt9@f}$!5~u+Z_!iHy?HcParx5<37!gK-NZodZ1RtsR%V?ZQNex0!;<~ew zaka$Bc>!_OGMAI97JYTQ2HC*?Zh=BQtUH%=m_rWhAdUf+4ZzI4jrx>GsmJ@h%02<8 z9+{2+8;ejtwpS^L4RIwIp>JgFXrYc*dA&^#H31RtP(OWzR`5m}cn5MYr`9AN$;mVq zp|!}nlwp4fbA<^n_0 zLgDVqF6VMP38sTlnospFMpWWC z?6aptx3>Cxt3h0QZIh^u=xdQPQt#z&hooY><r=^XBhRYn!+;ca<)jwq^0#ElIqtKF* zmSN(y$oN>t71R-80Kf4wMN2+xm(1Ja>U=LlO#{!WoOQ~v+nJJ7gOe9DX5g92LAI8j z1*V~N?lH84yL|w9{nOc!xHKS=NSG_Lt&ke{?%k)!458p*Yi?7=<_OWKA3}Y;| z`49Ars_K;ViVCA9eS%vWdRr0?+-f|1DV}v7%kxy}ES{g%??Gy$pV^wsIPH2@V8aWf zJbJzs8FHuhooZqz-nPB~aKg;vkkKU-x30<8KUCYVA@X&z4T9G{qD-(Ukemh~XSqPr z2jnyrvaKeAms)1BK^@1E0A$*Ih))^bs)ltGgAj$XwNwF&2=1~jSqx()7P zX~`r0mRUC+Ivwq(o|IaYy@wKg`mXc)FBOI@DhFx~>`BYJ8Dp7w{ZgM%Vo7q*jWHvE z?1ttl`tUKX)5*!r3j~J`R&p7wY48XK!O4l3o(s-Y?eDEkSQU1s&%J)WrEM|l8SYzK zoBZX&vH4r&KiqT_!oGBKVY+6}qZs@c@Yb`*$If=X9Ui_mQjZqfu}fWy#(y4;aA3yvQLuJS6ZQq4ZO}A{1 ze3x=dFem8F+*QW|ZuGwpv`*rlc$~whI%~mdF6QsvvD}r;g3o%l+L*}iWBwX1!91!^ z{R*Sux9>v3glp9iiBRrY)l1?1X0LM;=+(}&##_&e)bl0-C35y}yCB#-fj*GI-l7+H z^oVb~NoL|Dt{p1$Ndbnwga}4FQnI}--Y@RGOTjU*0jW=lkMHs|Bis(8Iy=bURM~wY za#7g;VIDu7pihp@s+kQ#{iPDx(Kxe`Pj4!3`rhg4g@d%M4uWI+*>ADqnjk@$4#2mp z7wfH8e68A2()-$clFE8O7`n<+uVM;P;j9|%Z+pT?QVEGYl;5M|H+}~fP|lw81870s zSV&qVa&6~M53srJ4ndsbeNG=1Sn8`L`4YRg)tS#BS3l{=X(9**f(VKTl4>M%k~f<_Ej zv&2WJJ3kCO)prQ{jIR_^=YJUl(66B7`*_(Z7xnG6f|k-M7OyKe05*zu1f`I*Q05%6 zD-h4%85bmD_Fy-_TbmDBHhh{^!|VwTICn9)+haaShtvI$rL5WgDDG*ipTBaNFR9u! z@DKCjaJ=<|kQRn%f9qFMMA)u%bF<*lc%X(aiB^M^@TbbS?J^ z;(dLfQBfp5@97QCmcDELpYG7(nseGC&J9luur!_JN?ZsKcp-KOxI^2&{=hXoBa~$C5YDU+nU}9uz~-np@@B!+z!0#}WJ(+Czg!0#6pk9Q zx*L85ks?$#uz~T83nP&)6fzvhXcCZ=GEF)y=Syd2pVpq zK@Rib!iQ4yWLmxtpD+KK{r;-2uS>-O?&R~hiZ;hy`KjV@WU_Ih)5@=vud%ny3xYrH zn{cKYrc+W#EhxSe3WZVfL1zRD+jA1Nm+mPBZF!XJjreI;3BYT-jGoYKP!@LP+A71j z=A6JjV4#DfgpnAi*aL)8IGCPh9W$T$O^uM0CCF$im4V{g%}L&vj^VfN<4d9Accy|q zEYf8W+{PO2*6Orf%-z|pS!nlOYnykyU)Sfx!U$P~$Z?!oO_1>uhVX1Ap+oY zbr%yk0Ycf*1dam91ob2vg^_e{gVQE&6C#zf-WbKrX9jGUed_dNX}|6)ul+DE17fX- zW}jm%o4(9^wnFp0d@IEtwA8E_{&QlW$*!ct#fb-*ZXFh05k0cg<_TB2$f7DX+8(E; zE?A_7HCMm&PRgBww>4gor2&0~1^)wY?d7Qd_Pz~IrPwFmqcc_F5&;AKjn~^!m>4YL z3D>6G&GDA1p4rAYIwkjbnb7UYcWqX(G?$FKoy;t1onK9T^^voD*R@z0PhigALVh{? zgRp{9OjaB={=otaRum%Nr!AETD?5^atS9{j@8NDl4s!$6!@ZJMHyRKfH=780PI?xd zMj1pH{X`I`NBUvAydVPa;c8^Um)jsGza`I$ES^|-%QRpfaw}eTv}DS6gm;ed9T8Sy zg2t+3BXOJCiLkCL)#jlN_`7TP^qKUA@Odq)%3WLB<{$3#wEL#2e4IyGiLk|5iIlp0 zreER6PjzCo92i|9?6I*5?<^x=&WkO;7gJSGEbUOTG7b9bq+6(k515CV`L>Ljmvh2b zsAE7Y%aSP{&~k3O$4vaF{e<_*Tg|1*13zAM8iC3{`YJ%+D-FXBz*bi12hmvNIa?eZDAIOqm_G&$5bCLb(o?Z7si(sS(@e*k2Nq43N?kpk&G-KK}+(nxDVp<72pT6n}wGC(m%>?fc8EC;T zhwIPtVpZnQIJzrfQJ_nL!0#b6`K5{Lodx?EflJc=3;?6V!Y5H+wX(q}0Ghk%;DVQ9 zi7*>o0s_mU#~siT9CGyz{n5QR)MTM_Z)M?D%U5#gE6jCND@DFm(c3JB2-iUGk9s3O zfA9RdX8JgBtTpiGvWZ*@R<}9!n%cOc%zB#1B zF@4n%tiO{XG+A*;2AjfzZ*ZxwHHRTZDn;H{r~%T&Bvb-w(2}5sKcdr^U_o&~z_0xl_^v)r;Wt+&1)MnGJF<1DKs5|_Tny#23RJ5G%2z=3y@?N0>J7@R zl=tppF#!hXj5fFrYUtxtLE%zRM!R+x)Kf}>rtuD^;05pV-Xp;V_h*(OWp=QYg z9L;YCPMKWw4IWnW$8J((QuX+sTPH}FGH^eX(^lgSXy+Xnk!%mvRwivT+yZ4L{^Dl9 zaDmGy1&c(^%~7%yq=w|Q2r0X_7SKrU%+un*P5`ma56Q>~$;nO=Q#6$+cuHr-&d%3Z zkPo1Sj|btvRwmp$9LJIR=$zj5+U(3HT2*K7=Y&RJ6Qc2kGWZ*9U<+H*8DMC1kkbwy zdi~K>UEiy-ytBjX3y3h!kGAxU*XAKw8`>l$pY_)Id{@y7YskW)Xeb|FCjAh5Uxw2+mTm=WceMpcUAMaAt5L^a@kP;H z-XC%*KBeXcAGkM0O(VOhpbEIf>#$RKuAeRBKf6YqIXwgK7DEbB=Qu0d=a`$`oyY{l zqYitpSL9ilQjOUz4*bdH!Xp@2-^84RfF}Wq8tGSrFeho9IWRl0!0hs+6B;1H-Eu;3 z{9}Zrg&|m*>U6(%a!xo=^h$UU(+9bg>ZgUrc;X#c&KGK?y#0M5~wfaA@)@68O=gd>o4SymR zjgmhh;5na@V3+asY(DGNI~;5UJc?Fxx<*1%9t73PAhPBaBKV++QsdNxOhyj(-2(-& zIu&tDMs^H7PlUk9MmOA{gE+S)Lan!q}9s9{|-;m-zyC zWU!f`FQm+~+DcA)yg{+i@awp%F{A=*6cxDJGwoq;QLI(p{_3tWHTZ(P0q^t3PNbY_ zAY24M_edsZg_oz=)o}N=$baMW_t|~^+gweGNWiw!9_*!D&bnjIM}}Fow42B0o~h(F zA2iK<=v?Sct?1FbOckEoYv|rpt}oW^StF6Vh2?7Z$9>CbxSd*8865?4GmRa+wXKU( z?D@SPYnQ1Mql&y|Cxa05$95fi6&03>H+}u+WcbC$ZCC9EKmF8+zAbqP{fgB?sEXSm zTI%x1tH_Y}0Q68H@<4Z%2JOw1N_H3Zw+-n8Y2#$Jc7^PpQ|B7`ruLOYI)uY3I=AYH zvb-sJTup6LL#4i87lNNQ;t`FXGWLY_TD1D?+9YOE_VI4Hj}QJ8)AFTb3v|T?ZqO&% zRV74ZO`=Z0E&2<|_Mv2C49hV)ZR3 zECTtN4HraxDni5a>V^UzEq4tX061d=CSOL?Tze zKi^_>V)_|D1+s`l*!NoJ6(_nX9#>@(_(&TZyL_<``y^#5%9-{ae7o0Ehtzj3GA!RA znBcWGG7X#VZ-1pe&*ig7bDykQ2A-o zW$H6{@$#@Plb4F#^7z_HC&9P%%_U>ER-UTB9l@Pq`nBh#_@@V(`@WZQ zNANzRxm)RQDbqT8Q7-TqLjHJF6je~!aPGagAMKOh(uj52K8bpm^?@#{bO?QBhFf%m z(YaDqd=ufTQi3M+tD3Li0g&BFa8K8cW1F%cW8Me%hE~V9?kT$4<{&DuWG`V8&end=OzBxxD7@i#>;942v6=0n?P($tmUJ=N%B%ulM~?vP zhGWDPEhY;#eUD#ePwjR__t}@B>oD)Ehb~_abV>*{HxE=fIIbmD(I13ui^UtXxi_U; z8J?D!`9&8YN$0`G6zsm02#ZBTJ`B3j3+T#8^CN`!q?7SGqA1bWLoB{$hg0=7r@~| znRHaZ#krf`$1}XV;dChq@!9@mql0?(S-l_H?})I=x0c@xP5OCiWznE;qFTisx=r0h zIk%_IJvx1DSlP89e6H@XB47LLFdNgDLZ;TrLOc<6lDaeHf^W;GWYOb1Y04~mn?FE7 ziVSM1X4P)<7~xYCRq}-uB-006W&rbW&^x zm}~_{Tjxc%rw2j&m-otDP@ko$aPg$&Er;gp;GBThz|}t(a2K@e=GN@`M1(b@8Ev=z zsVF$z&8<4dM)1|2x?>#|R%YMY@r$q{8k>>CJI@51u!^!#TYI5a4-?*Rh6wwnXuQI~ z>SAv7=}MSp4**XI&BELk3EURcgOfYCu*sJcb7uiYuSyNwcP{xlzw%w{=N^39(nN04 z^VOqrd-oAxtcnl`{uT+^=vA{VcrWKlMdRJ9WAQ@L9k?y7tUVQ9WyFsXVMRAH@Re%C zJEw4S^>Yn#Ia*U0&Z1G1?;Eb0VZ3D=mh&$L5@7`l=>7wzVn&oTBIVjF+4(*hSz!}d zw%35?Wp0M`ZHiPsF`{Pfhc|3-Z^2x#tZ~;ox|mytCNR(qTGyeCtY2YKT#mCBX61r7 zc(ugsaEOERP2BtaEXVko3+bj7wF(-=dL9Ng2xsh@^9 z*bfExmx6R=`VAp_i>kx*-40!zoDR)ra8lMHr9JFnPm5lkLrme*tV@k}g362cTyc3- zX82L`qayne{_Z>Q?^_KO*c1BOUkme3c=d*cvFHeEkatxkK%f|UMk9HPCMjV6;XROB zAwOqgGwQGVdsfjo1crC$)B|KqhKb~7Y5bNzq@ZmwO{lTK4w8P@0ItcvI;M$Aw3;A^ ztoe9@2s82^!cJK&FicPS2oVk(m%z>O;`Wej-%BQaDK)13IYSNC0cOyzyMlf`5YYX6 zJ8HB-1h0huFrOJQYR_LIqi}1iJikINX*hJIn8Vu(~-Mb>&U! z_%i~Re|2vWYZ&H7H^;&u81j^{I_DCmyiZZM0XfbGLhyLK;*dn$+X-_ffWk1dVAZyBHw4}UH|l~-}glh-Vmp2*!W&EfWq=FAm84KZq^2n^>{@!!PpeO zFv*M6(>Qc)uyC|m_+sBZb=JVs0+%mceROm$T0itFuJ24qZb`OH?(W7dd~-FUOj#Us z-&!(E^&$)kmkpb5XOs(XpWbUYv|O=Wdh&wZ71_I{ZnxY-;znOvJ1Zo`TOb|m*!{Ql zrzl)l_V9PBG{y*Fk7Jy44TpD?&>cDaU9rSXwa=@%0FjOm)w?Gbl%EILHPH{n0(N~M zepm#x84cD((?+|dj}TM~CKvZZ-}QB|KBKNeLO+YzdTjAQoyS*}04}v}^E3@#H1-7O z^6unfGH6(L@jS{U(uPI*@}6hPEWwbuB*H$zn+c$VWVji#S+HoR*BBqPqDA;e{!Se6N*s%5@>FMx@oon{;#tntfiBq29EEmGJD8)TI!xT|? z3fSv|kST0}OyQ2#evbX^drZ@Q@%Owg?d}^v6e^u^4AbL#U~2|nVovqe7#a4ve9Kt^ ze^3(`SjjZ7HLgvM2xG9sR;D1gzqo;Y8$O#+o{8YqzkSsO{nb-L>|8=X7w>7#>|Xo!XYcd6WA{}Z!=)XiqE(NGapk=#uj*dZUeD#m zj`S62(h!d=OMe+?jscl6gjBOrbS08e{V3WX=j2Zv`iTn;?MzJA>tJtMNPw}HSU{KSJkQ1&*uXsWS)BZq^nzKm3Wjo+q=4GBU+viq8QbPn z?Go_%t?5q;RoG?Om;L2a;F5=9h?@gv<@#iAdi zQB?gqG+yt0w4I(WmgcVP_V8f?E7|vRE3Bn>nd!&v<2x;#Uq;=SPu;>7yfR`^!=ad- z@IcEoWh}^W{(6gV&{;!Lo{I8N-A%yetr6U0j!^`9P7$co{u@ejHJL)Ll3II&A}On~ z(T;Agp@1^|jm(5Dxba2eJ7O9@_RMjpN@9s49aOA*K@Qs(RT>?e_9m!Az;Uz2E9~=$ z@F%F~Eu|i{E^Or)a$R~@tDBfzuDd=TdNUL-`;vM11P9N2Uwnp(d6{0_!#>`Q_s@}i z6-1bRW11z0POEANHo5%15nGFX+O!%xeId`hr>-@O%U(@c$2a2oF~iGYkcr|`c}9d? z+he=JnKPH@+Sc<^UN;{3?PM3gCmC)+mo9X)`a!tK%<7cA9cyBC?ylErv8VWbT!c9U zi|yKEFN12AvGaL76I|~)R$7K*+*qiQ}yMLbJzJhm2%e za%64Jq-w@=m_4G;Oz!FOikUw0pPHf;t{*R6dSxa=pYVvu4DRXt1}{IAzYS?x z+BGSCWLBo5(K#@t{eAbQVCrGy0UW&}Q#oJKGi$)WfvpT9$R3)hx)mxhhy(nExVC)Q z6(p6B!sIkxy-(X0@2H$H z-px7XhhN-}JG11XGJB1V;FQ(R6mL$zv9hx1YMg85d7;e0S<@_9rYHb*EG)i47O)YX z=`T7@p`RLqt*XoE0CHN5;7?z`p1ZK!y3?Hv7)8aVlAk1m>3Dw3Xl$y`v}YlbFxQ1U zHk!(;)hpL7Vkx@oDpZtoOzWvPFB1sg0TPJUDcoH&*=q}5|GqOfG0W5N>rgilhWC35 zSxhYmVH!Fnf0$h22Ep`e*J#|K0}G!%xsVX2%!P1B7kOo^%X#=(U?U43OXd!zii&uK zUL4!AWwVqQC(uf)C5|_OI;?7&AuhP za|_&01aRLL(Q|Z7MA#Guye!+~*i)ugbaTmwV-44DL~pe?+os%vZf${=MVt0?ik>Z8 zzUUWiW20E+8S5kTNsa%i-8a2ZOXa<9pzS_XB8=Ltvet%MHS(GzE~QHz?DI=^rloFv z$hF5$b0jXxz&K}nZov-7WKX)Rc`Ai zo5Ac#mqyVvSB%5%UR2T&TYjiLSyR_zL~}HwN3b=Cb$?L(h&+|!%|e$1N&YR@9rw_s zX4^bKcAwDdF;;4;D`6V{xmcaA@j#<&@_6@M244W(+*o;i-Dods*=xAavIJk)eGuA$ zvS~E~xW?#`?4N(b8d0fr+GK7e);o#{V5ykL@4y! zm{KSy2=J!66p-GJrT}n8FCRx_Awu!zrwA$OSk#x1aJR}&8Sgmi8Dl0*eF8Tlt1=@r zH2N=KwInwe#rJI8)m^nv5DY4y#|W%lsFM?NGN2GUb1G=bw$|CUoKbF8bE@Lq;zHw( zF9-W=0y7&=Y<;AlT9RX^7vAtf_pGLAR_6UG#htm+M&W$-cKkdQnj6j0?&2|;%!PAc zZRkaPI2_qf-)Qw^DT)Zwdfzyi^rrYprQKE=2Y4)99-qGpDqY#7+P9FK33c<7ijMe6 znClC*Zoh&(D}gQxa6i`tx;Gg&3B7N;6^|0FxgPMfj8#vW9{_<7qZv^K-y^XZERD^=EIBWF zw;YV0T3K9hm5}{$@*_j+hYm+Np{8SaQN<+8`yPS}$N}WyweR0eKSS^O>KK7`@>ftO z5J-e#g|JvBtpn54Y(){`9KW-Z2fPQ@+^$+)bjRjinYfmwWBQp-O$C}9dsJ&5u|6GQ zANiSoMg6jLRBb`FGgCPeQzTniR-K$pK{I{SnfZ&=H6@Q`=LHKm?@3jkP)7~D&sP`S z4AP3(+cX9U`y5@Ck!7>=VMp%058t-^9Gxf@-OMm*kq{aAAuWwpd@u*kn;uD5iE|tL z^}6uUmZd&ZF8jHoaL-%Vh@Ocz#X`k~e43#yLdZixD$3{g1eQo|_vV{o4S|=VS=pa+ z^JoY72|Ps)&)2tG+i#Vri%nrP$GZ0pj-AK~#0jB>%i^xvJ>uGSWR%f@v-IcU2_x2N znZ9a7^!!(e6&NBPHC^(QhquMn)mWi2G{VVlS4Fh^`A`GT4;=UdBki~VUDJm`tZf$2 zyJQvURC1ea#Kk}sfZ=PV`sdw4+vs!+mL08)0n55Q4XY2w>LAOcFwsj7txB-;fEv3Z z%T{n~QXs1hS=Qf%9BMiXICEPftg-=MaWvp#qZOnHD<8puHbHp_%>DZL6GYgpK%}Se zRB=9hkS-P3y$6uC2Q^HGUJMsAW5ZIde+pzb*^1u`6j^-KAnHYX{&m$Qzm}1lULKly zhbvkfBlp=jZ{T;tzc&y4-hzkoMNvAWNw3sq$)6q|Fny?KBjVBgJ6LBG`Uh zEwaN->2oo@byuG)HGZ?7jy$XNNv>ToK)-yZZ(HRRk*M|cDJZ{pEPPT#5B>CQ zRW~Dg{`7sw+;$0NF%E4Prs$If@71?(cv`64?GZA=b>6NhZNr6x+@NXsN#9vsIAA-+ zEBmfzL4t7T{-7(KPuy7cH=vCj3aoVb{j z(xRBggF){O(QeLp9=IXF<<5qg^ZK9cb!qp$&|&wJLR7A4hbA<%Yt%L*wsbgf`lOw6 z)!Vy=9ckFwC+3WwNA2UKg`Qt`>kvPs#bL`8S#!M51+rixXEU?>UWzyIWVT@cYn69>wqjPfE{Xl* zwb>u(LxZb>i#o-6Z`tHu9A$lOM$3jrmFh)#9VRa8J7U7QSxa@Avj7B+lSp5(%B=yTP10TA3h2((`)u22- z=elK)9V})@;<6P-ny@gVR%%+49D@r-Vo!O`FBd%|u8B(;5Y;-wb^TG?Ny)u9f|yXt z#U!yGm<)ziA5nh@bsJ)@J&J!a0Aj}8PV-bH0 zlG3!YBT?3z7aUx=G|^1X0uX%$xdzl>4p%@zrV5JwV-6*Hh6UdTsVfGRA8{`fN;PTm zSf?Q2Wg}ZV`bM3 zoIDbzPw?mun9WWGTWz)sYBz9|>y<@PD#QFWytjvlTb_u!2!|w)9uv|-JsrNsI^o?3 zQ5dE9EARv*X&^*8tisE~?_sNrm5pn}9ZN*gu9x z2a16Ri1I;IK4!xuYe3F@rXJ%gg%2Y7geI4tGs3SDHesTsCd{nN(Ez2FrEi1u<25t* zt>K%I1A)RZfh0^EGw~+s1m&ehNs`Dp1#N=3R7DSB$b-Z~Tn_vYPP+#(%3Vgsuhl~oH0OXjBond!kPtDULrH!zwGH_Oq zgyz*Y3hWe&NH2IDl08`doe3|Z2=1t2r~zo9;vrJR7#YRqrBq`SL7pX45sxfdT0jh1 z`dUH=(9bEEkVJ5cSo@Z59fBQIf&@^GT+}dxf)4Zs9N_BA8mbvEVKSZs2;+xOPDSxC z6Mg^yv2-ZED{0+|OQU1EePFkVwJ5=DYUXfz5R%vfwuc8s|Fhck0z^nsKLl+DZ7Q^P zji^2V-o)TCfM@Z7B5C;36?tvcwvg zxNt`c;#vgNo>(|?<5N&1d7mX22C75~IX(PtNzG(8BJ2O-&nP$VP?aW^=8HLX3@lMO z*)Fo^_S5<Retr->2)V=#q@E3UVQo~raa8lKAFgRYE-9PtKnFK+NzS=-P<8Ik(~=1$lV>Nhqr zKeEr=y?@#3${SvedU4cZ*rmYOI_tQoitbkycJZptbU5bM`B{|2Bm0DJ%d diff --git a/documentation/Add-FabricWorkspaceRoleAssignment.md b/documentation/Add-FabricWorkspaceRoleAssignment.md deleted file mode 100644 index 0e79c1f8..00000000 --- a/documentation/Add-FabricWorkspaceRoleAssignment.md +++ /dev/null @@ -1,147 +0,0 @@ -# Add-FabricWorkspaceRoleAssignment - -## SYNOPSIS -Adds a role assignment to a user in a workspace. - -## SYNTAX - -``` -Add-FabricWorkspaceRoleAssignment [-WorkspaceId] [-principalId] [-Role] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Adds a role assignment to a user in a workspace. -The User is identified by the principalId and the role is -identified by the Role parameter. -The Workspace is identified by the WorkspaceId. - -## EXAMPLES - -### EXAMPLE 1 -``` -Add-RtiWorkspaceRoleAssignment ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -PrincipalId '12345678-1234-1234-1234-123456789012' ` - -Role 'Admin' -``` - -## PARAMETERS - -### -principalId -Id of the principal for which the role assignment should be added. -The value for PrincipalId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -This parameter is mandatory. -At the -moment only principal type 'User' is supported. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Role -The role to assign to the principal. -The value for Role is a string. -An example of a string is 'Admin'. -The values that can be used are 'Admin', 'Contributor', 'Member' and 'Viewer'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the role assignment should be added. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -This parameter is mandatory. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionallity to add role assignments to groups. -TODO: Add functionallity to add a user by SPN. - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/add-workspace-role-assignment?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/add-workspace-role-assignment?tabs=HTTP) - diff --git a/documentation/Confirm-FabricAuthToken.md b/documentation/Confirm-FabricAuthToken.md deleted file mode 100644 index 7e118174..00000000 --- a/documentation/Confirm-FabricAuthToken.md +++ /dev/null @@ -1,54 +0,0 @@ -# Confirm-FabricAuthToken - -## SYNOPSIS -Check whether the Fabric API authentication token is set and not expired and reset it if necessary. - -## SYNTAX - -``` -Confirm-FabricAuthToken [-ProgressAction ] [] -``` - -## DESCRIPTION -The Confirm-FabricAuthToken function retrieves the Fabric API authentication token. -If the token is not already set, it calls the Set-FabricAuthToken function to set it. -It then outputs the token. - -## EXAMPLES - -### EXAMPLE 1 -``` -Confirm-FabricAuthToken -``` - -This command retrieves the Fabric API authentication token. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None. You cannot pipe inputs to this function. -## OUTPUTS - -### Returns object as Get-FabricDebugInfo function -## NOTES - -## RELATED LINKS diff --git a/documentation/Connect-FabricAccount.md b/documentation/Connect-FabricAccount.md deleted file mode 100644 index ea2e9576..00000000 --- a/documentation/Connect-FabricAccount.md +++ /dev/null @@ -1,72 +0,0 @@ -# Connect-FabricAccount - -## SYNOPSIS -Connects to the Fabric WebAPI. - -## SYNTAX - -``` -Connect-FabricAccount [-TenantId] [-ProgressAction ] [] -``` - -## DESCRIPTION -Connects to the Fabric WebAPI by using the cmdlet Connect-AzAccount. -This function retrieves the authentication token for the Fabric API and sets up the headers for API calls. - -## EXAMPLES - -### EXAMPLE 1 -``` -Connect-FabricAccount ` - -TenantID '12345678-1234-1234-1234-123456789012' -``` - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -TenantId -The TenantId of the Azure Active Directory tenant you want to connect to -and in which your Fabric Capacity is. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-12-22 - FGE: Added Verbose Output - -## RELATED LINKS - -[Connect-AzAccount https://learn.microsoft.com/de-de/powershell/module/az.accounts/connect-azaccount?view=azps-12.4.0]() - diff --git a/documentation/Export-FabricItem.md b/documentation/Export-FabricItem.md deleted file mode 100644 index 8b687676..00000000 --- a/documentation/Export-FabricItem.md +++ /dev/null @@ -1,128 +0,0 @@ -# Export-FabricItem - -## SYNOPSIS -Exports items from a Fabric workspace. -Either all items in a workspace or a specific item. - -## SYNTAX - -``` -Export-FabricItem [[-path] ] [[-workspaceId] ] [[-filter] ] [[-itemID] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -The Export-FabricItem function exports items from a Fabric workspace to a specified directory. -It can export items of type "Report", "SemanticModel", "SparkJobDefinitionV1" or "Notebook". -If a specific item ID is provided, only that item will be exported. -Otherwise, all items in the workspace will be exported. - -## EXAMPLES - -### EXAMPLE 1 -``` -Export-FabricItem -workspaceId "12345678-1234-1234-1234-1234567890AB" -path "C:\ExportedItems" -``` - -This example exports all items from the Fabric workspace with the specified ID to the "C:\ExportedItems" directory. - -### EXAMPLE 2 -``` -Export-FabricItem -workspaceId "12345678-1234-1234-1234-1234567890AB" -itemID "98765432-4321-4321-4321-9876543210BA" -path "C:\ExportedItems" -``` - -This example exports the item with the specified ID from the Fabric workspace with the specified ID to the "C:\ExportedItems" directory. - -## PARAMETERS - -### -filter -A script block used to filter the items to be exported. -Only items that match the filter will be exported. -The default filter includes items of type "Report", "SemanticModel", "SparkJobDefinitionV1" or "Notebook". - -```yaml -Type: System.Management.Automation.ScriptBlock -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: { $_.type -in @("Report", "SemanticModel", "Notebook", "SparkJobDefinitionV1") } -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -itemID -The ID of the specific item to export. -If provided, only that item will be exported. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -path -The path to the directory where the items will be exported. -The default value is '.\pbipOutput'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: .\pbipOutput -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceId -The ID of the Fabric workspace. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -This function is based on the Export-FabricItems function written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -## RELATED LINKS diff --git a/documentation/FabricTools.md b/documentation/FabricTools.md deleted file mode 100644 index 58d04acc..00000000 --- a/documentation/FabricTools.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -Module Name: FabricTools -Module Guid: f2a0f9e6-fab6-41fc-9e1c-0c94ff38f794 -Download Help Link: {{ Update Download Link }} -Help Version: {{ Please enter version of help manually (X.X.X.X) format }} -Locale: en-US ---- - -# FabricTools Module -## Description -{{ Fill in the Description }} - -## FabricTools Cmdlets -### [Add-FabricWorkspaceRoleAssignment](Add-FabricWorkspaceRoleAssignment.md) -{{ Fill in the Description }} - -### [Confirm-FabricAuthToken](Confirm-FabricAuthToken.md) -{{ Fill in the Description }} - -### [Connect-FabricAccount](Connect-FabricAccount.md) -{{ Fill in the Description }} - -### [Export-FabricItem](Export-FabricItem.md) -{{ Fill in the Description }} - -### [Get-AllFabricCapacities](Get-AllFabricCapacities.md) -{{ Fill in the Description }} - -### [Get-AllFabricDatasetRefreshes](Get-AllFabricDatasetRefreshes.md) -{{ Fill in the Description }} - -### [Get-FabricAPIclusterURI](Get-FabricAPIclusterURI.md) -{{ Fill in the Description }} - -### [Get-FabricAuthToken](Get-FabricAuthToken.md) -{{ Fill in the Description }} - -### [Get-FabricCapacity](Get-FabricCapacity.md) -{{ Fill in the Description }} - -### [Get-FabricCapacityRefreshables](Get-FabricCapacityRefreshables.md) -{{ Fill in the Description }} - -### [Get-FabricCapacitySkus](Get-FabricCapacitySkus.md) -{{ Fill in the Description }} - -### [Get-FabricCapacityState](Get-FabricCapacityState.md) -{{ Fill in the Description }} - -### [Get-FabricCapacityTenantOverrides](Get-FabricCapacityTenantOverrides.md) -{{ Fill in the Description }} - -### [Get-FabricCapacityWorkload](Get-FabricCapacityWorkload.md) -{{ Fill in the Description }} - -### [Get-FabricDatasetRefreshes](Get-FabricDatasetRefreshes.md) -{{ Fill in the Description }} - -### [Get-FabricDebugInfo](Get-FabricDebugInfo.md) -{{ Fill in the Description }} - -### [Get-FabricEventhouse](Get-FabricEventhouse.md) -{{ Fill in the Description }} - -### [Get-FabricEventstream](Get-FabricEventstream.md) -{{ Fill in the Description }} - -### [Get-FabricItem](Get-FabricItem.md) -{{ Fill in the Description }} - -### [Get-FabricKQLDashboard](Get-FabricKQLDashboard.md) -{{ Fill in the Description }} - -### [Get-FabricKQLDashboardDefinition](Get-FabricKQLDashboardDefinition.md) -{{ Fill in the Description }} - -### [Get-FabricKQLDatabase](Get-FabricKQLDatabase.md) -{{ Fill in the Description }} - -### [Get-FabricKQLQueryset](Get-FabricKQLQueryset.md) -{{ Fill in the Description }} - -### [Get-FabricSQLDatabase](Get-FabricSQLDatabase.md) -{{ Fill in the Description }} - -### [Get-FabricTenantSettings](Get-FabricTenantSettings.md) -{{ Fill in the Description }} - -### [Get-FabricUsageMetricsQuery](Get-FabricUsageMetricsQuery.md) -{{ Fill in the Description }} - -### [Get-FabricWorkspace](Get-FabricWorkspace.md) -{{ Fill in the Description }} - -### [Get-FabricWorkspace2](Get-FabricWorkspace2.md) -{{ Fill in the Description }} - -### [Get-FabricWorkspaceDatasetRefreshes](Get-FabricWorkspaceDatasetRefreshes.md) -{{ Fill in the Description }} - -### [Get-FabricWorkspaceRoleAssignment](Get-FabricWorkspaceRoleAssignment.md) -{{ Fill in the Description }} - -### [Get-FabricWorkspaceUsageMetricsData](Get-FabricWorkspaceUsageMetricsData.md) -{{ Fill in the Description }} - -### [Get-FabricWorkspaceUsers](Get-FabricWorkspaceUsers.md) -{{ Fill in the Description }} - -### [Get-Sha256](Get-Sha256.md) -{{ Fill in the Description }} - -### [Import-FabricItem](Import-FabricItem.md) -{{ Fill in the Description }} - -### [Invoke-FabricAPIRequest](Invoke-FabricAPIRequest.md) -{{ Fill in the Description }} - -### [Invoke-FabricDatasetRefresh](Invoke-FabricDatasetRefresh.md) -{{ Fill in the Description }} - -### [Invoke-FabricKQLCommand](Invoke-FabricKQLCommand.md) -{{ Fill in the Description }} - -### [New-FabricEventhouse](New-FabricEventhouse.md) -{{ Fill in the Description }} - -### [New-FabricEventstream](New-FabricEventstream.md) -{{ Fill in the Description }} - -### [New-FabricKQLDashboard](New-FabricKQLDashboard.md) -{{ Fill in the Description }} - -### [New-FabricKQLDatabase](New-FabricKQLDatabase.md) -{{ Fill in the Description }} - -### [New-FabricWorkspace](New-FabricWorkspace.md) -{{ Fill in the Description }} - -### [New-FabricWorkspace2](New-FabricWorkspace2.md) -{{ Fill in the Description }} - -### [New-FabricWorkspaceUsageMetricsReport](New-FabricWorkspaceUsageMetricsReport.md) -{{ Fill in the Description }} - -### [Register-FabricWorkspaceToCapacity](Register-FabricWorkspaceToCapacity.md) -{{ Fill in the Description }} - -### [Remove-FabricEventhouse](Remove-FabricEventhouse.md) -{{ Fill in the Description }} - -### [Remove-FabricEventstream](Remove-FabricEventstream.md) -{{ Fill in the Description }} - -### [Remove-FabricItem](Remove-FabricItem.md) -{{ Fill in the Description }} - -### [Remove-FabricKQLDatabase](Remove-FabricKQLDatabase.md) -{{ Fill in the Description }} - -### [Remove-FabricKQLQueryset](Remove-FabricKQLQueryset.md) -{{ Fill in the Description }} - -### [Remove-FabricSQLDatabase](Remove-FabricSQLDatabase.md) -{{ Fill in the Description }} - -### [Remove-FabricWorkspace](Remove-FabricWorkspace.md) -{{ Fill in the Description }} - -### [Resume-FabricCapacity](Resume-FabricCapacity.md) -{{ Fill in the Description }} - -### [Set-FabricAuthToken](Set-FabricAuthToken.md) -{{ Fill in the Description }} - -### [Set-FabricEventhouse](Set-FabricEventhouse.md) -{{ Fill in the Description }} - -### [Set-FabricEventstream](Set-FabricEventstream.md) -{{ Fill in the Description }} - -### [Set-FabricKQLDatabase](Set-FabricKQLDatabase.md) -{{ Fill in the Description }} - -### [Set-FabricKQLQueryset](Set-FabricKQLQueryset.md) -{{ Fill in the Description }} - -### [Suspend-FabricCapacity](Suspend-FabricCapacity.md) -{{ Fill in the Description }} - -### [Unregister-FabricWorkspaceToCapacity](Unregister-FabricWorkspaceToCapacity.md) -{{ Fill in the Description }} - diff --git a/documentation/Get-AllFabricCapacities.md b/documentation/Get-AllFabricCapacities.md deleted file mode 100644 index dee563bf..00000000 --- a/documentation/Get-AllFabricCapacities.md +++ /dev/null @@ -1,75 +0,0 @@ -# Get-AllFabricCapacities - -## SYNOPSIS -This function retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. - -## SYNTAX - -``` -Get-AllFabricCapacities [[-subscriptionID] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -The Get-AllFabricCapacities function is used to retrieve all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. -It uses the Az module to interact with Azure. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-AllFabricCapacities -subscriptionID "12345678-1234-1234-1234-123456789012" -``` - -This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in the subscription with the ID "12345678-1234-1234-1234-123456789012". - -### EXAMPLE 2 -``` -Get-AllFabricCapacities -``` - -This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in all subscriptions. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -subscriptionID -An optional parameter that specifies the subscription ID. -If this parameter is not provided, the function will retrieve resources from all subscriptions. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Alias: Get-AllFabCapacities - -## RELATED LINKS diff --git a/documentation/Get-AllFabricDatasetRefreshes.md b/documentation/Get-AllFabricDatasetRefreshes.md deleted file mode 100644 index 5d8be806..00000000 --- a/documentation/Get-AllFabricDatasetRefreshes.md +++ /dev/null @@ -1,35 +0,0 @@ -# Get-AllFabricDatasetRefreshes - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-AllFabricDatasetRefreshes -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricAPIclusterURI.md b/documentation/Get-FabricAPIclusterURI.md deleted file mode 100644 index 73e87835..00000000 --- a/documentation/Get-FabricAPIclusterURI.md +++ /dev/null @@ -1,35 +0,0 @@ -# Get-FabricAPIclusterURI - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricAPIclusterURI -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricAuthToken.md b/documentation/Get-FabricAuthToken.md deleted file mode 100644 index 585352ea..00000000 --- a/documentation/Get-FabricAuthToken.md +++ /dev/null @@ -1,56 +0,0 @@ -# Get-FabricAuthToken - -## SYNOPSIS -Retrieves the Fabric API authentication token. - -## SYNTAX - -``` -Get-FabricAuthToken [-ProgressAction ] [] -``` - -## DESCRIPTION -The Get-FabricAuthToken function retrieves the Fabric API authentication token. -If the token is not already set, it calls the Set-FabricAuthToken function to set it. -It then outputs the token. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricAuthToken -``` - -This command retrieves the Fabric API authentication token. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None. You cannot pipe inputs to this function. -## OUTPUTS - -### String. This function returns the Fabric API authentication token. -## NOTES -This function was originally written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -## RELATED LINKS diff --git a/documentation/Get-FabricCapacity.md b/documentation/Get-FabricCapacity.md deleted file mode 100644 index bd4f32b8..00000000 --- a/documentation/Get-FabricCapacity.md +++ /dev/null @@ -1,68 +0,0 @@ -# Get-FabricCapacity - -## SYNOPSIS -Retrieves the fabric capacity information. - -## SYNTAX - -``` -Get-FabricCapacity [[-capacity] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -This function makes a GET request to the Fabric API to retrieve the tenant settings. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricCapacity -capacity "exampleCapacity" -Retrieves the fabric capacity information for the specified capacity. -``` - -Get-FabricCapacity -Retrieves the fabric capacity information for all capacities. - -## PARAMETERS - -### -capacity -Specifies the capacity to retrieve information for. -If not provided, all capacities will be retrieved. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricCapacityRefreshables.md b/documentation/Get-FabricCapacityRefreshables.md deleted file mode 100644 index c61eefba..00000000 --- a/documentation/Get-FabricCapacityRefreshables.md +++ /dev/null @@ -1,68 +0,0 @@ -# Get-FabricCapacityRefreshables - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricCapacityRefreshables [[-top] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -top -{{ Fill top Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricCapacitySkus.md b/documentation/Get-FabricCapacitySkus.md deleted file mode 100644 index bbf9de59..00000000 --- a/documentation/Get-FabricCapacitySkus.md +++ /dev/null @@ -1,96 +0,0 @@ -# Get-FabricCapacitySkus - -## SYNOPSIS -Retrieves the fabric capacity information. - -## SYNTAX - -``` -Get-FabricCapacitySkus [-subscriptionID] [-ResourceGroupName] [-capacity] - [-ProgressAction ] [] -``` - -## DESCRIPTION -This function makes a GET request to the Fabric API to retrieve the tenant settings. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricCapacitySkus -capacity "exampleCapacity" -Retrieves the fabric capacity information for the specified capacity. -``` - -## PARAMETERS - -### -capacity -Specifies the capacity to retrieve information for. -If not provided, all capacities will be retrieved. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ResourceGroupName -{{ Fill ResourceGroupName Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -subscriptionID -{{ Fill subscriptionID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricCapacityState.md b/documentation/Get-FabricCapacityState.md deleted file mode 100644 index 4c51e75b..00000000 --- a/documentation/Get-FabricCapacityState.md +++ /dev/null @@ -1,99 +0,0 @@ -# Get-FabricCapacityState - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricCapacityState [-subscriptionID] [-resourcegroup] [-capacity] - [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -capacity -{{ Fill capacity Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -resourcegroup -{{ Fill resourcegroup Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -subscriptionID -{{ Fill subscriptionID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricCapacityTenantOverrides.md b/documentation/Get-FabricCapacityTenantOverrides.md deleted file mode 100644 index 01cd9e3b..00000000 --- a/documentation/Get-FabricCapacityTenantOverrides.md +++ /dev/null @@ -1,35 +0,0 @@ -# Get-FabricCapacityTenantOverrides - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricCapacityTenantOverrides -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricCapacityWorkload.md b/documentation/Get-FabricCapacityWorkload.md deleted file mode 100644 index 125abf5a..00000000 --- a/documentation/Get-FabricCapacityWorkload.md +++ /dev/null @@ -1,68 +0,0 @@ -# Get-FabricCapacityWorkload - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricCapacityWorkload [-capacityID] [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -capacityID -{{ Fill capacityID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricDatasetRefreshes.md b/documentation/Get-FabricDatasetRefreshes.md deleted file mode 100644 index bb1fe167..00000000 --- a/documentation/Get-FabricDatasetRefreshes.md +++ /dev/null @@ -1,88 +0,0 @@ -# Get-FabricDatasetRefreshes - -## SYNOPSIS -Retrieves the refresh history of a specified dataset in a PowerBI workspace. - -## SYNTAX - -``` -Get-FabricDatasetRefreshes [-DatasetID] [-workspaceId] [-ProgressAction ] - [] -``` - -## DESCRIPTION -The Get-FabricDatasetRefreshes function uses the PowerBI cmdlets to retrieve the refresh history of a specified dataset in a workspace. -It uses the dataset ID and workspace ID to get the dataset and checks if it is refreshable. -If it is, the function retrieves the refresh history. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricDatasetRefreshes -DatasetID "12345678-90ab-cdef-1234-567890abcdef" -workspaceId "abcdef12-3456-7890-abcd-ef1234567890" -``` - -This command retrieves the refresh history of the specified dataset in the specified workspace. - -## PARAMETERS - -### -DatasetID -The ID of the dataset. -This is a mandatory parameter. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceId -The ID of the workspace. -This is a mandatory parameter. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### String. You can pipe two strings that contain the dataset ID and workspace ID to Get-FabricDatasetRefreshes. -## OUTPUTS - -### Object. Get-FabricDatasetRefreshes returns an object that contains the refresh history. -## NOTES -Alias: Get-PowerBIDatasetRefreshes, Get-FabricDatasetRefreshes - -## RELATED LINKS diff --git a/documentation/Get-FabricDebugInfo.md b/documentation/Get-FabricDebugInfo.md deleted file mode 100644 index a12d9afe..00000000 --- a/documentation/Get-FabricDebugInfo.md +++ /dev/null @@ -1,58 +0,0 @@ -# Get-FabricDebugInfo - -## SYNOPSIS -Shows internal debug information about the current session. - -## SYNTAX - -``` -Get-FabricDebugInfo [-ProgressAction ] [] -``` - -## DESCRIPTION -Shows internal debug information about the current session. -It is useful for troubleshooting purposes. -It will show you the current session object. -This includes the bearer token. -This can be useful -for connecting to the REST API directly via Postman. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricDebugInfo -``` - -This example shows the current session object. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-12-22 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Get-FabricEventhouse.md b/documentation/Get-FabricEventhouse.md deleted file mode 100644 index 1ee60c03..00000000 --- a/documentation/Get-FabricEventhouse.md +++ /dev/null @@ -1,148 +0,0 @@ -# Get-FabricEventhouse - -## SYNOPSIS -Retrieves Fabric Eventhouses - -## SYNTAX - -``` -Get-FabricEventhouse [-WorkspaceId] [[-EventhouseName] ] [[-EventhouseId] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -Retrieves Fabric Eventhouses. -Without the EventhouseName or EventhouseID parameter, all Eventhouses are returned. -If you want to retrieve a specific Eventhouse, you can use the EventhouseName or EventhouseID parameter. -These -parameters cannot be used together. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' -``` - -This example will give you all Eventhouses in the Workspace. - -### EXAMPLE 2 -``` -Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' -``` - -This example will give you all Information about the Eventhouse with the name 'MyEventhouse'. - -### EXAMPLE 3 -``` -Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' -``` - -This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. - -### EXAMPLE 4 -``` -Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -Verbose -``` - -This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. -It will also give you verbose output which is useful for debugging. - -## PARAMETERS - -### -EventhouseId -The Id of the Eventhouse to retrieve. -This parameter cannot be used together with EventhouseName. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventhouseName -The name of the Eventhouse to retrieve. -This parameter cannot be used together with EventhouseID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the Eventhouses should be retrieved. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to list all Eventhouses in the subscription. -To do so fetch all workspaces -and then all eventhouses in each workspace. - -Revsion History: - -- 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName -- 2024-11-16 - FGE: Added Verbose Output -- 2024-11-27 - FGE: Added more Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/list-eventhouses?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/list-eventhouses?tabs=HTTP) - diff --git a/documentation/Get-FabricEventstream.md b/documentation/Get-FabricEventstream.md deleted file mode 100644 index 3568167f..00000000 --- a/documentation/Get-FabricEventstream.md +++ /dev/null @@ -1,135 +0,0 @@ -# Get-FabricEventstream - -## SYNOPSIS -Retrieves Fabric Eventstreams - -## SYNTAX - -``` -Get-FabricEventstream [-WorkspaceId] [[-EventstreamName] ] [[-EventstreamId] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -Retrieves Fabric Eventstreams. -Without the EventstreamName or EventstreamID parameter, all Eventstreams are returned. -If you want to retrieve a specific Eventstream, you can use the EventstreamName or EventstreamID parameter. -These -parameters cannot be used together. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' -``` - -This example will give you all Eventstreams in the Workspace. - -### EXAMPLE 2 -``` -Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamName 'MyEventstream' -``` - -This example will give you all Information about the Eventstream with the name 'MyEventstream'. - -### EXAMPLE 3 -``` -Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' -``` - -This example will give you all Information about the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -EventstreamId -The Id of the Eventstream to retrieve. -This parameter cannot be used together with EventstreamName. -The value for EventstreamId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventstreamName -The name of the Eventstream to retrieve. -This parameter cannot be used together with EventstreamID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the Eventstreams should be retrieved. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to list all Eventhouses. -To do so fetch all workspaces and - then all eventhouses in each workspace. - -Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName - - 2024-11-27 - FGE: Added Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/get-eventstream?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/get-eventstream?tabs=HTTP) - diff --git a/documentation/Get-FabricItem.md b/documentation/Get-FabricItem.md deleted file mode 100644 index 322b5bd2..00000000 --- a/documentation/Get-FabricItem.md +++ /dev/null @@ -1,121 +0,0 @@ -# Get-FabricItem - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -### WorkspaceId -``` -Get-FabricItem -workspaceId [-type ] [-itemID ] [-ProgressAction ] - [] -``` - -### WorkspaceObject -``` -Get-FabricItem -Workspace [-type ] [-itemID ] [-ProgressAction ] - [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -itemID -{{ Fill itemID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -type -{{ Fill type Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: itemType - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Workspace -{{ Fill Workspace Description }} - -```yaml -Type: System.Object -Parameter Sets: WorkspaceObject -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -workspaceId -{{ Fill workspaceId Description }} - -```yaml -Type: System.String -Parameter Sets: WorkspaceId -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### System.Object - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricKQLDashboard.md b/documentation/Get-FabricKQLDashboard.md deleted file mode 100644 index 63cc86bf..00000000 --- a/documentation/Get-FabricKQLDashboard.md +++ /dev/null @@ -1,111 +0,0 @@ -# Get-FabricKQLDashboard - -## SYNOPSIS -Retrieves Fabric KQLDashboards - -## SYNTAX - -``` -Get-FabricKQLDashboard [-WorkspaceId] [[-KQLDashboardName] ] [[-KQLDashboardId] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -Retrieves Fabric KQLDashboards. -Without the KQLDashboardName or KQLDashboardID parameter, all KQLDashboards are returned. -If you want to retrieve a specific KQLDashboard, you can use the KQLDashboardName or KQLDashboardID parameter. -These -parameters cannot be used together. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricKQLDashboard -``` - -## PARAMETERS - -### -KQLDashboardId -The Id of the KQLDashboard to retrieve. -This parameter cannot be used together with KQLDashboardName. -The value for KQLDashboardID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDashboardName -The name of the KQLDashboard to retrieve. -This parameter cannot be used together with KQLDashboardID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the KQLDashboards should be retrieved. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to list all KQLDashboards. -To do so fetch all workspaces and - then all KQLDashboards in each workspace. - -Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName - - 2024-12-08 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Get-FabricKQLDashboardDefinition.md b/documentation/Get-FabricKQLDashboardDefinition.md deleted file mode 100644 index 18caa1a3..00000000 --- a/documentation/Get-FabricKQLDashboardDefinition.md +++ /dev/null @@ -1,140 +0,0 @@ -# Get-FabricKQLDashboardDefinition - -## SYNOPSIS -Retrieves Fabric KQLDashboard Definitions for a given KQLDashboard. - -## SYNTAX - -``` -Get-FabricKQLDashboardDefinition [-WorkspaceId] [[-KQLDashboardName] ] - [[-KQLDashboardId] ] [[-Format] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -Retrieves the Definition of the Fabric KQLDashboard that is specified by the KQLDashboardName or KQLDashboardID. -The KQLDashboard Definition contains the parts of the KQLDashboard, which are the visualizations and their configuration. -This is provided as a JSON object. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricKQLDashboardDefinition ` - -WorkspaceId "12345678-1234-1234-1234-123456789012" ` - -KQLDashboardName "MyKQLDashboard" -``` - -This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the -Workspace with the ID "12345678-1234-1234-1234-123456789012". - -### EXAMPLE 2 -``` -$db = Get-FabricKQLDashboardDefinition ` - -WorkspaceId "12345678-1234-1234-1234-123456789012" ` - -KQLDashboardName "MyKQLDashboard" -``` - -$db\[0\].payload | \` - Set-Content \` - -Path "C:\temp\mydashboard.json" - -This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the -Workspace with the ID "12345678-1234-1234-1234-123456789012". -The definition is saved to a file named "mydashboard.json". - -## PARAMETERS - -### -Format -{{ Fill Format Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDashboardId -The Id of the KQLDashboard to retrieve. -This parameter cannot be used together with KQLDashboardName. -The value for KQLDashboardID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDashboardName -The name of the KQLDashboard to retrieve. -This parameter cannot be used together with KQLDashboardID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace in which the KQLDashboard exists. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revision History: - - 2024-11-16 - FGE: First version - - 2024-12-08 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Get-FabricKQLDatabase.md b/documentation/Get-FabricKQLDatabase.md deleted file mode 100644 index 16b7d1c6..00000000 --- a/documentation/Get-FabricKQLDatabase.md +++ /dev/null @@ -1,133 +0,0 @@ -# Get-FabricKQLDatabase - -## SYNOPSIS -Retrieves Fabric KQLDatabases - -## SYNTAX - -``` -Get-FabricKQLDatabase [-WorkspaceId] [[-KQLDatabaseName] ] [[-KQLDatabaseId] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -Retrieves Fabric KQLDatabases. -Without the KQLDatabaseName or KQLDatabaseID parameter, -all KQLDatabases are returned. -If you want to retrieve a specific KQLDatabase, you can -use the KQLDatabaseName or KQLDatabaseID parameter. -These parameters cannot be used together. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' -``` - -This example will retrieve the KQLDatabase with the name 'MyKQLDatabase'. - -### EXAMPLE 2 -``` -Get-FabricKQLDatabase -``` - -This example will retrieve all KQLDatabases in the workspace that is specified -by the WorkspaceId. - -### EXAMPLE 3 -``` -Get-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseId '12345678-1234-1234-1234-123456789012' -``` - -This example will retrieve the KQLDatabase with the ID '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -KQLDatabaseId -The Id of the KQLDatabase to retrieve. -This parameter cannot be used together with KQLDatabaseName. -The value for KQLDatabaseID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDatabaseName -The name of the KQLDatabase to retrieve. -This parameter cannot be used together with KQLDatabaseID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the KQLDatabases should be retrieved. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to list all KQLDatabases. -To do so fetch all workspaces and - then all KQLDatabases in each workspace. - -Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName - - 2024-12-08 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Get-FabricKQLQueryset.md b/documentation/Get-FabricKQLQueryset.md deleted file mode 100644 index 699d6ef8..00000000 --- a/documentation/Get-FabricKQLQueryset.md +++ /dev/null @@ -1,136 +0,0 @@ -# Get-FabricKQLQueryset - -## SYNOPSIS -Retrieves Fabric KQLQuerysets - -## SYNTAX - -``` -Get-FabricKQLQueryset [-WorkspaceId] [[-KQLQuerysetName] ] [[-KQLQuerysetId] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -Retrieves Fabric KQLQuerysets. -Without the KQLQuerysetName or KQLQuerysetId parameter, -all KQLQuerysets are returned in the given Workspace. -If you want to retrieve a specific -KQLQueryset, you can use the KQLQuerysetName or KQLQuerysetId parameter. -These parameters -cannot be used together. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetName 'MyKQLQueryset' -``` - -This example will retrieve the KQLQueryset with the name 'MyKQLQueryset'. - -### EXAMPLE 2 -``` -Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' -``` - -This example will retrieve all KQLQuerysets in the workspace that is specified -by the WorkspaceId. - -### EXAMPLE 3 -``` -Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' -``` - -This example will retrieve the KQLQueryset with the ID '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -KQLQuerysetId -The Id of the KQLQueryset to retrieve. -This parameter cannot be used together with KQLQuerysetName. -The value for KQLQuerysetId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLQuerysetName -The name of the KQLQueryset to retrieve. -This parameter cannot be used together with KQLQuerysetId. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the KQLQuerysets should be retrieved. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -This parameter is mandatory. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to list all KQLQuerysets. -To do so fetch all workspaces and - then all KQLQuerysets in each workspace. - -Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLQuerysetName - - 2024-12-22 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Get-FabricSQLDatabase.md b/documentation/Get-FabricSQLDatabase.md deleted file mode 100644 index dcea1818..00000000 --- a/documentation/Get-FabricSQLDatabase.md +++ /dev/null @@ -1,128 +0,0 @@ -# Get-FabricSQLDatabase - -## SYNOPSIS -Retrieves Fabric SQLDatabases - -## SYNTAX - -``` -Get-FabricSQLDatabase [-WorkspaceId] [[-SQLDatabaseName] ] [[-SQLDatabaseId] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -Retrieves Fabric SQLDatabases. -Without the SQLDatabaseName or SQLDatabaseID parameter, -all SQLDatabases are returned. -If you want to retrieve a specific SQLDatabase, you can -use the SQLDatabaseName or SQLDatabaseID parameter. -These parameters cannot be used together. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricSQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -SQLDatabaseName 'MySQLDatabase' -``` - -This example will retrieve the SQLDatabase with the name 'MySQLDatabase'. - -### EXAMPLE 2 -``` -Get-FabricSQLDatabase -``` - -This example will retrieve all SQLDatabases in the workspace that is specified -by the WorkspaceId. - -### EXAMPLE 3 -``` -Get-FabricSQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -SQLDatabaseId '12345678-1234-1234-1234-123456789012' -``` - -This example will retrieve the SQLDatabase with the ID '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLDatabaseId -The Id of the SQLDatabase to retrieve. -This parameter cannot be used together with SQLDatabaseName. -The value for SQLDatabaseID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLDatabaseName -The name of the KQLDatabase to retrieve. -This parameter cannot be used together with SQLDatabaseID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the SQLDatabases should be retrieved. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revision History: - - 2025-03-06 - KNO: Init version of the function - -## RELATED LINKS diff --git a/documentation/Get-FabricTenantSettings.md b/documentation/Get-FabricTenantSettings.md deleted file mode 100644 index 68ba030e..00000000 --- a/documentation/Get-FabricTenantSettings.md +++ /dev/null @@ -1,32 +0,0 @@ -# Get-FabricTenantSettings - -## SYNOPSIS -Retrieves the tenant settings from the Fabric API. - -## SYNTAX - -``` -Get-FabricTenantSettings -``` - -## DESCRIPTION -The Get-FabricTenantSettings function makes a GET request to the Fabric API to retrieve the tenant settings. -It returns the 'tenantSettings' property of the first item in the response. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricTenantSettings -Retrieves the tenant settings from the Fabric API. -``` - -## PARAMETERS - -## INPUTS - -## OUTPUTS - -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricUsageMetricsQuery.md b/documentation/Get-FabricUsageMetricsQuery.md deleted file mode 100644 index d39f9840..00000000 --- a/documentation/Get-FabricUsageMetricsQuery.md +++ /dev/null @@ -1,114 +0,0 @@ -# Get-FabricUsageMetricsQuery - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricUsageMetricsQuery [-DatasetID] [-groupId] [-reportname] - [[-ImpersonatedUser] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -DatasetID -{{ Fill DatasetID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -groupId -{{ Fill groupId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ImpersonatedUser -{{ Fill ImpersonatedUser Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -reportname -{{ Fill reportname Description }} - -```yaml -Type: System.Object -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricWorkspace.md b/documentation/Get-FabricWorkspace.md deleted file mode 100644 index a1acc54a..00000000 --- a/documentation/Get-FabricWorkspace.md +++ /dev/null @@ -1,70 +0,0 @@ -# Get-FabricWorkspace - -## SYNOPSIS -Retrieves all Fabric workspaces. - -## SYNTAX - -``` -Get-FabricWorkspace [[-workspaceId] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -The Get-FabricWorkspace function retrieves all Fabric workspaces. -It invokes the Fabric API to get the workspaces and outputs the result. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricWorkspace -``` - -This command retrieves all Fabric workspaces. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceId -{{ Fill workspaceId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None. You cannot pipe inputs to this function. -## OUTPUTS - -### Object. This function returns the Fabric workspaces. -## NOTES -This function was originally written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -## RELATED LINKS diff --git a/documentation/Get-FabricWorkspace2.md b/documentation/Get-FabricWorkspace2.md deleted file mode 100644 index dd232248..00000000 --- a/documentation/Get-FabricWorkspace2.md +++ /dev/null @@ -1,198 +0,0 @@ -# Get-FabricWorkspace2 - -## SYNOPSIS -Retrieves Fabric Workspaces - -## SYNTAX - -``` -Get-FabricWorkspace2 [[-WorkspaceId] ] [[-WorkspaceName] ] [[-WorkspaceCapacityId] ] - [[-WorkspaceType] ] [[-WorkspaceState] ] [-ProgressAction ] - [] -``` - -## DESCRIPTION -Retrieves Fabric Workspaces. -Without the WorkspaceName or WorkspaceID parameter, -all Workspaces are returned. -If you want to retrieve a specific Workspace, you can -use the WorkspaceName, an CapacityID, a WorkspaceType, a WorkspaceState or the WorkspaceID -parameter. -The WorkspaceId parameter has precedence over all other parameters because it -is most specific. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricWorkspace -``` - -This example will retrieve all Workspaces. - -### EXAMPLE 2 -``` -Get-FabricWorkspace ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' -``` - -This example will retrieve the Workspace with the ID '12345678-1234-1234-1234-123456789012'. - -### EXAMPLE 3 -``` -Get-FabricWorkspace ` - -WorkspaceName 'MyWorkspace' -``` - -This example will retrieve the Workspace with the name 'MyWorkspace'. - -### EXAMPLE 4 -``` -Get-FabricWorkspace ` - -WorkspaceCapacityId '12345678-1234-1234-1234-123456789012' -``` - -This example will retrieve the Workspaces with the Capacity ID '12345678-1234-1234-1234-123456789012'. - -### EXAMPLE 5 -``` -Get-FabricWorkspace ` - -WorkspaceType 'Personal' -``` - -This example will retrieve the Workspaces with the type 'Personal'. - -### EXAMPLE 6 -``` -Get-FabricWorkspace ` - -WorkspaceState 'active' -``` - -This example will retrieve the Workspaces with the state 'active'. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceCapacityId -The Id of the Capacity to retrieve. -This parameter cannot be used together with WorkspaceID. -The value for WorkspaceCapacityId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: CapacityId - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace to retrieve. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceName -The name of the Workspace to retrieve. -This parameter cannot be used together with WorkspaceID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceState -The state of the Workspace to retrieve. -This parameter cannot be used together with WorkspaceID. -The value for WorkspaceState is a string. -An example of a string is 'active'. -The values that -can be used are 'active' and 'deleted'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: State - -Required: False -Position: 5 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceType -The type of the Workspace to retrieve. -This parameter cannot be used together with WorkspaceID. -The value for WorkspaceType is a string. -An example of a string is 'Personal'. -The values that -can be used are 'Personal', 'Workspace' and 'Adminworkspace'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Type - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-12-22 - FGE: Added Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/get-workspace?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/get-workspace?tabs=HTTP) - -[https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces?tabs=HTTP) - diff --git a/documentation/Get-FabricWorkspaceDatasetRefreshes.md b/documentation/Get-FabricWorkspaceDatasetRefreshes.md deleted file mode 100644 index e78fe968..00000000 --- a/documentation/Get-FabricWorkspaceDatasetRefreshes.md +++ /dev/null @@ -1,69 +0,0 @@ -# Get-FabricWorkspaceDatasetRefreshes - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricWorkspaceDatasetRefreshes [-WorkspaceID] [-ProgressAction ] - [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceID -{{ Fill WorkspaceID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricWorkspaceRoleAssignment.md b/documentation/Get-FabricWorkspaceRoleAssignment.md deleted file mode 100644 index 063fd8c0..00000000 --- a/documentation/Get-FabricWorkspaceRoleAssignment.md +++ /dev/null @@ -1,75 +0,0 @@ -# Get-FabricWorkspaceRoleAssignment - -## SYNOPSIS -Retrieves Fabric Workspace Role Assignments - -## SYNTAX - -``` -Get-FabricWorkspaceRoleAssignment [[-WorkspaceId] ] [-ProgressAction ] - [] -``` - -## DESCRIPTION -Retrieves Fabric Workspace Role Assignments. -Without the WorkspaceName or WorkspaceID parameter, - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-FabricWorkspaceRoleAssignment ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' -``` - -This example will retrieve all Role Assignments for the Workspace with the ID '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the Role Assignments should be retrieved. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/get-workspace-role-assignment?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/get-workspace-role-assignment?tabs=HTTP) - -[https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/list-workspace-role-assignments?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/list-workspace-role-assignments?tabs=HTTP) - diff --git a/documentation/Get-FabricWorkspaceUsageMetricsData.md b/documentation/Get-FabricWorkspaceUsageMetricsData.md deleted file mode 100644 index 3620f978..00000000 --- a/documentation/Get-FabricWorkspaceUsageMetricsData.md +++ /dev/null @@ -1,84 +0,0 @@ -# Get-FabricWorkspaceUsageMetricsData - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-FabricWorkspaceUsageMetricsData [-workspaceId] [[-username] ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -username -{{ Fill username Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceId -{{ Fill workspaceId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-FabricWorkspaceUsers.md b/documentation/Get-FabricWorkspaceUsers.md deleted file mode 100644 index 611f94a6..00000000 --- a/documentation/Get-FabricWorkspaceUsers.md +++ /dev/null @@ -1,89 +0,0 @@ -# Get-FabricWorkspaceUsers - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -### WorkspaceId -``` -Get-FabricWorkspaceUsers -WorkspaceId [-ProgressAction ] [] -``` - -### WorkspaceObject -``` -Get-FabricWorkspaceUsers -Workspace [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Workspace -{{ Fill Workspace Description }} - -```yaml -Type: System.Object -Parameter Sets: WorkspaceObject -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -WorkspaceId -{{ Fill WorkspaceId Description }} - -```yaml -Type: System.String -Parameter Sets: WorkspaceId -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### System.Object - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Get-Sha256.md b/documentation/Get-Sha256.md deleted file mode 100644 index 664fb93f..00000000 --- a/documentation/Get-Sha256.md +++ /dev/null @@ -1,50 +0,0 @@ -# Get-Sha256 - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Get-Sha256 [[-string] ] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -string -{{ Fill string Description }} - -```yaml -Type: System.Object -Parameter Sets: (All) -Aliases: - -Required: False -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Import-FabricItem.md b/documentation/Import-FabricItem.md deleted file mode 100644 index 05e9dcba..00000000 --- a/documentation/Import-FabricItem.md +++ /dev/null @@ -1,116 +0,0 @@ -# Import-FabricItem - -## SYNOPSIS -Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source. - -## SYNTAX - -``` -Import-FabricItem [[-path] ] [[-workspaceId] ] [[-filter] ] - [[-fileOverrides] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### EXAMPLE 1 -``` -Import-FabricItems -path 'C:\PBIPFiles' -workspaceId '12345' -filter 'C:\PBIPFiles\Reports' -``` - -This example imports PBIP files from the 'C:\PBIPFiles' folder into the Fabric workspace with ID '12345'. -It only searches for PBIP files in the 'C:\PBIPFiles\Reports' folder. - -## PARAMETERS - -### -fileOverrides -This parameter let's you override a PBIP file without altering the local file. - -```yaml -Type: System.Collections.Hashtable -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -filter -A filter to limit the search for PBIP files to specific folders. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -path -The path to the PBIP files. -Default value is '.\pbipOutput'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: .\pbipOutput -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceId -The ID of the Fabric workspace. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -This function requires the Invoke-FabricAPIRequest function to be available in the current session. -This function was originally written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -## RELATED LINKS diff --git a/documentation/Invoke-FabricAPIRequest.md b/documentation/Invoke-FabricAPIRequest.md deleted file mode 100644 index 7abb350d..00000000 --- a/documentation/Invoke-FabricAPIRequest.md +++ /dev/null @@ -1,158 +0,0 @@ -# Invoke-FabricAPIRequest - -## SYNOPSIS -Sends an HTTP request to a Fabric API endpoint and retrieves the response. -Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response - -## SYNTAX - -``` -Invoke-FabricAPIRequest [[-authToken] ] [-uri] [[-method] ] [[-body] ] - [[-contentType] ] [[-timeoutSec] ] [[-retryCount] ] [-ProgressAction ] - [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -authToken -{{ Fill authToken Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -body -{{ Fill body Description }} - -```yaml -Type: System.Object -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -contentType -{{ Fill contentType Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 5 -Default value: Application/json; charset=utf-8 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -method -{{ Fill method Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: Get -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -retryCount -{{ Fill retryCount Description }} - -```yaml -Type: System.Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: 7 -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -timeoutSec -{{ Fill timeoutSec Description }} - -```yaml -Type: System.Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: 6 -Default value: 240 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -uri -{{ Fill uri Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -## RELATED LINKS diff --git a/documentation/Invoke-FabricDatasetRefresh.md b/documentation/Invoke-FabricDatasetRefresh.md deleted file mode 100644 index d25d448b..00000000 --- a/documentation/Invoke-FabricDatasetRefresh.md +++ /dev/null @@ -1,70 +0,0 @@ -# Invoke-FabricDatasetRefresh - -## SYNOPSIS -This function invokes a refresh of a PowerBI dataset - -## SYNTAX - -``` -Invoke-FabricDatasetRefresh -DatasetID [-ProgressAction ] [] -``` - -## DESCRIPTION -The Invoke-FabricDatasetRefresh function is used to refresh a PowerBI dataset. -It first checks if the dataset is refreshable. -If it is not, it writes an error. -If it is, it invokes a PowerBI REST method to refresh the dataset. -The URL for the request is constructed using the provided dataset ID. - -## EXAMPLES - -### EXAMPLE 1 -``` -Invoke-FabricDatasetRefresh -DatasetID "12345678-1234-1234-1234-123456789012" -``` - -This command invokes a refresh of the dataset with the ID "12345678-1234-1234-1234-123456789012" - -## PARAMETERS - -### -DatasetID -A mandatory parameter that specifies the dataset ID. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Alias: Invoke-FabDatasetRetresh - -## RELATED LINKS diff --git a/documentation/Invoke-FabricKQLCommand.md b/documentation/Invoke-FabricKQLCommand.md deleted file mode 100644 index c5a48ed6..00000000 --- a/documentation/Invoke-FabricKQLCommand.md +++ /dev/null @@ -1,129 +0,0 @@ -# Invoke-FabricKQLCommand - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Invoke-FabricKQLCommand [-WorkspaceId] [[-KQLDatabaseName] ] [[-KQLDatabaseId] ] - [-KQLCommand] [-ReturnRawResult] [-ProgressAction ] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -KQLCommand -{{ Fill KQLCommand Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDatabaseId -{{ Fill KQLDatabaseId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDatabaseName -{{ Fill KQLDatabaseName Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ReturnRawResult -{{ Fill ReturnRawResult Description }} - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -{{ Fill WorkspaceId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/New-FabricEventhouse.md b/documentation/New-FabricEventhouse.md deleted file mode 100644 index c7588752..00000000 --- a/documentation/New-FabricEventhouse.md +++ /dev/null @@ -1,152 +0,0 @@ -# New-FabricEventhouse - -## SYNOPSIS -Creates a new Fabric Eventhouse - -## SYNTAX - -``` -New-FabricEventhouse [-WorkspaceID] [-EventhouseName] [[-EventhouseDescription] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Creates a new Fabric Eventhouse - -## EXAMPLES - -### EXAMPLE 1 -``` -New-FabricEventhouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' ` - -EventhouseDescription 'This is my Eventhouse' -``` - -This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. - -### EXAMPLE 2 -``` -New-FabricEventhouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' ` - -EventhouseDescription 'This is my Eventhouse' ` - -Verbose -``` - -This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. -It will also give you verbose output which is useful for debugging. - -## PARAMETERS - -### -EventhouseDescription -The description of the Eventhouse to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventhouseName -The name of the Eventhouse to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceID -Id of the Fabric Workspace for which the Eventhouse should be created. -The value for WorkspaceID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName -- 2024-11-27 - FGE: Added Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP) - diff --git a/documentation/New-FabricEventstream.md b/documentation/New-FabricEventstream.md deleted file mode 100644 index fa7129d8..00000000 --- a/documentation/New-FabricEventstream.md +++ /dev/null @@ -1,140 +0,0 @@ -# New-FabricEventstream - -## SYNOPSIS -Creates a new Fabric Eventstream - -## SYNTAX - -``` -New-FabricEventstream [-WorkspaceID] [-EventstreamName] [[-EventstreamDescription] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Creates a new Fabric Eventstream - -## EXAMPLES - -### EXAMPLE 1 -``` -New-FabricEventstream - -WorkspaceID '12345678-1234-1234-1234-123456789012' - -EventstreamName 'MyEventstream' - -EventstreamDescription 'This is my Eventstream' -``` - -This example will create a new Eventstream with the name 'MyEventstream' and the description 'This is my Eventstream'. - -## PARAMETERS - -### -EventstreamDescription -The description of the Eventstream to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventstreamName -The name of the Eventstream to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceID -Id of the Fabric Workspace for which the Eventstream should be created. -The value for WorkspaceID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName -- 2024-11-30 - FGE: Added Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/create-eventstream?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/create-eventstream?tabs=HTTP) - diff --git a/documentation/New-FabricKQLDashboard.md b/documentation/New-FabricKQLDashboard.md deleted file mode 100644 index 91e66ac5..00000000 --- a/documentation/New-FabricKQLDashboard.md +++ /dev/null @@ -1,138 +0,0 @@ -# New-FabricKQLDashboard - -## SYNOPSIS -Creates a new Fabric KQLDashboard - -## SYNTAX - -``` -New-FabricKQLDashboard [-WorkspaceID] [-KQLDashboardName] - [[-KQLDashboardDescription] ] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -## DESCRIPTION -Creates a new Fabric KQLDashboard - -## EXAMPLES - -### EXAMPLE 1 -``` -New-FabricDashboard ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -KQLDashboardName 'MyKQLDashboard' ` - -KQLDashboardDescription 'This is my KQLDashboard' -``` - -This example will create a new KQLDashboard with the name 'MyKQLDashboard' and the description 'This is my KQLDashboard'. - -## PARAMETERS - -### -KQLDashboardDescription -The description of the KQLDashboard to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDashboardName -The name of the KQLDashboard to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceID -Id of the Fabric Workspace for which the KQLDashboard should be created. -The value for WorkspaceID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName -- 2024-12-08 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/New-FabricKQLDatabase.md b/documentation/New-FabricKQLDatabase.md deleted file mode 100644 index a5b16581..00000000 --- a/documentation/New-FabricKQLDatabase.md +++ /dev/null @@ -1,160 +0,0 @@ -# New-FabricKQLDatabase - -## SYNOPSIS -Creates a new Fabric KQLDatabase - -## SYNTAX - -``` -New-FabricKQLDatabase [-WorkspaceID] [-EventhouseID] [-KQLDatabaseName] - [[-KQLDatabaseDescription] ] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -## DESCRIPTION -Creates a new Fabric KQLDatabase. -The KQLDatabase is created in the specified Workspace and Eventhouse. -It will be created with the specified name and description. - -## EXAMPLES - -### EXAMPLE 1 -``` -New-FabricKQLDatabase ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseID '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' ` - -KQLDatabaseDescription 'This is my KQLDatabase' -``` - -This example will create a new KQLDatabase with the name 'MyKQLDatabase' and the description 'This is my KQLDatabase'. - -## PARAMETERS - -### -EventhouseID -Id of the Fabric Eventhouse for which the KQLDatabase should be created. -The value for EventhouseID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDatabaseDescription -The description of the KQLDatabase to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDatabaseName -The name of the KQLDatabase to create. -The name must be unique within the eventhouse and is a -mandatory parameter. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: True -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceID -Id of the Fabric Workspace for which the KQLDatabase should be created. -The value for WorkspaceID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName -- 2024-12-08 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/New-FabricLakehouse.md b/documentation/New-FabricLakehouse.md deleted file mode 100644 index c6527acd..00000000 --- a/documentation/New-FabricLakehouse.md +++ /dev/null @@ -1,149 +0,0 @@ -# New-FabricLakehouse - -## SYNOPSIS -Creates a new Fabric Lakehouse - -## SYNTAX - -``` -New-FabricLakehouse [-WorkspaceID] [-LakehouseName] [-LakehouseSchemaEnabled] [[-LakehouseDescription] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Creates a new Fabric Lakehouse - -## EXAMPLES - -### EXAMPLE 1 -``` -New-FabricLakehouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -LakehouseName 'MyLakehouse' ` - -LakehouseSchemaEnabled $true ` - -LakehouseDescription 'This is my Lakehouse' -``` - -This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my Lakehouse'. - -### EXAMPLE 2 -``` -New-FabricLakehouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -LakehouseName 'MyLakehouse' ` - -LakehouseSchemaEnabled $true ` - -LakehouseDescription 'This is my Lakehouse' ` - -Verbose -``` - -This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my MyLakehouse'. -It will also give you verbose output which is useful for debugging. - -## PARAMETERS - -### -EventhouseDescription -The description of the Eventhouse to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventhouseName -The name of the Eventhouse to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceID -Id of the Fabric Workspace for which the Eventhouse should be created. -The value for WorkspaceID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP) - diff --git a/documentation/New-FabricWorkspace.md b/documentation/New-FabricWorkspace.md deleted file mode 100644 index e604ceb9..00000000 --- a/documentation/New-FabricWorkspace.md +++ /dev/null @@ -1,121 +0,0 @@ -# New-FabricWorkspace - -## SYNOPSIS -Creates a new Fabric workspace. - -## SYNTAX - -``` -New-FabricWorkspace [[-name] ] [-skipErrorIfExists] [-ProgressAction ] [-WhatIf] - [-Confirm] [] -``` - -## DESCRIPTION -The New-FabricWorkspace function creates a new Fabric workspace. -It uses the provided name to create the workspace. -If the workspace already exists and the skipErrorIfExists switch is provided, it does not throw an error. - -## EXAMPLES - -### EXAMPLE 1 -``` -New-FabricWorkspace -Name "NewWorkspace" -SkipErrorIfExists -``` - -This command creates a new Fabric workspace named "NewWorkspace". -If the workspace already exists, it does not throw an error. - -## PARAMETERS - -### -name -The name of the new Fabric workspace. -This is a mandatory parameter. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -skipErrorIfExists -A switch that indicates whether to skip the error if the workspace already exists. -If provided, the function does not throw an error if the workspace already exists. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### String, Switch. You can pipe a string that contains the name and a switch that indicates whether to skip the error if the workspace already exists to New-FabricWorkspace. -## OUTPUTS - -### String. This function returns the ID of the new Fabric workspace. -## NOTES -This function was originally written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -## RELATED LINKS diff --git a/documentation/New-FabricWorkspace2.md b/documentation/New-FabricWorkspace2.md deleted file mode 100644 index 88e3d6a8..00000000 --- a/documentation/New-FabricWorkspace2.md +++ /dev/null @@ -1,136 +0,0 @@ -# New-FabricWorkspace2 - -## SYNOPSIS -Creates a new Fabric Workspace - -## SYNTAX - -``` -New-FabricWorkspace2 [-CapacityId] [-WorkspaceName] [[-WorkspaceDescription] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Creates a new Fabric Workspace - -## EXAMPLES - -### EXAMPLE 1 -``` -New-FabricWorkspace ` - -CapacityID '12345678-1234-1234-1234-123456789012' ` - -WorkspaceName 'TestWorkspace' ` - -WorkspaceDescription 'This is a Test Workspace' -``` - -This example will create a new Workspace with the name 'TestWorkspace' and the description 'This is a test workspace'. - -## PARAMETERS - -### -CapacityId -Id of the Fabric Capacity for which the Workspace should be created. -The value for CapacityID is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceDescription -The description of the Workspace to create. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceName -The name of the Workspace to create. -This parameter is mandatory. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-12-22 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/New-FabricWorkspaceUsageMetricsReport.md b/documentation/New-FabricWorkspaceUsageMetricsReport.md deleted file mode 100644 index 6bf0c332..00000000 --- a/documentation/New-FabricWorkspaceUsageMetricsReport.md +++ /dev/null @@ -1,100 +0,0 @@ -# New-FabricWorkspaceUsageMetricsReport - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -New-FabricWorkspaceUsageMetricsReport [-workspaceId] [-ProgressAction ] [-WhatIf] - [-Confirm] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceId -{{ Fill workspaceId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Register-FabricWorkspaceToCapacity.md b/documentation/Register-FabricWorkspaceToCapacity.md deleted file mode 100644 index 4ec77577..00000000 --- a/documentation/Register-FabricWorkspaceToCapacity.md +++ /dev/null @@ -1,137 +0,0 @@ -# Register-FabricWorkspaceToCapacity - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -### WorkspaceId -``` -Register-FabricWorkspaceToCapacity [-WorkspaceId ] -CapacityId - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -### WorkspaceObject -``` -Register-FabricWorkspaceToCapacity [-Workspace ] -CapacityId - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -CapacityId -{{ Fill CapacityId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Workspace -{{ Fill Workspace Description }} - -```yaml -Type: System.Object -Parameter Sets: WorkspaceObject -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -WorkspaceId -{{ Fill WorkspaceId Description }} - -```yaml -Type: System.String -Parameter Sets: WorkspaceId -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### System.Object - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Remove-FabricEventhouse.md b/documentation/Remove-FabricEventhouse.md deleted file mode 100644 index 12c12006..00000000 --- a/documentation/Remove-FabricEventhouse.md +++ /dev/null @@ -1,154 +0,0 @@ -# Remove-FabricEventhouse - -## SYNOPSIS -Removes an existing Fabric Eventhouse - -## SYNTAX - -``` -Remove-FabricEventhouse [-WorkspaceId] [[-EventhouseId] ] [[-EventhouseName] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Removes an existing Fabric Eventhouse - -## EXAMPLES - -### EXAMPLE 1 -``` -Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' -``` - -This example will delete the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' from -the Workspace with the Id '12345678-1234-1234-1234-123456789012'. - -### EXAMPLE 2 -``` -Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' -``` - -This example will delete the Eventhouse with the name 'MyEventhouse' from the Workspace with the -Id '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -EventhouseId -The Id of the Eventhouse to delete. -The value for EventhouseId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -EventhouseId and EventhouseName cannot be used together. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventhouseName -The name of the Eventhouse to delete. -EventhouseId and EventhouseName cannot be used together. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the Eventhouse should be deleted. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName -- 2024-11-27 - FGE: Added Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/delete-eventhouse?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/delete-eventhouse?tabs=HTTP) - diff --git a/documentation/Remove-FabricEventstream.md b/documentation/Remove-FabricEventstream.md deleted file mode 100644 index df261e17..00000000 --- a/documentation/Remove-FabricEventstream.md +++ /dev/null @@ -1,148 +0,0 @@ -# Remove-FabricEventstream - -## SYNOPSIS -Removes an existing Fabric Eventstream - -## SYNTAX - -``` -Remove-FabricEventstream [-WorkspaceId] [[-EventstreamId] ] [[-EventstreamName] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Removes an existing Fabric Eventstream - -## EXAMPLES - -### EXAMPLE 1 -``` -Remove-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' -``` - -This example will delete the Eventstream with the Id '12345678-1234-1234-1234-123456789012' from -the Workspace. - -### EXAMPLE 2 -``` -Remove-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamName 'MyEventstream' -``` - -This example will delete the Eventstream with the name 'MyEventstream' from the Workspace. - -## PARAMETERS - -### -EventstreamId -The Id of the Eventstream to delete. -The value for Eventstream is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventstreamName -{{ Fill EventstreamName Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Name, DisplayName - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the Eventstream should be deleted. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName -- 2024-12-08 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Remove-FabricItem.md b/documentation/Remove-FabricItem.md deleted file mode 100644 index 29ada66c..00000000 --- a/documentation/Remove-FabricItem.md +++ /dev/null @@ -1,135 +0,0 @@ -# Remove-FabricItem - -## SYNOPSIS -Removes selected items from a Fabric workspace. - -## SYNTAX - -``` -Remove-FabricItem [-workspaceId] [[-filter] ] [[-itemID] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -The Remove-FabricItems function removes selected items from a specified Fabric workspace. -It uses the workspace ID and an optional filter to select the items to remove. -If a filter is provided, only items whose DisplayName matches the filter are removed. - -## EXAMPLES - -### EXAMPLE 1 -``` -Remove-FabricItems -WorkspaceID "12345678-90ab-cdef-1234-567890abcdef" -Filter "*test*" -``` - -This command removes all items from the workspace with the specified ID whose DisplayName includes "test". - -## PARAMETERS - -### -filter -An optional filter to select items to remove. -If provided, only items whose DisplayName matches the filter are removed. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -itemID -{{ Fill itemID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceId -The ID of the Fabric workspace. -This is a mandatory parameter. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### String. You can pipe two strings that contain the workspace ID and filter to Remove-FabricItems. -## OUTPUTS - -### None. This function does not return any output. -## NOTES -This function was written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -## RELATED LINKS diff --git a/documentation/Remove-FabricKQLDatabase.md b/documentation/Remove-FabricKQLDatabase.md deleted file mode 100644 index 9bb358c5..00000000 --- a/documentation/Remove-FabricKQLDatabase.md +++ /dev/null @@ -1,122 +0,0 @@ -# Remove-FabricKQLDatabase - -## SYNOPSIS -Removes an existing Fabric Eventhouse - -## SYNTAX - -``` -Remove-FabricKQLDatabase [-WorkspaceId] [-KQLDatabaseId] [-ProgressAction ] - [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Removes an existing Fabric Eventhouse. -The Eventhouse is removed from the specified Workspace. - -## EXAMPLES - -### EXAMPLE 1 -``` -Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' -``` - -This example will remove the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -KQLDatabaseId -{{ Fill KQLDatabaseId Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace from which the Eventhouse should be removed. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to remove Eventhouse by name. - -Revsion History: - -- 2024-12-08 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Remove-FabricKQLQueryset.md b/documentation/Remove-FabricKQLQueryset.md deleted file mode 100644 index 41fb343a..00000000 --- a/documentation/Remove-FabricKQLQueryset.md +++ /dev/null @@ -1,125 +0,0 @@ -# Remove-FabricKQLQueryset - -## SYNOPSIS -Removes an existing Fabric KQLQueryset. - -## SYNTAX - -``` -Remove-FabricKQLQueryset [-WorkspaceId] [-KQLQuerysetId] [-ProgressAction ] - [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Removes an existing Fabric KQLQueryset. -The Eventhouse is identified by the WorkspaceId and KQLQuerysetId. - -## EXAMPLES - -### EXAMPLE 1 -``` -Remove-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' -``` - -## PARAMETERS - -### -KQLQuerysetId -The Id of the KQLQueryset to remove. -The value for KQLQuerysetId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -This parameter is mandatory. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the KQLQueryset should be removed. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -This parameter is mandatory. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to remove KQLQueryset by name. - -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-12-22 - FGE: Added Verbose Output - -## RELATED LINKS diff --git a/documentation/Remove-FabricSQLDatabase.md b/documentation/Remove-FabricSQLDatabase.md deleted file mode 100644 index 29491122..00000000 --- a/documentation/Remove-FabricSQLDatabase.md +++ /dev/null @@ -1,92 +0,0 @@ -# Remove-FabricSQLDatabase - -## SYNOPSIS -Removes an existing Fabric SQL Database - -## SYNTAX - -``` -Remove-FabricSQLDatabase [-WorkspaceId] [-SQLDatabaseId] [-ProgressAction ] - [] -``` - -## DESCRIPTION -Removes an existing Fabric SQL Database. -The SQL Database is removed from the specified Workspace. - -## EXAMPLES - -### EXAMPLE 1 -``` -Remove-FabricSQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -SQLDatabaseId '12345678-1234-1234-1234-123456789012' -``` - -This example will remove the SQL Database with the Id '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLDatabaseId -The Id of the SQL Database to remove. -The value for EventhouseId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace from which the SQL Database should be removed. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to remove SQLDatabase by name. - -Revsion History: -- 2025-03-06 - KNO: Init version - -## RELATED LINKS diff --git a/documentation/Remove-FabricWorkspace.md b/documentation/Remove-FabricWorkspace.md deleted file mode 100644 index 1ca54e14..00000000 --- a/documentation/Remove-FabricWorkspace.md +++ /dev/null @@ -1,100 +0,0 @@ -# Remove-FabricWorkspace - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Remove-FabricWorkspace [-workspaceID] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -workspaceID -{{ Fill workspaceID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Resume-FabricCapacity.md b/documentation/Resume-FabricCapacity.md deleted file mode 100644 index c6128a02..00000000 --- a/documentation/Resume-FabricCapacity.md +++ /dev/null @@ -1,130 +0,0 @@ -# Resume-FabricCapacity - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Resume-FabricCapacity [-subscriptionID] [-resourcegroup] [-capacity] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -capacity -{{ Fill capacity Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -resourcegroup -{{ Fill resourcegroup Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -subscriptionID -{{ Fill subscriptionID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Set-FabricAuthToken.md b/documentation/Set-FabricAuthToken.md deleted file mode 100644 index 771edf1e..00000000 --- a/documentation/Set-FabricAuthToken.md +++ /dev/null @@ -1,184 +0,0 @@ -# Set-FabricAuthToken - -## SYNOPSIS -Sets the Fabric authentication token. - -## SYNTAX - -``` -Set-FabricAuthToken [[-servicePrincipalId] ] [[-servicePrincipalSecret] ] - [[-credential] ] [[-tenantId] ] [-reset] [[-apiUrl] ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -The Set-FabricAuthToken function sets the Fabric authentication token. -It checks if an Azure context is already available. -If not, it connects to the Azure account using either a service principal ID and secret, a provided credential, or interactive login. -It then gets the Azure context and sets the Fabric authentication token. - -## EXAMPLES - -### EXAMPLE 1 -``` -Set-FabricAuthToken -servicePrincipalId "12345678-90ab-cdef-1234-567890abcdef" -servicePrincipalSecret "secret" -tenantId "12345678-90ab-cdef-1234-567890abcdef" -``` - -This command sets the Fabric authentication token using the provided service principal ID, secret, and tenant ID. - -## PARAMETERS - -### -apiUrl -{{ Fill apiUrl Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 5 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -credential -The credential. -If provided, the function uses this credential to connect to the Azure account. - -```yaml -Type: System.Management.Automation.PSCredential -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -reset -{{ Fill reset Description }} - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -servicePrincipalId -The service principal ID. -If provided, the function uses this ID and the service principal secret to connect to the Azure account. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -servicePrincipalSecret -The service principal secret. -Used with the service principal ID to connect to the Azure account. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -tenantId -The tenant ID. -Used with the service principal ID and secret or the credential to connect to the Azure account. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### String, SecureString, PSCredential. You can pipe a string that contains the service principal ID, a secure string that contains the service principal secret, and a PSCredential object that contains the credential to Set-FabricAuthToken. -## OUTPUTS - -### None. This function does not return any output. -## NOTES -This function was originally written by Rui Romano. -https://github.com/RuiRomano/fabricps-pbip - -## RELATED LINKS diff --git a/documentation/Set-FabricEventhouse.md b/documentation/Set-FabricEventhouse.md deleted file mode 100644 index ba764814..00000000 --- a/documentation/Set-FabricEventhouse.md +++ /dev/null @@ -1,179 +0,0 @@ -# Set-FabricEventhouse - -## SYNOPSIS -Updates Properties of an existing Fabric Eventhouse - -## SYNTAX - -``` -Set-FabricEventhouse [-WorkspaceId] [-EventhouseId] [[-EventhouseNewName] ] - [[-EventhouseDescription] ] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -## DESCRIPTION -Updates Properties of an existing Fabric Eventhouse - -## EXAMPLES - -### EXAMPLE 1 -``` -Set-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -EventhouseNewName 'MyNewEventhouse' ` - -EventhouseDescription 'This is my new Eventhouse' -``` - -This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' -in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to -have the name 'MyNewEventhouse' and the description -'This is my new Eventhouse'. - -### EXAMPLE 2 -``` -Set-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -EventhouseNewName 'MyNewEventhouse' ` - -EventhouseDescription 'This is my new Eventhouse' ` - -Verbose -``` - -This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' -in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to -have the name 'MyNewEventhouse' and the description 'This is my new Eventhouse'. -It will also give you verbose output which is useful for debugging. - -## PARAMETERS - -### -EventhouseDescription -The new description of the Eventhouse. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventhouseId -The Id of the Eventhouse to update. -The value for EventhouseId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventhouseNewName -The new name of the Eventhouse. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: NewName, NewDisplayName - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the Eventhouse should be updated. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to update Eventhouse properties using EventhouseName instead of EventhouseId - -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added NewDisplaName as Alias for EventhouseName -- 2024-11-27 - FGE: Added Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP) - diff --git a/documentation/Set-FabricEventstream.md b/documentation/Set-FabricEventstream.md deleted file mode 100644 index a0190c79..00000000 --- a/documentation/Set-FabricEventstream.md +++ /dev/null @@ -1,160 +0,0 @@ -# Set-FabricEventstream - -## SYNOPSIS -Updates Properties of an existing Fabric Eventstream - -## SYNTAX - -``` -Set-FabricEventstream [-WorkspaceId] [-EventstreamId] [[-EventstreamNewName] ] - [[-EventstreamDescription] ] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -## DESCRIPTION -Updates Properties of an existing Fabric Eventstream - -## EXAMPLES - -### EXAMPLE 1 -``` -Set-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' ` - -EventstreamNewName 'MyNewEventstream' ` - -EventstreamDescription 'This is my new Eventstream' -``` - -This example will update the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. - -## PARAMETERS - -### -EventstreamDescription -The new description of the Eventstream. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description, NewDescription, EventstreamNewDescription - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventstreamId -The Id of the Eventstream to update. -The value for EventstreamId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -EventstreamNewName -The new name of the Eventstream. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: NewName, NewDisplayName - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the Eventstream should be updated. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -TODO: Add functionality to update Eventstream properties using EventstreamName instead of EventstreamId - -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamNewName -- 2024-12-08 - FGE: Added Verbose Output - Added Aliases for EventstreamNewName and EventstreamDescription - Corrected typo in EventstreamNewName Variable - -## RELATED LINKS diff --git a/documentation/Set-FabricKQLDatabase.md b/documentation/Set-FabricKQLDatabase.md deleted file mode 100644 index 705625f0..00000000 --- a/documentation/Set-FabricKQLDatabase.md +++ /dev/null @@ -1,165 +0,0 @@ -# Set-FabricKQLDatabase - -## SYNOPSIS -Updates Properties of an existing Fabric KQLDatabase - -## SYNTAX - -``` -Set-FabricKQLDatabase [-WorkspaceId] [-KQLDatabaseId] [[-NewKQLDatabaseName] ] - [[-KQLDatabaseDescription] ] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -## DESCRIPTION -Updates Properties of an existing Fabric KQLDatabase. -The KQLDatabase is updated -in the specified Workspace. -The KQLDatabaseId is used to identify the KQLDatabase -that should be updated. -The KQLDatabaseNewName and KQLDatabaseDescription are the -properties that can be updated. - -## EXAMPLES - -### EXAMPLE 1 -``` -Set-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseId '12345678-1234-1234-1234-123456789012' ` - -NewKQLDatabaseNewName 'MyNewKQLDatabase' ` - -KQLDatabaseDescription 'This is my new KQLDatabase' -``` - -This example will update the KQLDatabase with the Id '12345678-1234-1234-1234-123456789012'. -It will update the name to 'MyNewKQLDatabase' and the description to 'This is my new KQLDatabase'. - -## PARAMETERS - -### -KQLDatabaseDescription -The new description of the KQLDatabase. -The description can be up to 256 characters long. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLDatabaseId -The Id of the KQLDatabase to update. -The value for KQLDatabaseId is a GUID. -An example of a GUID is '12345678-1234-123-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -NewKQLDatabaseName -The new name of the KQLDatabase. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: NewName, NewDisplayName - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the KQLDatabase should be updated. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName -- 2024-12-08 - FGE: Added Verbose Output - Renamed Parameter KQLDatabaseName to NewKQLDatabaseNewName - -## RELATED LINKS diff --git a/documentation/Set-FabricKQLQueryset.md b/documentation/Set-FabricKQLQueryset.md deleted file mode 100644 index 14d7edf7..00000000 --- a/documentation/Set-FabricKQLQueryset.md +++ /dev/null @@ -1,166 +0,0 @@ -# Set-FabricKQLQueryset - -## SYNOPSIS -Updates Properties of an existing Fabric KQLQueryset - -## SYNTAX - -``` -Set-FabricKQLQueryset [-WorkspaceId] [-KQLQuerysetId] [[-KQLQuerysetNewName] ] - [[-KQLQuerysetDescription] ] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -## DESCRIPTION -Updates Properties of an existing Fabric KQLQueryset. -The KQLQueryset is identified by -the WorkspaceId and KQLQuerysetId. - -## EXAMPLES - -### EXAMPLE 1 -``` -Set-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetNewName 'MyKQLQueryset' ` - -KQLQuerysetDescription 'This is my KQLQueryset' -``` - -This example will update the KQLQueryset. -The KQLQueryset will have the name 'MyKQLQueryset' -and the description 'This is my KQLQueryset'. - -## PARAMETERS - -### -KQLQuerysetDescription -The new description of the KQLQueryset. -This parameter is optional. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Description - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLQuerysetId -The Id of the KQLQueryset to update. -The value for KQLQuerysetId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -This parameter is mandatory. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: Id - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -KQLQuerysetNewName -{{ Fill KQLQuerysetNewName Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: NewName, NewDisplayName - -Required: False -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WorkspaceId -Id of the Fabric Workspace for which the KQLQueryset should be updated. -The value for WorkspaceId is a GUID. -An example of a GUID is '12345678-1234-1234-1234-123456789012'. -This parameter is mandatory. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -Revsion History: - -- 2024-11-07 - FGE: Implemented SupportShouldProcess -- 2024-11-09 - FGE: Added NewDisplaName as Alias for KQLQuerysetNewName -- 2024-12-22 - FGE: Added Verbose Output - -## RELATED LINKS - -[https://learn.microsoft.com/en-us/rest/api/fabric/KQLQueryset/items/create-KQLQueryset?tabs=HTTP](https://learn.microsoft.com/en-us/rest/api/fabric/KQLQueryset/items/create-KQLQueryset?tabs=HTTP) - diff --git a/documentation/Suspend-FabricCapacity.md b/documentation/Suspend-FabricCapacity.md deleted file mode 100644 index 112ad86b..00000000 --- a/documentation/Suspend-FabricCapacity.md +++ /dev/null @@ -1,130 +0,0 @@ -# Suspend-FabricCapacity - -## SYNOPSIS -{{ Fill in the Synopsis }} - -## SYNTAX - -``` -Suspend-FabricCapacity [-subscriptionID] [-resourcegroup] [-capacity] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -{{ Fill in the Description }} - -## EXAMPLES - -### Example 1 -```powershell -PS C:\> {{ Add example code here }} -``` - -{{ Add example description here }} - -## PARAMETERS - -### -capacity -{{ Fill capacity Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -resourcegroup -{{ Fill resourcegroup Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -subscriptionID -{{ Fill subscriptionID Description }} - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### None - -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS diff --git a/documentation/Unregister-FabricWorkspaceToCapacity.md b/documentation/Unregister-FabricWorkspaceToCapacity.md deleted file mode 100644 index 7f84c740..00000000 --- a/documentation/Unregister-FabricWorkspaceToCapacity.md +++ /dev/null @@ -1,132 +0,0 @@ -# Unregister-FabricWorkspaceToCapacity - -## SYNOPSIS -Unregisters a workspace from a capacity. - -## SYNTAX - -### WorkspaceId -``` -Unregister-FabricWorkspaceToCapacity -WorkspaceId [-ProgressAction ] [-WhatIf] - [-Confirm] [] -``` - -### WorkspaceObject -``` -Unregister-FabricWorkspaceToCapacity -Workspace [-ProgressAction ] [-WhatIf] - [-Confirm] [] -``` - -## DESCRIPTION -The Unregister-FabricWorkspaceToCapacity function unregisters a workspace from a capacity in PowerBI. -It can be used to remove a workspace from a capacity, allowing it to be assigned to a different capacity or remain unassigned. - -## EXAMPLES - -### EXAMPLE 1 -``` -Unregister-FabricWorkspaceToCapacity -WorkspaceId "12345678" -Unregisters the workspace with ID "12345678" from the capacity. -``` - -### EXAMPLE 2 -``` -Get-FabricWorkspace | Unregister-FabricWorkspaceToCapacity -Unregisters the workspace objects piped from Get-FabricWorkspace from the capacity. -``` - -## PARAMETERS - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: System.Management.Automation.ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Workspace -Specifies the workspace object to be unregistered from the capacity. -This parameter is mandatory when using the 'WorkspaceObject' parameter set. -The workspace object can be piped into the function. - -```yaml -Type: System.Object -Parameter Sets: WorkspaceObject -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -WorkspaceId -Specifies the ID of the workspace to be unregistered from the capacity. -This parameter is mandatory when using the 'WorkspaceId' parameter set. - -```yaml -Type: System.String -Parameter Sets: WorkspaceId -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### System.Management.Automation.PSCustomObject -## OUTPUTS - -### System.Object -## NOTES -Author: Your Name -Date: Today's Date - -## RELATED LINKS diff --git a/helper/Get-RootPath.ps1 b/helper/Get-RootPath.ps1 deleted file mode 100644 index dc88bf23..00000000 --- a/helper/Get-RootPath.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -#$Host.name - -$rootPath = Switch ($Host.name) { - 'Visual Studio Code Host' { split-path $psEditor.GetEditorContext().CurrentFile.Path } - 'Windows PowerShell ISE Host' { Split-Path -Path $psISE.CurrentFile.FullPath } - 'ConsoleHost' { $PSScriptRoot } -} -#$parentPath = $rootPath | Split-Path -Parent -$rootPath -#$parentPath - - diff --git a/helper/createdocumentation.ps1 b/helper/createdocumentation.ps1 deleted file mode 100644 index 20b90558..00000000 --- a/helper/createdocumentation.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -#============================================================================ -# Datei: createdocumentation.ps1 -# -# Summary: This script will help to create the documentation for -# the PowerShell module. Markdown Code will be created -# with platyPS -# -# Datum: 2024-11-05 -# -# Revisionen: yyyy-dd-mm -# - ... -# -# PowerShell Version: 7.1 -#------------------------------------------------------------------------------ -# Geschrieben von -# Frank Geisler, GDS Business Intelligence GmbH -# -# Dieses Script ist nur zu Lehr- bzw. Lernzwecken gedacht -# -# DIESER CODE UND DIE ENTHALTENEN INFORMATIONEN WERDEN OHNE GEWÄHR JEGLICHER -# ART ZUR VERFÜGUNG GESTELLT, WEDER AUSDRÜCKLICH NOCH IMPLIZIT, EINSCHLIESSLICH, -# ABER NICHT BESCHRÄNKT AUF FUNKTIONALITÄT ODER EIGNUNG FÜR EINEN BESTIMMTEN -# ZWECK. SIE VERWENDEN DEN CODE AUF EIGENE GEFAHR. -#============================================================================*/ - -#---------------------------------------------------------------------------- -# 01. Define the variables we will need further down the -# road. -#---------------------------------------------------------------------------- - -Import-Module platyPS - -$rootPath = Switch ($Host.name) { - 'Visual Studio Code Host' { split-path $psEditor.GetEditorContext().CurrentFile.Path } - 'Windows PowerShell ISE Host' { Split-Path -Path $psISE.CurrentFile.FullPath } - 'ConsoleHost' { $PSScriptRoot } -} - -$basepath = Split-Path $rootPath -Parent -$moduleName = 'FabricTools' -$modulePathName = "$basepath\$moduleName\$moduleName.psd1" -$markdownPath = "$basepath\documentation" -$mamlPath = "$basepath\en-US" - -#---------------------------------------------------------------------------- -# 02. Import the module to be documented -# ATTENTION!!!!!! The module must be loaded in the PowerShell session -#---------------------------------------------------------------------------- - -# FGE: Remove the module from PowerShell -Remove-Module ` - -Name FabricTools ` - -ErrorAction SilentlyContinue - -Import-Module ` - -Name $modulePathName - -#---------------------------------------------------------------------------- -# 03. Create the markdown documentation -#---------------------------------------------------------------------------- -New-MarkdownHelp ` - -Module $moduleName ` - -OutputFolder $markdownPath ` - -AlphabeticParamsOrder ` - -UseFullTypeName ` - -WithModulePage ` - -ExcludeDontShow ` - -NoMetadata ` - -Force diff --git a/helper/createmodule.ps1 b/helper/createmodule.ps1 deleted file mode 100644 index 2ec8d95e..00000000 --- a/helper/createmodule.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -#============================================================================ -# Datei: createmodule.ps1 -# -# Summary: This script helps creating the module. It has the -# New-ModuleManifest function in it. -# -# Datum: 2024-11-04 -# -# Revisionen: yyyy-dd-mm -# - ... -# Kunde: Kunde -# -# Projekt: PowerRTI -# -# PowerShell Version: 7.1 -#------------------------------------------------------------------------------ -# Geschrieben von -# Frank Geisler, GDS Business Intelligence GmbH -# -# Dieses Script ist nur zu Lehr- bzw. Lernzwecken gedacht -# -# DIESER CODE UND DIE ENTHALTENEN INFORMATIONEN WERDEN OHNE GEWÄHR JEGLICHER -# ART ZUR VERFÜGUNG GESTELLT, WEDER AUSDRÜCKLICH NOCH IMPLIZIT, EINSCHLIESSLICH, -# ABER NICHT BESCHRÄNKT AUF FUNKTIONALITÄT ODER EIGNUNG FÜR EINEN BESTIMMTEN -# ZWECK. SIE VERWENDEN DEN CODE AUF EIGENE GEFAHR. -#============================================================================*/ - -New-ModuleManifest ` - -Path '.\powerrti.psd1' ` - -RootModule '.\powerrti.psm1' ` - -Author 'Frank Geisler' ` - -CompanyName 'GDS Business Intelligence GmbH' ` - -Description 'PowerRTI is a PowerShell module to automate Fabric Real-Time Intelligence in PowerShell' ` - -ModuleVersion '0.1.0' ` - -PowerShellVersion '7.1' ` - -FunctionsToExport '*' ` - -VariablesToExport '*' ` - -AliasesToExport '*' ` - -CmdletsToExport '*' ` - -TypesToProcess '*' ` - -FormatsToProcess '*' - -# FGE: Import the module for testing -Import-Module ` - -Name 'C:\Users\fgeisler\repos\github\powerrit\powerrti\powerrti.psd1' - -Remove-Module ` - -Name 'PowerRTI' ` - -Force \ No newline at end of file diff --git a/helper/startscriptanalyzer.ps1 b/helper/startscriptanalyzer.ps1 deleted file mode 100644 index 2e0420eb..00000000 --- a/helper/startscriptanalyzer.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -$rootPath = Switch ($Host.name) { - 'Visual Studio Code Host' { split-path $psEditor.GetEditorContext().CurrentFile.Path } - 'Windows PowerShell ISE Host' { Split-Path -Path $psISE.CurrentFile.FullPath } - 'ConsoleHost' { $PSScriptRoot } -} - -$basepath = Split-Path $rootPath -Parent -$basepath = Join-Path (Split-Path $rootPath -Parent) (Split-Path $basePath -Leaf) - - -Invoke-ScriptAnalyzer ` - -Path $basepath ` - -Recurse ` - -Severity All - -#Invoke-ScriptAnalyzer -Path $basepath -Severity Error -Recurse -Invoke-ScriptAnalyzer -Path $basepath -Severity @('Error', 'Warning') -Recurse diff --git a/images/FabricToolsLogo.ico b/images/FabricToolsLogo.ico deleted file mode 100644 index 572524ca021b5025fdcb92b3fe9abc9a252feeeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195868 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 D2%R*> diff --git a/images/FabricToolsLogo.png b/images/FabricToolsLogo.png deleted file mode 100644 index cdaf7b6a25703ed1e5635fb60326ff9a103b85f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35558 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 B>qY - - - \ No newline at end of file diff --git a/images/Fabtools.ico b/images/Fabtools.ico deleted file mode 100644 index dc4f2f13f670b2d0d05ab5e0ff5451dcf2b9308d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90225 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

m%s(q1)$v5?L)Z2|n`t`$N> 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
aaeAhX8=r!FcZZ90fMg40i%W1I?= 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

M?L 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 z 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ţ|<|#R^ z<&E5(A^Xq8`MuV-%BHWnDh@~kW}<*I4Q|ELqBOwsh*$_*+8?hWzP;yXC8I*kH(O!} zzrUW9G6Dv0HVy0}VhxkBEC7o%kF-MZF$obBt!eXPN6C5^)!a)Rt$S!as?O`UAs~e5R#; zhbIRVDQ;(OJNCS*PANO3EKX`zf@fsIyt;b&@}R8OTKp=vsMbjhjd)-5(`;4qsFTQbI3?O zuW!EC*mmEmt5<5`j>u!&&#~*XHG0~uy&54Il>6vgBus`hs#z~#5r++Ck3bb%r%ddX55wcKRm|9W_rfar2q!N79TBz2KfWxZVjeX4T zLea~L98`VGQw&P`Wu^N?s+wj?ddQ*6o|0D};IG$K^<=bdfeVg#rKj~h*-A$Nz`*9a zLAxS4yr}cl0=_Wh zYg@1v9WsT_Bw_H>GL2TnQ)*AMJzGvj%fU!*?wRfX{9y)N*GSPxMW{Hc z6;!|RU_woi6Y1jgW!G~8i0L(s^tk&6k?^m*1$FXV3Dl#i_Ugu(Zr)@-tKoIFsD_ee zNfsmz*ils+wvH$qre4AIOe@1LNce8|jf+n!2A>S3q<_0OftUWL@i_aOd?&_^zNe0n zQGEPTl?5%IP=zEVhO}QbbR9U_FSwhCM+xe4>2jM39Z~1!*M$mwQIeeI)NW(%nm*=+U_bf5^f~oK@rUQ3 z;cPMreE|g=$c2Ov7ZxytWp~A@>VVGR8U9k>mb`ByAP$bBi6(-?^NC#=P{vO&^S4a3 zk^UT!3>;Q<{eFzR&!H;xG$*;EJ|n^K)|l^dH2v=+%FyK z4q;4T5aj>k5yKTV{#8wf#`c|8OpjuNT+L_bFPsRt@khh9(?8^gN9t)+YxG6cg%~nh z&e-+%t>9K=^WzS$EufcjwI5dl9-mWg!5uJc8E@Ew9bcol_flTH4^I0T_DA_R%#ngpKcadn&&poSV9~qG$Cuhos94_bq@A*F6&e%$CH&qeb+*A z#+c`e(>uM!nVT7(c{MKRN|P|Q-FYX=|AlLOMVR9E;lpXWGE9+TN3oc^yOc);o(oo% z)2>b#xcKw7QxAH~T)INX8A-HhcTDNV`x9eYlq(X?t68A+XzD#is8QBDi3C4Ni4Jcn zQ)XwU!>9;#holofBBeEqZF00K^ELTGcDxI>jk1;hg=Z9l=RfrCB#=n}C@fmOP152j zs{uOM#D+sBsAPKmz9NPUt>$ZbcpE5)O75$Z7{w%X30_X>KL>d9N-e`jEvNf*~Ik2H=HHlG?& zu%AWZa=4Y_sx>z!A%AmHlI%=J+7F#4vb=0e8|odQBzB29S28JYZcY)8z^=C*pcave zCJ>Od@K~WFR{o9A(sm@QT#d^ffnn8#4$Up)b(~B0wO18QkMTNA@9}yW$~I z6D5K7Zgph^F5s`7*Q-(@{PZ-XfKI(ZIxDHz?zLEC$R)bhdrNv;I4Bxj|5{s{4SWp9`WcG?xvJ&%LDJ9 zO;@6^lqlBf<`@d_pte!F9p&{{^D8yLL) z|6YKYLX2Z#u_T$jPQIUvonSvuYe)?L9jcXjWuMa;b*SM2B=EuvZGd*-pF;dC?7kEi zRIxYYjB3S`Yrlj8ZgKI_6+H8zjxiECUI+DM3lVMKmTA6qC43c500LL|biMuMSnpbB2}dF$aB%1F zIQR#^$C+*W1*O3AFjebwQ3ej7RxlAo*WB4W93pYK6JJ!(K$uiMuV%y-i4p6!E@o{f z?Nc#&itXB;^6_53AJ*LjaDQs)4&`+>$H<*>Id1bDIxP&|H#EB_5)d6O@1GqUY^ z{?6F>^b1~}aaRLlETCA9&BRhLveb@}>_A9jSq`@R2L(_J|7WpF!-e-|aY6mJN~F!t z2Ui>a04bn@LZu|>MPp59IvhA`cm4gM+n4LM&2u_0JkWuD2TSl%GnKeiQuJ5TVcQRH z?{{kfPc}OBDj>Vc&QiQh(x+LPqzyeFT9~1g)vk6rTFHSQK$wk}p2eIeY4S0l$y|-S-XM#OWApU?m zz6^&k`tCO%#1q~(N$?DdWyT?Rf0s^^ zdQw}-xxI1!Y%&-K5x6swNMS7ZKolDhC*X5wXq;W2AE@1>a(iArjP0`eHk6pmul(E7 z($f7+^_A_6T8Bq<$goo(TFz;8qS+qA-`?`H6Hx$9=j`eVHkbZtYvTpm!8>k7=^%#& zRE4t%TBpCqAJyXd7OgL=^x)!+^c4txzgZ( zE+CB@L;4FgRQJd$X?GM3Ls$f+LnA+pEzU_Kf|Z4)GN54wE*v8QM#@i|AE|^HSWp>N zNJf_Fdf{%p;l=q6A%q6%t7OcNS}8Gt8h9N-8>r>b0K+timi*tOoG#^#G~$%8iE+)r zLb*w#F`~^mhyR*4Ma@T>G{9RJq`8W#Xm)c#hC|4Penet z&c=5{JuERJmcRc{62kM9V3^VK7MSi3BO{P@mBdE)&iH#^6stnJGhAFId1`YrhEBIg zKGJv;OSLr;&R@#Esg25P!R6)S3srnMg@_=cSkht3kfen2J*jYR&`e1htB*M~Ay2Q% zqQQ`!>-xa^sIP4k#2URqbu;tK*0joQ#*FXn6$U%f1AbBSUSE2Szopx$R4+UJBVXFpr-vF6VNp6;<>Vz_F> z+ISbnpt(z(0e&L5dN`JxINIv522A&hG5*4b^wwMbzEWMvrgRO~#W&CH**>?6`6G|x zqnok(Qnco%M1O{F?6|9g-h&PewvK8Gq(DI>W$0XGlgre)DoZilNg||Cz=`XnT9==X z;Gv`M8R0D0iuA`Bz1pxT(w+-HQPDtPp~QJXAtZUB31cC!xj1ARi%zrKf&Y}tCZ8a2 z`Sv6R9R(FlW_Qpz#T37Ic&4&~C#3@C{$5Rk zjhh>%cdc%eHv)N1C}jLJBLo@CuJeO3B$~*gtv4M{G^Axm&*O>N~(*$$gyZpx9mm-{hh^xnYo#V`hL7t z=K}}t-++o}88Ie`Wp^pyx2iz`k8XGE`01?08DH?Z6;$VOe?qQVo-`>x76Kr_5Gzps zOD3F@Z^(b3FeE5LWjcYXiZxB%kvM9&_LNa{`~}mI2OQ$oYIMZHU$8SAnAv~o+rP>a z9q(`@u%Z1vjfh1m&sZ}0IH=l;Q;6npML04zDh4%zoF56vEGG6S<#hA!n5F@~#rYOW z*I`**;C46X;2`T~>)npb5%@0ION%ywd6G0WZkY&WGU`iOR4J__E=ZdMQ0uIqT#ll9 zz1Dbpn;o?|1}6zXJr3l#43?HE*8^r4R%t$ual;^WR4KJ{D@0O{M~n+sW>~IZt!N+@EVsBktMa2F?osRE!Wn6CZJa(uBG3&|s}oS#fbl{UmP$ z0%KcjWBFn%=+N!7k*iczRtJi~czvWijMHU+luZ(X^kgcQKI^ADDlm4qB!ok&e!9y< zBnnPcHn+l|$j^nWV9g#J@Hq2*!mV1X4Y;VXT|0U%C$2Gv?o(zj_@#&gx#vYhC4f}F`#ZEb@U&JRyP|4TO| zwJ6KoKi`hm)Td$^+T?8P7t9G}kYcX~HBb8(l|`B#1dNLv5+IkMMJZr$@bPyCuziPB zHa`V6-QOItJx*u_qa&BEskzav7Y{T-D8^HbYC0;6H4J}tgFRzpxf>&@TaLF&BD&PHO!8G450}m5q>S!77eR;OKpJH&(dYHYNW|qC( ziGf1smV|h!l4XmHg+EA_71%9m*iTq2f-EJXytC>h#q(A{h{7o2Vi(9_#KpdVV z>`<4!7XNcS)dQ-jR?i|S&rz6DI!S(|o`#To&~cph1i>+(^~UzUj>ik( zhx64Te7lZby6TQTI=imHy!T6g6#nzhM1T0g9d<||`K^6;oB}vHtP*o`SDHQeam?ae z^VDXy)cG-qY3+KCd&?&44O!pMFIMW+rq}rc4KA4GWBW8oGEY34l;I-UzlHviby{|m ztKJ|c1PZRu?ebiT@$+1>#UyP)m?U5gYf0qDyDmC;#W`d8n|EXpVUL<)u zCsJSO)kPx?VT4Lm0hpj2chpZ$Z7c=>E2<%?rFvH;L9Y+@AFmg#ZZF5h?%U5qE48#w zd#kHTBF4a)1_vV$3gPrjdMR}T9=v(m=17$=1_BxZdh+f-QvW+RW{QIH&ylQ9UU<0OxGK+&Ri6(y8r2J#yI`sL5H`GUsOaYYK^d!+}z z5dZ6f`ehk^T$=yBmKkyQI#QG)v@uHjEr>QnxHo><1QBV{%-K1S5X`5x8~~_S>Pjmp zRIjh)ZM-k1Mw&>lQYzmpta?88rF8Z9enf2sx}Up6rz~5$tVv6EI+o{-zO*_1eukCV??%ec<+wAOu4p;f@jmQGO15Y!c{(xP!g7Qh!Pi1 ztDgrS1lGV~>ldoc=8xbH?jPz*C-Bjef^B1_$C8e!M>vc%9pIk8xz-yJoSHHNEKhM+6s@ zpRrW)6&X^@_a*Mxrccg;S%?`8gSQlNaF${m^T*~CcyxuLiR!Z0X54O9#DX5Ta{BK3 zSpV@|9?q8kO&c4R5CgeY_WJj@3$OF_tO?P#vCoRlp=Q!2{A=_DXH8%Y&7E0zRb@j+ z7WJF5C}fhEeS4Yzb2y9M9?mWW@q2Je zYMtPrCXB^M7VD0WNhMDXbm+Y6{`P$BRauBWI1nOsJsuZV&|2FmsT9z?Kdl4IhD{5% zFd7v?RV{(U3TI=BjEQl2>vsS{smHsTA8QXQ`3ZPDmY~~LP*p{ufAI8e7f6Dk`@g62 zgWR$uOO;KpcY-1rb^LZ)5SegHkB61vpZWOt`JG+Gp|g~8qej2ipeK=Wy$A&9532tZ z(Ok5$QL1erS$FEBCB^5@a6_~~1EycKR;9%6M#mWZ{1h$x5|D@tu-f3M$&u{7XXtvE zufD0Y69nX9NdW9ChW$C`Db0)#X<;$Qt8_CD{N)ZEz|=&?cO zelB|)7iRiyKvigV5irqm4mh_emI7FM`=bLyV>4zUTNrP=E$Bzh!r8fRh1Nv-3kvtg;dxrvoNU60x4?QYux z*zU*sDQ^4w8s~Y0Hk?B-M7vY%mtSivE0atxMVNWP+5m%&`Q_Q(n$x6F?-cXqT_a>o%2hSYUK`msyse9AZ&qi%LTYE zcxaCUHgiV(bOjUo+i|p5{W18vTVLGH8dF1+zcO{cu^N1oGu@^be6TXTPvsBmD@exw zJ!;WfN`gIJvOFJ0tU67&_2RTy>Yv27{k_^`W4%@rmwCy>aa&dxgp)C?SgM+unZDEc zxM2*^vAYIW*3@2}H{;^t8~<|5+;K)DA>YsK%MQ4#vHuWBrq`BL(BSHe%j~qkAd5(; zF`p()W-^f1`L9L1I2J?&qP1;`I<&$g0sKUgku?2wV17|lesTPd7dd+OA0M)HqB`!0 z@*4hkDgui{VunS07=+_oKIuLe)xWXx8%2P^ew1wAY{5_8Pyg=E7r~nYglggH5NGe7 z)qU`Nw+?|=3SdfY$qqXXx7lthB9r#;w{};j$<{+w-w(sYxgoi^dmn+u+p_65g>J#F z>XlqB+Z|b$8I~*=!wAyrOQ0vg&f^?(ngbfe#YE|>GAd^*kHZ@dMgj&nQ{i~ECH!!) zKBnXOVE@Z2lNb@!Ki09d-_Lb(|Gs%Mzr8&IDnN9PFXxv9^tQJ2DR`X{s9kHj&YMiH zDZ2pzdw?mT=|-M;_!jHo6cz8cV$o>T2*)s^7yyC0EMfThd83ChL*TD+;0!hhoA5ri z-_Jzbp;EatHs+xzH5iKuH!NK}v*PGXbC9}nx5z7i6osTvEl})d9E6daBTiNjucK_U z>0DblDb2**9yt}5xeI1e=P5T}uUx$o4E#bPL-h9cc6RxLrV6pL0uR=Mbym_biGRoU zEVIm`1!vHPitqmVcC+%Mrwk^1SM2@E3c0YSuGEw^mGg`y6zG<|a;$%+LAqKs&?BQ_ zO04dq;9B61_5aSHOYcj#uG=M7x0_c5)<4elJHGbC!MP6k>BTT_1Y3o?K`u=o1A|ZA&o|KL_+!^6IT)z^m&u-&*ZK?EyH82udL&^; zjqYHiT&>S8G^gFh`23>oKkz_}`vb43M5sb=bGRT^*dHGTA3zfwO}O8db0QZHl^-LK$B)H`yy`0B$mNJ=9zwhdeBm-1*M8r^$ z(Prs^8vGxhEms-rsAu_eh6964kgH2mZ~e@A^UP-q?lpnIYO-FjrjZ%sZ1tQ5Yg*aS z4b$40h~%)rWQRcbg+4onSo26}|4#1VXgW~#p^0aqa?(3{h+x#yfZEaBArQZ5*Yu+$ zUt4CD$Kws>bz@@)fvKx!79{X5f~mWIBjD1I8SxQW@ybg+6KCjNZ*sXKNR3Xt?$5hc zW~T$j?IFs_-*C(sf3c9ZDvg3B2d;#k`CMV`a>PzHA4ON@$6Qb)=D{B^W~^&D;JH7guXk z)&aT@3*2Dan1e;zw$g4N*x8k)47d3>wX#(V(VeP|{^-{KYJ>{e^hx5f3CR4D8~K`B8;Av16a^c@;p46|3g$gx3^Sf`1xDq0Z#lyW+1Nyw}V$GU=*$ zv;ZM7%%>B~d6sN!)I3xGRVT->Avvr+@E^CM#iX-nLYV5q_Ja`9S!_4V4NR5K{@NkM&*8e6g4R>Rs zK82|B8u}HykGonzd${n-2)HB!cGd@gRhXIiX>g@vL4{s>vU{hu^B-sOBdNDD@Qfwm z7n$Q8>DE$}G4AI)>+WF#M*=qbg6scez~1I+WzMfIgj}Mt5#53;ERz|69tlJrpJ$!! ziG+*pL{IH9^Ew`)TzEfwkceBDh{cujrA87P#q>>hZaA~uT=)93Gr8H!CS_TTi4F;t z!!W3fhbEUs9DeCE{SHr*bwCr3!=wo(9`N22itX=m{?^%DSa#8QJJh*ZS$)z8O|m*d zE#2(|6#cS(Ip6M5e)8(QY3Kd{)$y@n_1{dY@4M^LaW(F=&yfO=p9Wq-2vJjKiIo|% zUvAfuDEIt^0{+e6Wqcr6Xsmf%%aUps#7fbRe%!GCHpzd$lt zmJzpD4%ghvu?j_=LXe6zGQ;vkP(CoI6n66L_#(l}Rs7fMtvp)* zFqF?;bJti+(0-O{JM{PJ`jlbE8b`n3MqPwaYm%hM3hxeLjX|s3Mk7tV4}PM{t}3V0 z1)9X!IC`Dw-}69|==QpJ>7|#jckf=j^wLW>apD9{pPs|X*^}6R;4Pr%i?q?r(->_u zF*dmlKl+JJ;FV;-E;KdVly;dBKa)K|ah zSmva}rxe87O%l)HUg@m(2?qo;qet)uE&UzYj@C6Xd;-BA%EM?rbt+*!Y>-nPxN}q# zY@_Nn#KA=@1XM{`qCzU#WxA3UNtA&)&^Oki*Y9KI)GU^k7LgYP78VvUcls0>jTDifhd1YaDD? zYyCYfbZTQ(3j-sxZ}^^%(?|>o+3EALBKI^SDWdMmSOXpIm$BzSf?-h>u~Kj$K$zsv zaaapT89@lw5)z=QH@51)GJY4qHAE$tHif5Ci73$aqH!%1`n_I7Ge>)hiqM!CZ`u3N z)I_QZeVIS=_`N90GM_b(vTQp6Ad?M@9p^=i=fb z=H}+m>2`5q_5}9t-;X1Q4rBJzEDpT=4qo544~O46000=-unsMiVt#26#YztV(3%=Y zQ>U1lI|bwp z3|g{Y4+wNcMSxa{idGFo%L?>H$DR}PEHK7+P{!`NX`24uFTecqzqsugcMX7i zr+|K8sggeg*sY@5jMI2eGiQ zfJTyH{q%ZBrO+!feBj2LuxsZ|?73zSF2CXm=(K@Wvjs_=KPFPJ@H|-OT;#i-smNY= z*4PSliWhG60`F`mLc@Nx{(fgWEvXceLnc#V2zXg78)6vv zdbbyidx?`hH#diw*;yPrb_~Z)oWRoZ5{jb0%JMQ+R#vdm@8Rk_dvM9_i?Ds`HcU@V z#R67F0VS{X-5J-Fs2iNBQ7^2EqCl_T_tY4z&}cN!?`J5A9P6egp_tPO*-Z}P^jE#| z#ZaL(FWMA^bk(vkhJl*s>Jti7dWhDiKwJn;x(q5v`JcZX!v+5Rs2FH88gblD1#%4G zlf$zv>Vox3@fnJC>Pi=Rfh0|0!9w7v4lI3fAYe=Zp%k?AnFN07Q_{P9{(-GF4FUzi zXA}gRTQ)r!HU+7JJr%mT{z0uFrHFfls4mu;P+aO#e9!#$si3V}!PY@7*@{weMY%F` z4vaCeYf0-C?@a-KP|gEb_#by3qheMBnrJET_l3B0Ui3Zy@q?MCdZeHS5<&oqQCb5Zz6efIN~yRN%~nWmH`Sn9T5qLLNwQ)4i5&cRz$L$Sq7#)XsjJ+I+5uh^ET9DNGRq>*yRBYbp~ZPbE^3c1AicdQWE^3>=a~`i7HJT>!6eJeo0zN>ukB+O2uYdP$+uM3)vKh zNth4*K`n6PPV^&(Rtk!LD7q&~CSpq$xIS+=ynQ2`wd>Ns2tnA->s`X*zwm|mpUlA0JK{z&w(ur zOb}JZ&W@;k26GjwA2aWQ`jJDRW{7I%e>JP+yeQy;dk)WbAVUD!??oQ62BaA3w_m3j zaq8c&?)uOPS1l`H;NJtEogO&WSSVczo4egE07QN~e~-Fgm)_4+~`~no08V4chN?f{WpQsO41>l ze3DTJL;$W*`YaO4o{3YAQfw(qE99x#|&F?#PK0s_hxXRv(-G8=MxN{g^ z7fy?F%n+sg3{iCbAggoQj&!8+L^p4kVqAeSx6m^qbQ147`NN)$~`C2 zvXZp2$4T2m+7|9yM^sR2{j@KxkM(xNO45|d#z~h7h6Hi%I}78OuEzO&*f07pp%!SQ zP4s&`tgI}f)9b-Hi%pw0!4?+%eh(ufqiDBUFhv0yETgTp=;t|1QJ|GJpk**iaV{pA z)BaJhDd&)7c@*9IXO@gpNK3{q;T#u59vT)cq_I)sv!9BhSX4@V-Z}egS=OIvw@249 zKh^-qcN_@0+FA2C02kAUz(IW5w4(|gtCm+p1+fiCU4Lw!ms&}CS#X=vN}|{A zBhea(PLLOQkS@pwJ|s$z6R%wk#6mNKH+wjUBsvs*f104wU;TbRQj54|fKL>LQE%fU z0wNaId7gW^N}87UvRc(Zy>m}-p*?OPRXLk*0o+QV;EXTA`E!Qr8{)*4QL}gsiqhIs z)dEnX>=v%~s)GwEzKfzjuh&Df*(|%`;aVaJ4%cSUH9&xmPoH>$ML;0Sv$$?rmD+*` zsMx*0((i}onKk8<3lYfsb)-pEkr#2gLK6zQcku5)pGWsDsu$>eI-L&EG)1G)i0h-I zzkUi+MBrv@IW=+CvTPh`2$}Hw@#oWpd#O|*=r~y68?sMF2!ds_@GQ@vm5O11Nt(hr zi`*EP!l0R?fi5V$NM5TLBIxaMMYNr#J*2gc&+)=|uwtwa)ud;8x7&pf5~Cv{P*R}N z>BhQ6DfLLB(fGhxmAeK&zN23z@BgZE?l+`VBarfpOZje{|5dXgSeMC#@h*2wp_|Ov zP?LtaO%0_ht#W{zm%UntnmDm(nljfBwd(ZY-Hv$}wddQJcAPB$Sq&5ofJAG*ZGcG> zBF;Ij3ztdo9J|2O;TFU56IPp6@fQ^y728GXj18l@pyc`vvr;DBxs(#ER%<}}5DRD0 zG(;h4RUo_Ci#W__D0a?7%d1Qh>cEWAu()8qZkkZ(?iYSCRJZ0GmQ-Dt;(+Lrc3`T( zK`3`y8MyGnH5pyFLg7RlYyw90P&5vXSJRtudP~#^pgd2jQ60H9XV}z=cYA%_OHr}o z<GY^6I)AZBPYBz%7HcXD{a})7fF}YV`L}H^jal|4y_|>&+_; z-g$9;CNRw>Fg8xsONTS=02be)uoe4ePJ!Y8l&?=kyBI9U{hxhjTq_-)JzNj8>ODi2 z#WWqiF(v{vKDFk#88m5XwOX-p!L8-#z35)gr+t;{77qP>25bFin!Jc0hpdqEyeRFv z*F~FC19+# zi_VJzjb$7Fy}s@*@9((b3UI)+*dJ0P-FE%Cc;|QsQA) zcd1ZXoK2Ct+D+W865_*8l5oQx*mbetyA|ZC(T8}UKeU5>1{&3{Ki7dn#e*JIF7B`# zka|&6e;A$ZYd{QqmeY`7e`kXrlk8{7%Jhtrt-uY|i5~6%X1xs}7lqb;jvlPsJYb-h zt*t$M=RD7YRj3)7;?4IZU*igOs#fS%iW>SEfePs4v^AbLbGFyXFiq$^9dbw zg8>BBlrosALb-BfXPpXTF6JF(x=^1W4a>Z+#-cC<8jZ$)-Q&9f-eI{q^r}w|hW$Q2 zegYjl$DsK&Ana^%|-)Ki6}JoILqf8l%_KVMX=)-cAdYG=(86rJ?>^!Efnc);Om$5Ow?(tfG$L2GTG(v%G#XUkC1 zXc=oQ@_vR^vlX_Si%wtE(4qVSt$c=rQhxH21$QbD3hfGb6|3(VDmrOWrgbSIr{a^Q zS_JMm2&nHhq5(A;jY`9dCS;X08gkt_?>yl%scJ?QF$geByIi=>(tU$#z>F*Djt+nc zMJCrI(f&@8Gy=5xy@LaUNZn-j&3NsEYlQ68X{tdJg}N4mKeLFj00umjQqlH2PCF>{ zFibE=Ku@|DKK&;k#J^`e?g0L1qC>PSSIf@ffRx`40I!WGwN@fp^0pd{NNW=CYnP-6 z5ZvWdnnDN{jR~yvT93T&nl>dR@~k{pu4ar=-_Nu^>E2D$O9G12fE{)}B|B;o>NlFr zuzsFus&(RB-k8<3v+ie;B)LN>d3vq7{jc{@YfJFgd;Ozd{)bH|)p(LNZUB(HTXD5o z&c@lmKrwj#7$}6^tuQ|u0WMEcl2D?7jDvpF;H5^o1*Uw4gY>zG&^`AOTlm*BykE10ZrbqtasV? zB#KjWxK4yqUAX;Zuh)y3hk-)d4L>)6rKqpgb);h;7nQRCgM5Dt?7bnhL7vpjk3rSK z8hIx&a_2+gRRIUCdQEWfP+xmK4XJ~Y`sWXP4_7{HV_;$Jn&x0(wf(Ug@O8H8C{%wR zP63OJfbgE4OYI$^eD<6PYbAlGJuz@7U9X(8Z0{(qdw>{Nw=;qKx>iUjPqpCpFB5p} zG1L`eRb5kQk2{!M#E)s9VZyExeoPgLPE1f2fsgKj=pRuV;T(eNUJ`}e+@K~d-PM;&_=eulmS9WQ|+4j$`zDq1@m?z7a%=D;u-IfN+$pIi*HJ*H#h*9@OZMH7qA z;ZIW^)-RYUQJEBz5TAZtu|4VU*KChS5y2Rrs&d*I&p@JkSv)6oYG?ot zA~@sGam9TdXxf0QoYQDp0K{oYEb0mPN{N1+d!36=;oO!@2|vNqLyc>w2}FA)(yWLw zspJ5~l8%d>V??#1?;a;?0dx=`?DuJDJ}5=cU4!WibnG6q80`mhj_QV-Zfp#E_Ybi;IOVM zQ6kDZ-&KIYFn;$LdHrHIu=tzggYbvQncinu;Nq~Ul?!S-DP$0Z11t&`)?4UTaw&#V z7+kG@WC3*p6rb>CDirG8>_h{ocgDCwln^3@vk}FEcRHy^iB`YEK%ZD0?D3+AyY%ox z@Or`Lm<2BvE61M|tM6|9jTegb%nI&g7e%~b zFW12sWiv$XdMN6sbICi{R20znsR-nUgo5&aBaeTrHK41VaXuBOuYaXm?Wz22wg$N5DIX!yRG{2;I;`Z7rfjCPc-kfU6SU`yK-dhJ* zbRXowh7^eEtb-G!kiTwq%PAq$ajgSeV+;fXuk}zxa|*ZMi_Ae|hb2Xe|~ z1Ulh56jDGdU77k4po(iQ5bw3L4=CU;3iCq)B@3Dt(mrrX7dgTuI`P0TOdjgfN}>&O z`W4TksXO^aeMC6#iLT}WaSaNeUI$n>UWIl`=R|9bPN!38z;FsJXH8T>I%^C8-b7I0nUsdT=$jhqn1lPQ5aKtS zb8A6^YXIcGxz{T~%3lC50^q|DREX8BXX_3@16|DkNG$Kdyh|oUohCvL(>gV)Zg07o zaQ5A_5yNO9^dKK*-TQ8|Fbdp~LaquBxMk*L6y=fP7C0wfS|5z83>L;r>q;2Z!| zoRwH{s-%i^p;(};a$Ru~0dAj6T|Hj-aMzuo*LvJ{;ZrNXSq8@zWsp!zg)C zn5q=CR-VIv?-)OCc*dr)IMW z5veqTpwm#M30SZwyS6xZ_duqaA+j2`ceP13WxT|DlPMjdXivt@bXXfMhUTEPPFji_gvlLo6aX`D#%8@FeW-Z(sPbq$69-UQ)C4atsy08A0=$ihH=naF+~VYj*-SE z15ksjOe-r*0l3QjhMEBNj4Zw;k)Fjwqv)nZ#wPH9HP8G#Jx$Z-@tJig;gy@1I=f(42^fQqUFJ9b-TVg z-C!7ik+lap@n0$Ppx5igB5r`vF{H!)Zq&5;M5x}4tb>WO6{A;fJZGsJsn}80^aPNA zgp3m)A!H?Uf+z-xx$N))C$4=BG&$C#jZ*_9SKS*nh;q3-Wo17wktzKgJvi$EU=CQx z1Kl}?w2?+|aJK7Kr#%g$9M*GWA_z}qv=HTBsGgn;H#kLEjVf6A{9wgOS{NGX!(agH zaViKeg0jqa9;vHGLRNZcat?OAh~;#Ex_~;SJ%d@Wo*|Efcea6&hU6@ul&{ALL^-$m zH9;MH%4Fedz>9w{ItJ06N{kga?9O`UqX5c?cIDVqqCSnRXb@QEyfk7c??FmxFLt+P z0L*a8&HxxhIgN_I*4YX$;{_t02vz_R6M9#Yx{Zy66}i<}Yq7k%jPddDihCOElSC)c z0yQ=cLa>^GfF=QR2vnsY!w70z71&zXN{5((AX-<_7OZwpq!jdMn=D;9{ei1wxJ2*< z0Sn)WuDg)&J$5GD8G#tOj`?$oXjI(6E526%W#k%0fXO*uu*M03^j&hwOVO#U0#!ye z<&6a@>{AJS#yC`@WP7RJj)liP6!~xSy$Muo^{{rWp06K$+Dc2cA#yoVZd)D3y%I z(`+`;Znb^lcUtbZUatoMfo7}e*DL@5Zr#kULH|rskv2&CiOfj&JwKIcbh#ri90UE5 zw9=HJHKX9axK7-1TS!=0b)amdK7%PUS^Qb8$|*I`6zfeR05M-FTn}F=isCm%Mn;}o zt78BEeaW>Y_?x|i5DvhjLWti}N_|60Y3r^RtAUR*(UOLBzt1)}t={3Oca?@6!!VI3 z6+|2)BrNrpx{16%T&;kv(^u;MHcWuc0ZkNEGCSBhIQyGYws^k6a1pRWVNKwEqe?Lf z9d*YkH?>z5M&7RcQW2;oX&V1r|6f^0SCj0Aokt-B&-$u#&WC9)=mE$J&(gsLRm#qg z^`(wrJ@2Cq9{G7olEjB(DH$g-)cG1VDXg0paIjW4FW}&M*aWKL$|lPoJU1lcARe=I zQP4@YzC?#13WqVKLapLF>^ivS>$S47f_~PoOiDQIVBmVMfG`A){Q5aXX&1|Bge-@m z0^UUVXFh@IbUMLJ3~|aKVt1Sc%ApMM^S?jb#KKyOey@)#%LdE}=rgH^s%y8XGcQ@^ zpIo@8xT0%`yMhTRDjjl~V(>{Ltu+a)Bh9Ne5vc=Q6YN6idWl&%%vzR*Q^x66 zWUhAYS0O=V+bj{Bt)kvWSk8cV{b& z(E5MVKBZ>^Sp+-h&}y|RlWnrrrE@~Y02Bh<>-8#oIf6|8y&Zs8rIeppTwHu^t$O_q zsGrxC;BW6`jJX29{m!|KXCt;k&m3pRv{@t=Pcrh347>7`n22!k94#80O9*F4G zK8(UsFQ7@hx0)coPJ1HGaK*wf@L(H_Mj3oK%w1{~3QHx4SyZViLyJ8U%mLnkg?CHq z)F={jhORJwFgfSY?e_o(Xzjz`;z(FZSU3o~I^C(>AsV*7_>H~Z8Lm&lhIS}5UGD>4tR>9s8AgEw8SuXq&hgMP|HFAM(3$Q^o5GaZ14erws>o^?GgLSZyASyHcSF1PH8N+p_KZrzZM9VpiSqMJ zAjX&?1khRJSssglay@R$4N^OU(I82dS903oaN(kvi%~{?L zMX9QANQD@*dgc^VzDMhHPTu(yLST7$Inqpc@yWpi2ZlUAabT}H3F6g~j2$Ribsrj~R(G#%kq&T$Pwk~bBP9LVtNcbJfwI|~_O;^dnHYJMD? ze#&W;w7+O-6ve}Zflllb8B_Gme3I>tdQby&p~D`161! zIQ^_2X>JtgD463@{k%t^H#?_M2U8S2ccmsqrSDC)xGDq&*4Li&KD-v;$Id5a+?JWL zRq6qy{CLUfjgFf@D%sDIhNjVMKuS3)gt*lh^Us73^GYeVR{9b zresojn*TLltA)6osndI=$qRqTucjB^1`i0UV~RZQEFlWIWNKkZNNX65xU6^ zN=OveLew<_z^dS??&uS{Zj2(r!}BV@HarCD9bN9Q!t*8S0EDj*&zX3)ph50bQU$XV z(zZ(CJu9rJkfEY=0HC2dC8!sW9C%V^GEVd&unu5M44FtW5KYq&3O}o8M3^H_#R$Tc z(}L&_6^f#AdQnaTI6u17fVdF!hJk>zay@DjR&KZJFwj__0*k&&NWb%Ir(~cU4%$@H z_bs{l5_u5{2caw1FxTx+aY5gYLhz_N8#~7EnFH1o5HfJgf)Gk@+XqpdTJdvLZ=4Lf zzUud_VHp&|obhBBH&KxlJK146O!OTpilP-O3(KTpmDYh%cd58-HkxouTSK95yfe;e zzr&7s@LCqI##N|)G-;wPI!z+DP9sv9w62K%c`?Z+gUx0$UI$#f&CNEr>*D3$SIpjrwoM|rvpE9zh+d%R_UKzUx&kt z1Ng*`g!x)4z#2aVbe_cxXyGp4$*DBQStk;?bbjzl&0p%f7gio(y7z)f9s8vDlJBj&FEHZqOYv=<8&|OicFuQ)wAO$HtgzKi4{0_+!PxKjkt$!TDxm@o+Ap3pemX*3 z+xk=`mYmU5#KqKe%L-G@fvr;?>QklqTC4B?B>ptN;nM>suBlL~PRc6j>qDkrwD0Hv zS*P37O%N*duJ~SM*VPv-GHRy=z;-Ywpa&g=Zqc6N-R@XS*)lDi^P2sh7ZB2~0i_Ic zcPcfcaE;Ro8Ky%79#qxoil#ZuW;1pn`L*Dwdm;+7odcZl|7xtIvL|*A>wu4f%J_Nn z-(5sd6 z9GK3nV$B`3I1oNX8xDD|51qCkbg-NA7ffXjP=TVg@@osnG9z3p9*KKizi0938Ua9V z^-1TV-|u_ZxNt5_RzPY6A%oMAwLVc=Noc1lAdw10D$FP}j)3qm*EPDQL}#L!wK4q; zH%g#?>-YN+v~YJke-4Zn=Zi2sfs0naMDyauP2a!KXjIR?Eyd}4Z`9ql=x4|ZmWzbx zzb%486w9umrbzDcShPlJjxE=gKVK?)t7N{#BHn{1=WTtJf-^yI?b*x#03ZNKL_t)? z1ObG+o7@yIril3~M6*njG#)brzEWeS-|r7((eUXPWn0koM>MA_%Sw6_O@Oo-x<5J1 zjDJVRzG$!4^M(hF6h&TC)|#FV+!~$VOR5tfOWPbu`0J63ARvIe?}2x-+492w-1y{j z*7V^`Fuwn|;X>hxNWbL!E7Ihgg)`;vHk(ZkNSTvYd~X3Lsh;e1yQi$RpKCUo4=SY& zuT`gO0OY^>*T~q!0)R()ot59$TAhGYA9T*H6GC|+Pt-NkK?zo+-39Xvi^ zs8e)eRUD4T7a%VB$vS?#zHajmhE=+T!{|?yZcDAGE{pTJ4{`Z@@;pbQBV_gtoTEdyDAaCoC@Hg$RMTnD^GL+VP!mp&Iw4NArn_gt>qnRvLbauXiqW} zE6%N+M-t0Y=LXE&AAhABB5fCV{vP(>~j)6VTtXt`L{QidrAOa9D5WzauK?eW& zLAz`!p6J0HX(kR8ph?Hc?RzU=Jzl#cmhyu%K%gi}k9`DdTdp7B6DlDFeXE_b6~N{k zv!Z1=55o$5>L_!14iz0zg^3RV0^z5C-kB<7GDuNUMZ)#$!HyVM8~Sff=i;5t3VSSq zM}*0Y*H~G~^l*SP1@e9$jb_s?ruXIb*Lm0{)Rk3QRa~itrKxi|N+WH=EaKy;l^nvtT=#F3@LCF~-k7Un^%^Wm3%ZT}U5Q2L;vh zAp>9vK(5H4bfAA(2gfEcbxIXoU-bC|+UlP{dx`|jT(iLcCxwI~D|6>y@&bZ|_f@7w z;HDn7GVvse9~Ibno_ohS?E@ZtA??Hegb*mg=h>q0^w=Z;oS#VgYbXF{t)qzmopS=K z2+vI$^drq?b70cLCrHtHJm`l2AZ?U%Dy0=P1v&;D5O3DdnsY-DAq9%Oh{h89-mH~| z5vI~4q5Tr4KeF05qxI&RMdvI`QT}|9=V%0Bw}YSRQ)ZB3`YPT8BfvoGNS{H?7RqL! zbt5oB6Ix#1D6Qafhr;^v&}cNAbFL?Z_;&#QO`hlTd7c+zV`KSRWx7Ve`XBCfdUmFn zB+0*Rx7$B1q#PGgFaQELAy?LF%Xcg#SkiQ$LB*SPpopB!E6?-DJ?4NW z7I*>J)OoKCfy1s6sF2}FSkj76Oj_qVpBp}7`|#7bCWD9kx8x1{g6;9ozW zFZUdAqUotJ(YQ=!E-6mF;B@Hsy`vMS;#5X8qFkqXLObu~@&`yge6JFa2%onQyZ8<) ze2W9WGR#c~1q?~6rUw`maFGwM5=bQ?m4_#P)1LAjOcAWE9R$h{LFg@%OSacf!nx2(uLV37&}h;Djy zr>=@FEe62(<`GQ>>-O5b^Gvi%@4QsN83P%#G$yzmHCye0i8nouIE9VqN8N50BO@b~ z=FQN_bFDGMDWXN5`>7ENKef)HAZk;cYDwXCg`ZH+^Ml@#=#NAb6T;K}JQc2V2#lb{ z8m39oC~^x(KW((mz*2ypb4Ya(pQV~6&2*kZgNSr@d_v1XHvcyQ1aacVv>I8SBax~? zFC|c*wT^lX`hA+P5CEfQg|!7-1XgZ<+8IF{yKhnxix?8lS@@Ze&+*{*Vbq)pmzI{$ zZjX4%7CYaS`yq3T9 z<;8`6B82#TYs_~$ga2J8dNba|t!Wrnd7zLNXP2>27D50|C3Yc4z%x=@rT&EQw;7^A zRO?fJ0}_#r5xUnks*z+J^;H2$jgiLbUsRA(#qe%mb^_r$PePD2p=12u;p;5k` z8C?)&o4T{gnL9jn8eX7x2^k8NATU*-IUyY2q=12gw7&TFdX8Wn9B3+GX5+mNLgAyx zniFNZGI8eXRG3Qll+zsp|K1pfB-MbmFwVf)0wNVbcYzW{kk~(nq!J~el!lf81T(!1 zQP=o&AM-2fSGKhf*7@uQ>*hT_D14O) zTBxYYqVJJtMi(J{kCFp=E+G`8)-Xi@8@j?&NauNuCWW^#V9vEcCS+Lv7LXc+F-T}~ zShGasAYTaKPdKVc&_v^*j<-tGKm)3xeF0#MgEc^^{K#7=FVrq4IX0Lmkd3s88YP}S z-fT99HgYP{c7ck8uz*0n=d&WDmJqd$xiuD6_-TL(``kGjD2paIxB2^(Y|4{B1{pn;{Jlnaij>wK*cubFQD_f#yWIi( z9W~SPJoouHEF*>h6Fcs{wYCA^TS+7NLu>3`0GJd)tfiT+0g$y9y++3;Rsg(s>g37e zt&#SaPW1Ofh@EhlI@{u9)iA+Tey92~Wz|b#ck0wQDts}hsSs3Th*BCF3uSA3fgri{ zY!L3n`;jeaW);97hJLqtv0JxUtQ-SG;Od6pDH${sa1}}lIpj!L?NIX@Z%pzg@tiB^ zA7uf}!qiACOj?@2`8Mz!Gbk!rMJ(|BnuoHD}WDFbqT(^C`-|j6j0z=A`I$@BugKs?-L4s7q+OhQ5Ih30{TfwK-CJ0QN&Od z;Y>iy!E0@y*!d_+d00PP@Y6|Oa697*7btf9h41WY&{|6EU4G!;EIV{7ARLfL4Jco^ zQz+P(CX`YtB#DD90&OGE!y-k~1z-@1<#2^q2j$WLevB`EoP$&>QQT6^&Fq?4aZ2A$ z2Ej0#j5Us<>oV*kp?uNpj6BPwkP5c&`&#O-8;$jox8Sl=(e>4ySElgh z26gb+Xf}f0%8Q$=@P)Utp{XO3-*aKgV+tEIcL>HEVTvsTjPVxAzED;WaA-7A4+@-x zfkUeOw9FZcL~AcNhlDi_{Vd1mSUaZHb1PdV6@>9qMJEFdk@irye^>`t&kth4tOO11)^&@zzS%ULP{N|pLD-9CO4@5?MSwJU=FJwdrd5YcZP#6f|N6|$N zlV_093lgmma4ra8h40BqSXc{KE1{(ZgzxU6mU=wc`7DKGj!!eF_;Nw3VybkZ$W4ls zkuNS?bWEY&2P=F%j~doNBuu<4ee$yBNVCt8qAdPNlwk!-H%B21bdo?O63`^rbwe(* zGf%A|1xt8=fD=P5SmR(Qhr$>%8cjGQDi*TbjRbL0<$+>lm{e+>Vc}rlyYXcJqhq*n zSPqRli|=O3F@D625|TM!l`^36+}m&*(p#|j^*6>(>;mZYx`gmhbMB1B1W^aWK#4D; zJe5L(wJ6m^7Dk3bOh~_WVU6Q8;k=U&(TghU8URvxA<-mElv_G;Fr6IB%S*^%(QdcV z6b)e6ppXUBXabRF2rVEImhnOd3jyl{kh@T92cdM{hqMwpRl%{%hX%5Xx;%5D_8$&# z1@rTF0o>|7cLi9pK?*QCh`2qS^aqTlVpCP30kku(z6%%Y$50pa=XI+!^iEGtM0 zL|fJYHg{N=naAk#1Vk#~go6#>lpv}xfGZr7ClUpUq{j0661v?En(Y?a<0H_i2BL#+ z*iWHIveHk1MF0Vu=5G&4n;7_5C9Ub!?Zy9PPa8EiX1v^U;vp8q1r{drc|DW8k!H3 zYE3#V6DE~fr$MA0fVBo(Zv45@62=8hK?sDi(Wyzrd&AJc+IVYlVWJ@+gH9uc##v8i zb0SdEjK7|w^4Hwy`8iCCk3lO5h>#5h54ww@fK=KS$>A15<`TkK5;OvZgUO6HTNnuz z$EJXR^Ew{~w3}_%tbo)SPLY6`ns_E$iVni**nDuXC~WSh;7N*Zw;x@lT(I|+&eJc+ zPL^oRWST%U+Pv@*26D}+H}Hh;`@ospJJtn(xHX}v6Df1b1A;s>$fST!{y8~7VRk4d z_hC{ioG53X@iF4UG_43zmqABF%6Lk25~s$|*(}h=_@s{Koup~H*z5KFRlD8(*7EZ5 zNS0+^ZM9m?7?V#B+>;Ic-h4i zv8D}VE$1~6@rG{!sN(X?CezKaaAXDt z4j+M7FgSkrD3Vr!?Yqvw)}1>rzHuFp%ChhjXR|lTNrE*NwlLmZM^pW^8< zD87wUEar+-)|Tub4B+|(-A)%03TfJabkeW6c7Sp~A_90dfD*7S6beJMOCpRC+%TG4 z?dRxGC!CA`IpQOY$ZMlOHaG!f7PiPCnhEl(3*Br0QZkt5%@L$4CmVdtMQHBY%s>e5 z@FR5)G6uk{95ZJS0;cb~&q=By7%XXmQBC3tn+zzj0w<0hgKB7ui81u&7qHY{MmpZc z^p;IXMpEzMM8}l_r(Qjbr=NZb$BrGx$wSA{>36Yy;|6S=+KlP(bvSqHIY`qKM;1>Y z8x`2FV>7mH--^-65yY*XSsZ=i2wr;X1&BmqV%;S2ZiYPXBWWdAw|*VgZ`p|P$qAq4 zo+v*NVc?xemmr+2{IpIufu&PRkWOP{x&@&bn5T1N1eWKQ@bc3y;??I~!APrxmQB#h zaxCaGKwg=M_@)C)ND*4y~d z$3B9I^QIvr{P`7#R2$_-j6WLP+DYfm8xbJfK`)xwDKkZ9umYxR8i0X*wDvT~f*zV#+6Q6=RQjJHT3K z2%uM>FhK9rB1Eb&GCl66p~_d-rk?}7K18CSI|7Fe9E2K8G1?wSyWNCt2#A&rehM;n z`nA#(i}lqk+M_!pQIU+lU+E-?6XL9!qu1#`Wd#g2z?lLrchFLLTx$iyih)cV)OZt4xNv?wl@cN}_x#`;^E0QB zHXCRNg_T|hMmn@cN02sBKXoM+NKsDm054((ygQY!5<;Yr0Bv0W8G=kEJcZcx9kPW! zoC2DoO$aSfI13AZ?-cYXF`+qv0*Ok10TwN#tiAr$fQWbwz;k|#Z>D;^-dB}U@8Q;UXH&4! z7;~sr#09~)0i;vFvPGxY0UDkwy?A;N))^#;#z z_1vvkw{>c;kR=hb1G@9exc`p(uyEuwj=psSrdgB=z>b`s}7$Jds;E z88saxobZk@LVLh??9efE`(13?u^r8kR+-Wc*6kI1L=$=}{{IOG5 z$@8w6@`&jpfkIOtSB%xIRTaWiI-!b zN09Aq_&Z?)kO8wZC*T|~wQ(A%rQ-C%5zs6gdP^OgI&>Uzxr6oR?Le01P>rOL1EB&B zqO-gVtpt*G3)UDwvRsQm-^%AQeC;b=!<%otf$ML$4tqZE9whB1jBv3_YBPs7o_q~Y zKCl;8-LMCjz3(a{;|(|^U`2Si2QX!O2D5LUz*CPuj%S{H8qYuVEH-T4jBQgJpfX^h zHH8m<@Au)n8+IevnD{QTby%KX!dQ2U*W`|6KJ%?00qcpi!QnV9H!6PjLkc? zp;*pv^x4BWjs<-BH~%p{@iRY!b(MXfsI?YV#D@rxaf+DaK&|3qCGtlCX%R-7X_nM2_dnv(7_-5_8;QJ!VJFW`#+8k zeDniQiHwL&SOMENxc7_q;!C&Pjw8<;!1Ae6P>^U$jbdbM4BZ3ESilSbf!2-*jEsz< zH`7P=R3E>6>%YbaKYok9K7tNo@z^{byZ4*;?C1UjqoR#n7hQrY&fn-MCecdVLOKa# z4xO1LJn``3`1AkxXE=5ABo^PC!!mjhP-tyzW80jJ_;r=HRx!*pf#ofEj@_Ag`OrcL2YraLboLu z^g}l2;D{RMB#IR;=uR48uZdD`Mjhsr#TC5%)Ju5s;k}T?;@bDU57*vu6P)%_5?CNr zDUQ7IHtxIkUbq67d+jK?r&cg8ma+Acb8x{$J8}M%7i0Uj9hjV64?U7VxzNFaZ+P|l zeJJVaY;{L20LX0-v@zOGpq$rNzzWPBKZ)0#ejbnBaSvvDiykWM1|X2n9PMHjg$&DbC`MSAijFXJ$Ud7ccDGr!nv34#^&kuSUx?E6US!J z7ZRH`ZNbhh=i-X%uf@4HTnLP+=$N(d`B(Avdmg~i=F*W_%;q6I0)Czux-~4T=>4L&>U+bO?7OZ2`eFuKzDWtFTVT|p1S*C9GW?X z^*c6VYHA9jqoX)~*G^n`$wgSVZ93$VR5Ot%v&VbkI=uDTKIkOH*7LVRHid5}Mng6k zsbzDAX7R$q&*I=ahj9HZ@5S!xE`}IYF!0WK+DQllbWe40^cxSOG1WI=1ytjZb#XK?5fWLE@o>jm z78VwMc4A`UkJl>4H2|{q`dfajtgOrf_<1SibpWncO6_sZ?UYhZ)(cbqs<>oXR}qu@ zPNJt!^p-k!{khk0bl(x=b6p%dauBE8GFq)MG>{-$T!s?{jj1-aZrOrsZ+b7zyK<)= zw~A7KAtBH?zJNPFdmE-F*W;F-_%CS1DbGK@}*1?p4DgEIXBZ@lyd ze&<&|gZcS6wA2`qB!%cXbkTu;fF4V+>B7y}^Zd2=;Kx3MtFE~k6Pwm~3W^Hp9U%we zvAuh7`~Pz*+T)}6e{cIkOm3NkZbSqU`@%v1=sCRo;(pwJ=Y6>EOLt@8)e}f{1Ih^G z2Rb887oB*kQRXBz~oCWt@r;bq5kK z4hNrk6Zd@O9vq!HhEM&(kKwXwF85R&$*683_C+I}JoNS<-1DV-@H_vxBf zp*Y${k@u0Iz`Aob;ruIikyuOWG@=)M)A=f{sctoDI8vJAY)ND zdvFGKeBnzt_{5tyedr{vyzv^eR8?cMa%u(l-FqLt`S0(8{d)z@-?a-9BMm^7Q#j!S z@}(Tl?|lki{mZ|?6L&v^)3dYKdg)G_eD!S{+W#c{Y;g=v?|mLW_P_lUKK!YV0i!^# z)5EiSpTeDYeHrtIjsnmyU2lWxq(BR86t2MO1E;ZgWD(uG2f!g2O)z`>cy!}3a7fxI zW=_svY2OSWBwDfwoD}FTEa61AgM;pMbg+WPj&YoK)kXNwJ0HP^Kl%}D+_BNqx)2P= z!W@L`bx{bMID7)%yysCIed{QiBE_yM?5Zp9 z+7qwfi7)NN_x;olMG7f_x?$n5l|n3JwiHE%Bx%6pIh0D#UF_oNZ#;?HKKqxL+Aswp zGkoGlei*VPN%mH~-x4#_T8K>Gr3atE{ulRQ*JT&svKy~PGMU09E-(oF^J3=M2|V=R z1Gx9MK9425iQWJC_F&8)0OMdSAQvr;ymkOz`Zu2g77A!+7+9oI@4+n}{s{KG_j;_`wBFwcgumBVXHaB0mS&gn zwcGE;fBN;`#bh#yjsa#K*^fR}P{0966Gmzj{Q?5OkN@MJ#!p^*J{lrHVRFDYy!yhc zxcf_YVd?eLIQ7mf&bw$QMkYpMK8PR4C3yA8Z{eOl{UY`~_Zp7BwGTh>8^4Ol^EPAq z*tvl^3!o4vPIvImEBo-^gAd}K&)kYE&ET9smiN&^1_Ocd3#W1Qjo0DBANwe-zv%|w zlyf0X9)JzsQ9~l{<+$tCJ5Y4{_|aedX>2`zn}5zp0V`Mo$^ggTJc0+l{8c>n4AGZLW5C8j*&N&U>X6M{JLWt(7Cr|6)9dzf5IB5ZCC7yZwDct(UpT#%7 zbPvX{1&xUovIT=htRRCy8!0B%kDxf!!*T4#Kl`13GQhTi001BWNklQga&OIM{KK_2lw(_n;T=X0S*8)J1<#_y&z4*dkd>)6N zIDjTnEFN$8RPX?8>?wSM_Viouo}P~nF?!59;Tc#ibJ+LvtN8q%{skVp^-P(Lm?e3UZT&Ga<2b=ro=?@)(}J z>zmki>ACpSFa0FG=ab)$=7ttzA}SIR31De{5s!WCG2DCi-I(bt;O9U6_p#-ob39;@ z{$2glDd;c?htmg6;`Yyd0S|uRE{Ilw@A}AxF}-C2)JRC{g?DvIGT_ChpT#4ezY}eY zVBd#c!zDLfi^+9kWk=8go#Tu6)*TPy4}bl4aQ!E5!UfyT$L{N|0MZ0-zHoCEP)49| zIS#)34tlc&rlpa{q}28VzksaU!y7NX3T+cCyfY8ItbG9yepEVNsJ;ID%lOh4Zo|WW zawj%SZNyLigHL1k?n|Hwhp#_)AD+4AAt*>JynYfdJn<|peeY%1dhvF&nk`&^-F5i& zfBqYI_4$`EvoMF*r3D;*^B|5qxgXg|7rL2Z%bs)5YPR8e7L(i7qQ)S;s}~q3*Yl& z--Fh4D`wgF(EdUV8E+?0@YojA8^&-v0z{y!n0Da?Td$b`o5=JS}CAe~bhvvZBD~t|{!8 z+KQ|9TnVELo_q0my#2y{WD9-V_qn?<-|67ze);Ed*-e*2q-3e(Mc^hk*f_cYOAAX_ zd~E@@H*ZDJa`?bUZh;sVFtCuq0YYJH!z4O3Ljx^zl0KSA%WFPT@2He#8JdkoCBuT8 zek`n~B1(xgNqh$#e&PTNoH{;(=N@?)`D-hfd9#N{Ha&zrd#=IxSL_P9DRzqr`At#^ zIH}O@^zgu4_u=J7p2N;dFG4TRam~%w0+SlLDPgTQofvJl(OF!E1~9EJLej~QXB{LX zBali3X^z6_`2}?wZusa;xbBwsqtR{#t8hQPzxwJu*t%mo-h5*TZ@#<_FFf)Bwrt&k z#$*#hX}~yS3q3sZ_>*}3;pfpgwSp_Jdp|z@gFoQcLb(uLBd~IE5f6Rke%$-HFJk{g zFL|()Ik;3pi4@v-JyZY94xTvp2<8u-Lci0+&EIVHo?ZYjO1igN}+1t0!?f!Px$u@T!K+9}rW+=Q%KU`Nu#^v-FR)M57IESC4r zr27z!URW0% z)}DhpKCuo9Cl}C`Ei~E*#y3x(n`cP6O^j}uM&|mMKX?NDw>GJ$ihI1d}P zZ-|ahq)?qXb{y+4iC*5rMOR&dox3l@mMxp1lLU*43pjT4DBgViO`O_4hyBm*!@YOk zhjVvdfL&MbDm!lg@=k{B8@C}}&S94fW)@~WKRyU&9Ha+Pg}}SJR_<(|>|NQ@gbo~gHRu*vh$N@b6 z!n4@>&2QrE$KOKd*ylYD(!{>racHy7?)~}(r(m_+E7->!5 zao)|l@acc>53&8i?HEg2&^?K3UwQ>!zVl8zbo&EHkl^V%9>R`uwqk12B(`3>or(Gd zRHC8AnlNu!=tSe~y>H?x?K?3!F@@cGcB8qzG+K># zk4V#$M|=YSdMjNta*fX6MZB`_DJ(3_VPaww=U=xQQa9lskXwV1(Q))ot)S6pAe~Hc z=+I$Yqk{xXN#Aryfn#(3AA9c^EXR4Dd;Z?H!}R2EasqIWGYJ9&K`==Sq8KGBaAjLs zd)Moo9R)uZ7s#dww@gR61cGC5AZz?i9t;Y zrf!U5+hNu`w3C1SFaB3bqR!031Y_4mIe+Ll*AINc#L*G{BKIFywQdcocWy>nqRptu zbak@xuAQ7ce7fy%s^jv)Z~q4CHm)Nl%1lg-<6y9H`xb`Rt*3wGiohS${ERT6bEuoA zpLv%5c<3_z%{u$vdyl*B*~-w4RT#p+tNKiwzsZq(hbZLBbZzV9sV_fIW}qvOCu@bF z4E&1E>o32`8$b9Nv&U{Cq>WZSJHGq?hG(*Ncs))e%$4g`ICuIq3zugY{ooAWxN?hd zIK-!)eF71)Kqy?_52CJ(5LYkXWbVu~j+DIl@~g%+U5DNl2CWQ3caTxMeQDtTsj z=Vz}oU6|sx)NiwC?^evH2_gtx5Qb>`rc|>552ATR8@L#WM37Qauh-ks#2_NS3A_zt zi&6-2H&DG(9o}g`?i|15V{&pbCWN?uIbHf6OUhD8Ez?T|g{Enm!Fs#qkxFMMFP0Ee zGJNkk{_ywzDTysz6v~Sz*CiT`kcg!y`V~rxi}ZH(5YHr*Vo6n?*B?E1nF~kG5h8+R z8oc$Ne#ZK{x3ahE5$ucusHQo$r6CUt!FJdo)!R+|MiubbxoZcXeeP-2?%GUA)yd_G zEL@xC&^!A$`oRIDFFAknJXg<+lIhK0n4%r0>or`wYz=2c1d)ULci+pSPd|>UJuG2j z3KPHH;MR=^e)!reoc`z>AHMh=FZ}A4(e>4BtvNo#>?mCW-IOSxuPP!PQG_J{kL7~f z`eKF4M?T@iO9v1&#ipmX@$3sP8=X_(}vvmAQw zATNIXZ&A}V91+3IYNjS;n7Tg2imfYxmy--M%#}u+YQBaWGmuJ9^F1O)I5@G^S3+q_ zEGFir2@^wReGdH9J9PE*@p#mscl}^fVi3IG=BH-}57?9^YAjAHP$(>tR_WUk+6s$B zy5hY|H74+?np`bUkBkLgfR+`&56q&AQ&~a{hgf$fb|Q(C2BEmg+IzS2YJ)GqtTScEV|IsJewgC>qAbq2QP3UC~dnGexpLwG`seOqy#0OB)_e zG)A@I;^Wi3x}VT+lvr@9J1Id}f@nHQG#PH|77L|3 zk%1U$be?*tfry#dj*aOEuqE11%OatdR(!c?j>Vffs&18dBt|y3z-nU+sFuAzP%TzC zw*LsX&fmf`Z3gZe;t&4$AF=I$y9g)3?M>D_8}_p1!OdibyZQNF{RCZCqY%@{!=4I_}g`99UEOUQD_jm&WDgG0<-yhSqR(C~a* zx5m-84|3n`Jq&GFg<+Zjpi(uP1<_2f@&(isnVt^(YK?Fx!ub0a_{-d%@WTJ}n>_H` zqsWwrZ9%rSh*cqwTa^?=>0;R-lx)U#OK6BXxE|)9h2biOHVxCaV`CKAKY#8-9k*sndus+B7c@em3{y-~w5jUd?K?~-1DNHWUZdv@^Y&p*LC|M?Zp z{`3$pWitHQ@B9XX+lI+c=Qz6m7`KjG$3(H?$p_f|+#`X^&j6tf{F=}7#Yx_N82hW_QwlXg&sIo`VFVH#AiS}Hy zaM}Sf0zo(yp)j38Am~`x&Cb2MdFCr$L?#Vt^(wh+ma%gqy!^K>^6>{Bb8-J^e%}8I zT|F6kHV@zm4^JuFhK~~pVcS6nL#XIfjGHAZd+klxZ@_7KtR5WT0wAMtUQ9~*9 zF!1v`72=%+}1#Zb@^y7;ZZe_6yiR(4c5?+7#$85WMGad0k zume+`loB6}CP;J<3JME((lW)28t0xJJGu8$dr7bDg;4XQG_M%!@8{TC2N0HKaV*Eg z>?C_+Yl^Qx2Z6q#^sc8xE*u{OH^3Hv}oZ( zCU1^&>cj~&K9-P(MuV^uqI|x@2XDQ{`nxyMzorj`ce{5)YcK_#<)X8}HiEK`H4tX) zBb)J*i}GA_!>5`rv;CeO^bHU3qwl`NL;!KuT?`QD#@6L&I8%1jaxHZ`HSeB=_-Gt;cRYh_!? zhk#18OgI{+R%oD5Bx7;B#S(PKmO1kUGHfw9F^L_uv6R4-@Z*2?eY~>I7k>Avq*kOM z6r>aR)do(FLujhZtJ74r;@#Rg`?qDr0l$vhRK9)v0^gGq**5Zwd4gzXrM zexBK>d9Hmj%JA;BgrW|ibd-A^+1t`=1wX%aYRhPyxY& z7$92A1HYYz7dugwN>oMxa~qpJA9%P+F|!7ao?iKPhLR%V6gwRL0zKln(ahA>K z)YdYondEbOG(}cb#1dY0jv8cFmLtlqSaa6Hbblc%|O_#87Mx9A(} z$A}sz)x74*Js}bt!^V|9iU1%PvTWwhO!DrJe@_4KFvGjoA{@{am)?N^>Ry9NsX}6~ z3)xJBYbAOg(m-~0_rN3{G>zA(a(X0DtP-gS4yV>*b zUXsI|C>i*WOeM%oWjXN9en$6S#xM<>{s@o$%2Ry)t6#?G2?vRT5^y2DBF*4Et5~yf z6@T)-{cCbpb1aT8@Wb!E$RGakKO>xq;8+36MK^q^GX;|Uak7(Tv{sZy=lRLM{RWk( zEMNGoUuVs>4Vcj&y{lF&k{-%%@#aL6?`f%%0T2`|nJpsPN1Uj(Da zgb;*9m~d)w;oI-c)ghwL_t zv-PV5NBz6d&6ZzM2!ZRm1BPKdbf*FN|JNb!T!Q~^9pP}eD;|%3SxVW}j-36)GOhMY zxs{p~MBoKPQ=x+C76RrLX0b8x@KII8d@UOoilqkaf%fpFhmXR=Lz~O)3@xCc$=zDu zOTl3Ls zJX#>8RuN-1sqIp>{4^eCKR(TwQ>PIath#G8dtdl8M#dx-jd1dZA2IUD1-xpbspl8V zeEiLrWeq#--+`=qRObsM(rKh^VP?YEy>XnuH0kyItlPVtho5?kwHr4;Rde~&Mb4i- z*LJ;1r4dRHO{Q4PTZn-sD_kk@8fYid&`DXdsX%Pw~oM z{fKY<-``;B(gb({<@=;kX=+|0;6}y`!Xnfzidy$;+>S#y_yJ{I!3l?#cqx9fU^k$z zub0pM`ty`2bLg#o{Q3X!A9(FQ|A2!pz009D_jCQibtCTGu`Z6_Qp;X0OAN+6>K^TjLS0T}n zK;ToOO#e`Sdj#dVUXVZ}O;9j(V~VpsK831yXgs1FVWQC}LMhH4`-FF1dpj^(x9w6I zB{i4^n8AI&a^)%r3vA25>57m^W&#g)Gxn1fMOrAd_R*e)mO&$u8pvQF$=;YIok=fM z+0ApA9GhU_+AJE)(C`pjw{OF=4O9yg-pXMXAWXsVy48H*h^vj$TJ7 zg;XMd*?B&W<)Ca8uqK4YY-Sx_J9&k#|LK3^)SHL#7F`l%6w49V805z1NhXqk_s(;J z@3Is~LpA|}w}SweWR=YwvoJ^|lQcY+`5RL#UYsR0(8=Cs9^+U4=(i9F6Mv$?i{Jkq z3%91*joR|=BQS6a>~M&MLKZ1almypRglvcF$1d>74_;#O^i_1xC#D_jm_rqhG7Z93 zghsjAWIzSKEZfG2SQO?KsLhn=?CHhujo<^2y1jjj#3Q`$Yrjq+ok8a{H?H4=*+u|h zRQ{6Ow0Xa1bQAs>oI}0Q!1p|C%f>QHsJmpM38Kja8qL(mbzIa^q`3hiYJy|pL?cuv z5-~#*O2sA6ZJ_<6S}D_+$>29Ue6PXURU2_C7Nr{tTsuF?{$N!d(-#t#F&;SLoP?$(}1P6=B z%QtY9T8cn#Zedl~qkBa!9v+KhS)!eh!22c@N_Z&C$Lx%-^O5^`>@!cI5oE8=bMERW z^s@*1PK9`919Ip1 zB_3|WU2hmhkCaj@CxibZevzeaQru3V^_!&E{Ny}|?i35NSr%s(@D0B`IYyhH{lKiI z1*T}C%mOSnASo>@a^dJHZk)Kt+Ldc~=!=h$>gi;x^ylP87TEXEezrWcn{_)jAS^rB zg6IJ7)hZf>`Uh#4E|#$Q@WT&L(x5ligI`iK>J4($JeMagqcQNb%hm@rFxWj9`~}Ow zMj^p`HH#uhy{Z=KWan~p#k<`YikCAnC+eoLC?^ zoC)q%72G0`WQ0qb*TCD_4Q#_11W~zf^8Z>JgaVk9W@$U80;Owj@yK>i4P^oSm|IPDVsbZ zLohozMTig$Tq4mZT|?dNl&9re2HgxFutPRmc5Y+s=Cxd!zR1F97rU2Eb=*$;S_WE z95zv8G(vGJ*5c5b}CEh|+m_@0nUusR()KTRW$1GsO`y{s5s(Pr8#2mDD(fT4+ZCRnp+1HJv-j9;E+ z?(#G@$8OTMWeDYa2r2Phmt;7KU#uh1q}OyYymFWmZy!LRxpw3te{Fw@r|0JQ)Z?Eb zoykzI)QA`n7E489PAp(TiNHlF6(qwcjF^o^(7+`XkE4Ar*j^&Q2;_2!t|VI?*v7zy zmE1T#LT)O{)U65DbgW0B@lkl5izI+)nljh~y6zH?vmHC%8n!k;jKWy*P}S>oqS0u> zb={$Ixok(H(Z-!h@lFGB=lCTY^+tV{Wm&SNh5WlFm$49 z80cZ|)pPvt!i!vb=Nv~LJjmdhA;O&zmU6I~gE}d2ZJ4?-0a8=U=K1KS?_hc+P0SaT zX;bvdc)};%8|R@1AEvvvyFK00O^J#|Gcz+oB9Wn7sF1rjN8yt!opd0&C4?n-(3q() zaci0iWjq=T4fQX9`kESi+cxRy>7;tAgomahn!&7E;50cL22^V`Dup8X*##s5t2;t8 z5(Sngel05sTmMZq;apA6z%MeKw~khvvakL2?+XlhqHRtUfU5h9T^-}pPmiH|#rka< z+3?U#;&-j&k;k9p^2e76XH9O5j&S45D5=gp$k6S;IW&Y~4*BdnF*AyY8cg4~i7))X zcY#I;Pyt++R3b&JGl`9bi(-0olI(Pr{`f$9T9_~8NOYtaApJmWlJk zb5AinKa1&k7=)r7~Hd&?s^C#4+~2{~phO@rykF!WZb?)V&0#T(*YRT7xf0 z8d1`nNkCJjMk=1buQo869s=5DPMhXkDin~O#;QZ8VKDOcS^li@@9AB$lCJJ<%qlF5 zO>_Li{S;>kM2Ay6`NA{wuNVXwOdL(iLJI{xRA)+DIDHO*38tWTaDctfd>Y4gxKX^t z^nV%Sz}p}4z^5Lfv#$#~5o*_3N&|*O3Mem@=#BL-&kSKK7V>lSiNU3bxM+K)1Hh%x z{N9BH*@<~VGDN03O@3;Qes2&%OAvvSCQg{>_Bigj91GP2BC%MoO}H(zLEV-D*V&O_ z_rrVmbEL@CTv7ly>&g?GdmcY8E5j?O^&~G07Fak zGfy*fb_{%vj?N6#ToI)M5MQI-AmoGsy?ZlC)K@;XWjEt_TSaGZr<-Q*%)WBz5|>V& zM`AL(YM8zaLu7`#+5W%|KK|Z8YS{|MKRCeMdv~&O%Nh`ZhTFiexr8hmiKJXCVHyU- z#S-^Cd>@bO{S-%sk8%0cL&%EDd*A;l9hnSwKl>=9Tmd`gU_>?ZGeNT{mNjmE-KRX6 zBifh1T~N%AjZ^WeM9kRj)O7`&;S{UZ4x{jxJb4ox4G8PL=PymSn<;bc&2RIMTej7l zf@@SWuDsN!Q@*i)B`r#Gd4BZLi$pUi77B|@=jX8_4zri8vslW~vvG)3D_0UuMBCR? z5J(r(=?sJn48Sl1V<#_gb>B%A$s(}vgw#R**9bd!PDYFn>7F!hsYaDDJGboN`4_&- zBVYSGKm1SM9q*pD^ zM+Gx4=EEV)P&LfV7&F<&YmF z?E+TSJ)(A)@?r^z!Pv#?eB{4*n2v?MrKv0wvHS?-av3*QMR!Dlhpr5K1a7&3tb^HQ;goG| zoxj1mKYEK57e^V~vVxvfJ%lPYNA@4)qxbd^Cyp!Y=t_ga%p9?v4AN{CuThJ-c1e+9 zjyYsqP{`*=rIW!HM;LgOMnDXosiJ{oJb_X0AQ5V=%|)O_xE&+WehmgwQ2h&=kJe}B z`#z>=Vp-O0Bv{!beXFJ%Yhg0W@dL*x`g5d7@zDKyS-)i+b|%cKdp5EAzWX@#`T??s z#`*ZO=rI6SVJA zY+AFK{u77D&Mn~Gst`&>F%1jtw@ODrsa&K!Q$iRL)sS=!_hMlLeiq+vx{?K%&Q9uH z1KSAUb{J%OGVFR}5A~SOPi9^wH!;tP|I61YUYln=yMRRq1uV`MiFC!A?|;$0Uu)GW zT?1WAo}9!hH>kL!HpN_OfkFmS7z3o;!X^9x_#n`3`T<3^1WFCSp^y|buF0|SL9y3Cj6Sz~U*lGfi3l2Fa~ZOcUYEy;? zTB}egmPn`4IN>PT_lb8UsZt@?-NB{@cCg~!4SeJ3btaCEBE~fb4}L@>mSFvgwH&J) zLY4$hB0?w}n3a_gbdr9}C1u4>p&-R-x z%EdB5nk)yv3qxXtL*(Zch!_#1R#d7b6m=3f^sFC1rya`mm8@C4ifAg{77lCCls#%t z%0(I)X$qumvU1aEo_hXS{`U0OF_nqudu-WzH+%Lzh!HkXM(_+-V!%pd++yPTEqv7= zCTte63z+Spf^5qMTMe`nK~tLP>_LZZBo;h8nuhG)7LOWuVTku9DNGg^zdAvD zpoI&g(+A6?+vw`SS0aWk?WoMT7@|Bkfm3BG#G_gGOEDNN=8$Rsip zvjt>}Hr#B`qzn`Q>8=dg^GMh!a3twO8trO|^)jYq5DP~F{E1R%KS*wCG#VJD(Pm*S zS9-K0S*B^?x-LouzR4ypWHg`8`**6uI}OO4CA8K^0Tw3VM1<08fruGL7kzHLJ%Wpi4qPD!Q!;Vp2E(gYv*W&d z15K*X=$cP;uEeQB$0*O{>DLm+_f#hK7gvjoeY?>D=7K;)Nm$(+kv9W2u|akVvG7_H|%lD(m_A397IJ#?BW z2GiXX7xI)V9<`}bu-tbgF)PmLHx6<6u?N_+XWNpzxA|Gr7Rz);(oEJB_M#w~j-cvQ zlrIpi-~$E1gL<)s;c5gvmNIbC5#mmwh1f-Viq2#jZGs(2Fna4cPptSn+xP5b_w$c% z>eY`p|Mo#HaTqN%A#5K0(vz&aXA99pnt?5=IQrTLG;kS6_d-LX%>b}x3PHVCA(c)y z6P_edJ4|jqOQJu8DFfm& zII#$(Vc{yDdaOY-8VeX9P0k$x;!d27{!UCGspm^XS{{&=pGO80eASd?N!ws}>snT> zU(GX5KF7?NYaD%lKd=4xHD>1)m^^!f)2B~z|Bd_U-#pm%uC#mCW)5v#xa4w+sDvaG ziZC}fhY>N`glE~-6)jc!sdyYKY+w)+yCQ6%;{s7rINHKCBwnt{Q(t?Ud%ySyJv&wq z?TO-xI)-6_2!bAj3KHMOu3ly8%mjuE5jMgMrB|TF8XUd)G3Sq*2@13ZoIHMtn^$hK zwQt*P>0nv~e)gfRL9C>OL^7Y7Cz^}}8xAEAQnm@~QVL{?ed7uiXL5w1HZCr?8(AV9 zu_cVA0O3fCPKW+XFBe!KJ2K0mj}GvKjW1xvt#(RS)6mo2A=XP3X0K1+Nte#y?tq^W zvfB7X-&0g(N?7Yda9QIfJv#b3aeAXX^6cYe^Lf7iufB;-gG#o56ao{;{IPLFEO_R6 zp4XoC`@Y|PcDIeUx+%$QLQ$2{Xjd_RYmzG`FQ7d|EEU7eRvCTgG-bO%)QIEP15cNm zt8wt>?{oJ(JL!!N6OSbbI}wZ~K+wjfu3bz*OiWKvi+c>OUdK~^_*MR9{!b}g$#M4G zkEvD_rfH*;i^6B1Zvd$RX^p}QwvXDNZ(xwZ%{)SDLY+}8!(8g`n$J+71XZ`rjjPu& zEStpk9t_I}AeolcPC9Bm*IL7u7W;u^+EI-}Gtf|8O%M=#y0`sm&?M8j*) zVMgc*C%86pnTz{R@ybtrOk`ybTOZuj3|0w%k%fGL?3M9=lY(aL&Q09&z#jTm58#*v ztA~b=EBmn$Ht|dnC0fIPmW^FvSwZvPa6L>7$=);%eC8ppT)D)7@4Ssd^Uza|vGJZQ zXuFC0Xvs7YNDCIdBC%wg>{x-tn+y1!hp6}vHe0<%a1C1T5gkO0m+Ky*m#-s*Npd(H z$QPS~5bXxvAjfft4W$T8&LJ&}WJj8KcZ$cKdzyG8!guT6=Hk0&nYuASVJt_;w6HKJ zPGs>c)$D&nyB8`hE|Q4Hs8XeoZ{W!w-k)~xL2%znpj|ZLKSpHz_UXPb(!yA`rGs)-9q@DCHxRPoyVI zeMS-OPGI&&DKsiHMsjp^cQ+?5!2?Ovd?rS2aQ)y#RK-Wv6(ggUsqV8e8XmR961D4j zXlSge!5go<&dvuP#9kiFqJo!8M@NQeR|1O=11nds{lQ)IuO0*^c!~MGhg+zlQYM#g zUFGue%e?->SGjxF-K2XnK@kQxkub5IB(?b>5#l6WDL4WnEJ<$6 zkiT5u*oP2wE``9&-b3T^?%4zqE`U3}>)U!l0L$jIv_gLE6ypl{t;zVbXG} z${udLhKvNsL|SNKMub#fCsk@#gegsADc8#_-BMdBxR}e~D2tL?#+nWh?n+^XZOoOu zyzo!{2g30rKlnG_#V|~~s@pW$D#G1ya?=I+*ZRRrw)xS-<8kWsI^j+S*dJ$qC+h zWMT>8=_HtnhJt({kK;s%gdEUmyBUexjpHRp|`UYoDe?qxZU4B>w52{m1dNh_&OV;}(lea>=wqtWi=5qeoV#}2#fAsAF1tQK#Ur$! znyWB!;w&G${w}roDx3G*$MzjN2{{hBxo^|1qPM#b*K$!(k=UFe7LEndk*)}z`L*ZB z&n@uIU%o=4R&D!us!p9mJc&>O%Lrn6>y3bG&`QB-ZG74h&P|VjX;@TpB`%)3$keeL zgfOYimU#QkH#s(P9IxQvS9}WjJT_tcy5#El%bYoMjO1V!5i5o%jJAI)5{*z^ED{Sv znOmGA7KuOzc0Rh7x$y~p@|WMGe51(dsY^&SHi8<8sBhp_Dwxqo+ovJJl9J;gwZ_FI zY&#V4dD5}2Hp@dPjp4$=#4I;HzCz8dv31=lq-7vYiKVUEHuS9tw^-(r31pXnH^^5( z*hEjFlkh-_3U!1Q{K3Ea7j&%}q<`fAiQW!e<>KZ`9D4O_#xLFA;?E8<_uMo1cWuQ; zI&B%OU#YTkV2JDPCnQL6|MO4sB1XnIzX6pI`gNfDc z$mrWARRUG>n7BU1t%Eo5wPMrOP4o=(HaBUShUZ~hlD<`a)Z98!8ic!|M70$>8)Jf< z&pb@Bzk_dQzQOUA4g`KrZD9*ZZef;qI?;|smMv47uSpN2(J1CF&lAsNZUaBH-e9Wg zF@E_5Hx6H==9gK&c_X2ig%{Nb?O~v)luP)&CLWKsn~j#_%JV!-!@LdD*-FJS41-uK zhU>aC+y;hWXwx)Dl~R*;D#beu$erVtb!awetrNl!ZPROe_$LIWWwyV>tp|DAH=sRC zBN(fiLV_<*C}w76h=vpR)nFa|FaOQ|PIyIvxxy^fd==$t(p^bbZ(koIIW>>`T$a-( zPf(bjBQ8@^r>eaA=RYFre;q3pCDoh4Xb8-r0A(_M<~kp}_W@7;#^(ddFn|%5Qlp7D z`Fw$p6Gj_~unaX5KZ4@Tv@J|5)~s1W_h1iKkB_i$X^JanF0tj_ZNwvqpqMZOg~B2U z;^Zl_anDws`|9)b^mXHSAx6(#;d_O@;o8BgbPaa#%=6EaT-SjwT+EP(w1aEnqcEgN zDw8HVyMXI8uo5P^>NU;xIwKKV=2Rk0v%fO~}SkVymdYw?iBG%JE&Gwm|o53?QR#QhIfM_yKMb)tgV@i|7 zsd-|NSUcBViJ;*1%XJccDLi~?^&(w;y}@5#Nje9*dFEHYfN6*M>DPZqW2PQVlW7o- z#%MHZO?ilFE_Nhd$fsPWA}vWQ9mTIy(8h9#vql;cuj+Dq-!ay#Sxe_oCprYa^f6L4 zy(@-D^md>Tm_m>pnI#r)ZjV~qv1XK4OFD@bq|mNy zRFV2Z70=Sh7!2+n;^E)@ELwSNo|xh4Czr^d7~|xLQ+&AZ13tUzi#YvJs`Un;kb`5| z!QbTy=EkPDdE+J;sCjj&jVi^XIgTFr2!)4D2rC@Iuh-D3!NntI898;4dp>n^uFVyS9Fq0C+Xcd1g6=J7tfB(apJ&HUis!rlqU;#_-y~w zE{3+PMw+eWUm>JP@9F{EQjNN*k?KiPDVIo_L6}3TJH-=Ud6ryufx|ED!>u=vQsAsi zQn;DJ+u~w6Rx>xlXr|XKD`KTU)qRj)n8E#q5BbRjM$TWLK^05J0yBFJ@|Wf)R?4{e zM2rMx(nObi3Zo0W@#Y(>*tCw#_wPX1vMKoyGz^chYhxIacq#^E4=ps2ju?BNdz^f} zz+2z>8U9Qip}`g=A{}C3Hc!Lwu_PF+QMH2X^a6>l4(8@&SeTrnQmc|_o{~@k!-MH- z6CC_tAGw)%z~i1>JL&H03F5JX2-UU}t5&PwIL=Z7(j2ULDrihC!$34Nv91&g1I@q$5O&bSi?~fASEHiJbfhv=izVVl223B*maM*K6aVc$`+d?K zX%2kn=eTu+kP>qsilxn_=RzTjVEC9yCh<%!Fr{Z4x+2{H8p~{bw*$b`rBfGq{l~Ae zSe~ad-O2i`8z3anegi2r%JVS|6Wd;@Sy~|s(P*^2_3)M7PP=M1A)XKBl0UM!UHr9h zIQ$Kuc&AXj(}3JLehG(^a<|edc#sL&F|Ny3*MU68U;07|iPApG_uCZL*2JP-uj7~M z$h3iS6|s#O(&;3tcW(hlqJ529cR^?@#|pIR%|Cnj;w6qBIMUY3dkr5?yM#m-vjUB= zVql0<3h>EZo#o8QGi=|plc9|(ntA*nLz24Dpf*!S*A&rI6u;rNAEd1pfbV*^wK_91 zQ%C{f?kL6FB8F|Cw1*IZxv!Ql5nG!if3?C)ewN-f1N5xy0Ux$!VvJn5#HE85IeFp~ z_kHd`c5LdzcATbXBk)rg&19;ws0qqRmihTlf5zC{Ek5(~lWe?iE7Fo}^QmaYW&J~j z^wxwBXzRr*#S&-Fe8R{l=UFMcka&>7k?9E|!;+pAU3~2iew#PH z|3jo>asQ_t#Aa{xKrCEBd4a=z0nnum#`k4{F|5ty*-aQZT(9@65wW1r-Fi@V) z$fZ%vUcP{dg+`Ow)PO3=(>-H8Nc9R5lq#q zg(~q#nz~y@=BkAIBg8vm5SJuYWmvgs4Oco&^UlGKc=KoPv0}q|Hhg+3$#g6j6i7kC z^P%pt|Ii^WUc5>j4>M}fx3v$WuIY@dr)#JS=~(0!7P)-%JjK~OXFfc}=w~mne#-`8 z{fXc_BoGp4!>4EM07u^cn8lezs>(yOq)07UpwNMFU1&nKjZl(W$fLYaKp8$E$E1)i z1X8hPRI)GtU$AA*-E7#kg~OL;aBCii-r0|njBwAV?_+S)|HIy!2U~XB_kEvU&N=tq zx6Yd#vtcmU7h)$U5F|koq{I~zsU?bRIV>mM5~pIj;y+Th6{nQNiQ}?kn|7j#R1&3< zEm5>AQ-mmx6d@Ag0sIVYkJs7p$$G6WXJbwn5vMo z&qj7;uIT`F>{McoIiUWJt$|@dI!~Tk+ph?VSq16=8T1{n$5K--oS7)!ZxtA zxj|P1=65Y&2WEy^2hfpO;JqVwkI@C=i<>NzbBxEE?BBZ=+XRA*+C8JBxu-2=D2fW) z^R|2V(rq{J^%wq#5td*4U;a6vb3E|j2Uy&@6CYZ}lQF6_JolZa_`@%MiLd_tBNz&< zy6IYOyz>?YDQBo*3^@l1BVWA>Xp}Un^V@B4R%LKl>y2@$+b6M&xe2F<7{ni z@%ZmNir?rsw6e^Fi|5(JzW6SB(1F3?kaMeN80ryA`&ZaFvB~C@n-ue634i{?X)c~! z;|rhtJYW6YFQJv<#&_S$p=%CYMbdXj(5kk%T;--cN-FtMO_U@>`dl!x z)U?pF64Xu3Pbs}+VQzsloisf0&Bu84>6fs!!tSfY8~z;3 z9oxld&jOQ;EjT&lz`+BYJbr@JQ|Ed7i{IeA?|na495{ekoGEQ5>r?jb+rx?HUgG>4 zr}@?&{V_*3uVsGU5{t{rgxc}acfZfqANd+Df9rV&uRE^PE4lhig*i~`M4R7JtTd(7pZ+(xi|Nf&Kz5Xgz4(^P-8$yitLb1R{0DHtu!@gw)YgRedCb)Nf!=Qwx#EWiCLzsY!QlY8FvHufIa zjSGRZ=g)HX`8Rp!)1Tw`#pBe?6uUIw>g%t@c|l@Sc;vSp=7n!POQ8(Io%7uCw%b|Uy#!WzfoR{ODr$)sY-u>ZdX@ziD0h^Y&d`RI zl1mbhp5VxBb=bL*mI;BHrNw#bZbD_}2+m{X3+&-#R54)fsZD<4AOAWJx{q`3$KQqa zilCt|h6`KkJoDY>IQQlnS{2;%&YStGfBmnq=hz;`(@pFYb}#IrG4TKXpZ}7FfBkbT z*7JPr;XmZPKlx+K_o~OrD^`|Q80?&5oefUEyvp+O4$12^D5EgSqC&JJ_AUpGTU(o~ zEbqWO$N3+u(%3FH@JzjhRH0bDVucU-);Nx zOM&TR%IY&G2wR@PpyKdbui|h14}XU%uemC7-@9PM7*9bx*%3!S*G@L3o2V|Z`oeL(_N$-A?Jii}y~2gpPx08-zQOCyz0COKEk=76_`uJ7 zjDuGmlELiAL1!wKrDlvF3cQ1NC?y59E`F~uhBK#E>DC6lY*p5oQlU*|V}`PaGdgE!fC{wo$M=`}r8ZK;| zWw$*ddcDZJwaQ9!Q7y%S!OlfK@rj@0xvxLN#w+J3&MAKNU;Znec=B7^aO+#xy>o@N z)8}~l$!9q6#v8ox^lMDk8+Kp6pO62QpXA_?Lj)D*VxJkj7)LCbIhGHt&@o}Mu}-lx zkminJK?x}AT**!M+|Exr&#Bi}dG?Vfu*OomM%)_l_|T!1Vq?6H8H;mOv%bZb9{vJX zy>XP2=TBf3G|ibU9{b`qSv`H4NxjbDw;$udpZf`R9oPj0Xl+Cr6pIbhdYaL|6wf&4 zvKv-elvz8~wr!72PF^b^1ct-mca&1?VLk5UCvLnXhFeYL=#8Y1b0JdeDGtu?Z@83#qJ_&=Pxj@70Zh|DCP##Tbn%o@Yne4Z+wQ$<7+(nm9KK$9oMnE zv@=7>w)QOVnP-6J8V(}3cTa}@8*uT-7WW; z+7xt(x#fAzzO>3_vq?E$aPi^=b{*UUD0Hbfde7C|{my%M_1TyCqes8Oz3+Y-OM90j zfzx2LRGz)}G)>3Cl{;8Fzs}hU7nm>SM88^!N>~L&FmUkbVLtKGpWsuke~Pouta9?y zaen<@eVQ*l@-T<4In46>5+|QO&MS|-h|?`oKjo{R`zklwd<)lHeGRHGc%W%TcYWl3-ui+2819IA5`qYiL*U?%BaD_8*|}>cRcRS5&U1S8G&`1- z87_=iKDe8Ee)L^vG|T(=9z@4dnm z`}eW1ve-)srMvj@G5_5#21PyTNn z{?bFNtgLYQvDauf$CQ+4rMT|Co4Nl--%Yur6zU-wuT=EEnRWTDUCnrPgJQ{I=M5)M zpUk?GKe=K7WhmL|#5&ecG2Iw5ZE8w4ClrmL=HNAlc;GL6kbn30ev4C2p5T$s{sC89 zdj-etx>oFmmEr8_D&rT|2(9qj?!NDCZhq^{=p`*Dg$J6zU>f+)gCFDTUw)Lem(DVw z;q)7)SUIvHg%Q_cREe1{*=i>2SlP+myRKmO(jKXvTOAo>5yeevd@nWIswmlWbRQQv zL!jY`mEF)fs8j@?#AaMqh8yp?g%AGZgZysyTb%y(8&tJn8a6ooGo_?AiFJgA#<$bhEBgVF)WS&*d z;5$cG*A#OlK1ecp<2&!)#Ll5x5pZO?@ON+E!n}bIJKhzCgYn@cr zSu0a-ZJ~g=nGo8V12-LD{K_gLJ>;98dYDH)^?B?q^Xxvf7dLKr=_}tsQ!>A@gL^;n z0QbJ%HJtXzA5 z_Z)g3g)Xwegi<}`L2zR4J6w>avvfT7?eDSartFR)?cgP;HY#=*bpqWLqk^07yPf~$ z7yb^v{Ez=Nr@r$7`gM5X*S^L#e(e#_xfLb0)c7rj#&G1Wqx|SkeVF(D?E9!j6>Zm{ z47jeNh~_7Ydv?&3o@zA5*fcDzEJbuwJ~t>u+d0fobJKly^Rs{BFY_-iuCsIBE|yk= zI;J!PFKDRtaLrq;~QbX6yNpFydR_Z9Lwa((PSwE^6$S5UEyPRh*8e)c)(P+qYJmvh_1x9<8va3#( zM8^)b!3T6_IDLARS08zSF`FE|^*U~U&pjNz?I?$uKn&63z|v)bA8Pj8c!0CteVPjwE^zk38Lm;+Wp4ib?gj4s=-YYYl~;J=x4+0E zU;0C?zTp}cj;+L^{p?UVbmbAs#Svl2u=Ch%2E`Dsd`uB$f#G0j#QT2i{cK!V=a>K4 zFLCCT(+u^1H=jSr@n>H{;o?uo-0ax5i=Y16e~CNZez&;xSWVs3I3K90Qj(y^F-{cZ z)XXjO#uc(o^{q^G%#&Gikw{!5`W0WgH$sL_X>jI@EXb(-`z`+9?`H^c_ef1RI z`NG%f{?=cCI=<}z7IrML@7hDm?b$`MHpVMSURs34OgYwb?&0|2fSDI>R#Gz}C^41@J2QNJSeeQVoz3e!+M<|Msf)qMW zRTRAY1MlI-^~X4R<1q&1AXes+ zZVA*-bN#J1^2X~YIezjuqxm_Z*>!0g6+3tD=BNIPpXKP4SM&Mb`dyxT?0amSIM3=+ zucJf90K<-(_HoV4H}H|4`zdaI>uvZV;B(`#NcRq2)=lKhUw_LD{P9DN0-g)SI<_jZ z-X67G3lc)WmX?#JPtq`D<={RpY^-BO1!!r5X_cnjIpCTbuI0#mM>zA$n{1A!xULfe z2n=rOIr+kI>hUIX47ur}cW}!cH>2kjK|8ryC8(hqXs)>a03ZIDALrrUeVD^nUxhaT zr8V9Ku|rkj4EE8V{upn0>ut{Ljv@;1QZ6m&@Hr5*rFt~o|P+haqRZ%_{4wx zNhXsm4jjB<)_EHVs9bF4IJY#<10Q}ri`9_Nnt#jlk35B=Wf*oaI5MOPbNtBt@8OQO z-NRdMyN!KUA3%>x-{?-}K7jUth21-_^Ft24KVWF*x#qU(F>?lw z$7)IDl(BdtUD>a?{V1RKi$Be$H!iTL8dgud$>N@+XlJZpsQAExAHq!J5Ge^O#58TIf_uS4q9(+FsuRRRf2~}5| zmnn*Z>u$e=Z~gJ3C_HPgoB#|54_`&Odx_1pG490y`;P48iW`oKjo!s$>`*{2P@8qs~?_p{0GNDpTn<;~;L^(-}x~7ryd^F0) zM^O|x8E?Bbe=Y50Wm)3AXFMJ=nM`P!hN`MiN;OT>eCKj__~)$Pet8J~Y+d8=_~X{v z-%c-T^1)7i@`0H!T`6hwq_m0+2t#z0N%n{y?KQ7F^&Fr6Ma@xsy>VuZ z&;8nG_|!lC=Pd7D=4bxeC;6Gb{1;KB=pWvE`8faTpZp7+fAVRj=Qr4V{yZ&HN(K~* zBb@fQHMssmw{hn?euNJ__z`v<+=(g84_TI;e&bEP@VPJW_~Vap$DOzFz=z(?fx`zg zgAA{zFV=kLE8pg!FFeHN#dUu6lYfCb-t$&`8Jp~(@Gh`%evQxl`_J*>b1!o5oj=0; z54?-P%79>J8jXO$H67Mz#%p6l3=vcen@a5lph=21dpJsmVGXLW*{6ki*+@E`bs1CmW$KT_tzyD=k{O)u3 zX~)XJJrs*WPM>^}1wZ2En{Q#y)d#uuo*UVDbT_6lQn`=%1n&dhMGcaN6W@P@7r*~J zN3Xw@!#5nGb2WvrNOw6UJ@YiUE?})B4Y+{QuyJk;H*HwivrF_cN~l*FFjgjPa{K}p zCtIxS-NkUn9Il9~!HKhy?qKcpRi6Il6O8uk;OaYWWMS75b=zX1Bae3u<)F|7%~s9% zQ)d}eLzefi2rW#{Xl1m}ZM2*^af-8N&T-|@D_P#Zj4Hq@hqf`u@rsQz>pcJcAMpL} zJ;RI7e4ov;>#XeB$=*ZzIdtF(-g56-S-E02#X`ZPt?|lJnF6B>-UsToK^sM7N~qz* zC!XgEzxN01TwdYM`|sk~JFb)d2>b~NNaMM9;sQ^6``b*WV>Z^-x$%Y@x%u8Z&_hET zf*AaCFh0A<3s1ek>Kkuzc+Wwuy!|NU-Vt4)anqJZKKuK8`8R)$5B!CnWdFV^IC#Th z=6B8!lw)IKok2Ok+7j(8e&TrJrPq1>+2=WQ_z*{LI7+ctiI~;uOcPD0n|Fd%ITcW` z&5&y)Q1?n4-^#L}nY5H;$?ECTY+l%4G#s+~ioK#w(^Ap(O3|snbUL97hSC(crsLwt zRo-~Bm&n>MVyagIg> zI_25F_W))D9nyJP`X5NYm{14KpFYRpXaT!aqKu*qBDgNgQtX){{it(7Z_!#r&(3!k zWzk+!pWEb#KYomJXU=o`eRp%<+QWDyN@O3rMAAx2+nYA9aq2t||K8_Wo38W0kA8&x z#|{X(VGKqaeB(Iv>It5G_8Io<-p$Q--b&COU1*fDvThqVf8s1pJ@Gw4=eXm2oulp=v@s0rfE(_)je~~| zvSZ&0c3Jv`1TC~U=TnaxLGzUhoPT|l6W@Q0E3P@jj%)VetVcO{2OSW!7>3{#ZrU;3 z+Mv*u`5g;l01*}N%80;xI-PR%jWe8l^#rfI`YPw%TxEVZ)iX;w|s*$RT!mY#M z@WSON@p1xk`TA43E?&G?EiNwpvQp}2(o2|xe0_%`?|eo;5{)Md9#g~bNN;fCnx~(9 zl5suZhMR9@ab+28j0CWKE;>OPyeZ9OeZtG%e~I%a&vD~jH?eZXibQBJ$qAXh9vks_ zIPuCGJn@ZhGq*g?bvInkfuo1yR5vi$n(*8cPjmY9lMHl8)3#h#yU6C&ChMopa^>E` z9KGopiaiTlf6I;7q2bbY(IIZaD!}HM4Xm+*!cz`Qj4`xrgDs5cd9iGr-(ut326j*| zS{O1~oC7Q4BW6zyt2Nrm!Jr6Yd1_`oJkm2d=LoIXu69$$E6=~ecc1tUuRQw_Wv$t> zZx_pl_OQIN#I5(;$=t#`YNXj}T8hHtoNd;eFtXjer)?WLCr%*77@YHTtsGi1?@{BD z=?t?zFiE(ljLyl74VHRs%Gq;g8P{X3zWQoZ4D53g<`&v<%jSg**3Mpl5ZJkMC$;aO z2n?whEiIttHB>&LYLR+{c?P$-S^&~z?$%vQ+Z?>3#-s%noZ4R&B>?rG|hUE4!9ZE;OYSrj-OXxkbWI!5zz zcr-d_vE}fRFF07k#!K?2k$-F2$fXUwmhKf(RGMl_ez3# zTx?>d6a@-u2Q9cloVG&SVTUDXExC}68Wb(=Ow-8}Ta*l|N}Q9N^yTOX1Srmm)_EJ{J$ zbf8TZXctN`zOcb`I$>_d5=B+WeQCMhwr#1(3Zo3J>p&~K^0J30jkQsG(@J8hwGxV% zjV&aXCjbB-07*naRBWN%S)d!wWMdQ61k7;6w62+MjhQcp3|8jF623C{NUM`_4ViA$ z=lpA>vwg2IqPuC^j?Q(Il~BYwCqEZDPf5XaeM~oQSS*GxKcaID9S*Avc2LO~7~d5t z=zNQJnnFj)S?aFcI(*krII*Vp1blF)rooJcUTHc_)F+BmUo*BGPd>W=BfG3}&A*D$xVjIRV0&;vu)HR769 zSem+tB6ou+ELtg??+6}Lt59tK8>npy8myM@2}*iOb)6%)fNMR=i}Ty|66YM#aV_Y4 zVWEr3NGmkY&b2gMLlF$cTqQ|0P8{s4th0^Hbw<^Isw{C{_7|mv0g-y-BzL&F-fmN! zimj<9R@Zei`l2L%Uo5NgeU~G|%L&Nk>re4=&S|C8!y$zCBpz|wwz-;=XjrauLS^W6 zKu^#nTK+zy7uu!%u1HW2q_cQRKvMG~MTqX85(wo0%?2DU>rWE!A%f}XAQPJ;PBtc# zRT(Qr*2iXu8A8M;;u>Sog5@eJmM(Qr)VkCFM5 zIZ`sbOq&UHJ*BD&tWj94rCE&R2D=D0F=;-w2Iw7TLt24A} zZ4nD9AyX6kw6oTAy+Af5FwSALAYBfRv6ku97G+tWBcD8YD60x>EY3OVrlu-N2{hyU zs}OKXs>YMa1n)h=(J0f0ymJ_9BmFc|S98-)p~xnbvvXZUu?sOx&Qh~3+|n1g~fFplgWhPu!=bn9gIY#+ax*iOt>; zs;;$`_cOi=fqvuLAd;3M7M{EpM{jL%F+{2=iU#pf=-QUT7@>VdjavwTjg1XVT*bh`B&|o=wvrsjm?t`>eYa_vCzVovI5#)< zE0-h0KVO%*JOqEXuAl$;pI0G-)e!uLwbla_-BkL{5SKP7O#k1u!l|?=Q9<~%A$aLr zs%ITuLqGjgy<&*!e<5SZXqy5^HLDb-27K|4Mq>+u9Td@CS8Ypi(*bQtOEE05qk>?; z4lKo7i8GQEV4`O}UaBV%B?#VQwH#=PuOINZ-~c5ZYl9q;o%0l_t2dFRV@wSA-IDp`|+OodgFe!sJP&6Ox7^W>QQWBz0L2I?GxmR8Xk&36Z0;6c#Mh;jL`vgS6r&ocXrIH`C2)D5c ztfF%c7aXS0bjs642%I2?P;f~U7}t^(s@Tj`SnebEkh@oB{ZbG8Y1=k(i6r6j&SQ;1 z86!cgR@kz{YTN6Aom|4A&1DFZL~5z5x30z`)n%nMt#^{x5oo(k=r7Eg-gQobKvNj% zrUn&cJ?oHvrm#gal99EkV>2}(XF9IMgbsO~5@nB5(&xq@mDM6TIXU0Org}QNJB1LK zPN!lGJ=qd>x@ zIYz^DGA0D6@ETj7mCiw_G27R7USYAa+W&geU8qR+)7r}WXH5*)1cfMVfl@ltL9DSd zRz48ClSDhxnVjz!loh2dvBpGD&(A93`5t1h8WXP+bhVw#TdL}cg{zkL*Za7iNi(~k zeC`+IUB_TJKq-TBj>&Y)bUbEIRw$!syCxsb3Ppl771l=&e3^C2tK|d0T+u|&PRi|AZWp*=tosm7;7lXB0D0bJvKF-tLs`2 zRb$1N0C9MnPHVY88xvZ!@Djg(!qJUwiP5l77=VANK&@{wHjU829j@JDXW+B?nRpOh8#h=R4U4V}GDjsPQ301bY_Ti_4*>XFos7v)IDYHf_|g6lf#dN2daf z6C3=Zw3*snmPG_3HP*)D(aZU&3Pm+6Q3kqB-p|q899o-P_)2Tu2bb4bN-}~E(SQZ4 zwM-_H+1~|038&L(Mn|l*Ne?o1&i$?V`S~wgjt>9)U6)1Zf7UWWWBg>Yd0HF0Xd|TE z7jE?{@+mk?`9ju;BrDhWk1{5gN^>XLei=3EJ5{C#}s3QTVY;43JSY@u3 z@7ipgn5x=J3EjghMbii>p@Pl_u=lCz>SKT!;z6;!8Dt7RQk5{dR-vOqSrmr5)bX_{ zgt{ISK2;e5s2~t^+e#(3ss>S1C;_4%A3aAiPfk42TxZm*b2464m1%k)u1`jpZF3}t zqKBX`CL#zLq@Wsdup5v7%mGClYiq0|Y$4!Uhbb)aS4jii&f$DVS(dq)rM2{1NfmS- z=}$g(!$m1V*OF4S;6p^upsia9D}jviv)&_#My8ZhGrbbgx=|Wa#egojr~vgjuqp3Ne9*)nS4J=%$ba2ovWOh4X>dJFK1cGyn;DJ0DF7 zf}(Q@jHav#T-(O=sdDwND2nU~Q&wdT%w_?M%7p|?psEVIlRiL&HTaYg_l*{+gp0u@PC7t)0&Ux% zm5%|wr}dsm+cFppXuFnTP+=%gy)N|D6m(si5nI=FXk(-gL-1%L$wjt{jTEO;FHjL3 zFcE3;-l0^ah&TJE2edeaZa8=8?1_EEU}Q_I)@5@vJzxIq2H!3=wLBgdQ}7hgW*ULf*>fGPKd6P zHBb}<>l^EGZrOt2+z_J#VHysGLVb%4Z%xxsn1aF<`T4D}42J`3+ffz;#@e`tu+3y9 zgqgIE6fOvT z^5TVybX6&diVl(xJI~g5%4lvxQIvQu=knHMjL`)Hr37(|S{`FG*2)>wIfqWJj$H?V z9LG#1lic5d@xnW z`{KPE6ce38VufaVgF>MbiL;eQx@aWGe&)(TpXQJnL&XY9rtQRkOGjAg;+sAzX;dwB(ELxLoNi0 zNR#nM(qxiD4k{*hCMO_?k}w{RDGF)EmEN5c;HIi^SxQos8hsUZBptDvE_R&upgcY} zj80TDEq@{mGM&`1IvN956oaBfDT@vPbVuksP1B&Y!5FEk*G&Ufp(2{36j4|zqSBxc zT8NScr^*-<9&4n&ZH$fYnTWdUx}j@3Mx#+oG;~fXLKHTSx3-vHSj_v8wH9k6xe$7k zmuxy}Mx1lhbxmO{Rau~{%&94D=Hd(eQVKUjT9xyGA_ncvWP&p~-cQ_j<6LHud8C-6 zimujzZf$OEQkErERWTS;v`ve1ezqS(v_Pv4yh0aNxc706(|%*DWn*KD5MXXJ!kB1P zt(DLpgU|0)*NV%O>-E)8N~oKO7M1phv=>-q8EfL#nsH3S%IW!rD!GlkdlNoCO7zy$FnS? zFH90uTWcj*4lWn0^4{u2hv7OGH6JFTr>PjEFs8tDJ{INzt!welQw>B97+hd55DMnB zp3+U_GmSP>RhidEzsq<^_!8x*umikz7!1}Fv|WeqIyr|>7+X?S6+Q&ehSqyYYK*7A zqpi(!KdmjJ(Y&w?;&C~s3YyN*buPvsLZwp#Mx#+a+l?`_UAyhPlVt;)pgzNp!Qp^h6q3FwMHR)hbzN`c@CxN% zM(xP!&5MXQaegx$rPm=kRR{GpBr8xB#WoLq6=Ke;G(NO6lbYdZ6ty`T=OWq{6KfR( zu}VcCQ(b$q_IZE2c@!C;UB#FRjGoiufHVb<6#&3{@yNk=lH3ksh- zjd>0Nqs~k8Kw<~$yoV4G%@DN`aSqz9rHg^9?$M!S{YrA&Hti1uwq;pL034B!;5<#; z#6-5BYnvFf8Bn6XDoe3!-Q3(H5VZ?-X24OD1&elx5ENR4Ow}3;26Pc|l=+BNS0&cQ zvc^R)Zwo8$i&JcFZZR4S8I&SeZe2%RH&n$y>_nBpH4%w1nxZHrD2*a{=S4$O6a|Zm zi@69eE40ikckE&qwc-$_HKi$I4-KC!TGRcc_rf_T2&7i9$!IjnW2kjSQOF+GA2VYOlXjB# zRVlEjtPN61;bU`38ApRR9@n)vB~Fc9*H8@xc@0@3nv%g_$g~kOE1}~3b(0c0g$UxO zZpyi}HAbV66qP*vbPwd=4&kz)(A=vB&6ni5YY69$8kP^FVb(iobi z%Q44zJZ3l?W?EHU*W2i5eVwb)QD3A2MQKpzuXJ7e&&sl@FGq!!6OhZ-f5b~E^Lt&_ zJQ)c4eJ`NvA8Ki{OF}uNRp$Q^0;LBnS)6Lr=h-s{ZM4>Id3BdiAGjMREpd znkH`~mueEyLELsN+DLW9#Y7+-U{>qw4&@?>lZ2fK*45EIu)Y`r8LG}&5P|f|s?6wtQc8liu46i#&b-wrwrfG++=LLe(Xvv2I72@qU>n+D zKD1KMkPomJETn)nyD@1++cYA^EiBD+N>S*HqHSNyGkRcpmP}_cHrom7p3NyLV6EAv zf9hhAsSR|VNbPbot^pS)hXZgP6RBKc$thxXss8&|G>)z;&gY<75~*3DP1xS0J_%D( z6}gK-;dIY^eU^)2Q>8V7(Ht(w;p}2iBS?-2b_athC!A@$C(&+79CVKYB#r)j7X9}% zVA+r&RjuQineo_V4t>{BPo`AEA*QfY!wRc(wDi{4QiQ3*HIXcyl~y=6Tf5$QRH3ki zopIaQj-EO%Nr7$$dj>!1gbG%HpZ~I zxyj<V$pMv}^b(4NxsI_KEj++;Kw2{(Q!qW8tc#cllfrfDe4ilUGp&IHj+ zj7B30Q)H`a9nD3mY9LMh5;7J8=tSr1BNJ&qNqd9qIr6o!5}lIDw67FJArw9DvW>Xt zrQ#kQ5vjH(MwcX|$>?#v(35(DqwmOOn!`w6=$F+)_|4<7z-TTnHE?^iLNl zW@4WayPOwBXr4t8tNDGFf$Rf=s><(OEDa@bv?LW#_tE(-kC9S}$#_EBwIcX8g0yA? zML|p8MU9pe_2@twJ=QAx+dufyL;vA&H2CLOa&UPF{%l?s zE}R=V=WZ#B@^)>E?FaUW`ji4EVN>~;CZTO3Rm$eTT*bzPNn1`7&nCB;bSR1_t``W# z&r6*CbU<9%u{7UD{92!yb!i1|woyf!z$|8-ahGLtj=We+7R6->TF-XaXt|fRYjfkP z6cD9*(pqRuTF+=R+XV8xKyI7eaj)Xswr&1;rkyeCXx_9<)-a^s*Z-5|EghIX_-%Y~ zLJTq^qG7D<@z_(Msq?+0s&@)-Qqa&pi27syCklE~vJ`{nOiAfgsD!86=433-ZKNLA zC{#`dcpqJ3M#SnR65Y0O%ZY0sujLd-ZcCP;Hi5u46i#U^pC1M7Wvo=UT^EJddK*p2 zq~JYLR;-ChcMxH)*j*Co6ON!>yf9ew{_We&gGyyYi}+=yR=RekcfoEF-+zNO(&I9=N!{|nv=GUqxeYz(UbCYl7KgecoWD={?4Hqt)iJ;-XI>C}z9F9{9w(XFm53#viIg^L%c zn}*S7lo1slpChfM6yRstN!NjrHbnOk>1gi!1NQ?{=1jvFnwP_UN2m{z|zuE-sjRh z+e?0D+Mm;0&MNl0hc4?#aaG%%ukWtd|M#W)SW`E-AY!!8Y*OMeolavRqs;q2nzMcd zJk95&drVsAX^%_yo}MKaHKJ{92qDub)A?bviCP6u+s5ZlbC$&A-sSn76?}-us@oPw zr+c1ECb_th?l+BjGD%5vslH=nLawr5K&0W#kOnffI7Bqp#5I)mqxt!HRCHu4BQjHz zg-}biAUl_`4wTXPyhsRnQ5G{wRCEJOG*}zQzikoy-qyS-0I=v7wJp`4yAaffxhM;9xkO7*NQFFmwlRW(!5e8^5r>0UGccDu#jHniqa z-OIKOTq(A;wzgI7(m|4Vt^F#r_kQN26~{243Q2(1_sZ{IeIY9dWyquoQ?DUq9}Vk2 zQ~x>p$V2+ROAiF^W(Zedi`=!ipH${9wodr|{p2PkFnwe$X=74g-(SC~=Q7rRj*X2C z=0!@+>EC}%e2%Kz=F-+jUQ@D=(9))9sLG0>Eb=|4J+X@-aBFQ&6jJp&6;{&P z?C;e?+wAoBsal_sl9Z67G4Inj`^E(F{U*M%ayBJLrot9ENwwByx@glh5lQqJ@o8H@ zG*YshMAZF0UCDqW_k!v5>hj*_Vn%=eO*Y~QUzqs8f;@Y$a}10PYg;rqsQivZKQdM z)0mOH#C6!1K&ACJ7z~(9COGF4p5!6+>R{&D;ySmD zPSx*;k$%Ue!C<<-bolivZ94XVxRgGY4(@coHcgW^XRTGX_e&a*OE>KqH7Gu#mSDI4 zdHpKhwuGYBP3?W$wj(bLdum`8eF=hVL?gh5|outq?J-&=@JB_ zL15{nq`O-{Vu5Ah+24!bfAG9~&gZw8XkzV_2Qk3VcCB>vL! z*2MVtnQXDK%bt*l)jW;Rh@L=xu#(qutbqTxo6d*AKgPI1uafw_p}tx$?vo;MTZ4VZ zDnFH|6w2!-j39p$Y6x`bJJ~Qilyxd#K{cgsn*bcvK?w81ZA>HQ!K1C*yI(A8rKas8pOB)L1HJ^mI;&oXVV$ROanvEY*wH>WHEc{ou{rwN$@bjFp+5bEXD;<825@Mgh^ zll*4uRmf&2%XRH^Gz_35<4Y61SxMaRveVyvciIwM2FDU~=a+qgbDE42kEQ;$@PaVx zA`DdeW`0qf{g5g=(hvyb)M5)s?_3zsZ5Wb`+Zw4`a^w+JuPWHF_2X^ zY@aPud9o$;b)@pBKvYA1DU`M@ISQFC2zxpH^!$*M)2onVq6Zurya;VL@0}+*yRq@(*e{lexkkL+H?d;IxaYqr zcMPn*n}q20YABi}`abH}N?aE)-7piBD(uq&>zkKJ%`Nubgm4qHaWeAq?io^HzapA8-dcQbB}A4Ui>XuTuY8iWfTHZ}4Fm%T6p(+kx}aXr?yr zWO*-c*lH#vmUMxUDgNchnlK-fsU*@3+m)=u5clcR-Tw5Cxf}o z0_*+VjF7_nsXj>5h4_`>v$3k5i%o(4UW-pXcAJ}BTQgvHVLoyvqXvfEA7^k~u3}#h4G{*W z{GVwxwyHPR4WhWqPfnLQ?!}~T{&b(6e(IlK-5+Om7C3nNx$@`D*jJqgWAjAo_XCYs zwnU!sVF`>wrG=FV$1gV7Mx3FrO(kjaG_12zE@kR-8E+9NJhe*jWycRem&zU$y8#l? zr<9u3pT^Ww-)UN*?-yD4p3U9Ya}R0^uPYReG5h@D)Vaw>j|^TE!e1T2Qmq(=!gs2CD{vjoi%yjcSj6!XLN!44-+O~pm^>vmPKKk z`~aO7X*W%u&!eISp@c}UcXA)TVzE9jn9NR5NzCZ>E&NdHtz=&bHWb|=E#9_6dFJ}} z`}X&eXxG4!5@UQnsCTJjD1N!-+IlPhhz+s+c$>~EZ>@TCrUA`>?c$`u^C&T+-)js0i0cz&uR3!OAj~F0!|jiZ-(vD zBlg8TluYlqx^FvvF?m&x7L?`_<*;rpJ1wvFj@j3=gwBwO_(y(CPYI%WKBjWqFSM#) zI-T{@7GZV282xxj4PC^x9yjSDE7(C3S}@kJvc=b}=q3Dok@|@y7?Xb8;%_9p(eu3K#FQ^>$+LA!dtrWDez8ta zt8hFbxMH7t^{{go&0v`AlKKrD#FpkFOdnglsFp{%Wb25R;4F(;oDWga998I$?2-JXqQ+{1SRedbw(n|YmEU+3r8m9%wThG zr><&e3r3_muhu)6Y-IH*i9E8b2OXWXZ_IPHw@OpG*O+`z^@9^0iqz++xi1!yP+7~z zCm#;T$;k!u8uNN;ZYbGyR@(cSD40N2*F6MN zNuJhl-F0*y!R+PUTTpT+ODX2AfHiy2e>p22%M}5bzuqo>^^O^Dqz1yaw0~qWV%4$TU?5aZQI4@rQG}bvx z#dwKF_Tp=^saO<@$;%idZ$GrB?(=zy4OPP(%hF+MC{Y zV$WZ@oBK0yX{p;*zdGKn%)9SD{nV0;TJV;FgzuZO)Pb^s?y1h1+H_lT=rP7zfZjB| z*xK6kV}KwXIH(uuNbUS>j1%+lO@+M-!=qNW5yEx&y02804yl99ei{97JY``9Ghq6V zOZ>h8zO{h~m)vHH{8lH;X??f=I2^8L&^0zm{9;qmvp7otxsj9hR6O&+%30O} zhLiReLTd0i`jK?`6AsQIi>JL$`c9&p96Z_HI(b}#3MWM{*$KW#;g68D!skCz1k(05 zUaCbSqqj~jZH9VV;!+5ub9fJN`}t>79!g!tGzW8q<0Mtz!r}%?BZYT-N|4(*}D zsnq!5wx6YS_Ab-Bt8_9-sj)sO{MGRp4)oQ@F_fSih3g>uK3H+&Vf?=#EE?UnNmUzg z=&yZ6bHz1Q0{QPGp@J*T8cAGQF+_AvJM{^Rbe=LR<&w7$X#Jn4mEC^T`_}xzgV}$X z4l;yt{9_#nRB}s^+5r|W6#a?JvmVLQ$D4lPQ;~zj{c(>geOa7)4?%L=V*x8qBeR%I zbHPC*_V{{n;)PzXe+r!&59Mj!=6shR3wY`!OdLzq#qhGhf_{|r5@-MI?IB~OdBbbE z=lKvK4kHq?^_msjOL>;9r8bk)Y7HqJL#+TQsy0PSW2QTd1e*%LOlD+LsrO&5D^hp6bSF{7jUZ|E|bt^SL>11PmxCBevIAB$CpYwd^k z(A7jvQd?7sQn$?SH+?n*$y}TW_iGK@H@OtjREx{f`7)Gb+TYAdOx8htPW~!k*h`co z&X=h8Y_*JCMT=PBoq?(eOcF9mGt3$3K9?HQ^^whK2DRqdz-nNk8tP<48oX!>dz0?3 z0NrsH>ulu+y)7Is9t^*=n)1DMmdy&)7i?yg|4F)zMfkO@G1rf#Q!~KRAg3HN7b|8u zeErH|VldY}Y}|VZBa+A?1E$nOGE-Gkej(-uAKg6oEK)7h?V>own~S{;n%$a(^dTVq zby;hLKhx@o#X$i)9!A_}hRF>-Igomx(Y5kryyv7Zg~OnGswSoiKY7hfchl2KgPwxS3^Z5 zJ<*Tp59@Gy<{Mj|tAv57D(PIx#c|^6ED{zdbC}DoBVzjin}LFmY+kl_tsOQE&gv5D zdhPL3qU(Pr6zu2E=9%)XMA|=OQ_ZkhzYjvaXw8TtBXZnAXc|*|J=3dACwX=Pty7*S z+KD^fN*z;@AbJ)$LNvj8;Z`)@7gO`Ky6m~*%{sQ*;mPIGJy^4?K>Uh@Bj=&$unCi21Rd|8I?wKcI~O?tT{T#qfDSq7uihAtdwzbH?3>O{v+6KlB7X&Z{nye**R3L zxu@1$jx4a3g@5Ny|FDBoe7s((weJ_XD9n5iRelviG{tGM4Y9KRwd-768OO)K|8^XH zke=M|i(BtI*SVW#jxpGYUO9vze4Yfv_jdS-8MMv3CQOKa){oJ1w6uSt;@II|fG*&$ z7JC&klO@oZ{FL^ZX*wdMdc2t=04$RuTwUq5{KCYLLNxJV-R7|T}OGz+p-Tc(?cV*JS~mUyT;YRFAcMzDhCj#Fuvr>9U)e=VMn0&J zLv1nSOXk4XZ^-xj0@Jn7r(qmvKRg-fACYukk~@yL@x?cK0UdfJ>F|ArH_qcv7XCCZ z3HT;_w#dI}CBMTbK*~n`fcqYCF=Wi7mIozq(NN&hKM%66{dPlS`X)6e!%comOjPIO zHTupIS=qM*E+qP|0$52;c<-ltVeGcq31gK5{~7q>WeSw`RYgc-w$D+5dl#`~Io7b{ zhUT)vtptP73`S)a3H7xXjuaP76TX%d3NzTQ_)-fLPxL#lnbd1b?FFT+rif%OmrO36 z&L_wSNsnJac{U9esR{)2FP8l>D3|B2GdU}MuqwHFGJoHy?bF}h@wq#&;Wa`xS10%f zW(hr(s*xeetC40x{^)0{5(sI0HMtc84$`$}P4!_yjy~qNG$=+?e&GB$BbrF^;)~>X z8zE*8y%R}`e7!B#tNHq_ksDR&ybtmE7AfmDMoF4yQJ1z>OcsA{SFGq5AKR*V%Cr2( zt(+=MuwLNlgnhpUaS(h(Wpbu~LG`+<1?M~|?T3S-5>hGy*FtK|QgGq%wxOgBG%-tt zRZX$s-nyfnZgfJ-#<4CQChq*Yx4Q1_I8(N^Apagh}A1cCGZ>FCj#CeVj!vM(3JBxb?h&;_(qd-ucHP!@sx zOIZkdK!hONT8FoV6tKc$O!JMhWZC*LjPTxtU9WJT znIkjpxR@;ZS9C*HI{`)0jy?ej!w#fOXJ)mfUJ0oMrlSafy@VN&UR6no zhbuM5u!W@J6-7L+R#Yv#Nz7iyF})e1$9S)_?p>b`;b^oN8I8!9TJa7kk*d-+@Y9Bm zyXHq z9h9m$nTqkzzl{6MZqvy;{%uD2UIv%9S3@6}T~l>a9UR1x=(;UHrf<4JXpsV1_U!r3 zy7r>6-1+Q66EzoD9WL6VR{o@p@}c-5_a?sajT49i;nIefnvBxN!#=NusLNogxw*N$ z?J5zK>J_aP<@Qy&fVSm9V!11~?JD-w7GaWmkMa$EMTk>YYorgj*&Dqa=e6e7+VHFA zIa8vF^04V@tiAf2>?=q#=i!`i+&UpKG_q~JJ|-li2DuBPc+1nW*5+#5?ZVrU$UqX} zJobJ5#TZd|1T^!7`r3yXJZUv#|M?6r4aFv<2Cp?0Tc3d}T#$*GS(%JocZ`0c*S5IJG$;L%_L5VYIN|0NBnxbtM1G0o_m#EThrruf~Ue9x_u08bV?-gR*6 zq*~}}0WUJ5Y?~AN>cVoJ;vO|-E;*Prne+-qem6PMlL@PH#C)Iz4ii_CQ%pc<@tcW$K z(?f?zgBV@)75dOa`q(>euU{|Geq$si9-%m&taHct$cC_^EF|F-uyG?LD=IH7GN6LdNZ-oB2M_8MDY}<9^hKD!yH*wV@=s_>rYRhMu{Zm zQmb_M85d0xueN4!Z}%kmY{$)v>WE^$n7t+^qW22 zMVap!UBa)b>ED3!$4$8I#H#;bO6I8{65}6jD#^_cN3wGO>!!@_xx+ByoyhO)k)<(Z zj%^B34}Ppm_9ZK-naJ?*KYs`vcdRr?;Ylef&8z`qJ*NS$$p=oo$vgeeWOTB@Xp$^} z*DH;I#dS$jgb~!1Mg@D1*07+BWs8crPrOrJehX$PYEbdMrgj{?v8Q`?l1ZW)IM^%{!*+@K0;W=CNr(!2bM>`@(Z5zxM zIaWo!XvNU-4?LJ*Ulv#{U^mXpxXDT+cyGIXZ-VblCXfD(+*d8G?g*PaKZ%Ewbkq*2 zDO(Cpwi3PneiYq=kTF*n2}%qVC42nv9Ntrt)vV4hhRcyzlID57`dzpvYTM0e`cT3{ z$pTjWTSl*oRFCSdO-{LksXdkbk*~&?%8o@fNGQmhM){O<=DxEDX&F%VC^h@X;9qA6 zv*9uoQqemA_(%a`-haP;=%2VuLX~;#a@ig8Gv&y|?-_C24kmd57Quz;}M5rT;9)_6WStZM#+HC_lQt3fQ2*nDFkB5BCbaj11cNZc?}S6 zmE43qtH`J)vDKpK4Ah%&{M6|v&aBor=9ez_`%zw5q*G)DhhKGOu0$nPZ}Z-In3_#_ zBn?g$d7e=2h)G(F^^*Cmbr*?tX7p<@B!teO?mC~)SqsK zaI(bO$)l%t!XbbHYI=R_9zdidsw##`r^AYI!0|p)gO^|(n?Ui98qn54{uE%a8!>}F z=W>I=V6=8!H%&`O$op_dgg-y>rwA&O1V}O{!D0?Z7{-z5I=RY82Ckr^J=9lHz{VUB z^V-i++7liB1nE%Tl43A>2gopH2w?`!ZF6Zu1aSmlmO?t60!Wg=nkHgX1SN4NyhM>~ zD}m>6xch#SKj`R}w5k&-J=q}e4k$@D9!RI^tWk!i) z4BBm5vx4@$qfvN=;Lh@LhJD)|*B|wr5`I0JAmm)0jqA2ImlD9NOQ8bC(=;`JjPQnXu zuVWHa-X-#s6( z1<(w3*+4iPl^y~PdN*H%Z23U}U#{wQ=Q8ZcJZ7<71e1e>?Dsi5P27!v;#ly@XJ|zAaEW*p(f#rvQ zJiVQ~CWKW2f zd`HHr!Mo4?yA+kal%>U`OVkl5igF=anKr`)0KbTVEd-Al{8lsWSQOl7A1CwK;A+!f z{^qUd<|xhYcgr|UnB{F{NeK%coNNH4X0W><0tW)qYF&lEvZ5*cmKa#1w+j}!IAQ?b zG}fMn5&hN%{AHt#Xn^b1ADezO9!m)g^S%pUHDKKqEXSH<0V+dU5F0{ z>cR-z2oT@S^mxqK%#wz48$b>11`Br^Tc1Ou%WB(50ah-kbX-RW#9sk*lu}X>+VQPn z@$zrrT;0Ho+qtki?5Q?n>KTQx3ba;qlRJF55ABDwlMXG~YxJA&b&nG@MvP5t*fv;B zOf>D4EJ{w!15x|~8)I@?p~WR7E^){2MK}3?>rBzjJ%oSF?Qjp-c0I*ceuazCV1=;e z5wa>Macm=?^Hw8wK-K_aE zo(V#Id>hObPA#!t#m}V+%*`d)_VGEKdS7?U%t6+H^_}pori!5Ji;IhaprD`}f^ap0 z6=;l$*VYj_S~pwXg<$zdNl680^i|yP(&^Nx5jb!aoh29cBxC5Ns$wAoavFzOaDW6` zkH?XNgpFXn-3%v8AbbS&S9U9#FuW8vTC$B>@<6xXziUnTPT>==mf)){H>udk*%0s* zt=-^j+R*?=FcWZY7ng@0x_=K2T3*Hlpk(~)YR!f~D^8$gTHwOX(pVa3XaC)%B=L!G z1a^qSK~BZduqJfF0OV(mK_dd8=u(~qUKK~B@1dvhnwNze^_E2aTBdu&nMAX zf+GS*pe*OK80uOKR@y?dH)L`` z2V9pjS&0niuUFE%vo71)GzUCD1UaNpkANd(;$ZT)06wJu1ZkK!@^o}3|4JY?0FB?S zJWE@viI75_Noz)3}OMSiubNs zl2;Yj_Su$oIaWjPD|<_w_UYp%h5Fjsv$wV9gkYi z`&!pY`DTTd?Z@BCHx*+*R4ha2-$X4kpCA+Ara}R5(*!@J%?w#zc z808^eaPjWg{~9m2(#hGqj%Zxn!Oi@~ub6KgrJ`MrV!H{QYG!oJ;H(_EILM9y6#0SgUY%lzb;OnuBmmM38)$Ix%bgD(*O*D8%5&6+ z_90FUk55w|1%?URYiWKDF{~A46kI+5EGXIc>)nGQd0Ip=w8O}O^AFoxAZ7-;TS1Ve zY8Nhfl>)RB?>tO9IzDscQi*q|B_&s$c7sTUwX=#n7Ed(J9|q$Fwq2pe4HfQ+`))W& zyFpI7{n`z$V?yL9cCF9;6C!(W_FOgYIB)!*{6An>gEja9HDHa)>k)qXfaBc>nZL}% zQTy@nDC@yNRxknJq;zW6;pg<}<41+C)%9XwB4n)6&*;gJM1%$FhY)boQTxiu3h8k6 z(>b9mScVo@cX;N?v><OVyUVA=EEHY?omc3>W=eq~@uV06SBXqhLSjpKlS>sh%4;#_!t1fnf-JPACJNN|^3R(Qpdq;y7-iqCsy1C#N zw#1rC+d*CAQ2zL^`k%AJ*TQ5i=+5KECXKVqm_rR_l_$E8fj;|x2MYWW_t-7ed3}@m z*5SOIr;CTR?m_>Kj*f;%@hrkpAebmh+;ZvwU(TlZSF?_55ld@sZeCe7Ah4QRdycrB zuPZ-je04ZkZeGt4xc!|N7`cSesyz8G*gqsFsD4PXejdtesnl+)y4IJ~ZTk}8EF5)2 ze~2qB)X54IM82TacPP)QT*AKDZDG~ssxiOzi;j*?%F4=m_lqRWV3y@mPyOYg3fWM` z>sH3$EIDJ>)xV1}nXOm5rLo*XiW&gWdg9oc{(7l31pAlclYHV8g()QQ5?*fbQUeLUnSlM7P1E z3po!fK1?EfxJEJ+;1ldz=O`CB9CmPKe01G_9T=`HeRt0iPf6pqwYtDqn9SvCxpWm9 zy7cIEb3~zT&P{gwRCF}y|8p4U5u`%vbgK0bpuK=4ZEyGb?w%{8GpMmDsqgWt`N}}q ztT%gbYtuH@o|=U$yGsf`KR+DCpY#~dn>H&^!P9^1*N}=g-In!N-<#C@Li44KcJtOc z?+tWc_ZXFSFCB{0ih~2I_Zr*$JUou5@uQSs&FW}{5&Zk6#sa!?GvRvH8QX39=aHEm zIMC;ZNhV#Jw>0WvK3L(F6feq8XE|Y$LARZy6T1et;@T7|Dae)(d( zZpGCh=AA@8N}DEDPnYFTB8%6HYCX|%OMz>(2H%=ZSem+5{n?&}VE$UxTpk*gPOusJ z`}o*XQB#xVu;$%nCr;u`Z`#1dB&x$hm;T=o+C8uIg+AT$|C|3if1s7)mU`iEVrS)? RJ`TVSjpsU$3Kg5T{{w8(Z(0BV diff --git a/images/Fabtools50.png b/images/Fabtools50.png deleted file mode 100644 index bd3393bff2bf0dacb75cacb005295de52c685714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180186 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%0DS65-z2@^L|?(U)7 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%0A!rDA0suf=Ga_qkUJ{U24eZ!O%jNEiH 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� zFX`TEGG^Y1?{Ba(q!=S7V2@zm0X4w>Ml5^6){AJ`TN#poRwnM4h9W8LdCF=1Umw}t zgfXWrlDQ7Deh8X{q4Qfk)FdfLf#hl`#Q@>E9LEVpY<}|$uG5Q;*EVM& z)@-!qRd;10mbGpzRr7BZ=V^sKzj6^>VI6&O%!yn`f%Kx*YsV48D}ya*|A4 zK7WvYzZlrh<}?_@NjE+DuOQ|St$#AXx=dk`TXZ$fJ|bs>=kt{YuNOIAMth*=m>DV`iJ-G5t-q>iGo`wJ3OEO?oHljk~A@fl+Zj#UgPblAjZ zwVX3>&hdA5F}^SS6mRq zKJi_ed|gc!{&_8Kb`txaJZH*CP@1<`%PMA)V zPG)L({T_*bKJ}Z=hts8;=l6XjlY7l+vVH0U7CeP*V)#;KXKz=%e1?JFe0|(b2e>xD z>nEQ%7fprR1b%{93bZkXl!Z~fFh?u* z0J(2H+x54HOq@67ZR|u5T2pK$7eiC49eX?dx;Ljo0cS2Xvo`gL{7>{DXKyUeH7}1Y zXJ$TkA#7U~kGy0yh+0>Ty6iFn_K2+-{4w48i6)(z5e3#14}TxO4c`O^OIUCA6>yal4p@oKwTw!1YZekG3Jx7c6W~ zNbnFZ;0%r3{Tnu~%tm{kh~Kjg?J-^~`@=KhGN$F}6>Uu?a-?w^@at5rpHZ*mXV z?g=AU_V#=_0pQlaM>IN$)yv^eUTxOv4R#w6=BsXQ2SAAT%Z1)b%7_SMjO4dZA!7ee fx8?v|`^~n3XjRQ~VH4N?UN0$ed9kvezkUA)C{r=t From 22fbb005220233f8210b23c1305f9166343af5ec Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 17:05:02 +0100 Subject: [PATCH 02/76] adding sampler --- .gitattributes | 15 + .github/ISSUE_TEMPLATE/General.md | 7 + .../ISSUE_TEMPLATE/Problem_with_module.yml | 103 ++ .../ISSUE_TEMPLATE/Problem_with_resource.yml | 87 ++ .github/ISSUE_TEMPLATE/Resource_proposal.yml | 39 + .github/ISSUE_TEMPLATE/config.yml | 6 + .github/PULL_REQUEST_TEMPLATE.md | 63 + .gitignore | 17 + .markdownlint.json | 10 + .vscode/analyzersettings.psd1 | 44 + .vscode/extensions.json | 8 + .vscode/settings.json | 41 + .vscode/tasks.json | 125 ++ CHANGELOG.md | 31 + CODE_OF_CONDUCT.md | 3 + CONTRIBUTING.md | 7 + GitVersion.yml | 40 + README.md | 13 + RequiredModules.psd1 | 29 + Resolve-Dependency.ps1 | 1060 +++++++++++++++++ Resolve-Dependency.psd1 | 76 ++ SECURITY.md | 43 + azure-pipelines.yml | 324 +++++ build.ps1 | 538 +++++++++ build.yaml | 161 +++ codecov.yml | 31 + source/Classes/1.class1.ps1 | 15 + source/Classes/2.class2.ps1 | 14 + source/Classes/3.class11.ps1 | 13 + source/Classes/4.class12.ps1 | 13 + source/FabricTools.psd1 | 143 +++ source/FabricTools.psm1 | 5 + source/Private/Get-PrivateFunction.ps1 | 31 + source/Public/Get-Something.ps1 | 41 + source/en-US/about_FabricTools.help.txt | 24 + tests/QA/module.tests.ps1 | 216 ++++ tests/Unit/Classes/class1.tests.ps1 | 46 + tests/Unit/Classes/class11.tests.ps1 | 46 + tests/Unit/Classes/class12.tests.ps1 | 46 + tests/Unit/Classes/class2.tests.ps1 | 46 + .../Private/Get-PrivateFunction.tests.ps1 | 31 + tests/Unit/Public/Get-Something.tests.ps1 | 91 ++ 42 files changed, 3742 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/General.md create mode 100644 .github/ISSUE_TEMPLATE/Problem_with_module.yml create mode 100644 .github/ISSUE_TEMPLATE/Problem_with_resource.yml create mode 100644 .github/ISSUE_TEMPLATE/Resource_proposal.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .markdownlint.json create mode 100644 .vscode/analyzersettings.psd1 create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 GitVersion.yml create mode 100644 README.md create mode 100644 RequiredModules.psd1 create mode 100644 Resolve-Dependency.ps1 create mode 100644 Resolve-Dependency.psd1 create mode 100644 SECURITY.md create mode 100644 azure-pipelines.yml create mode 100644 build.ps1 create mode 100644 build.yaml create mode 100644 codecov.yml create mode 100644 source/Classes/1.class1.ps1 create mode 100644 source/Classes/2.class2.ps1 create mode 100644 source/Classes/3.class11.ps1 create mode 100644 source/Classes/4.class12.ps1 create mode 100644 source/FabricTools.psd1 create mode 100644 source/FabricTools.psm1 create mode 100644 source/Private/Get-PrivateFunction.ps1 create mode 100644 source/Public/Get-Something.ps1 create mode 100644 source/en-US/about_FabricTools.help.txt create mode 100644 tests/QA/module.tests.ps1 create mode 100644 tests/Unit/Classes/class1.tests.ps1 create mode 100644 tests/Unit/Classes/class11.tests.ps1 create mode 100644 tests/Unit/Classes/class12.tests.ps1 create mode 100644 tests/Unit/Classes/class2.tests.ps1 create mode 100644 tests/Unit/Private/Get-PrivateFunction.tests.ps1 create mode 100644 tests/Unit/Public/Get-Something.tests.ps1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..96c2e0d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Needed for publishing of examples, build worker defaults to core.autocrlf=input. +* text eol=autocrlf + +*.mof text eol=crlf +*.sh text eol=lf +*.svg eol=lf + +# Ensure any exe files are treated as binary +*.exe binary +*.jpg binary +*.xl* binary +*.pfx binary +*.png binary +*.dll binary +*.so binary diff --git a/.github/ISSUE_TEMPLATE/General.md b/.github/ISSUE_TEMPLATE/General.md new file mode 100644 index 00000000..fbcdf240 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/General.md @@ -0,0 +1,7 @@ +--- +name: General question or documentation update +about: If you have a general question or documentation update suggestion around the resource module. +--- + diff --git a/.github/ISSUE_TEMPLATE/Problem_with_module.yml b/.github/ISSUE_TEMPLATE/Problem_with_module.yml new file mode 100644 index 00000000..d699a261 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Problem_with_module.yml @@ -0,0 +1,103 @@ +name: Problem with the module +description: If you have a problem using this module, want to report a bug, or suggest an enhancement to this module. +labels: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + TITLE: Please be descriptive not sensationalist. + + Your feedback and support is greatly appreciated, thanks for contributing! + + Please provide information regarding your issue under each section below. + **Write N/A in sections that do not apply, or if the information is not available.** + - type: textarea + id: description + attributes: + label: Problem description + description: Details of the scenario you tried and the problem that is occurring, or the enhancement you are suggesting. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Verbose logs + description: | + Verbose logs showing the problem. **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as plain text._ + placeholder: | + Paste verbose logs here + render: text + validations: + required: true + - type: textarea + id: reproducible + attributes: + label: How to reproduce + description: Provide the steps to reproduce the problem. + validations: + required: true + - type: textarea + id: expectedBehavior + attributes: + label: Expected behavior + description: Describe what you expected to happen. + validations: + required: true + - type: textarea + id: currentBehavior + attributes: + label: Current behavior + description: Describe what actually happens. + validations: + required: true + - type: textarea + id: suggestedSolution + attributes: + label: Suggested solution + description: Do you have any suggestions how to solve the issue? + validations: + required: true + - type: textarea + id: targetNodeOS + attributes: + label: Operating system the target node is running + description: | + Please provide as much as possible about the node running FabricTools. _Will be automatically formatted as plain text._ + + To help with this information: + - On a Linux distribution, please provide the distribution name, version, and release. The following command can help get this information: `cat /etc/*-release && cat /proc/version` + - On macOS, please provide the product version and build version. The following command can help get this information: `sw_vers` + - On a Windows OS please provide edition, version, build, and language. The following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` + placeholder: | + Add operating system information here + render: text + validations: + required: true + - type: textarea + id: targetNodePS + attributes: + label: PowerShell version and build the target node is running + description: | + Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `$PSVersionTable` + placeholder: | + Add PowerShell information here + render: text + validations: + required: true + - type: textarea + id: moduleVersion + attributes: + label: Module version used + description: | + Please provide the version of the FabricTools module that was used. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `Get-Module -Name 'FabricTools' -ListAvailable | ft Name,Version,Path` + placeholder: | + Add module information here + render: text + validations: + required: true + diff --git a/.github/ISSUE_TEMPLATE/Problem_with_resource.yml b/.github/ISSUE_TEMPLATE/Problem_with_resource.yml new file mode 100644 index 00000000..a74a61de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Problem_with_resource.yml @@ -0,0 +1,87 @@ +name: Problem with a resource +description: If you have a problem, bug, or enhancement with a resource in this resource module. +labels: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + Please prefix the issue title (above) with the resource name, e.g. 'ResourceName: Short description of my issue'! + + Your feedback and support is greatly appreciated, thanks for contributing! + - type: textarea + id: description + attributes: + label: Problem description + description: Details of the scenario you tried and the problem that is occurring. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Verbose logs + description: | + Verbose logs showing the problem. **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as plain text._ + placeholder: | + Paste verbose logs here + render: text + validations: + required: true + - type: textarea + id: configuration + attributes: + label: DSC configuration + description: | + The DSC configuration that is used to reproduce the issue (as detailed as possible). **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as PowerShell code._ + placeholder: | + Paste DSC configuration here + render: powershell + validations: + required: true + - type: textarea + id: suggestedSolution + attributes: + label: Suggested solution + description: Do you have any suggestions how to solve the issue? + validations: + required: true + - type: textarea + id: targetNodeOS + attributes: + label: Operating system the target node is running + description: | + Please provide as much as possible about the target node, for example edition, version, build, and language. _Will be automatically formatted as plain text._ + + On OS with WMF 5.1 the following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` + placeholder: | + Add operating system information here + render: text + validations: + required: true + - type: textarea + id: targetNodePS + attributes: + label: PowerShell version and build the target node is running + description: | + Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `$PSVersionTable` + placeholder: | + Add PowerShell information here + render: text + validations: + required: true + - type: textarea + id: moduleVersion + attributes: + label: FabricTools version + description: | + Please provide the version of the FabricTools module that was used. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `Get-Module -Name 'FabricTools' -ListAvailable | ft Name,Version,Path` + placeholder: | + Add module information here + render: text + validations: + required: true + diff --git a/.github/ISSUE_TEMPLATE/Resource_proposal.yml b/.github/ISSUE_TEMPLATE/Resource_proposal.yml new file mode 100644 index 00000000..2ddd0986 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Resource_proposal.yml @@ -0,0 +1,39 @@ +name: New resource proposal +description: If you have a new resource proposal that you think should be added to this resource module. +title: "NewResourceName: New resource proposal" +labels: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + Please replace `NewResourceName` in the issue title (above) with your proposed resource name. + + Thank you for contributing and making this resource module better! + - type: textarea + id: description + attributes: + label: Resource proposal + description: Provide information how this resource will/should work and how it will help users. + validations: + required: true + - type: textarea + id: proposedProperties + attributes: + label: Proposed properties + description: | + List all the proposed properties that the resource should have (key, required, write, and/or read). For each property provide a detailed description, the data type, if a default value should be used, and if the property is limited to a set of values. + value: | + Property | Type qualifier | Data type | Description | Default value | Allowed values + --- | --- | --- | --- | --- | --- + PropertyName | Key | String | Detailed description | None | None + validations: + required: true + - type: textarea + id: considerations + attributes: + label: Special considerations or limitations + description: | + Provide any considerations or limitations you can think of that a contributor should take in account when coding the proposed resource, and or what limitations a user will encounter or should consider when using the proposed resource. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..9917040e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false +contact_links: + - name: "Virtual PowerShell User Group #DSC channel" + url: https://dsccommunity.org/community/contact/ + about: "To talk to the community and maintainers of DSC Community, please visit the #DSC channel." + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..4b839df3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,63 @@ +# Pull Request + + + +## Pull Request (PR) description + + + +## Task list + + + +- [ ] The PR represents a single logical change. i.e. Cosmetic updates should go in different PRs. +- [ ] Added an entry under the Unreleased section of in the CHANGELOG.md as per [format](https://keepachangelog.com/en/1.0.0/). +- [ ] Local clean build passes without issue or fail tests (`build.ps1 -ResolveDependency`). +- [ ] Resource documentation added/updated in README.md. +- [ ] Resource parameter descriptions added/updated in README.md, schema.mof + and comment-based help. +- [ ] Comment-based help added/updated. +- [ ] Localization strings added/updated in all localization files as appropriate. +- [ ] Examples appropriately added/updated. +- [ ] Unit tests added/updated. See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). +- [ ] Integration tests added/updated (where possible). See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). +- [ ] New/changed code adheres to [DSC Resource Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md) and [Best Practices](https://github.com/PowerShell/DscResources/blob/master/BestPractices.md). diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..17c483df --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +output/ + +**.bak +*.local.* +!**/README.md +.kitchen/ + +*.nupkg +*.suo +*.user +*.coverage +.vs +.psproj +.sln +markdownissues.txt +node_modules +package-lock.json diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..87b7da56 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,10 @@ +{ + "default": true, + "MD029": { + "style": "one" + }, + "MD013": true, + "MD024": false, + "MD034": false, + "no-hard-tabs": true +} diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 new file mode 100644 index 00000000..78312d2c --- /dev/null +++ b/.vscode/analyzersettings.psd1 @@ -0,0 +1,44 @@ +@{ + CustomRulePath = '.\output\RequiredModules\DscResource.AnalyzerRules' + includeDefaultRules = $true + IncludeRules = @( + # DSC Resource Kit style guideline rules. + 'PSAvoidDefaultValueForMandatoryParameter', + 'PSAvoidDefaultValueSwitchParameter', + 'PSAvoidInvokingEmptyMembers', + 'PSAvoidNullOrEmptyHelpMessageAttribute', + 'PSAvoidUsingCmdletAliases', + 'PSAvoidUsingComputerNameHardcoded', + 'PSAvoidUsingDeprecatedManifestFields', + 'PSAvoidUsingEmptyCatchBlock', + 'PSAvoidUsingInvokeExpression', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidShouldContinueWithoutForce', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingWriteHost', + 'PSDSCReturnCorrectTypesForDSCFunctions', + 'PSDSCStandardDSCFunctionsInResource', + 'PSDSCUseIdenticalMandatoryParametersForDSC', + 'PSDSCUseIdenticalParametersForDSC', + 'PSMisleadingBacktick', + 'PSMissingModuleManifestField', + 'PSPossibleIncorrectComparisonWithNull', + 'PSProvideCommentHelp', + 'PSReservedCmdletChar', + 'PSReservedParams', + 'PSUseApprovedVerbs', + 'PSUseCmdletCorrectly', + 'PSUseOutputTypeCorrectly', + 'PSAvoidGlobalVars', + 'PSAvoidUsingConvertToSecureStringWithPlainText', + 'PSAvoidUsingPlainTextForPassword', + 'PSAvoidUsingUsernameAndPasswordParams', + 'PSDSCUseVerboseMessageInDSCResource', + 'PSShouldProcess', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUsePSCredentialType', + + 'Measure-*' + ) + +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..bbd4a82c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "davidanson.vscode-markdownlint", + "ms-vscode.powershell", + "streetsidesoftware.code-spell-checker", + "redhat.vscode-yaml" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8bf1c69c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,41 @@ +{ + "powershell.codeFormatting.openBraceOnSameLine": false, + "powershell.codeFormatting.newLineAfterOpenBrace": true, + "powershell.codeFormatting.newLineAfterCloseBrace": true, + "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, + "powershell.codeFormatting.whitespaceBeforeOpenParen": true, + "powershell.codeFormatting.whitespaceAroundOperator": true, + "powershell.codeFormatting.whitespaceAfterSeparator": true, + "powershell.codeFormatting.ignoreOneLineBlock": false, + "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationAfterEveryPipeline", + "powershell.codeFormatting.preset": "Custom", + "powershell.codeFormatting.alignPropertyValuePairs": true, + "powershell.developer.bundledModulesPath": "${cwd}/output/RequiredModules", + "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1", + "powershell.scriptAnalysis.enable": true, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, + "files.associations": { + "*.ps1xml": "xml" + }, + "cSpell.words": [ + "COMPANYNAME", + "ICONURI", + "LICENSEURI", + "PROJECTURI", + "RELEASENOTES", + "buildhelpers", + "endregion", + "gitversion", + "icontains", + "keepachangelog", + "notin", + "pscmdlet", + "steppable" + ], + "[markdown]": { + "files.trimTrailingWhitespace": false, + "files.encoding": "utf8" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..29911402 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,125 @@ +{ + "version": "2.0.0", + "_runner": "terminal", + "windows": { + "options": { + "shell": { + "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command" + ] + } + } + }, + "linux": { + "options": { + "shell": { + "executable": "/usr/bin/pwsh", + "args": [ + "-NoProfile", + "-Command" + ] + } + } + }, + "osx": { + "options": { + "shell": { + "executable": "/usr/local/bin/pwsh", + "args": [ + "-NoProfile", + "-Command" + ] + } + } + }, + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "&${cwd}/build.ps1", + "args": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "new", + "clear": false + }, + "runOptions": { + "runOn": "default" + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + }, + { + "label": "test", + "type": "shell", + "command": "&${cwd}/build.ps1", + "args": ["-AutoRestore","-Tasks","test"], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..0590d24a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog for FabricTools + +The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- For new features. + +### Changed + +- For changes in existing functionality. + +### Deprecated + +- For soon-to-be removed features. + +### Removed + +- For now removed features. + +### Fixed + +- For any bug fix. + +### Security + +- In case of vulnerabilities. + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..d7589ddb --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +This project has adopted the [DSC Community Code of Conduct](https://dsccommunity.org/code_of_conduct). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..3544bccb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# Contributing + +Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). + +## Running the Tests + +If want to know how to run this module's tests you can look at the [Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines/#running-tests) diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..cfbd9d96 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,40 @@ +mode: ContinuousDelivery +next-version: 0.0.1 +major-version-bump-message: '(breaking\schange|breaking|major)\b' +minor-version-bump-message: '(adds?|features?|minor)\b' +patch-version-bump-message: '\s?(fix|patch)' +no-bump-message: '\+semver:\s?(none|skip)' +assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}' +branches: + master: + tag: preview + regex: ^ It lets you pause and resume Fabric capacities.$ + pull-request: + tag: PR + feature: + tag: useBranchName + increment: Minor + regex: f(eature(s)?)?[\/-] + source-branches: ['master'] + hotfix: + tag: fix + increment: Patch + regex: (hot)?fix(es)?[\/-] + source-branches: ['master'] + +ignore: + sha: [] +merge-message-formats: {} + + +# feature: +# tag: useBranchName +# increment: Minor +# regex: f(eature(s)?)?[/-] +# source-branches: ['master'] +# hotfix: +# tag: fix +# increment: Patch +# regex: (hot)?fix(es)?[/-] +# source-branches: ['master'] + diff --git a/README.md b/README.md new file mode 100644 index 00000000..c3229a98 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# FabricTools + +A module to be able to do more with Microsoft Fabric. + +## Make it yours + +--- +Generated with Plaster and the SampleModule template + + +This is a sample Readme + +## Make it yours diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 new file mode 100644 index 00000000..5c3024f1 --- /dev/null +++ b/RequiredModules.psd1 @@ -0,0 +1,29 @@ +@{ + <# + This is only required if you need to use the method PowerShellGet & PSDepend + It is not required for PSResourceGet or ModuleFast (and will be ignored). + See Resolve-Dependency.psd1 on how to enable methods. + #> + #PSDependOptions = @{ + # AddToPath = $true + # Target = 'output\RequiredModules' + # Parameters = @{ + # Repository = 'PSGallery' + # } + #} + + InvokeBuild = 'latest' + PSScriptAnalyzer = 'latest' + Pester = 'latest' + ModuleBuilder = 'latest' + ChangelogManagement = 'latest' + Sampler = 'latest' + 'Sampler.GitHubTasks' = 'latest' + MarkdownLinkCheck = 'latest' + 'DscResource.Common' = 'latest' + 'DscResource.Test' = 'latest' + 'DscResource.AnalyzerRules' = 'latest' + xDscResourceDesigner = 'latest' + 'DscResource.DocGenerator' = 'latest' +} + diff --git a/Resolve-Dependency.ps1 b/Resolve-Dependency.ps1 new file mode 100644 index 00000000..17cc98ec --- /dev/null +++ b/Resolve-Dependency.ps1 @@ -0,0 +1,1060 @@ +<# + .DESCRIPTION + Bootstrap script for PSDepend. + + .PARAMETER DependencyFile + Specifies the configuration file for the this script. The default value is + 'RequiredModules.psd1' relative to this script's path. + + .PARAMETER PSDependTarget + Path for PSDepend to be bootstrapped and save other dependencies. + Can also be CurrentUser or AllUsers if you wish to install the modules in + such scope. The default value is 'output/RequiredModules' relative to + this script's path. + + .PARAMETER Proxy + Specifies the URI to use for Proxy when attempting to bootstrap + PackageProvider and PowerShellGet. + + .PARAMETER ProxyCredential + Specifies the credential to contact the Proxy when provided. + + .PARAMETER Scope + Specifies the scope to bootstrap the PackageProvider and PSGet if not available. + THe default value is 'CurrentUser'. + + .PARAMETER Gallery + Specifies the gallery to use when bootstrapping PackageProvider, PSGet and + when calling PSDepend (can be overridden in Dependency files). The default + value is 'PSGallery'. + + .PARAMETER GalleryCredential + Specifies the credentials to use with the Gallery specified above. + + .PARAMETER AllowOldPowerShellGetModule + Allow you to use a locally installed version of PowerShellGet older than + 1.6.0 (not recommended). Default it will install the latest PowerShellGet + if an older version than 2.0 is detected. + + .PARAMETER MinimumPSDependVersion + Allow you to specify a minimum version fo PSDepend, if you're after specific + features. + + .PARAMETER AllowPrerelease + Not yet written. + + .PARAMETER WithYAML + Not yet written. + + .PARAMETER UseModuleFast + Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies + faster. + + .PARAMETER ModuleFastBleedingEdge + Specifies to use ModuleFast code that is in the ModuleFast's main branch + in its GitHub repository. The parameter UseModuleFast must also be set to + true. + + .PARAMETER UsePSResourceGet + Specifies to use the new PSResourceGet module instead of the (now legacy) PowerShellGet module. + + .PARAMETER PSResourceGetVersion + String specifying the module version for PSResourceGet if the `UsePSResourceGet` switch is utilized. + + .NOTES + Load defaults for parameters values from Resolve-Dependency.psd1 if not + provided as parameter. +#> +[CmdletBinding()] +param +( + [Parameter()] + [System.String] + $DependencyFile = 'RequiredModules.psd1', + + [Parameter()] + [System.String] + $PSDependTarget = (Join-Path -Path $PSScriptRoot -ChildPath 'output/RequiredModules'), + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [ValidateSet('CurrentUser', 'AllUsers')] + [System.String] + $Scope = 'CurrentUser', + + [Parameter()] + [System.String] + $Gallery = 'PSGallery', + + [Parameter()] + [System.Management.Automation.PSCredential] + $GalleryCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $AllowOldPowerShellGetModule, + + [Parameter()] + [System.String] + $MinimumPSDependVersion, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $AllowPrerelease, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $WithYAML, + + [Parameter()] + [System.Collections.Hashtable] + $RegisterGallery, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UseModuleFast, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ModuleFastBleedingEdge, + + [Parameter()] + [System.String] + $ModuleFastVersion, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UsePSResourceGet, + + [Parameter()] + [System.String] + $PSResourceGetVersion, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UsePowerShellGetCompatibilityModule, + + [Parameter()] + [System.String] + $UsePowerShellGetCompatibilityModuleVersion +) + +try +{ + if ($PSVersionTable.PSVersion.Major -le 5) + { + if (-not (Get-Command -Name 'Import-PowerShellDataFile' -ErrorAction 'SilentlyContinue')) + { + Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion '3.1.0.0' + } + } + + Write-Verbose -Message 'Importing Bootstrap default parameters from ''$PSScriptRoot/Resolve-Dependency.psd1''.' + + $resolveDependencyConfigPath = Join-Path -Path $PSScriptRoot -ChildPath '.\Resolve-Dependency.psd1' -Resolve -ErrorAction 'Stop' + + $resolveDependencyDefaults = Import-PowerShellDataFile -Path $resolveDependencyConfigPath + + $parameterToDefault = $MyInvocation.MyCommand.ParameterSets.Where{ $_.Name -eq $PSCmdlet.ParameterSetName }.Parameters.Keys + + if ($parameterToDefault.Count -eq 0) + { + $parameterToDefault = $MyInvocation.MyCommand.Parameters.Keys + } + + # Set the parameters available in the Parameter Set, or it's not possible to choose yet, so all parameters are an option. + foreach ($parameterName in $parameterToDefault) + { + if (-not $PSBoundParameters.Keys.Contains($parameterName) -and $resolveDependencyDefaults.ContainsKey($parameterName)) + { + Write-Verbose -Message "Setting parameter '$parameterName' to value '$($resolveDependencyDefaults[$parameterName])'." + + try + { + $variableValue = $resolveDependencyDefaults[$parameterName] + + if ($variableValue -is [System.String]) + { + $variableValue = $ExecutionContext.InvokeCommand.ExpandString($variableValue) + } + + $PSBoundParameters.Add($parameterName, $variableValue) + + Set-Variable -Name $parameterName -Value $variableValue -Force -ErrorAction 'SilentlyContinue' + } + catch + { + Write-Verbose -Message "Error adding default for $parameterName : $($_.Exception.Message)." + } + } + } +} +catch +{ + Write-Warning -Message "Error attempting to import Bootstrap's default parameters from '$resolveDependencyConfigPath': $($_.Exception.Message)." +} + +# Handle when both ModuleFast and PSResourceGet is configured or/and passed as parameter. +if ($UseModuleFast -and $UsePSResourceGet) +{ + Write-Information -MessageData 'Both ModuleFast and PSResourceGet is configured or/and passed as parameter.' -InformationAction 'Continue' + + if ($PSVersionTable.PSVersion -ge '7.2') + { + $UsePSResourceGet = $false + + Write-Information -MessageData 'PowerShell 7.2 or higher being used, prefer ModuleFast over PSResourceGet.' -InformationAction 'Continue' + } + else + { + $UseModuleFast = $false + + Write-Information -MessageData 'Windows PowerShell or PowerShell <=7.1 is being used, prefer PSResourceGet since ModuleFast is not supported on this version of PowerShell.' -InformationAction 'Continue' + } +} + +# Only bootstrap ModuleFast if it is not already imported. +if ($UseModuleFast -and -not (Get-Module -Name 'ModuleFast')) +{ + try + { + $moduleFastBootstrapScriptBlockParameters = @{} + + if ($ModuleFastBleedingEdge) + { + Write-Information -MessageData 'ModuleFast is configured to use Bleeding Edge (directly from ModuleFast''s main branch).' -InformationAction 'Continue' + + $moduleFastBootstrapScriptBlockParameters.UseMain = $true + } + elseif($ModuleFastVersion) + { + if ($ModuleFastVersion -notmatch 'v') + { + $ModuleFastVersion = 'v{0}' -f $ModuleFastVersion + } + + Write-Information -MessageData ('ModuleFast is configured to use version {0}.' -f $ModuleFastVersion) -InformationAction 'Continue' + + $moduleFastBootstrapScriptBlockParameters.Release = $ModuleFastVersion + } + else + { + Write-Information -MessageData 'ModuleFast is configured to use latest released version.' -InformationAction 'Continue' + } + + $moduleFastBootstrapUri = 'bit.ly/modulefast' # cSpell: disable-line + + Write-Debug -Message ('Using bootstrap script at {0}' -f $moduleFastBootstrapUri) + + $invokeWebRequestParameters = @{ + Uri = $moduleFastBootstrapUri + ErrorAction = 'Stop' + } + + $moduleFastBootstrapScript = Invoke-WebRequest @invokeWebRequestParameters + + $moduleFastBootstrapScriptBlock = [ScriptBlock]::Create($moduleFastBootstrapScript) + + & $moduleFastBootstrapScriptBlock @moduleFastBootstrapScriptBlockParameters + } + catch + { + Write-Warning -Message ('ModuleFast could not be bootstrapped. Reverting to PSResourceGet. Error: {0}' -f $_.Exception.Message) + + $UseModuleFast = $false + $UsePSResourceGet = $true + } +} + +if ($UsePSResourceGet) +{ + $psResourceGetModuleName = 'Microsoft.PowerShell.PSResourceGet' + + # If PSResourceGet was used prior it will be locked and we can't replace it. + if ((Test-Path -Path "$PSDependTarget/$psResourceGetModuleName" -PathType 'Container') -and (Get-Module -Name $psResourceGetModuleName)) + { + Write-Information -MessageData ('{0} is already bootstrapped and imported into the session. If there is a need to refresh the module, open a new session and resolve dependencies again.' -f $psResourceGetModuleName) -InformationAction 'Continue' + } + else + { + Write-Debug -Message ('{0} do not exist, saving the module to RequiredModules.' -f $psResourceGetModuleName) + + $psResourceGetDownloaded = $false + + try + { + if (-not $PSResourceGetVersion) + { + # Default to latest version if no version is passed in parameter or specified in configuration. + $psResourceGetUri = "https://www.powershellgallery.com/api/v2/package/$psResourceGetModuleName" + } + else + { + $psResourceGetUri = "https://www.powershellgallery.com/api/v2/package/$psResourceGetModuleName/$PSResourceGetVersion" + } + + $invokeWebRequestParameters = @{ + # TODO: Should support proxy parameters passed to the script. + Uri = $psResourceGetUri + OutFile = "$PSDependTarget/$psResourceGetModuleName.nupkg" # cSpell: ignore nupkg + ErrorAction = 'Stop' + } + + $previousProgressPreference = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' + + # Bootstrapping Microsoft.PowerShell.PSResourceGet. + Invoke-WebRequest @invokeWebRequestParameters + + $ProgressPreference = $previousProgressPreference + + $psResourceGetDownloaded = $true + } + catch + { + Write-Warning -Message ('{0} could not be bootstrapped. Reverting to PowerShellGet. Error: {1}' -f $psResourceGetModuleName, $_.Exception.Message) + } + + $UsePSResourceGet = $false + + if ($psResourceGetDownloaded) + { + # On Windows PowerShell the command Expand-Archive do not like .nupkg as a zip archive extension. + $zipFileName = ((Split-Path -Path $invokeWebRequestParameters.OutFile -Leaf) -replace 'nupkg', 'zip') + + $renameItemParameters = @{ + Path = $invokeWebRequestParameters.OutFile + NewName = $zipFileName + Force = $true + } + + Rename-Item @renameItemParameters + + $psResourceGetZipArchivePath = Join-Path -Path (Split-Path -Path $invokeWebRequestParameters.OutFile -Parent) -ChildPath $zipFileName + + $expandArchiveParameters = @{ + Path = $psResourceGetZipArchivePath + DestinationPath = "$PSDependTarget/$psResourceGetModuleName" + Force = $true + } + + Expand-Archive @expandArchiveParameters + + Remove-Item -Path $psResourceGetZipArchivePath + + Import-Module -Name $expandArchiveParameters.DestinationPath -Force + + # Successfully bootstrapped PSResourceGet, so let's use it. + $UsePSResourceGet = $true + } + } + + if ($UsePSResourceGet) + { + $psResourceGetModule = Get-Module -Name $psResourceGetModuleName + + $psResourceGetModuleVersion = $psResourceGetModule.Version.ToString() + + if ($psResourceGetModule.PrivateData.PSData.Prerelease) + { + $psResourceGetModuleVersion += '-{0}' -f $psResourceGetModule.PrivateData.PSData.Prerelease + } + + Write-Information -MessageData ('Using {0} v{1}.' -f $psResourceGetModuleName, $psResourceGetModuleVersion) -InformationAction 'Continue' + + if ($UsePowerShellGetCompatibilityModule) + { + $savePowerShellGetParameters = @{ + Name = 'PowerShellGet' + Path = $PSDependTarget + Repository = 'PSGallery' + TrustRepository = $true + } + + if ($UsePowerShellGetCompatibilityModuleVersion) + { + $savePowerShellGetParameters.Version = $UsePowerShellGetCompatibilityModuleVersion + + # Check if the version is a prerelease. + if ($UsePowerShellGetCompatibilityModuleVersion -match '\d+\.\d+\.\d+-.*') + { + $savePowerShellGetParameters.Prerelease = $true + } + } + + Save-PSResource @savePowerShellGetParameters + + Import-Module -Name "$PSDependTarget/PowerShellGet" + } + } +} + +# Check if legacy PowerShellGet and PSDepend must be bootstrapped. +if (-not ($UseModuleFast -or $UsePSResourceGet)) +{ + if ($PSVersionTable.PSVersion.Major -le 5) + { + <# + Making sure the imported PackageManagement module is not from PS7 module + path. The VSCode PS extension is changing the $env:PSModulePath and + prioritize the PS7 path. This is an issue with PowerShellGet because + it loads an old version if available (or fail to load latest). + #> + Get-Module -ListAvailable PackageManagement | + Where-Object -Property 'ModuleBase' -NotMatch 'powershell.7' | + Select-Object -First 1 | + Import-Module -Force + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 0 -CurrentOperation 'NuGet Bootstrap' + + $importModuleParameters = @{ + Name = 'PowerShellGet' + MinimumVersion = '2.0' + MaximumVersion = '2.8.999' + ErrorAction = 'SilentlyContinue' + PassThru = $true + } + + if ($AllowOldPowerShellGetModule) + { + $importModuleParameters.Remove('MinimumVersion') + } + + $powerShellGetModule = Import-Module @importModuleParameters + + # Install the package provider if it is not available. + $nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable -ErrorAction 'SilentlyContinue' | + Select-Object -First 1 + + if (-not $powerShellGetModule -and -not $nuGetProvider) + { + $providerBootstrapParameters = @{ + Name = 'NuGet' + Force = $true + ForceBootstrap = $true + ErrorAction = 'Stop' + Scope = $Scope + } + + switch ($PSBoundParameters.Keys) + { + 'Proxy' + { + $providerBootstrapParameters.Add('Proxy', $Proxy) + } + + 'ProxyCredential' + { + $providerBootstrapParameters.Add('ProxyCredential', $ProxyCredential) + } + + 'AllowPrerelease' + { + $providerBootstrapParameters.Add('AllowPrerelease', $AllowPrerelease) + } + } + + Write-Information -MessageData 'Bootstrap: Installing NuGet Package Provider from the web (Make sure Microsoft addresses/ranges are allowed).' + + $null = Install-PackageProvider @providerBootstrapParameters + + $nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable | Select-Object -First 1 + + $nuGetProviderVersion = $nuGetProvider.Version.ToString() + + Write-Information -MessageData "Bootstrap: Importing NuGet Package Provider version $nuGetProviderVersion to current session." + + $Null = Import-PackageProvider -Name 'NuGet' -RequiredVersion $nuGetProviderVersion -Force + } + + if ($RegisterGallery) + { + if ($RegisterGallery.ContainsKey('Name') -and -not [System.String]::IsNullOrEmpty($RegisterGallery.Name)) + { + $Gallery = $RegisterGallery.Name + } + else + { + $RegisterGallery.Name = $Gallery + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 7 -CurrentOperation "Verifying private package repository '$Gallery'" -Completed + + $previousRegisteredRepository = Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue' + + if ($previousRegisteredRepository.SourceLocation -ne $RegisterGallery.SourceLocation) + { + if ($previousRegisteredRepository) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 9 -CurrentOperation "Re-registrering private package repository '$Gallery'" -Completed + + Unregister-PSRepository -Name $Gallery + + $unregisteredPreviousRepository = $true + } + else + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 9 -CurrentOperation "Registering private package repository '$Gallery'" -Completed + } + + Register-PSRepository @RegisterGallery + } + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 10 -CurrentOperation "Ensuring Gallery $Gallery is trusted" + + # Fail if the given PSGallery is not registered. + $previousGalleryInstallationPolicy = (Get-PSRepository -Name $Gallery -ErrorAction 'Stop').Trusted + + $updatedGalleryInstallationPolicy = $false + + if ($previousGalleryInstallationPolicy -ne $true) + { + $updatedGalleryInstallationPolicy = $true + + # Only change policy if the repository is not trusted + Set-PSRepository -Name $Gallery -InstallationPolicy 'Trusted' -ErrorAction 'Ignore' + } +} + +try +{ + # Check if legacy PowerShellGet and PSDepend must be used. + if (-not ($UseModuleFast -or $UsePSResourceGet)) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 25 -CurrentOperation 'Checking PowerShellGet' + + # Ensure the module is loaded and retrieve the version you have. + $powerShellGetVersion = (Import-Module -Name 'PowerShellGet' -PassThru -ErrorAction 'SilentlyContinue').Version + + Write-Verbose -Message "Bootstrap: The PowerShellGet version is $powerShellGetVersion" + + # Versions below 2.0 are considered old, unreliable & not recommended + if (-not $powerShellGetVersion -or ($powerShellGetVersion -lt [System.Version] '2.0' -and -not $AllowOldPowerShellGetModule)) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 40 -CurrentOperation 'Fetching newer version of PowerShellGet' + + # PowerShellGet module not found, installing or saving it. + if ($PSDependTarget -in 'CurrentUser', 'AllUsers') + { + Write-Debug -Message "PowerShellGet module not found. Attempting to install from Gallery $Gallery." + + Write-Warning -Message "Installing PowerShellGet in $PSDependTarget Scope." + + $installPowerShellGetParameters = @{ + Name = 'PowerShellGet' + Force = $true + SkipPublisherCheck = $true + AllowClobber = $true + Scope = $Scope + Repository = $Gallery + MaximumVersion = '2.8.999' + } + + switch ($PSBoundParameters.Keys) + { + 'Proxy' + { + $installPowerShellGetParameters.Add('Proxy', $Proxy) + } + + 'ProxyCredential' + { + $installPowerShellGetParameters.Add('ProxyCredential', $ProxyCredential) + } + + 'GalleryCredential' + { + $installPowerShellGetParameters.Add('Credential', $GalleryCredential) + } + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation 'Installing newer version of PowerShellGet' + + Install-Module @installPowerShellGetParameters + } + else + { + Write-Debug -Message "PowerShellGet module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + + $saveModuleParameters = @{ + Name = 'PowerShellGet' + Repository = $Gallery + Path = $PSDependTarget + Force = $true + MaximumVersion = '2.8.999' + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation "Saving PowerShellGet from $Gallery to $Scope" + + Save-Module @saveModuleParameters + } + + Write-Debug -Message 'Removing previous versions of PowerShellGet and PackageManagement from session' + + Get-Module -Name 'PowerShellGet' -All | Remove-Module -Force -ErrorAction 'SilentlyContinue' + Get-Module -Name 'PackageManagement' -All | Remove-Module -Force + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 65 -CurrentOperation 'Loading latest version of PowerShellGet' + + Write-Debug -Message 'Importing latest PowerShellGet and PackageManagement versions into session' + + if ($AllowOldPowerShellGetModule) + { + $powerShellGetModule = Import-Module -Name 'PowerShellGet' -Force -PassThru + } + else + { + Import-Module -Name 'PackageManagement' -MinimumVersion '1.4.8.1' -Force + + $powerShellGetModule = Import-Module -Name 'PowerShellGet' -MinimumVersion '2.2.5' -Force -PassThru + } + + $powerShellGetVersion = $powerShellGetModule.Version.ToString() + + Write-Information -MessageData "Bootstrap: PowerShellGet version loaded is $powerShellGetVersion" + } + + # Try to import the PSDepend module from the available modules. + $getModuleParameters = @{ + Name = 'PSDepend' + ListAvailable = $true + } + + $psDependModule = Get-Module @getModuleParameters + + if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) + { + try + { + $psDependModule = $psDependModule | Where-Object -FilterScript { $_.Version -ge $MinimumPSDependVersion } + } + catch + { + throw ('There was a problem finding the minimum version of PSDepend. Error: {0}' -f $_) + } + } + + if (-not $psDependModule) + { + Write-Debug -Message 'PSDepend module not found.' + + # PSDepend module not found, installing or saving it. + if ($PSDependTarget -in 'CurrentUser', 'AllUsers') + { + Write-Debug -Message "Attempting to install from Gallery '$Gallery'." + + Write-Warning -Message "Installing PSDepend in $PSDependTarget Scope." + + $installPSDependParameters = @{ + Name = 'PSDepend' + Repository = $Gallery + Force = $true + Scope = $PSDependTarget + SkipPublisherCheck = $true + AllowClobber = $true + } + + if ($MinimumPSDependVersion) + { + $installPSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Installing PSDepend from $Gallery" + + Install-Module @installPSDependParameters + } + else + { + Write-Debug -Message "Attempting to Save from Gallery $Gallery to $PSDependTarget" + + $saveModuleParameters = @{ + Name = 'PSDepend' + Repository = $Gallery + Path = $PSDependTarget + Force = $true + } + + if ($MinimumPSDependVersion) + { + $saveModuleParameters.add('MinimumVersion', $MinimumPSDependVersion) + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Saving PSDepend from $Gallery to $PSDependTarget" + + Save-Module @saveModuleParameters + } + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 80 -CurrentOperation 'Importing PSDepend' + + $importModulePSDependParameters = @{ + Name = 'PSDepend' + ErrorAction = 'Stop' + Force = $true + } + + if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) + { + $importModulePSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) + } + + # We should have successfully bootstrapped PSDepend. Fail if not available. + $null = Import-Module @importModulePSDependParameters + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 81 -CurrentOperation 'Invoke PSDepend' + + if ($WithYAML) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 82 -CurrentOperation 'Verifying PowerShell module PowerShell-Yaml' + + if (-not (Get-Module -ListAvailable -Name 'PowerShell-Yaml')) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 85 -CurrentOperation 'Installing PowerShell module PowerShell-Yaml' + + Write-Verbose -Message "PowerShell-Yaml module not found. Attempting to Save from Gallery '$Gallery' to '$PSDependTarget'." + + $SaveModuleParam = @{ + Name = 'PowerShell-Yaml' + Repository = $Gallery + Path = $PSDependTarget + Force = $true + } + + Save-Module @SaveModuleParam + } + else + { + Write-Verbose -Message 'PowerShell-Yaml is already available' + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 88 -CurrentOperation 'Importing PowerShell module PowerShell-Yaml' + } + } + + if (Test-Path -Path $DependencyFile) + { + if ($UseModuleFast -or $UsePSResourceGet) + { + $requiredModules = Import-PowerShellDataFile -Path $DependencyFile + + $requiredModules = $requiredModules.GetEnumerator() | + Where-Object -FilterScript { $_.Name -ne 'PSDependOptions' } + + if ($UseModuleFast) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking ModuleFast' + + Write-Progress -Activity 'ModuleFast:' -PercentComplete 0 -CurrentOperation 'Restoring Build Dependencies' + + $modulesToSave = @( + 'PSDepend' # Always include PSDepend for backward compatibility. + ) + + if ($WithYAML) + { + $modulesToSave += 'PowerShell-Yaml' + } + + if ($UsePowerShellGetCompatibilityModule) + { + Write-Debug -Message 'PowerShellGet compatibility module is configured to be used.' + + # This is needed to ensure that the PowerShellGet compatibility module works. + $psResourceGetModuleName = 'Microsoft.PowerShell.PSResourceGet' + + if ($PSResourceGetVersion) + { + $modulesToSave += ('{0}:[{1}]' -f $psResourceGetModuleName, $PSResourceGetVersion) + } + else + { + $modulesToSave += $psResourceGetModuleName + } + + $powerShellGetCompatibilityModuleName = 'PowerShellGet' + + if ($UsePowerShellGetCompatibilityModuleVersion) + { + $modulesToSave += ('{0}:[{1}]' -f $powerShellGetCompatibilityModuleName, $UsePowerShellGetCompatibilityModuleVersion) + } + else + { + $modulesToSave += $powerShellGetCompatibilityModuleName + } + } + + foreach ($requiredModule in $requiredModules) + { + # If the RequiredModules.psd1 entry is an Hashtable then special handling is needed. + if ($requiredModule.Value -is [System.Collections.Hashtable]) + { + if (-not $requiredModule.Value.Version) + { + $requiredModuleVersion = 'latest' + } + else + { + $requiredModuleVersion = $requiredModule.Value.Version + } + + if ($requiredModuleVersion -eq 'latest') + { + $moduleNameSuffix = '' + + if ($requiredModule.Value.Parameters.AllowPrerelease -eq $true) + { + <# + Adding '!' to the module name indicate to ModuleFast + that is should also evaluate pre-releases. + #> + $moduleNameSuffix = '!' + } + + $modulesToSave += ('{0}{1}' -f $requiredModule.Name, $moduleNameSuffix) + } + else + { + $modulesToSave += ('{0}:[{1}]' -f $requiredModule.Name, $requiredModuleVersion) + } + } + else + { + if ($requiredModule.Value -eq 'latest') + { + $modulesToSave += $requiredModule.Name + } + else + { + # Handle different nuget version operators already present. + if ($requiredModule.Value -match '[!|:|[|(|,|>|<|=]') + { + $modulesToSave += ('{0}{1}' -f $requiredModule.Name, $requiredModule.Value) + } + else + { + # Assuming the version is a fixed version. + $modulesToSave += ('{0}:[{1}]' -f $requiredModule.Name, $requiredModule.Value) + } + } + } + } + + Write-Debug -Message ("Required modules to retrieve plan for:`n{0}" -f ($modulesToSave | Out-String)) + + $installModuleFastParameters = @{ + Destination = $PSDependTarget + DestinationOnly = $true + NoPSModulePathUpdate = $true + NoProfileUpdate = $true + Update = $true + Confirm = $false + } + + $moduleFastPlan = Install-ModuleFast -Specification $modulesToSave -Plan @installModuleFastParameters + + Write-Debug -Message ("Missing modules that need to be saved:`n{0}" -f ($moduleFastPlan | Out-String)) + + if ($moduleFastPlan) + { + # Clear all modules in plan from the current session so they can be fetched again. + $moduleFastPlan.Name | Get-Module | Remove-Module -Force + + $moduleFastPlan | Install-ModuleFast @installModuleFastParameters + } + else + { + Write-Verbose -Message 'All required modules were already up to date' + } + + Write-Progress -Activity 'ModuleFast:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed + } + + if ($UsePSResourceGet) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking PSResourceGet' + + $modulesToSave = @( + @{ + Name = 'PSDepend' # Always include PSDepend for backward compatibility. + } + ) + + if ($WithYAML) + { + $modulesToSave += @{ + Name = 'PowerShell-Yaml' + } + } + + # Prepare hashtable that can be concatenated to the Save-PSResource parameters. + foreach ($requiredModule in $requiredModules) + { + # If the RequiredModules.psd1 entry is an Hashtable then special handling is needed. + if ($requiredModule.Value -is [System.Collections.Hashtable]) + { + $saveModuleHashtable = @{ + Name = $requiredModule.Name + } + + if ($requiredModule.Value.Version -and $requiredModule.Value.Version -ne 'latest') + { + $saveModuleHashtable.Version = $requiredModule.Value.Version + } + + if ($requiredModule.Value.Parameters.AllowPrerelease -eq $true) + { + $saveModuleHashtable.Prerelease = $true + } + + $modulesToSave += $saveModuleHashtable + } + else + { + if ($requiredModule.Value -eq 'latest') + { + $modulesToSave += @{ + Name = $requiredModule.Name + } + } + else + { + $modulesToSave += @{ + Name = $requiredModule.Name + Version = $requiredModule.Value + } + } + } + } + + $percentagePerModule = [System.Math]::Floor(100 / $modulesToSave.Length) + + $progressPercentage = 0 + + Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' + + foreach ($currentModule in $modulesToSave) + { + Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Saving module {0}' -f $savePSResourceParameters.Name) + + $savePSResourceParameters = @{ + Path = $PSDependTarget + TrustRepository = $true + Confirm = $false + } + + # Concatenate the module parameters to the Save-PSResource parameters. + $savePSResourceParameters += $currentModule + + # Modules that Sampler depend on that cannot be refreshed without a new session. + $skipModule = @('PowerShell-Yaml') + + if ($savePSResourceParameters.Name -in $skipModule -and (Get-Module -Name $savePSResourceParameters.Name)) + { + Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Skipping module {0}' -f $savePSResourceParameters.Name) + + Write-Information -MessageData ('Skipping the module {0} since it cannot be refresh while loaded into the session. To refresh the module open a new session and resolve dependencies again.' -f $savePSResourceParameters.Name) -InformationAction 'Continue' + } + else + { + # Clear all module from the current session so any new version fetched will be re-imported. + Get-Module -Name $savePSResourceParameters.Name | Remove-Module -Force + + Save-PSResource @savePSResourceParameters -ErrorVariable 'savePSResourceError' + + if ($savePSResourceError) + { + Write-Warning -Message 'Save-PSResource could not save (replace) one or more dependencies. This can be due to the module is loaded into the session (and referencing assemblies). Close the current session and open a new session and try again.' + } + } + + $progressPercentage += $percentagePerModule + } + + Write-Progress -Activity 'PSResourceGet:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed + } + } + else + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking PSDepend' + + Write-Progress -Activity 'PSDepend:' -PercentComplete 0 -CurrentOperation 'Restoring Build Dependencies' + + $psDependParameters = @{ + Force = $true + Path = $DependencyFile + } + + # TODO: Handle when the Dependency file is in YAML, and -WithYAML is specified. + Invoke-PSDepend @psDependParameters + + Write-Progress -Activity 'PSDepend:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed + } + } + else + { + Write-Warning -Message "The dependency file '$DependencyFile' could not be found." + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 100 -CurrentOperation 'Bootstrap complete' -Completed +} +finally +{ + if ($RegisterGallery) + { + Write-Verbose -Message "Removing private package repository '$Gallery'." + Unregister-PSRepository -Name $Gallery + } + + if ($unregisteredPreviousRepository) + { + Write-Verbose -Message "Reverting private package repository '$Gallery' to previous location URI:s." + + $registerPSRepositoryParameters = @{ + Name = $previousRegisteredRepository.Name + InstallationPolicy = $previousRegisteredRepository.InstallationPolicy + } + + if ($previousRegisteredRepository.SourceLocation) + { + $registerPSRepositoryParameters.SourceLocation = $previousRegisteredRepository.SourceLocation + } + + if ($previousRegisteredRepository.PublishLocation) + { + $registerPSRepositoryParameters.PublishLocation = $previousRegisteredRepository.PublishLocation + } + + if ($previousRegisteredRepository.ScriptSourceLocation) + { + $registerPSRepositoryParameters.ScriptSourceLocation = $previousRegisteredRepository.ScriptSourceLocation + } + + if ($previousRegisteredRepository.ScriptPublishLocation) + { + $registerPSRepositoryParameters.ScriptPublishLocation = $previousRegisteredRepository.ScriptPublishLocation + } + + Register-PSRepository @registerPSRepositoryParameters + } + + if ($updatedGalleryInstallationPolicy -eq $true -and $previousGalleryInstallationPolicy -ne $true) + { + # Only try to revert installation policy if the repository exist + if ((Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue')) + { + # Reverting the Installation Policy for the given gallery if it was not already trusted + Set-PSRepository -Name $Gallery -InstallationPolicy 'Untrusted' + } + } + + Write-Verbose -Message 'Project Bootstrapped, returning to Invoke-Build.' +} diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 new file mode 100644 index 00000000..c72178de --- /dev/null +++ b/Resolve-Dependency.psd1 @@ -0,0 +1,76 @@ +@{ + <# + Default parameter values to be loaded by the Resolve-Dependency.ps1 script (unless set in bound parameters + when calling the script). + #> + + #PSDependTarget = './output/modules' + #Proxy = '' + #ProxyCredential = '$MyCredentialVariable' #TODO: find a way to support credentials in build (resolve variable) + + Gallery = 'PSGallery' + + # To use a private nuget repository change the following to your own feed. The locations must be a Nuget v2 feed due + # to limitation in PowerShellGet v2.x. Example below is for a Azure DevOps Server project-scoped feed. While resolving + # dependencies it will be registered as a trusted repository with the name specified in the property 'Gallery' above, + # unless property 'Name' is provided in the hashtable below, if so it will override the property 'Gallery' above. The + # registered repository will be removed when dependencies has been resolved, unless it was already registered to begin + # with. If repository is registered already but with different URL:s the repository will be re-registered and reverted + # after dependencies has been resolved. Currently only Windows integrated security works with private Nuget v2 feeds + # (or if it is a public feed with no security), it is not possible yet to securely provide other credentials for the feed. + # Private repositories will currently only work using PowerShellGet. + #RegisterGallery = @{ + # #Name = 'MyPrivateFeedName' + # GallerySourceLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' + # GalleryPublishLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' + # GalleryScriptSourceLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' + # GalleryScriptPublishLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' + # #InstallationPolicy = 'Trusted' + #} + + #AllowOldPowerShellGetModule = $true + #MinimumPSDependVersion = '0.3.0' + AllowPrerelease = $false + WithYAML = $true # Will also bootstrap PowerShell-Yaml to read other config files + + <# + Enable ModuleFast to be the default method of resolving dependencies by setting + UseModuleFast to the value $true. ModuleFast requires PowerShell 7.2 or higher. + If UseModuleFast is not configured or set to $false then PowerShellGet (or + PSResourceGet if enabled) will be used to as the default method of resolving + dependencies. You can always use the parameter `-UseModuleFast` of the + Resolve-Dependency.ps1 or build.ps1 script even when this is not configured + or set to $false. + + You can use ModuleFastVersion to specify a specific version of ModuleFast to use. + This will also affect the use of parameter `-UseModuleFast` of the Resolve-Dependency.ps1 + or build.ps1 script. If ModuleFastVersion is not configured then the latest + (non-preview) released version is used. + + ModuleFastBleedingEdge will override ModuleFastVersion and use the absolute latest + code from the ModuleFast repository. This is useful if you want to test the absolute + latest changes in ModuleFast repository. This is not recommended for production use. + By enabling ModuleFastBleedingEdge the pipeline can encounter breaking changes or + problems by code that is merged in the ModuleFast repository, this could affect the + pipeline negatively. Make sure to use a clean PowerShell session after changing + the value of ModuleFastBleedingEdge so that ModuleFast uses the correct bootstrap + script and correct parameter values. This will also affect the use of parameter + `-UseModuleFast` of the Resolve-Dependency.ps1 or build.ps1 script. + #> + #UseModuleFast = $true + #ModuleFastVersion = '0.1.2' + #ModuleFastBleedingEdge = $true + + <# + Enable PSResourceGet to be the default method of resolving dependencies by setting + UsePSResourceGet to the value $true. If UsePSResourceGet is not configured or + set to $false then PowerShellGet will be used to resolve dependencies. + #> + UsePSResourceGet = $true + PSResourceGetVersion = '1.0.1' + + # PowerShellGet compatibility module only works when using PSResourceGet or ModuleFast. + UsePowerShellGetCompatibilityModule = $true + UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' +} + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..10e5bc57 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,43 @@ +## Security + +We take the security of our modules seriously, which includes all source +code repositories managed through our GitHub organization. + +If you believe you have found a security vulnerability in any of our +repository, please report it to us as described below. + +## Reporting Security Issues + +If the repository has enabled the ability to report a security vulnerability +through GitHub new issue (separate button called "Report a vulnerability") +then use that. See [Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) +for more information. + +> [!CAUTION] +> Please do not report security vulnerabilities through a **public** GitHub issues +> or other public forum. + +If the repository does not have that option then please +report the security issue privately to one or several maintainers of the +repository. The easiest way to do so is to send us a direct message via +Twitter (X), Slack, Discord, or find us on some other social platform. + +You should receive a response within 48 hours. If for some reason you do not, +please follow up by other means or to other contributors. + +Please include the requested information listed below (as much as you can +provide) to help us better understand the nature and scope of the possible issue: + +* Type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in English. diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..897de014 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,324 @@ +trigger: + branches: + include: + - It lets you pause and resume Fabric capacities. + paths: + exclude: + - CHANGELOG.md + tags: + include: + - "v*" + exclude: + - "*-*" + +variables: + buildFolderName: output + buildArtifactName: output + testResultFolderName: testResults + defaultBranch: It lets you pause and resume Fabric capacities. + Agent.Source.Git.ShallowFetchDepth: 0 # override ShallowFetchDepth + +stages: + - stage: Build + jobs: + - job: Package_Module + displayName: 'Package Module' + pool: + vmImage: 'ubuntu-latest' + steps: + - pwsh: | + dotnet tool install --global GitVersion.Tool --version 5.* + $gitVersionObject = dotnet-gitversion | ConvertFrom-Json + $gitVersionObject.PSObject.Properties.ForEach{ + Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'." + Write-Host -Object "##vso[task.setvariable variable=$($_.Name);]$($_.Value)" + } + Write-Host -Object "##vso[build.updatebuildnumber]$($gitVersionObject.FullSemVer)" + displayName: Calculate ModuleVersion (GitVersion) + + - task: PowerShell@2 + name: package + displayName: 'Build & Package Module' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks pack' + pwsh: true + env: + ModuleVersion: $(NuGetVersionV2) + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Build Artifact' + inputs: + targetPath: '$(buildFolderName)/' + artifact: $(buildArtifactName) + publishLocation: 'pipeline' + parallel: true + + - stage: Test + dependsOn: Build + jobs: + - job: test_linux + displayName: 'Linux' + timeoutInMinutes: 0 + pool: + vmImage: 'ubuntu-latest' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + + - task: PowerShell@2 + name: test + displayName: 'Run Tests' + inputs: + filePath: './build.ps1' + arguments: '-tasks test' + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Linux' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageLinux' + parallel: true + + - job: test_windows_core + displayName: 'Windows (PowerShell)' + timeoutInMinutes: 0 + pool: + vmImage: 'windows-latest' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + + - task: PowerShell@2 + name: test + displayName: 'Run Tests' + inputs: + filePath: './build.ps1' + arguments: '-tasks test' + pwsh: true + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Windows (PowerShell)' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageWinPS7' + parallel: true + + - job: test_windows_ps + displayName: 'Windows (Windows PowerShell)' + timeoutInMinutes: 0 + pool: + vmImage: 'windows-latest' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + + - task: PowerShell@2 + name: test + displayName: 'Run Tests' + inputs: + filePath: './build.ps1' + arguments: '-tasks test' + pwsh: false + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Windows (Windows PowerShell)' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageWinPS51' + parallel: true + + - job: test_macos + displayName: 'macOS' + timeoutInMinutes: 0 + pool: + vmImage: 'macos-latest' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + + - task: PowerShell@2 + name: test + displayName: 'Run Tests' + inputs: + filePath: './build.ps1' + arguments: '-tasks test' + pwsh: true + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'MacOS' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageMacOS' + parallel: true + + # If no code coverage should be reported, then this entire removed: + - job: Code_Coverage + displayName: 'Publish Code Coverage' + dependsOn: + - test_macos + - test_linux + - test_windows_core + - test_windows_ps + pool: + vmImage: 'ubuntu-latest' + timeoutInMinutes: 0 + steps: + - pwsh: | + $repositoryOwner,$repositoryName = $env:BUILD_REPOSITORY_NAME -split '/' + echo "##vso[task.setvariable variable=RepositoryOwner;isOutput=true]$repositoryOwner" + echo "##vso[task.setvariable variable=RepositoryName;isOutput=true]$repositoryName" + name: dscBuildVariable + displayName: 'Set Environment Variables' + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Pipeline Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact macOS' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageMacOS' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact Linux' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageLinux' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact Windows (PS 5.1)' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageWinPS51' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact Windows (PS7)' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageWinPS7' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' + + # Make sure to update build.yaml to support these tasks, then uncomment these tasks: + #- task: PowerShell@2 + # name: merge + # displayName: 'Merge Code Coverage files' + # inputs: + # filePath: './build.ps1' + # arguments: '-tasks merge' + # pwsh: true + #- task: PublishCodeCoverageResults@1 + # displayName: 'Publish Azure Code Coverage' + # inputs: + # codeCoverageTool: 'JaCoCo' + # summaryFileLocation: '$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' + # pathToSources: '$(Build.SourcesDirectory)/$(dscBuildVariable.RepositoryName)/' + + # Uncomment if Codecov.io should be used (see docs at Codecov.io how to use and the required repository configuration). + #- script: | + # bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" + # displayName: 'Publish Code Coverage to Codecov.io' + + - stage: Deploy + dependsOn: Test + # Only execute deploy stage if we're on master and previous stage succeeded + condition: | + and( + succeeded(), + or( + eq(variables['Build.SourceBranch'], 'refs/heads/ It lets you pause and resume Fabric capacities.'), + startsWith(variables['Build.SourceBranch'], 'refs/tags/') + ), + contains(variables['System.TeamFoundationCollectionUri'], ' Adds functionallity previously only available with the REST API as PowerShell functions.') + ) + jobs: + - job: Deploy_Module + displayName: 'Deploy Module' + pool: + vmImage: 'ubuntu-latest' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: publishRelease + displayName: 'Publish Release' + inputs: + filePath: './build.ps1' + arguments: '-tasks publish' + pwsh: true + env: + GitHubToken: $(GitHubToken) + GalleryApiToken: $(GalleryApiToken) + ReleaseBranch: $(defaultBranch) + MainGitBranch: $(defaultBranch) + - task: PowerShell@2 + name: sendChangelogPR + displayName: 'Send Changelog PR' + inputs: + filePath: './build.ps1' + arguments: '-tasks Create_ChangeLog_GitHub_PR' + pwsh: true + env: + GitHubToken: $(GitHubToken) + ReleaseBranch: $(defaultBranch) + MainGitBranch: $(defaultBranch) + diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..f4a0faec --- /dev/null +++ b/build.ps1 @@ -0,0 +1,538 @@ +<# + .DESCRIPTION + Bootstrap and build script for PowerShell module CI/CD pipeline. + + .PARAMETER Tasks + The task or tasks to run. The default value is '.' (runs the default task). + + .PARAMETER CodeCoverageThreshold + The code coverage target threshold to uphold. Set to 0 to disable. + The default value is '' (empty string). + + .PARAMETER BuildConfig + Not yet written. + + .PARAMETER OutputDirectory + Specifies the folder to build the artefact into. The default value is 'output'. + + .PARAMETER BuiltModuleSubdirectory + Subdirectory name to build the module (under $OutputDirectory). The default + value is '' (empty string). + + .PARAMETER RequiredModulesDirectory + Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency + and PSDepend where to save the required modules. It is also possible to use + 'CurrentUser' och 'AllUsers' to install missing dependencies. You can override + the value for PSDepend in the Build.psd1 build manifest. The default value is + 'output/RequiredModules'. + + .PARAMETER PesterScript + One or more paths that will override the Pester configuration in build + configuration file when running the build task Invoke_Pester_Tests. + + If running Pester 5 test, use the alias PesterPath to be future-proof. + + .PARAMETER PesterTag + Filter which tags to run when invoking Pester tests. This is used in the + Invoke-Pester.pester.build.ps1 tasks. + + .PARAMETER PesterExcludeTag + Filter which tags to exclude when invoking Pester tests. This is used in + the Invoke-Pester.pester.build.ps1 tasks. + + .PARAMETER DscTestTag + Filter which tags to run when invoking DSC Resource tests. This is used + in the DscResource.Test.build.ps1 tasks. + + .PARAMETER DscTestExcludeTag + Filter which tags to exclude when invoking DSC Resource tests. This is + used in the DscResource.Test.build.ps1 tasks. + + .PARAMETER ResolveDependency + Not yet written. + + .PARAMETER BuildInfo + The build info object from ModuleBuilder. Defaults to an empty hashtable. + + .PARAMETER AutoRestore + Not yet written. + + .PARAMETER UseModuleFast + Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies + faster. + + .PARAMETER UsePSResourceGet + Specifies to use PSResourceGet instead of PowerShellGet to resolve dependencies + faster. This can also be configured in Resolve-Dependency.psd1. + + .PARAMETER UsePowerShellGetCompatibilityModule + Specifies to use the compatibility module PowerShellGet. This parameter + only works then the method of downloading dependencies is PSResourceGet. + This can also be configured in Resolve-Dependency.psd1. +#> +[CmdletBinding()] +param +( + [Parameter(Position = 0)] + [System.String[]] + $Tasks = '.', + + [Parameter()] + [System.String] + $CodeCoverageThreshold = '', + + [Parameter()] + [System.String] + [ValidateScript( + { Test-Path -Path $_ } + )] + $BuildConfig, + + [Parameter()] + [System.String] + $OutputDirectory = 'output', + + [Parameter()] + [System.String] + $BuiltModuleSubdirectory = '', + + [Parameter()] + [System.String] + $RequiredModulesDirectory = $(Join-Path 'output' 'RequiredModules'), + + [Parameter()] + # This alias is to prepare for the rename of this parameter to PesterPath when Pester 4 support is removed + [Alias('PesterPath')] + [System.Object[]] + $PesterScript, + + [Parameter()] + [System.String[]] + $PesterTag, + + [Parameter()] + [System.String[]] + $PesterExcludeTag, + + [Parameter()] + [System.String[]] + $DscTestTag, + + [Parameter()] + [System.String[]] + $DscTestExcludeTag, + + [Parameter()] + [Alias('bootstrap')] + [System.Management.Automation.SwitchParameter] + $ResolveDependency, + + [Parameter(DontShow)] + [AllowNull()] + [System.Collections.Hashtable] + $BuildInfo, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $AutoRestore, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UseModuleFast, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UsePSResourceGet, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UsePowerShellGetCompatibilityModule +) + +<# + The BEGIN block (at the end of this file) handles the Bootstrap of the Environment + before Invoke-Build can run the tasks if the parameter ResolveDependency (or + parameter alias Bootstrap) is specified. +#> + +process +{ + if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') + { + # Only run the process block through InvokeBuild (look at the Begin block at the bottom of this script). + return + } + + # Execute the Build process from the .build.ps1 path. + Push-Location -Path $PSScriptRoot -StackName 'BeforeBuild' + + try + { + Write-Host -Object "[build] Parsing defined tasks" -ForeGroundColor Magenta + + # Load the default BuildInfo if the parameter BuildInfo is not set. + if (-not $PSBoundParameters.ContainsKey('BuildInfo')) + { + try + { + if (Test-Path -Path $BuildConfig) + { + $configFile = Get-Item -Path $BuildConfig + + Write-Host -Object "[build] Loading Configuration from $configFile" + + $BuildInfo = switch -Regex ($configFile.Extension) + { + # Native Support for PSD1 + '\.psd1' + { + if (-not (Get-Command -Name Import-PowerShellDataFile -ErrorAction SilentlyContinue)) + { + Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion 3.1.0.0 + } + + Import-PowerShellDataFile -Path $BuildConfig + } + + # Support for yaml when module PowerShell-Yaml is available + '\.[yaml|yml]' + { + Import-Module -Name 'powershell-yaml' -ErrorAction Stop + + ConvertFrom-Yaml -Yaml (Get-Content -Raw $configFile) + } + + # Support for JSON and JSONC (by Removing comments) when module PowerShell-Yaml is available + '\.[json|jsonc]' + { + $jsonFile = Get-Content -Raw -Path $configFile + + $jsonContent = $jsonFile -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/' + + # Yaml is superset of JSON. + ConvertFrom-Yaml -Yaml $jsonContent + } + + # Unknown extension, return empty hashtable. + default + { + Write-Error -Message "Extension '$_' not supported. using @{}" + + @{ } + } + } + } + else + { + Write-Host -Object "Configuration file '$($BuildConfig.FullName)' not found" -ForegroundColor Red + + # No config file was found, return empty hashtable. + $BuildInfo = @{ } + } + } + catch + { + $logMessage = "Error loading Config '$($BuildConfig.FullName)'.`r`nAre you missing dependencies?`r`nMake sure you run './build.ps1 -ResolveDependency -tasks noop' before running build to restore the required modules." + + Write-Host -Object $logMessage -ForegroundColor Yellow + + $BuildInfo = @{ } + + Write-Error -Message $_.Exception.Message + } + } + + # If the Invoke-Build Task Header is specified in the Build Info, set it. + if ($BuildInfo.TaskHeader) + { + Set-BuildHeader -Script ([scriptblock]::Create($BuildInfo.TaskHeader)) + } + + <# + Add BuildModuleOutput to PSModule Path environment variable. + Moved here (not in begin block) because build file can contains BuiltSubModuleDirectory value. + #> + if ($BuiltModuleSubdirectory) + { + if (-not (Split-Path -IsAbsolute -Path $BuiltModuleSubdirectory)) + { + $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuiltModuleSubdirectory + } + else + { + $BuildModuleOutput = $BuiltModuleSubdirectory + } + } # test if BuiltModuleSubDirectory set in build config file + elseif ($BuildInfo.ContainsKey('BuiltModuleSubDirectory')) + { + $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuildInfo['BuiltModuleSubdirectory'] + } + else + { + $BuildModuleOutput = $OutputDirectory + } + + # Pre-pending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder. + if ($powerShellModulePaths -notcontains $BuildModuleOutput) + { + Write-Host -Object "[build] Pre-pending '$BuildModuleOutput' folder to PSModulePath" -ForegroundColor Green + + $env:PSModulePath = $BuildModuleOutput + [System.IO.Path]::PathSeparator + $env:PSModulePath + } + + <# + Import Tasks from modules via their exported aliases when defined in Build Manifest. + https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks + #> + if ($BuildInfo.ContainsKey('ModuleBuildTasks')) + { + foreach ($module in $BuildInfo['ModuleBuildTasks'].Keys) + { + try + { + Write-Host -Object "Importing tasks from module $module" -ForegroundColor DarkGray + + $loadedModule = Import-Module -Name $module -PassThru -ErrorAction Stop + + foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($module)) + { + $loadedModule.ExportedAliases.GetEnumerator().Where{ + Write-Host -Object "`t Loading $($_.Key)..." -ForegroundColor DarkGray + + # Using -like to support wildcard. + $_.Key -like $TaskToExport + }.ForEach{ + # Dot-sourcing the Tasks via their exported aliases. + . (Get-Alias $_.Key) + } + } + } + catch + { + Write-Host -Object "Could not load tasks for module $module." -ForegroundColor Red + + Write-Error -Message $_ + } + } + } + + # Loading Build Tasks defined in the .build/ folder (will override the ones imported above if same task name). + Get-ChildItem -Path '.build/' -Recurse -Include '*.ps1' -ErrorAction Ignore | + ForEach-Object { + "Importing file $($_.BaseName)" | Write-Verbose + + . $_.FullName + } + + # Synopsis: Empty task, useful to test the bootstrap process. + task noop { } + + # Define default task sequence ("."), can be overridden in the $BuildInfo. + task . { + Write-Build -Object 'No sequence currently defined for the default task' -ForegroundColor Yellow + } + + Write-Host -Object 'Adding Workflow from configuration:' -ForegroundColor DarkGray + + # Load Invoke-Build task sequences/workflows from $BuildInfo. + foreach ($workflow in $BuildInfo.BuildWorkflow.keys) + { + Write-Verbose -Message "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')." + + $workflowItem = $BuildInfo.BuildWorkflow.($workflow) + + if ($workflowItem.Trim() -match '^\{(?[\w\W]*)\}$') + { + $workflowItem = [ScriptBlock]::Create($Matches['sb']) + } + + Write-Host -Object " +-> $workflow" -ForegroundColor DarkGray + + task $workflow $workflowItem + } + + Write-Host -Object "[build] Executing requested workflow: $($Tasks -join ', ')" -ForeGroundColor Magenta + + } + finally + { + Pop-Location -StackName 'BeforeBuild' + } +} + +begin +{ + # Find build config if not specified. + if (-not $BuildConfig) + { + $config = Get-ChildItem -Path "$PSScriptRoot\*" -Include 'build.y*ml', 'build.psd1', 'build.json*' -ErrorAction Ignore + + if (-not $config -or ($config -is [System.Array] -and $config.Length -le 0)) + { + throw 'No build configuration found. Specify path via parameter BuildConfig.' + } + elseif ($config -is [System.Array]) + { + if ($config.Length -gt 1) + { + throw 'More than one build configuration found. Specify which path to use via parameter BuildConfig.' + } + + $BuildConfig = $config[0] + } + else + { + $BuildConfig = $config + } + } + + # Bootstrapping the environment before using Invoke-Build as task runner + + if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') + { + Write-Host -Object "[pre-build] Starting Build Init" -ForegroundColor Green + + Push-Location $PSScriptRoot -StackName 'BuildModule' + } + + if ($RequiredModulesDirectory -in @('CurrentUser', 'AllUsers')) + { + # Installing modules instead of saving them. + Write-Host -Object "[pre-build] Required Modules will be installed to the PowerShell module path that is used for $RequiredModulesDirectory." -ForegroundColor Green + + <# + The variable $PSDependTarget will be used below when building the splatting + variable before calling Resolve-Dependency.ps1, unless overridden in the + file Resolve-Dependency.psd1. + #> + $PSDependTarget = $RequiredModulesDirectory + } + else + { + if (-not (Split-Path -IsAbsolute -Path $OutputDirectory)) + { + $OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath $OutputDirectory + } + + # Resolving the absolute path to save the required modules to. + if (-not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory)) + { + $RequiredModulesDirectory = Join-Path -Path $PSScriptRoot -ChildPath $RequiredModulesDirectory + } + + # Create the output/modules folder if not exists, or resolve the Absolute path otherwise. + if (Resolve-Path -Path $RequiredModulesDirectory -ErrorAction SilentlyContinue) + { + Write-Debug -Message "[pre-build] Required Modules path already exist at $RequiredModulesDirectory" + + $requiredModulesPath = Convert-Path -Path $RequiredModulesDirectory + } + else + { + Write-Host -Object "[pre-build] Creating required modules directory $RequiredModulesDirectory." -ForegroundColor Green + + $requiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName + } + + $powerShellModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator + + # Pre-pending $requiredModulesPath folder to PSModulePath to resolve from this folder FIRST. + if ($RequiredModulesDirectory -notin @('CurrentUser', 'AllUsers') -and + ($powerShellModulePaths -notcontains $RequiredModulesDirectory)) + { + Write-Host -Object "[pre-build] Pre-pending '$RequiredModulesDirectory' folder to PSModulePath" -ForegroundColor Green + + $env:PSModulePath = $RequiredModulesDirectory + [System.IO.Path]::PathSeparator + $env:PSModulePath + } + + $powerShellYamlModule = Get-Module -Name 'powershell-yaml' -ListAvailable + $invokeBuildModule = Get-Module -Name 'InvokeBuild' -ListAvailable + $psDependModule = Get-Module -Name 'PSDepend' -ListAvailable + + # Checking if the user should -ResolveDependency. + if (-not ($powerShellYamlModule -and $invokeBuildModule -and $psDependModule) -and -not $ResolveDependency) + { + if ($AutoRestore -or -not $PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build') + { + Write-Host -Object "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n" -ForegroundColor Yellow + + $ResolveDependency = $true + } + else + { + Write-Warning -Message "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter. Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task." + } + } + + <# + The variable $PSDependTarget will be used below when building the splatting + variable before calling Resolve-Dependency.ps1, unless overridden in the + file Resolve-Dependency.psd1. + #> + $PSDependTarget = $requiredModulesPath + } + + if ($ResolveDependency) + { + Write-Host -Object "[pre-build] Resolving dependencies using preferred method." -ForegroundColor Green + + $resolveDependencyParams = @{ } + + # If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency. + if ($BuildConfig -match '\.[yaml|yml]$') + { + $resolveDependencyParams.Add('WithYaml', $true) + } + + $resolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').Parameters.Keys + + foreach ($cmdParameter in $resolveDependencyAvailableParams) + { + # The parameter has been explicitly used for calling the .build.ps1 + if ($MyInvocation.BoundParameters.ContainsKey($cmdParameter)) + { + $paramValue = $MyInvocation.BoundParameters.Item($cmdParameter) + + Write-Debug " adding $cmdParameter :: $paramValue [from user-provided parameters to Build.ps1]" + + $resolveDependencyParams.Add($cmdParameter, $paramValue) + } + # Use defaults parameter value from Build.ps1, if any + else + { + $paramValue = Get-Variable -Name $cmdParameter -ValueOnly -ErrorAction Ignore + + if ($paramValue) + { + Write-Debug " adding $cmdParameter :: $paramValue [from default Build.ps1 variable]" + + $resolveDependencyParams.Add($cmdParameter, $paramValue) + } + } + } + + Write-Host -Object "[pre-build] Starting bootstrap process." -ForegroundColor Green + + .\Resolve-Dependency.ps1 @resolveDependencyParams + } + + if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') + { + Write-Verbose -Message "Bootstrap completed. Handing back to InvokeBuild." + + if ($PSBoundParameters.ContainsKey('ResolveDependency')) + { + Write-Verbose -Message "Dependency already resolved. Removing task." + + $null = $PSBoundParameters.Remove('ResolveDependency') + } + + Write-Host -Object "[build] Starting build with InvokeBuild." -ForegroundColor Green + + Invoke-Build @PSBoundParameters -Task $Tasks -File $MyInvocation.MyCommand.Path + + Pop-Location -StackName 'BuildModule' + + return + } +} diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000..f905f029 --- /dev/null +++ b/build.yaml @@ -0,0 +1,161 @@ +--- +#################################################### +# ModuleBuilder Configuration # +#################################################### +# Path to the Module Manifest to build (where path will be resolved from) +# SourcePath: ./Sampler/Sampler.psd1 +# Output Directory where ModuleBuilder will build the Module, relative to module manifest +# OutputDirectory: ../output/Sampler +BuiltModuleSubdirectory: module +CopyPaths: + - en-US +# - DSCResources + # - Modules +Encoding: UTF8 +# Can be used to manually specify module's semantic version if the preferred method of +# using GitVersion is not available, and it is not possible to set the session environment +# variable `$env:ModuleVersion`, nor setting the variable `$ModuleVersion`, in the +# PowerShell session (parent scope) before running the task `build`. +#SemVer: '99.0.0-preview1' + +# Suffix to add to Root module PSM1 after merge (here, the Set-Alias exporting IB tasks) +# suffix: suffix.ps1 +# prefix: prefix.ps1 +VersionedOutputDirectory: true + +#################################################### +# ModuleBuilder Submodules Configuration # +#################################################### + +NestedModule: +# HelperSubmodule: # This is the first submodule to build into the output +# Path: ./*/Modules/HelperSubmodule/HelperSubmodule.psd1 +# # is trimmed (remove metadata & Prerelease tag) and OutputDirectory expanded (the only one) +# OutputDirectory: ///Modules/HelperSubmodule +# VersionedOutputDirectory: false +# AddToManifest: false +# SemVer: +# # suffix: +# # prefix: + +#################################################### +# Sampler Pipeline Configuration # +#################################################### +# Defining 'Workflows' (suite of InvokeBuild tasks) to be run using their alias +BuildWorkflow: + '.': # "." is the default Invoke-Build workflow. It is called when no -Tasks is specified to the build.ps1 + - build + - test + + build: + - Clean + - Build_Module_ModuleBuilder + - Build_NestedModules_ModuleBuilder + - Create_changelog_release_output + + + pack: + - build + - package_module_nupkg + + + + # Defining test task to be run when invoking `./build.ps1 -Tasks test` + test: + # Uncomment to modify the PSModulePath in the test pipeline (also requires the build configuration section SetPSModulePath). + #- Set_PSModulePath + - Pester_Tests_Stop_On_Fail + # Use this task if pipeline uses code coverage and the module is using the + # pattern of Public, Private, Enum, Classes. + #- Convert_Pester_Coverage + - Pester_if_Code_Coverage_Under_Threshold + + # Use this task when you have multiple parallel tests, which produce multiple + # code coverage files and needs to get merged into one file. + #merge: + #- Merge_CodeCoverage_Files + + publish: + - Publish_Release_To_GitHub # Runs first, if token is expired it will fail early + - Publish_GitHub_Wiki_Content + + - publish_module_to_gallery + +#################################################### +# PESTER Configuration # +#################################################### + +Pester: + OutputFormat: NUnitXML + # Excludes one or more paths from being used to calculate code coverage. + ExcludeFromCodeCoverage: + + # If no scripts are defined the default is to use all the tests under the project's + # tests folder or source folder (if present). Test script paths can be defined to + # only run tests in certain folders, or run specific test files, or can be use to + # specify the order tests are run. + Script: + # - tests/QA/module.tests.ps1 + # - tests/QA + # - tests/Unit + # - tests/Integration + ExcludeTag: + # - helpQuality + # - FunctionalQuality + # - TestQuality + Tag: + CodeCoverageThreshold: 85 # Set to 0 to bypass + #CodeCoverageOutputFile: JaCoCo_$OsShortName.xml + #CodeCoverageOutputFileEncoding: ascii + # Use this if code coverage should be merged from several pipeline test jobs. + # Any existing keys above should be replaced. See also CodeCoverage below. + # CodeCoverageOutputFile is the file that is created for each pipeline test job. + #CodeCoverageOutputFile: JaCoCo_Merge.xml + +# Use this to merged code coverage from several pipeline test jobs. +# CodeCoverageFilePattern - the pattern used to search all pipeline test job artifacts +# after the file specified in CodeCoverageOutputFile. +# CodeCoverageMergedOutputFile - the file that is created by the merge build task and +# is the file that should be uploaded to code coverage services. +#CodeCoverage: + #CodeCoverageFilePattern: JaCoCo_Merge.xml # the pattern used to search all pipeline test job artifacts + #CodeCoverageMergedOutputFile: JaCoCo_coverage.xml # the file that is created for the merged code coverage + +DscTest: + ExcludeTag: + - "Common Tests - New Error-Level Script Analyzer Rules" + Tag: + ExcludeSourceFile: + - output + ExcludeModuleFile: + - Modules/DscResource.Common + # - Templates + +# Import ModuleBuilder tasks from a specific PowerShell module using the build +# task's alias. Wildcard * can be used to specify all tasks that has a similar +# prefix and or suffix. The module contain the task must be added as a required +# module in the file RequiredModules.psd1. +ModuleBuildTasks: + Sampler: + - '*.build.Sampler.ib.tasks' + Sampler.GitHubTasks: + - '*.ib.tasks' + + +# Invoke-Build Header to be used to 'decorate' the terminal output of the tasks. +TaskHeader: | + param($Path) + "" + "=" * 79 + Write-Build Cyan "`t`t`t$($Task.Name.replace("_"," ").ToUpper())" + Write-Build DarkGray "$(Get-BuildSynopsis $Task)" + "-" * 79 + Write-Build DarkGray " $Path" + Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" + "" + + + + + + diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..847b3d13 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,31 @@ +codecov: + require_ci_to_pass: no + # master should be the baseline for reporting + branch: It lets you pause and resume Fabric capacities. + +comment: + layout: "reach, diff, flags, files" + behavior: default + +coverage: + range: 50..80 + round: down + precision: 0 + + status: + project: + default: + # Set the overall project code coverage requirement to 70% + target: 70 + patch: + default: + # Set the pull request requirement to not regress overall coverage by more than 5% + # and let codecov.io set the goal for the code changed in the patch. + target: auto + threshold: 5 + +# Use this if there are paths that should not be part of the code coverage, for +# example a deprecated function where tests has been removed. +#ignore: +# - 'source/Public/Get-Deprecated.ps1' + diff --git a/source/Classes/1.class1.ps1 b/source/Classes/1.class1.ps1 new file mode 100644 index 00000000..22cc7dae --- /dev/null +++ b/source/Classes/1.class1.ps1 @@ -0,0 +1,15 @@ +class Class1 +{ + [string]$Name = 'Class1' + + Class1() + { + #default Constructor + } + + [String] ToString() + { + # Typo "calss" is intentional + return ( 'This calss is {0}' -f $this.Name) + } +} diff --git a/source/Classes/2.class2.ps1 b/source/Classes/2.class2.ps1 new file mode 100644 index 00000000..801c626d --- /dev/null +++ b/source/Classes/2.class2.ps1 @@ -0,0 +1,14 @@ +class Class2 +{ + [string]$Name = 'Class2' + + Class2() + { + #default constructor + } + + [String] ToString() + { + return ( 'This calss is {0}' -f $this.Name) + } +} diff --git a/source/Classes/3.class11.ps1 b/source/Classes/3.class11.ps1 new file mode 100644 index 00000000..a8202a35 --- /dev/null +++ b/source/Classes/3.class11.ps1 @@ -0,0 +1,13 @@ +class Class11 : Class1 +{ + [string]$Name = 'Class11' + + Class11 () + { + } + + [String] ToString() + { + return ( 'This calss is {0}:{1}' -f $this.Name,'class1') + } +} diff --git a/source/Classes/4.class12.ps1 b/source/Classes/4.class12.ps1 new file mode 100644 index 00000000..c04a82d1 --- /dev/null +++ b/source/Classes/4.class12.ps1 @@ -0,0 +1,13 @@ +class Class12 : Class1 +{ + [string]$Name = 'Class12' + + Class12 () + { + } + + [String] ToString() + { + return ( 'This calss is {0}:{1}' -f $this.Name,'class1') + } +} diff --git a/source/FabricTools.psd1 b/source/FabricTools.psd1 new file mode 100644 index 00000000..e8095fde --- /dev/null +++ b/source/FabricTools.psd1 @@ -0,0 +1,143 @@ +# +# Module manifest for module 'FabricTools' +# +# Generated by: mrrob +# +# Generated on: 08/05/2025 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'FabricTools.psm1' + +# Version number of this module. +ModuleVersion = '0.0.1' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '0ba3e49a-b47e-4beb-8434-5a34ad41ae72' + +# Author of this module +Author = 'mrrob' + +# Company or vendor of this module +CompanyName = 'mrrob' + +# Copyright statement for this module +Copyright = '(c) mrrob. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'A module to be able to do more with Microsoft Fabric.' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.0' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @() + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = @() + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + ReleaseNotes = '' + + # Prerelease string of this module + Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + + + + + + + + + + + + diff --git a/source/FabricTools.psm1 b/source/FabricTools.psm1 new file mode 100644 index 00000000..92d7cfb9 --- /dev/null +++ b/source/FabricTools.psm1 @@ -0,0 +1,5 @@ +<# + This file is intentionally left empty. It is must be left here for the module + manifest to refer to. It is recreated during the build process. +#> + diff --git a/source/Private/Get-PrivateFunction.ps1 b/source/Private/Get-PrivateFunction.ps1 new file mode 100644 index 00000000..b4ef195a --- /dev/null +++ b/source/Private/Get-PrivateFunction.ps1 @@ -0,0 +1,31 @@ +function Get-PrivateFunction +{ + <# + .SYNOPSIS + This is a sample Private function only visible within the module. + + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + + .EXAMPLE + $null = Get-PrivateFunction -PrivateData 'NOTHING TO SEE HERE' + + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. + + #> + [cmdletBinding()] + [OutputType([string])] + param + ( + [Parameter()] + [String] + $PrivateData + ) + + process + { + Write-Output $PrivateData + } + +} diff --git a/source/Public/Get-Something.ps1 b/source/Public/Get-Something.ps1 new file mode 100644 index 00000000..1bc54b75 --- /dev/null +++ b/source/Public/Get-Something.ps1 @@ -0,0 +1,41 @@ +function Get-Something +{ + <# + .SYNOPSIS + Sample Function to return input string. + + .DESCRIPTION + This function is only a sample Advanced function that returns the Data given via parameter Data. + + .EXAMPLE + Get-Something -Data 'Get me this text' + + + .PARAMETER Data + The Data parameter is the data that will be returned without transformation. + + #> + [cmdletBinding( + SupportsShouldProcess = $true, + ConfirmImpact = 'Low' + )] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] + [String] + $Data + ) + + process + { + if ($pscmdlet.ShouldProcess($Data)) + { + Write-Verbose ('Returning the data: {0}' -f $Data) + Get-PrivateFunction -PrivateData $Data + } + else + { + Write-Verbose 'oh dear' + } + } +} diff --git a/source/en-US/about_FabricTools.help.txt b/source/en-US/about_FabricTools.help.txt new file mode 100644 index 00000000..057961b8 --- /dev/null +++ b/source/en-US/about_FabricTools.help.txt @@ -0,0 +1,24 @@ +TOPIC + about_FabricTools + +SHORT DESCRIPTION + A module to be able to do more with Microsoft Fabric. + +LONG DESCRIPTION + A module to be able to do more with Microsoft Fabric. + +EXAMPLES + PS C:\> {{ add examples here }} + +NOTE: + Thank you to all those who contributed to this module, by writing code, sharing opinions, and provided feedback. + +TROUBLESHOOTING NOTE: + Look out on the Github repository for issues and new releases. + +SEE ALSO + - {{ Please add Project URI such as github }}} + +KEYWORDS + {{ Add comma separated keywords here }} + diff --git a/tests/QA/module.tests.ps1 b/tests/QA/module.tests.ps1 new file mode 100644 index 00000000..2b8c69c7 --- /dev/null +++ b/tests/QA/module.tests.ps1 @@ -0,0 +1,216 @@ +BeforeDiscovery { + $projectPath = "$($PSScriptRoot)\..\.." | Convert-Path + + <# + If the QA tests are run outside of the build script (e.g with Invoke-Pester) + the parent scope has not set the variable $ProjectName. + #> + if (-not $ProjectName) + { + # Assuming project folder name is project name. + $ProjectName = Get-SamplerProjectName -BuildRoot $projectPath + } + + $script:moduleName = $ProjectName + + Remove-Module -Name $script:moduleName -Force -ErrorAction SilentlyContinue + + $mut = Get-Module -Name $script:moduleName -ListAvailable | + Select-Object -First 1 | + Import-Module -Force -ErrorAction Stop -PassThru +} + +BeforeAll { + # Convert-Path required for PS7 or Join-Path fails + $projectPath = "$($PSScriptRoot)\..\.." | Convert-Path + + <# + If the QA tests are run outside of the build script (e.g with Invoke-Pester) + the parent scope has not set the variable $ProjectName. + #> + if (-not $ProjectName) + { + # Assuming project folder name is project name. + $ProjectName = Get-SamplerProjectName -BuildRoot $projectPath + } + + $script:moduleName = $ProjectName + + $sourcePath = ( + Get-ChildItem -Path $projectPath\*\*.psd1 | + Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) ` + -and $( + try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + } + ) + } + ).Directory.FullName +} + +Describe 'Changelog Management' -Tag 'Changelog' { + + It 'Changelog format compliant with keepachangelog format' -Skip:(![bool](Get-Command git -EA SilentlyContinue)) { + { Get-ChangelogData -Path (Join-Path $ProjectPath 'CHANGELOG.md') -ErrorAction Stop } | Should -Not -Throw + } + + It 'Changelog should have an Unreleased header' -Skip:$skipTest { + (Get-ChangelogData -Path (Join-Path -Path $ProjectPath -ChildPath 'CHANGELOG.md') -ErrorAction Stop).Unreleased | Should -Not -BeNullOrEmpty + } +} + +Describe 'General module control' -Tags 'FunctionalQuality' { + It 'Should import without errors' { + { Import-Module -Name $script:moduleName -Force -ErrorAction Stop } | Should -Not -Throw + + Get-Module -Name $script:moduleName | Should -Not -BeNullOrEmpty + } + + It 'Should remove without error' { + { Remove-Module -Name $script:moduleName -ErrorAction Stop } | Should -Not -Throw + + Get-Module $script:moduleName | Should -BeNullOrEmpty + } +} + +BeforeDiscovery { + # Must use the imported module to build test cases. + $allModuleFunctions = & $mut { Get-Command -Module $args[0] -CommandType Function } $script:moduleName + + # Build test cases. + $testCases = @() + + foreach ($function in $allModuleFunctions) + { + $testCases += @{ + Name = $function.Name + } + } +} + +Describe 'Quality for module' -Tags 'TestQuality' { + BeforeDiscovery { + if (Get-Command -Name Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue) + { + $scriptAnalyzerRules = Get-ScriptAnalyzerRule + } + else + { + if ($ErrorActionPreference -ne 'Stop') + { + Write-Warning -Message 'ScriptAnalyzer not found!' + } + else + { + throw 'ScriptAnalyzer not found!' + } + } + } + + It 'Should have a unit test for ' -ForEach $testCases { + Get-ChildItem -Path 'tests\' -Recurse -Include "$Name.Tests.ps1" | Should -Not -BeNullOrEmpty + } + + It 'Should pass Script Analyzer for ' -ForEach $testCases -Skip:(-not $scriptAnalyzerRules) { + $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" + + $pssaResult = (Invoke-ScriptAnalyzer -Path $functionFile.FullName) + $report = $pssaResult | Format-Table -AutoSize | Out-String -Width 110 + $pssaResult | Should -BeNullOrEmpty -Because ` + "some rule triggered.`r`n`r`n $report" + } +} + +Describe 'Help for module' -Tags 'helpQuality' { + It 'Should have .SYNOPSIS for ' -ForEach $testCases { + $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" + + $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName + + $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) + + $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } + + $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | + Where-Object -FilterScript { + $_.Name -eq $Name + } + + $functionHelp = $parsedFunction.GetHelpContent() + + $functionHelp.Synopsis | Should -Not -BeNullOrEmpty + } + + It 'Should have a .DESCRIPTION with length greater than 40 characters for ' -ForEach $testCases { + $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" + + $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName + + $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) + + $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } + + $parsedFunction = $abstractSyntaxTree.FindAll($astSearchDelegate, $true) | + Where-Object -FilterScript { + $_.Name -eq $Name + } + + $functionHelp = $parsedFunction.GetHelpContent() + + $functionHelp.Description.Length | Should -BeGreaterThan 40 + } + + It 'Should have at least one (1) example for ' -ForEach $testCases { + $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" + + $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName + + $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) + + $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } + + $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | + Where-Object -FilterScript { + $_.Name -eq $Name + } + + $functionHelp = $parsedFunction.GetHelpContent() + + $functionHelp.Examples.Count | Should -BeGreaterThan 0 + $functionHelp.Examples[0] | Should -Match ([regex]::Escape($function.Name)) + $functionHelp.Examples[0].Length | Should -BeGreaterThan ($function.Name.Length + 10) + + } + + It 'Should have described all parameters for ' -ForEach $testCases { + $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" + + $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName + + $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) + + $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } + + $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | + Where-Object -FilterScript { + $_.Name -eq $Name + } + + $functionHelp = $parsedFunction.GetHelpContent() + + $parameters = $parsedFunction.Body.ParamBlock.Parameters.Name.VariablePath.ForEach({ $_.ToString() }) + + foreach ($parameter in $parameters) + { + $functionHelp.Parameters.($parameter.ToUpper()) | Should -Not -BeNullOrEmpty -Because ('the parameter {0} must have a description' -f $parameter) + $functionHelp.Parameters.($parameter.ToUpper()).Length | Should -BeGreaterThan 25 -Because ('the parameter {0} must have descriptive description' -f $parameter) + } + } +} + diff --git a/tests/Unit/Classes/class1.tests.ps1 b/tests/Unit/Classes/class1.tests.ps1 new file mode 100644 index 00000000..1237a875 --- /dev/null +++ b/tests/Unit/Classes/class1.tests.ps1 @@ -0,0 +1,46 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } + ).BaseName + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe class1 { + Context 'Type creation' { + It 'Has created a type named class1' { + 'class1' -as [Type] | Should -BeOfType [Type] + } + } + + Context 'Constructors' { + It 'Has a default constructor' { + $instance = [class1]::new() + $instance | Should -Not -BeNullOrEmpty + $instance.GetType().Name | Should -Be 'class1' + } + } + + Context 'Methods' { + BeforeEach { + $instance = [class1]::new() + } + + It 'Overrides the ToString method' { + # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. + $instance.ToString() | Should -Be 'This calss is class1' + } + } + + Context 'Properties' { + BeforeEach { + $instance = [class1]::new() + } + + It 'Has a Name property' { + $instance.Name | Should -Be 'Class1' + } + } + } +} diff --git a/tests/Unit/Classes/class11.tests.ps1 b/tests/Unit/Classes/class11.tests.ps1 new file mode 100644 index 00000000..c51e3853 --- /dev/null +++ b/tests/Unit/Classes/class11.tests.ps1 @@ -0,0 +1,46 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } + ).BaseName + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe class11 { + Context 'Type creation' { + It 'Has created a type named class11' { + 'class11' -as [Type] | Should -BeOfType [Type] + } + } + + Context 'Constructors' { + It 'Has a default constructor' { + $instance = [class11]::new() + $instance | Should -Not -BeNullOrEmpty + $instance.GetType().Name | Should -Be 'class11' + } + } + + Context 'Methods' { + BeforeEach { + $instance = [class11]::new() + } + + It 'Overrides the ToString method' { + # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. + $instance.ToString() | Should -Be 'This calss is class11:class1' + } + } + + Context 'Properties' { + BeforeEach { + $instance = [class11]::new() + } + + It 'Has a Name property' { + $instance.Name | Should -Be 'Class11' + } + } + } +} diff --git a/tests/Unit/Classes/class12.tests.ps1 b/tests/Unit/Classes/class12.tests.ps1 new file mode 100644 index 00000000..f6b24dcc --- /dev/null +++ b/tests/Unit/Classes/class12.tests.ps1 @@ -0,0 +1,46 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } + ).BaseName + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe class12 { + Context 'Type creation' { + It 'Has created a type named class12' { + 'class12' -as [Type] | Should -BeOfType [Type] + } + } + + Context 'Constructors' { + It 'Has a default constructor' { + $instance = [class12]::new() + $instance | Should -Not -BeNullOrEmpty + $instance.GetType().Name | Should -Be 'class12' + } + } + + Context 'Methods' { + BeforeEach { + $instance = [class12]::new() + } + + It 'Overrides the ToString method' { + # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. + $instance.ToString() | Should -Be 'This calss is class12:class1' + } + } + + Context 'Properties' { + BeforeEach { + $instance = [class12]::new() + } + + It 'Has a Name property' { + $instance.Name | Should -Be 'Class12' + } + } + } +} diff --git a/tests/Unit/Classes/class2.tests.ps1 b/tests/Unit/Classes/class2.tests.ps1 new file mode 100644 index 00000000..16d327a8 --- /dev/null +++ b/tests/Unit/Classes/class2.tests.ps1 @@ -0,0 +1,46 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } + ).BaseName + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe class2 { + Context 'Type creation' { + It 'Has created a type named class2' { + 'class2' -as [Type] | Should -BeOfType [Type] + } + } + + Context 'Constructors' { + It 'Has a default constructor' { + $instance = [class2]::new() + $instance | Should -Not -BeNullOrEmpty + $instance.GetType().Name | Should -Be 'class2' + } + } + + Context 'Methods' { + BeforeEach { + $instance = [class2]::new() + } + + It 'Overrides the ToString method' { + # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. + $instance.ToString() | Should -Be 'This calss is class2' + } + } + + Context 'Properties' { + BeforeEach { + $instance = [class2]::new() + } + + It 'Has a Name property' { + $instance.Name | Should -Be 'Class2' + } + } + } +} diff --git a/tests/Unit/Private/Get-PrivateFunction.tests.ps1 b/tests/Unit/Private/Get-PrivateFunction.tests.ps1 new file mode 100644 index 00000000..d1a902bf --- /dev/null +++ b/tests/Unit/Private/Get-PrivateFunction.tests.ps1 @@ -0,0 +1,31 @@ +BeforeAll { + $script:dscModuleName = 'FabricTools' + + Import-Module -Name $script:dscModuleName +} + +AfterAll { + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe Get-PrivateFunction { + Context 'When calling the function with string value' { + It 'Should return a single object' { + InModuleScope -ModuleName $dscModuleName { + $return = Get-PrivateFunction -PrivateData 'string' + + ($return | Measure-Object).Count | Should -Be 1 + } + } + + It 'Should return a string based on the parameter PrivateData' { + InModuleScope -ModuleName $dscModuleName { + $return = Get-PrivateFunction -PrivateData 'string' + + $return | Should -Be 'string' + } + } + } +} + diff --git a/tests/Unit/Public/Get-Something.tests.ps1 b/tests/Unit/Public/Get-Something.tests.ps1 new file mode 100644 index 00000000..c9ed0bd2 --- /dev/null +++ b/tests/Unit/Public/Get-Something.tests.ps1 @@ -0,0 +1,91 @@ +BeforeAll { + $script:dscModuleName = 'FabricTools' + + Import-Module -Name $script:dscModuleName +} + +AfterAll { + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe Get-Something { + BeforeAll { + Mock -CommandName Get-PrivateFunction -MockWith { + # This return the value passed to the Get-PrivateFunction parameter $PrivateData. + $PrivateData + } -ModuleName $dscModuleName + } + + Context 'When passing values using named parameters' { + It 'Should call the private function once' { + { Get-Something -Data 'value' } | Should -Not -Throw + + Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 1 -Scope It -ModuleName $dscModuleName + } + + It 'Should return a single object' { + $return = Get-Something -Data 'value' + + ($return | Measure-Object).Count | Should -Be 1 + } + + It 'Should return the correct string value' { + $return = Get-Something -Data 'value' + + $return | Should -Be 'value' + } + } + + Context 'When passing values over the pipeline' { + It 'Should call the private function two times' { + { 'value1', 'value2' | Get-Something } | Should -Not -Throw + + Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 2 -Scope It -ModuleName $dscModuleName + } + + It 'Should return an array with two items' { + $return = 'value1', 'value2' | Get-Something + + $return.Count | Should -Be 2 + } + + It 'Should return an array with the correct string values' { + $return = 'value1', 'value2' | Get-Something + + $return[0] | Should -Be 'value1' + $return[1] | Should -Be 'value2' + } + + It 'Should accept values from the pipeline by property name' { + $return = 'value1', 'value2' | ForEach-Object { + [PSCustomObject]@{ + Data = $_ + OtherProperty = 'other' + } + } | Get-Something + + $return[0] | Should -Be 'value1' + $return[1] | Should -Be 'value2' + } + } + + Context 'When passing WhatIf' { + It 'Should support the parameter WhatIf' { + (Get-Command -Name 'Get-Something').Parameters.ContainsKey('WhatIf') | Should -Be $true + } + + It 'Should not call the private function' { + { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw + + Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 0 -Scope It -ModuleName $dscModuleName + } + + It 'Should return $null' { + $return = Get-Something -Data 'value' -WhatIf + + $return | Should -BeNullOrEmpty + } + } +} + From 6d38fc02d0c0602c1e3794a989f3fac25383ac07 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 17:07:06 +0100 Subject: [PATCH 03/76] Add commit message guidelines for contributors for github copilot This commit introduces a new markdown file that outlines best practices for writing commit messages. It aims to help contributors maintain consistency and clarity in their commit history. --- .github/copilot-commit-message-instructions.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/copilot-commit-message-instructions.md diff --git a/.github/copilot-commit-message-instructions.md b/.github/copilot-commit-message-instructions.md new file mode 100644 index 00000000..04696d99 --- /dev/null +++ b/.github/copilot-commit-message-instructions.md @@ -0,0 +1,7 @@ +Limit the subject line to 50 characters +Capitalize the subject/description line +Do not end the subject line with a period +Separate the subject from the body with a blank line +Use the imperative mood in the subject line +The subject line should be a single sentence with an action word and target with some reasoning +Use the body to explain what and why in a friendly kind manner \ No newline at end of file From 83304cdfff487d9054c3411e9560ee217fcf66ad Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 17:07:48 +0100 Subject: [PATCH 04/76] Update README.md to enhance module documentation Expanded the README to provide a clearer overview of the FabricTools PowerShell module, including features, installation instructions, usage examples, and contribution guidelines. This aims to improve user understanding and facilitate contributions during the early development stage. --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c3229a98..5eb44acb 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,106 @@ -# FabricTools +# FabricTools PowerShell Module -A module to be able to do more with Microsoft Fabric. +[![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/FabricTools?label=PowerShell%20Gallery&color=blue)](https://www.powershellgallery.com/packages/FabricTools) +![PowerShell Gallery Downloads](https://img.shields.io/powershellgallery/dt/FabricTools?label=PSGallery%20downloads) -## Make it yours +drawing ---- -Generated with Plaster and the SampleModule template +**FabricTools** is a PowerShell module to able to do more with Microsoft Fabric and Power BI. +It allows for various administrative tasks to be automated and integrated into workflows. +We are at an early stage of development and the module is in its **Public PREVIEW**. +Do NOT use it with Production environments. +
+
-This is a sample Readme +## Features -## Make it yours +- Manage Microsoft Fabric workspaces and datasets. +- Assign Microsoft Fabric workspaces to capacities. +- Retrieve and manipulate Microsoft Fabric tenant settings. +- Handle Microsoft Fabric access tokens for authentication. +- Suspend and resume Microsoft Fabric capacities. +- Fabric-friendly aliases for lots of the old PowerBI cmdlets + +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. + +### Prerequisites + +- PowerShell 5.1 or higher +- Access to PowerBI service and Azure subscription (for certain functions) +- Necessary permissions to manage PowerBI workspaces and Fabric capacities +- The following PowerShell modules: MicrosoftPowerBIMgmt, Az.Accounts, Az.Resources + +### Installing + +To install the FabricTools module, you can install it from the PowerShell Gallery: + +```powershell +Install-Module FabricTools +``` + +Or clone the repository to your local machine and import the module: + +```powershell +# Clone the repository +git clone https://github.com/dataplat/FabricTools.git + +# Import the module +Import-Module ./FabricTools/FabricTools.psm1 +``` + + + +## Usage + +Once imported, you can call any of the functions provided by the module. For example: + +```powershell +# Assign a workspace to a capacity +Register-FabricWorkspaceToCapacity -WorkspaceId "Workspace-GUID" -CapacityId "Capacity-GUID" +``` + +Refer to the individual function documentation for detailed usage instructions. + +Every now and again the authentication token might time out. Run this to get a new one: +```powershell +Set-FabricAuthToken +``` + +If you want to change user context run this: +```powershell +Set-FabricAuthToken -reset +``` + + +## Release Notes + +The entire history of changes to this module can be find here: [Release Notes](ReleaseNotes.md) + + +## Contributing + +Contributions to FabricTools are welcome. + +> Note: In this early stage of development, we are working hard to build strong foundations for this module and further contribution guidance to ensure the quality of this code. **Therefore, please get in touch with us before you commit any code and create a PR.** + +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. + +## Authors + +- **Ioana Bouariu** - *Initial work* - [Jojobit](https://github.com/Jojobit) +- **Frank Geisler** - *Author of RTI functions* - [Frank Geisler](https://github.com/Frank-Geisler) +- **Kamil Nowinski** - *Refactoring, unification, further commands* - [NowinskiK](https://github.com/NowinskiK) + +See also the list of [contributors](https://github.com/dataplat/FabricTools/contributors) who participated in this project. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- GitHub Copilot and ChatGPT for helping with the documentation +- [**Rui Romano**](https://github.com/RuiRomano) - His work on a [Fabric PowerShell module](https://github.com/RuiRomano/fabricps-pbip) has been included into this module with his permission. Thanks, Rui! From 66661401d3b6aec7cb5d6a68732d64fdccb9d190 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 17:22:17 +0100 Subject: [PATCH 05/76] Enhance CONTRIBUTING.md with detailed guidelines Updated the contributing guidelines to provide clearer instructions for new contributors. Added sections on development workflow, building the module, and testing procedures to facilitate better collaboration and understanding of the project. --- CONTRIBUTING.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3544bccb..5e2d049c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,87 @@ # Contributing -Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). +## Welcome -## Running the Tests +Before we go any further, thanks for being here. Thanks for using the module and especially thanks +for being here and looking into how you can help! -If want to know how to run this module's tests you can look at the [Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines/#running-tests) +## Important resources + +- docs TODO: link to docs +- bugs TODO: link to issues issue template +- communicate via issues, PRs, and discussions as well as the project + + +### Develop & Build + +We are using the [Sampler](https://github.com/gaelcolas/Sampler) Powershell Module to structure our module. +This makes it easier to develop and test the module locally. + +The workflow for using this and developing the code is shown below. + +1. Download or clone the repo locally and create a new branch to develop on + ```PowerShell + git checkout -b newStuff # give it a proper name! + ``` + +1. Develop your updates in the source repository + +You should also resolve all dependencies before you start developing. This will ensure that you have all the required modules installed and loaded into your session. +```PowerShell +.\build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet +``` +YOU MUST DEVELOP IN THE SOURCE DIRECTORY. + +This is important because the build process will create a new folder in the root of the repository called `output` and this is where the module will be built and loaded from. + +If you change the code in the output folder and then build the module again, it will overwrite the changes you made. + +Ask Rob how he knows this! + +1. Build the module. From the root of the repository run the following command: + ```PowerShell + ./build.ps1 -Tasks build + ``` + This will build the module and create a new folder in the root of the repository called `output`. It will also load the new module into your current session. + +You can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: + ```PowerShell + Invoke-Pester + ``` + This will run all the tests in the `tests` folder and output the results to the console. + +You can also simulate the deployment testing by running the following command: + ```PowerShell + ./build.ps1 -Tasks test + ``` + + +1. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. + +1. Thanks! + We will review your PR and get back to you as soon as we can. We are all volunteers and do this in our spare time, so please be patient with us. We will try to get back to you within a week, but it may take longer if we are busy. + If you have any questions or need help, please feel free to reach out to us on the [GitHub Discussions]( ) +## How to submit changes: +TODO: +Pull Request protocol etc. You might also include what response they'll get back from the team on submission, or any caveats about the speed of response. + +## How to report a bug: +TODO: +Bugs are problems in code, in the functionality of an application or in its UI design; you can submit them through "bug trackers" and most projects invite you to do so, so that they may "debug" with more efficiency and the input of a contributor. Take a look at Atom's example for how to teach people to report bugs to your project. + +## Templates: +TODO: +in this section of your file, you might also want to link to a bug report "template" like this one here which contributors can copy and add context to; this will keep your bugs tidy and relevant. + +## Style Guide +TODO: +include extensions and vscode settings we use to keep things neat + +## Code of Conduct +TODO: maybe beef this out - stolen from data sat repo for now. + +We expect and demand that you follow some basic rules. Nothing dramatic here. There will be a proper code of conduct for the websites added soon, but in this repository + +BE EXCELLENT TO EACH OTHER + +Do I need to say more? If your behaviour or communication does not fit into this statement, we do not wish for you to help us. From 377b2510a8bd3767558ea205dbd37f1104b630b6 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 17:26:36 +0100 Subject: [PATCH 06/76] Update CONTRIBUTING.md to include GitHub Copilot usage Added instructions for using GitHub Copilot to generate commit messages, enhancing the contribution process. This aims to streamline the workflow for contributors and improve commit message quality. --- CONTRIBUTING.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e2d049c..6ee62948 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,27 +38,42 @@ If you change the code in the output folder and then build the module again, it Ask Rob how he knows this! -1. Build the module. From the root of the repository run the following command: +2. Use GitHub CoPilot to write your commit messages by clicking on the sparkles in the commit message box. This will generate a commit message based on the changes you made. You can then edit the message to make it more descriptive if you want. This uses the prompt in the `.github\copilot-commit-message-instructions.md` file. + +Add this to your VS Code settings to enable it: +```json +"github.copilot.chat.commitMessageGeneration.instructions": [ + + + + { + "file": ".github/copilot-commit-message-instructions.md" + } + ], + ``` + + +3. Build the module. From the root of the repository run the following command: ```PowerShell ./build.ps1 -Tasks build ``` This will build the module and create a new folder in the root of the repository called `output`. It will also load the new module into your current session. -You can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: +4. You can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: ```PowerShell Invoke-Pester ``` This will run all the tests in the `tests` folder and output the results to the console. -You can also simulate the deployment testing by running the following command: +5. You can also simulate the deployment testing by running the following command: ```PowerShell ./build.ps1 -Tasks test ``` -1. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. +6. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. -1. Thanks! +# Thanks! We will review your PR and get back to you as soon as we can. We are all volunteers and do this in our spare time, so please be patient with us. We will try to get back to you within a week, but it may take longer if we are busy. If you have any questions or need help, please feel free to reach out to us on the [GitHub Discussions]( ) ## How to submit changes: From fab475c78fe4888d43e93d3164f794a6cb7a5ae5 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 18:09:35 +0100 Subject: [PATCH 07/76] Add PowerShell functions for managing Fabric workspaces - Implemented Add-FabricWorkspaceRoleAssignment to assign roles to users in a workspace. - Created Get-FabricWorkspace to retrieve all Fabric workspaces. - Developed Get-FabricWorkspace2 for enhanced workspace retrieval with multiple filtering options. - Added Get-FabricWorkspaceDatasetRefreshes to fetch refresh history of datasets in a workspace. - Introduced Get-FabricWorkspaceRoleAssignment to retrieve role assignments for a workspace. - Implemented Get-FabricWorkspaceUsageMetricsData to gather usage metrics for a workspace. - Created Get-FabricWorkspaceUsers to retrieve users associated with a workspace. - Developed New-FabricWorkspace to create a new Fabric workspace. - Added New-FabricWorkspace2 for creating workspaces with capacity and description. - Implemented New-FabricWorkspaceUsageMetricsReport to retrieve usage metrics dataset ID. - Created Register-FabricWorkspaceToCapacity to assign a workspace to a capacity. - Added Remove-FabricWorkspace to delete a workspace. - Implemented Unregister-FabricWorkspaceToCapacity to remove a workspace from a capacity. --- GitVersion.yml | 3 +- LICENSE | 21 ++ RequiredModules.psd1 | 10 +- images/FabricToolsLogo.ico | Bin 0 -> 195867 bytes images/FabricToolsLogo.png | Bin 0 -> 35558 bytes images/FabricToolsLogo.svg | 4 + images/Fabtools.ico | Bin 0 -> 90222 bytes images/Fabtools.png | Bin 0 -> 543892 bytes images/Fabtools50.png | Bin 0 -> 180186 bytes source/Classes/1.class1.ps1 | 15 - source/Classes/2.class2.ps1 | 14 - source/Classes/3.class11.ps1 | 13 - source/Classes/4.class12.ps1 | 13 - source/Private/Get-PrivateFunction.ps1 | 31 -- source/Private/Test-TokenExpired.ps1 | 66 ++++ .../Capacity/Get-AllFabricCapacities.ps1 | 75 +++++ .../Capacity/Get-FabricCapacity copy.ps1 | 92 ++++++ source/Public/Capacity/Get-FabricCapacity.ps1 | 39 +++ .../Get-FabricCapacityRefreshables.ps1 | 36 +++ .../Capacity/Get-FabricCapacitySkus.ps1 | 37 +++ .../Capacity/Get-FabricCapacityState.ps1 | 48 +++ .../Get-FabricCapacityTenantOverrides.ps1 | 33 ++ .../Capacity/Get-FabricCapacityWorkload.ps1 | 42 +++ .../Public/Capacity/Resume-FabricCapacity.ps1 | 51 ++++ .../Capacity/Suspend-FabricCapacity.ps1 | 52 ++++ source/Public/Confirm-FabricAuthToken.ps1 | 50 +++ source/Public/Connect-FabricAccount.ps1 | 56 ++++ source/Public/Copy Job/Get-FabricCopyJob.ps1 | 100 ++++++ .../Copy Job/Get-FabricCopyJobDefinition.ps1 | 75 +++++ source/Public/Copy Job/New-FabricCopyJob.ps1 | 145 +++++++++ .../Public/Copy Job/Remove-FabricCopyJob.ps1 | 61 ++++ .../Public/Copy Job/Update-FabricCopyJob.ps1 | 90 ++++++ .../Update-FabricCopyJobDefinition.ps1 | 134 ++++++++ .../Public/Dashboard/Get-FabricDashboard.ps1 | 54 ++++ .../Data Pipeline/Get-FabricDataPipeline.ps1 | 99 ++++++ .../Data Pipeline/New-FabricDataPipeline.ps1 | 84 +++++ .../Remove-FabricDataPipeline.ps1 | 60 ++++ .../Update-FabricDataPipeline.ps1 | 90 ++++++ source/Public/Datamart/Get-FabricDatamart.ps1 | 80 +++++ ...Assign-FabricDomainWorkspaceByCapacity.ps1 | 113 +++++++ .../Assign-FabricDomainWorkspaceById.ps1 | 83 +++++ ...ssign-FabricDomainWorkspaceByPrincipal.ps1 | 119 ++++++++ ...gn-FabricDomainWorkspaceRoleAssignment.ps1 | 102 +++++++ source/Public/Domain/Get-FabricDomain.ps1 | 121 ++++++++ .../Domain/Get-FabricDomainWorkspace.ps1 | 80 +++++ source/Public/Domain/New-FabricDomain.ps1 | 128 ++++++++ source/Public/Domain/Remove-FabricDomain.ps1 | 68 +++++ .../Domain/Unassign-FabricDomainWorkspace.ps1 | 96 ++++++ ...gn-FabricDomainWorkspaceRoleAssignment.ps1 | 103 +++++++ source/Public/Domain/Update-FabricDomain.ps1 | 112 +++++++ .../Environment/Get-FabricEnvironment.ps1 | 157 ++++++++++ .../Get-FabricEnvironmentLibrary.ps1 | 76 +++++ .../Get-FabricEnvironmentSparkCompute.ps1 | 76 +++++ .../Get-FabricEnvironmentStagingLibrary.ps1 | 76 +++++ ...t-FabricEnvironmentStagingSparkCompute.ps1 | 78 +++++ .../Environment/New-FabricEnvironment.ps1 | 123 ++++++++ .../Environment/Publish-FabricEnvironment.ps1 | 104 +++++++ .../Environment/Remove-FabricEnvironment.ps1 | 74 +++++ ...Remove-FabricEnvironmentStagingLibrary.ps1 | 85 ++++++ .../Stop-FabricEnvironmentPublish.ps1 | 76 +++++ .../Environment/Update-FabricEnvironment.ps1 | 108 +++++++ ...e-FabricEnvironmentStagingSparkCompute.ps1 | 177 +++++++++++ ...Upload-FabricEnvironmentStagingLibrary.ps1 | 79 +++++ .../Eventhouse/Get-FabricEventhouse copy.ps1 | 156 ++++++++++ .../Eventhouse/Get-FabricEventhouse.ps1 | 213 +++++++++++++ .../Get-FabricEventhouseDefinition.ps1 | 124 ++++++++ .../Eventhouse/New-FabricEventhouse copy.ps1 | 193 ++++++++++++ .../Eventhouse/New-FabricEventhouse.ps1 | 104 +++++++ .../Remove-FabricEventhouse copy.ps1 | 73 +++++ .../Eventhouse/Remove-FabricEventhouse.ps1 | 106 +++++++ .../Eventhouse/Set-FabricEventhouse.ps1 | 130 ++++++++ .../Eventhouse/Update-FabricEventhouse.ps1 | 105 +++++++ .../Update-FabricEventhouseDefinition.ps1 | 168 ++++++++++ .../Get-FabricEventstream copy.ps1 | 159 ++++++++++ .../Eventstream/Get-FabricEventstream.ps1 | 133 ++++++++ .../Get-FabricEventstreamDefinition.ps1 | 121 ++++++++ .../New-FabricEventstream copy.ps1 | 188 ++++++++++++ .../Eventstream/New-FabricEventstream.ps1 | 87 ++++++ .../Remove-FabricEventstream copy.ps1 | 73 +++++ .../Eventstream/Remove-FabricEventstream.ps1 | 105 +++++++ .../Eventstream/Set-FabricEventstream.ps1 | 113 +++++++ .../Eventstream/Update-FabricEventstream.ps1 | 107 +++++++ .../Update-FabricEventstreamDefinition.ps1 | 180 +++++++++++ .../Get-FabricExternalDataShares.ps1 | 50 +++ .../Revoke-FabricExternalDataShares.ps1 | 61 ++++ .../Public/Get-AllFabricDatasetRefreshes.ps1 | 57 ++++ source/Public/Get-FabricAPIClusterURI.ps1 | 43 +++ source/Public/Get-FabricAuthToken.ps1 | 39 +++ source/Public/Get-FabricConnection.ps1 | 49 +++ source/Public/Get-FabricDatasetRefreshes.ps1 | 69 +++++ source/Public/Get-FabricDebugInfo.ps1 | 46 +++ source/Public/Get-FabricUsageMetricsQuery.ps1 | 84 +++++ source/Public/Get-SHA256.ps1 | 36 +++ source/Public/Get-Something.ps1 | 41 --- source/Public/Invoke-FabricAPIRequest.ps1 | 175 +++++++++++ source/Public/Invoke-FabricDatasetRefresh.ps1 | 47 +++ source/Public/Item/Export-FabricItem.ps1 | 116 +++++++ source/Public/Item/Get-FabricItem.ps1 | 67 ++++ source/Public/Item/Import-FabricItem.ps1 | 239 +++++++++++++++ source/Public/Item/Remove-FabricItem.ps1 | 67 ++++ .../Get-FabricKQLDashboard copy.ps1 | 155 ++++++++++ .../KQL Dashboard/Get-FabricKQLDashboard.ps1 | 112 +++++++ .../Get-FabricKQLDashboardDefinition copy.ps1 | 120 ++++++++ .../Get-FabricKQLDashboardDefinition.ps1 | 119 ++++++++ .../New-FabricKQLDashboard copy.ps1 | 188 ++++++++++++ .../KQL Dashboard/New-FabricKQLDashboard.ps1 | 92 ++++++ .../Remove-FabricKQLDashboard.ps1 | 73 +++++ .../Update-FabricKQLDashboard.ps1 | 107 +++++++ .../Update-FabricKQLDashboardDefinition.ps1 | 166 ++++++++++ .../Get-FabricKQLDatabase copy.ps1 | 153 ++++++++++ .../KQL Database/Get-FabricKQLDatabase.ps1 | 228 ++++++++++++++ .../Get-FabricKQLDatabaseDefinition.ps1 | 129 ++++++++ .../New-FabricKQLDatabase copy.ps1 | 288 ++++++++++++++++++ .../KQL Database/New-FabricKQLDatabase.ps1 | 106 +++++++ .../Remove-FabricKQLDatabase copy.ps1 | 74 +++++ .../KQL Database/Remove-FabricKQLDatabase.ps1 | 77 +++++ .../KQL Database/Set-FabricKQLDatabase.ps1 | 114 +++++++ .../KQL Database/Update-FabricKQLDatabase.ps1 | 108 +++++++ .../Update-FabricKQLDatabaseDefinition.ps1 | 202 ++++++++++++ .../Get-FabricKQLQueryset copy.ps1 | 154 ++++++++++ .../KQL Queryset/Get-FabricKQLQueryset.ps1 | 130 ++++++++ .../Get-FabricKQLQuerysetDefinition.ps1 | 120 ++++++++ .../KQL Queryset/Invoke-FabricKQLCommand.ps1 | 230 ++++++++++++++ .../KQL Queryset/New-FabricKQLQueryset.ps1 | 188 ++++++++++++ .../Remove-FabricKQLQueryset copy.ps1 | 73 +++++ .../KQL Queryset/Remove-FabricKQLQueryset.ps1 | 77 +++++ .../KQL Queryset/Set-FabricKQLQueryset.ps1 | 111 +++++++ .../KQL Queryset/Update-FabricKQLQueryset.ps1 | 107 +++++++ .../Update-FabricKQLQuerysetDefinition.ps1 | 166 ++++++++++ .../Public/Lakehouse/Get-FabricLakehouse.ps1 | 154 ++++++++++ .../Lakehouse/Get-FabricLakehouseTable.ps1 | 104 +++++++ .../Lakehouse/Load-FabricLakehouseTable.ps1 | 139 +++++++++ .../Lakehouse/New-FabricLakehouse copy.ps1 | 136 +++++++++ .../Public/Lakehouse/New-FabricLakehouse.ps1 | 113 +++++++ .../Lakehouse/Remove-FabricLakehouse.ps1 | 73 +++++ .../Start-FabricLakehouseTableMaintenance.ps1 | 160 ++++++++++ .../Lakehouse/Update-FabricLakehouse.ps1 | 108 +++++++ .../ML Experiment/Get-FabricMLExperiment.ps1 | 156 ++++++++++ .../ML Experiment/New-FabricMLExperiment.ps1 | 130 ++++++++ .../Remove-FabricMLExperiment.ps1 | 72 +++++ .../Update-FabricMLExperiment.ps1 | 105 +++++++ source/Public/ML Model/Get-FabricMLModel.ps1 | 156 ++++++++++ source/Public/ML Model/New-FabricMLModel.ps1 | 130 ++++++++ .../Public/ML Model/Remove-FabricMLModel.ps1 | 72 +++++ .../Public/ML Model/Update-FabricMLModel.ps1 | 93 ++++++ .../Get-FabricMirroredDatabase.ps1 | 157 ++++++++++ .../Get-FabricMirroredDatabaseDefinition.ps1 | 109 +++++++ .../Get-FabricMirroredDatabaseStatus.ps1 | 52 ++++ .../Get-FabricMirroredDatabaseTableStatus.ps1 | 97 ++++++ .../New-FabricMirroredDatabase.ps1 | 186 +++++++++++ .../Remove-FabricMirroredDatabase.ps1 | 72 +++++ .../Start-FabricMirroredDatabaseMirroring.ps1 | 51 ++++ .../Stop-FabricMirroredDatabaseMirroring.ps1 | 53 ++++ .../Update-FabricMirroredDatabase.ps1 | 107 +++++++ ...pdate-FabricMirroredDatabaseDefinition.ps1 | 168 ++++++++++ .../Get-FabricMirroredWarehouse.ps1 | 157 ++++++++++ source/Public/Notebook/Get-FabricNotebook.ps1 | 155 ++++++++++ .../Notebook/Get-FabricNotebookDefinition.ps1 | 131 ++++++++ source/Public/Notebook/New-FabricNotebook.ps1 | 193 ++++++++++++ .../Public/Notebook/New-FabricNotebookNEW.ps1 | 158 ++++++++++ .../Public/Notebook/Remove-FabricNotebook.ps1 | 73 +++++ .../Public/Notebook/Update-FabricNotebook.ps1 | 107 +++++++ .../Update-FabricNotebookDefinition.ps1 | 179 +++++++++++ .../Get-FabricPaginatedReport.ps1 | 155 ++++++++++ .../Update-FabricPaginatedReport.ps1 | 105 +++++++ source/Public/Reflex/Get-FabricReflex.ps1 | 156 ++++++++++ .../Reflex/Get-FabricReflexDefinition.ps1 | 124 ++++++++ source/Public/Reflex/New-FabricReflex.ps1 | 193 ++++++++++++ source/Public/Reflex/Remove-FabricReflex.ps1 | 73 +++++ source/Public/Reflex/Update-FabricReflex.ps1 | 105 +++++++ .../Reflex/Update-FabricReflexDefinition.ps1 | 168 ++++++++++ source/Public/Report/Get-FabricReport.ps1 | 156 ++++++++++ .../Report/Get-FabricReportDefinition.ps1 | 124 ++++++++ source/Public/Report/New-FabricReport.ps1 | 150 +++++++++ source/Public/Report/Remove-FabricReport.ps1 | 73 +++++ source/Public/Report/Update-FabricReport.ps1 | 105 +++++++ .../Report/Update-FabricReportDefinition.ps1 | 145 +++++++++ .../SQL Database/Get-FabricSQLDatabase.ps1 | 78 +++++ .../SQL Database/New-FabricSQLDatabase.ps1 | 89 ++++++ .../SQL Database/Remove-FabricSQLDatabase.ps1 | 72 +++++ .../SQL Endpoints/Get-FabricSQLEndpoint.ps1 | 154 ++++++++++ .../Get-FabricSemanticModel.ps1 | 156 ++++++++++ .../Get-FabricSemanticModelDefinition.ps1 | 125 ++++++++ .../New-FabricSemanticModel.ps1 | 145 +++++++++ .../Remove-FabricSemanticModel.ps1 | 73 +++++ .../Update-FabricSemanticModel.ps1 | 105 +++++++ .../Update-FabricSemanticModelDefinition.ps1 | 135 ++++++++ source/Public/Set-FabricAuthToken.ps1 | 104 +++++++ .../Get-FabricSparkJobDefinition.ps1 | 156 ++++++++++ ...Get-FabricSparkJobDefinitionDefinition.ps1 | 124 ++++++++ .../New-FabricSparkJobDefinition.ps1 | 194 ++++++++++++ .../Remove-FabricSparkJobDefinition.ps1 | 72 +++++ ...Start-FabricSparkJobDefinitionOnDemand.ps1 | 118 +++++++ .../Update-FabricSparkJobDefinition.ps1 | 104 +++++++ ...ate-FabricSparkJobDefinitionDefinition.ps1 | 168 ++++++++++ .../Spark/Get-FabricSparkCustomPool.ps1 | 161 ++++++++++ .../Public/Spark/Get-FabricSparkSettings.ps1 | 119 ++++++++ .../Spark/New-FabricSparkCustomPool.ps1 | 190 ++++++++++++ .../Spark/Remove-FabricSparkCustomPool.ps1 | 72 +++++ .../Spark/Update-FabricSparkCustomPool.ps1 | 164 ++++++++++ .../Spark/Update-FabricSparkSettings.ps1 | 189 ++++++++++++ ...t-FabricCapacityTenantSettingOverrides.ps1 | 72 +++++ ...Get-FabricDomainTenantSettingOverrides.ps1 | 55 ++++ .../Tenant copy/Get-FabricTenantSetting.ps1 | 78 +++++ ...-FabricWorkspaceTenantSettingOverrides.ps1 | 54 ++++ ...e-FabricCapacityTenantSettingOverrides.ps1 | 60 ++++ ...e-FabricCapacityTenantSettingOverrides.ps1 | 136 +++++++++ .../Update-FabricTenantSetting.ps1 | 164 ++++++++++ .../Tenant/Get-FabricTenantSettings.ps1 | 27 ++ .../Get-FabricUserListAccessEntities.ps1 | 68 +++++ source/Public/Utils/Convert-FromBase64.ps1 | 55 ++++ source/Public/Utils/Convert-ToBase64.ps1 | 60 ++++ .../Get-FabricLongRunningOperation copy.ps1 | 88 ++++++ .../Utils/Get-FabricLongRunningOperation.ps1 | 88 ++++++ ...-FabricLongRunningOperationResult copy.ps1 | 67 ++++ .../Get-FabricLongRunningOperationResult.ps1 | 67 ++++ .../Public/Utils/Invoke-FabricAPIRequest.ps1 | 133 ++++++++ .../Utils/Set-FabricApiHeaders copy.ps1 | 116 +++++++ source/Public/Utils/Set-FabricApiHeaders.ps1 | 116 +++++++ .../Public/Utils/Test-FabricApiResponse.ps1 | 53 ++++ .../Public/Warehouse/Get-FabricWarehouse.ps1 | 101 ++++++ .../Public/Warehouse/New-FabricWarehouse.ps1 | 83 +++++ .../Warehouse/Remove-FabricWarehouse.ps1 | 61 ++++ .../Warehouse/Update-FabricWarehouse.ps1 | 91 ++++++ .../Add-FabricWorkspaceIdentity.ps1 | 101 ++++++ .../Add-FabricWorkspaceRoleAssignment.ps1 | 112 +++++++ .../Assign-FabricWorkspaceCapacity.ps1 | 83 +++++ .../Workspace copy/Get-FabricWorkspace.ps1 | 149 +++++++++ .../Get-FabricWorkspaceRoleAssignment.ps1 | 153 ++++++++++ .../Workspace copy/New-FabricWorkspace.ps1 | 122 ++++++++ .../Workspace copy/Remove-FabricWorkspace.ps1 | 68 +++++ .../Remove-FabricWorkspaceIdentity.ps1 | 94 ++++++ .../Remove-FabricWorkspaceRoleAssignment.ps1 | 74 +++++ .../Unassign-FabricWorkspaceCapacity.ps1 | 67 ++++ .../Workspace copy/Update-FabricWorkspace.ps1 | 103 +++++++ .../Update-FabricWorkspaceRoleAssignment.ps1 | 104 +++++++ .../Add-FabricWorkspaceRoleAssignment.ps1 | 94 ++++++ .../Public/Workspace/Get-FabricWorkspace.ps1 | 45 +++ .../Public/Workspace/Get-FabricWorkspace2.ps1 | 190 ++++++++++++ .../Get-FabricWorkspaceDatasetRefreshes.ps1 | 53 ++++ .../Get-FabricWorkspaceRoleAssignment.ps1 | 56 ++++ .../Get-FabricWorkspaceUsageMetricsData.ps1 | 60 ++++ .../Workspace/Get-FabricWorkspaceUsers.ps1 | 57 ++++ .../Public/Workspace/New-FabricWorkspace.ps1 | 80 +++++ .../Public/Workspace/New-FabricWorkspace2.ps1 | 89 ++++++ .../New-FabricWorkspaceUsageMetricsReport.ps1 | 48 +++ .../Register-FabricWorkspaceToCapacity.ps1 | 71 +++++ .../Workspace/Remove-FabricWorkspace.ps1 | 37 +++ .../Unregister-FabricWorkspaceToCapacity.ps1 | 58 ++++ tests/Unit/Classes/class1.tests.ps1 | 46 --- tests/Unit/Classes/class11.tests.ps1 | 46 --- tests/Unit/Classes/class12.tests.ps1 | 46 --- tests/Unit/Classes/class2.tests.ps1 | 46 --- .../Private/Get-PrivateFunction.tests.ps1 | 31 -- tests/Unit/Public/Get-Something.tests.ps1 | 91 ------ 255 files changed, 25109 insertions(+), 441 deletions(-) create mode 100644 LICENSE create mode 100644 images/FabricToolsLogo.ico create mode 100644 images/FabricToolsLogo.png create mode 100644 images/FabricToolsLogo.svg create mode 100644 images/Fabtools.ico create mode 100644 images/Fabtools.png create mode 100644 images/Fabtools50.png delete mode 100644 source/Classes/1.class1.ps1 delete mode 100644 source/Classes/2.class2.ps1 delete mode 100644 source/Classes/3.class11.ps1 delete mode 100644 source/Classes/4.class12.ps1 delete mode 100644 source/Private/Get-PrivateFunction.ps1 create mode 100644 source/Private/Test-TokenExpired.ps1 create mode 100644 source/Public/Capacity/Get-AllFabricCapacities.ps1 create mode 100644 source/Public/Capacity/Get-FabricCapacity copy.ps1 create mode 100644 source/Public/Capacity/Get-FabricCapacity.ps1 create mode 100644 source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 create mode 100644 source/Public/Capacity/Get-FabricCapacitySkus.ps1 create mode 100644 source/Public/Capacity/Get-FabricCapacityState.ps1 create mode 100644 source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 create mode 100644 source/Public/Capacity/Get-FabricCapacityWorkload.ps1 create mode 100644 source/Public/Capacity/Resume-FabricCapacity.ps1 create mode 100644 source/Public/Capacity/Suspend-FabricCapacity.ps1 create mode 100644 source/Public/Confirm-FabricAuthToken.ps1 create mode 100644 source/Public/Connect-FabricAccount.ps1 create mode 100644 source/Public/Copy Job/Get-FabricCopyJob.ps1 create mode 100644 source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 create mode 100644 source/Public/Copy Job/New-FabricCopyJob.ps1 create mode 100644 source/Public/Copy Job/Remove-FabricCopyJob.ps1 create mode 100644 source/Public/Copy Job/Update-FabricCopyJob.ps1 create mode 100644 source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 create mode 100644 source/Public/Dashboard/Get-FabricDashboard.ps1 create mode 100644 source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 create mode 100644 source/Public/Data Pipeline/New-FabricDataPipeline.ps1 create mode 100644 source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 create mode 100644 source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 create mode 100644 source/Public/Datamart/Get-FabricDatamart.ps1 create mode 100644 source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 create mode 100644 source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 create mode 100644 source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 create mode 100644 source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Domain/Get-FabricDomain.ps1 create mode 100644 source/Public/Domain/Get-FabricDomainWorkspace.ps1 create mode 100644 source/Public/Domain/New-FabricDomain.ps1 create mode 100644 source/Public/Domain/Remove-FabricDomain.ps1 create mode 100644 source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 create mode 100644 source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Domain/Update-FabricDomain.ps1 create mode 100644 source/Public/Environment/Get-FabricEnvironment.ps1 create mode 100644 source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 create mode 100644 source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 create mode 100644 source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 create mode 100644 source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 create mode 100644 source/Public/Environment/New-FabricEnvironment.ps1 create mode 100644 source/Public/Environment/Publish-FabricEnvironment.ps1 create mode 100644 source/Public/Environment/Remove-FabricEnvironment.ps1 create mode 100644 source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 create mode 100644 source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 create mode 100644 source/Public/Environment/Update-FabricEnvironment.ps1 create mode 100644 source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 create mode 100644 source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 create mode 100644 source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 create mode 100644 source/Public/Eventhouse/Get-FabricEventhouse.ps1 create mode 100644 source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 create mode 100644 source/Public/Eventhouse/New-FabricEventhouse copy.ps1 create mode 100644 source/Public/Eventhouse/New-FabricEventhouse.ps1 create mode 100644 source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 create mode 100644 source/Public/Eventhouse/Remove-FabricEventhouse.ps1 create mode 100644 source/Public/Eventhouse/Set-FabricEventhouse.ps1 create mode 100644 source/Public/Eventhouse/Update-FabricEventhouse.ps1 create mode 100644 source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 create mode 100644 source/Public/Eventstream/Get-FabricEventstream copy.ps1 create mode 100644 source/Public/Eventstream/Get-FabricEventstream.ps1 create mode 100644 source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 create mode 100644 source/Public/Eventstream/New-FabricEventstream copy.ps1 create mode 100644 source/Public/Eventstream/New-FabricEventstream.ps1 create mode 100644 source/Public/Eventstream/Remove-FabricEventstream copy.ps1 create mode 100644 source/Public/Eventstream/Remove-FabricEventstream.ps1 create mode 100644 source/Public/Eventstream/Set-FabricEventstream.ps1 create mode 100644 source/Public/Eventstream/Update-FabricEventstream.ps1 create mode 100644 source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 create mode 100644 source/Public/External Data Share/Get-FabricExternalDataShares.ps1 create mode 100644 source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 create mode 100644 source/Public/Get-AllFabricDatasetRefreshes.ps1 create mode 100644 source/Public/Get-FabricAPIClusterURI.ps1 create mode 100644 source/Public/Get-FabricAuthToken.ps1 create mode 100644 source/Public/Get-FabricConnection.ps1 create mode 100644 source/Public/Get-FabricDatasetRefreshes.ps1 create mode 100644 source/Public/Get-FabricDebugInfo.ps1 create mode 100644 source/Public/Get-FabricUsageMetricsQuery.ps1 create mode 100644 source/Public/Get-SHA256.ps1 delete mode 100644 source/Public/Get-Something.ps1 create mode 100644 source/Public/Invoke-FabricAPIRequest.ps1 create mode 100644 source/Public/Invoke-FabricDatasetRefresh.ps1 create mode 100644 source/Public/Item/Export-FabricItem.ps1 create mode 100644 source/Public/Item/Get-FabricItem.ps1 create mode 100644 source/Public/Item/Import-FabricItem.ps1 create mode 100644 source/Public/Item/Remove-FabricItem.ps1 create mode 100644 source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 create mode 100644 source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 create mode 100644 source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 create mode 100644 source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 create mode 100644 source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 create mode 100644 source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 create mode 100644 source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 create mode 100644 source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 create mode 100644 source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 create mode 100644 source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 create mode 100644 source/Public/KQL Database/Get-FabricKQLDatabase.ps1 create mode 100644 source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 create mode 100644 source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 create mode 100644 source/Public/KQL Database/New-FabricKQLDatabase.ps1 create mode 100644 source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 create mode 100644 source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 create mode 100644 source/Public/KQL Database/Set-FabricKQLDatabase.ps1 create mode 100644 source/Public/KQL Database/Update-FabricKQLDatabase.ps1 create mode 100644 source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 create mode 100644 source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 create mode 100644 source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 create mode 100644 source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 create mode 100644 source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 create mode 100644 source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 create mode 100644 source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 create mode 100644 source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 create mode 100644 source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 create mode 100644 source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 create mode 100644 source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 create mode 100644 source/Public/Lakehouse/Get-FabricLakehouse.ps1 create mode 100644 source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 create mode 100644 source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 create mode 100644 source/Public/Lakehouse/New-FabricLakehouse copy.ps1 create mode 100644 source/Public/Lakehouse/New-FabricLakehouse.ps1 create mode 100644 source/Public/Lakehouse/Remove-FabricLakehouse.ps1 create mode 100644 source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 create mode 100644 source/Public/Lakehouse/Update-FabricLakehouse.ps1 create mode 100644 source/Public/ML Experiment/Get-FabricMLExperiment.ps1 create mode 100644 source/Public/ML Experiment/New-FabricMLExperiment.ps1 create mode 100644 source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 create mode 100644 source/Public/ML Experiment/Update-FabricMLExperiment.ps1 create mode 100644 source/Public/ML Model/Get-FabricMLModel.ps1 create mode 100644 source/Public/ML Model/New-FabricMLModel.ps1 create mode 100644 source/Public/ML Model/Remove-FabricMLModel.ps1 create mode 100644 source/Public/ML Model/Update-FabricMLModel.ps1 create mode 100644 source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 create mode 100644 source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 create mode 100644 source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 create mode 100644 source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 create mode 100644 source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 create mode 100644 source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 create mode 100644 source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 create mode 100644 source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 create mode 100644 source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 create mode 100644 source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 create mode 100644 source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 create mode 100644 source/Public/Notebook/Get-FabricNotebook.ps1 create mode 100644 source/Public/Notebook/Get-FabricNotebookDefinition.ps1 create mode 100644 source/Public/Notebook/New-FabricNotebook.ps1 create mode 100644 source/Public/Notebook/New-FabricNotebookNEW.ps1 create mode 100644 source/Public/Notebook/Remove-FabricNotebook.ps1 create mode 100644 source/Public/Notebook/Update-FabricNotebook.ps1 create mode 100644 source/Public/Notebook/Update-FabricNotebookDefinition.ps1 create mode 100644 source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 create mode 100644 source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 create mode 100644 source/Public/Reflex/Get-FabricReflex.ps1 create mode 100644 source/Public/Reflex/Get-FabricReflexDefinition.ps1 create mode 100644 source/Public/Reflex/New-FabricReflex.ps1 create mode 100644 source/Public/Reflex/Remove-FabricReflex.ps1 create mode 100644 source/Public/Reflex/Update-FabricReflex.ps1 create mode 100644 source/Public/Reflex/Update-FabricReflexDefinition.ps1 create mode 100644 source/Public/Report/Get-FabricReport.ps1 create mode 100644 source/Public/Report/Get-FabricReportDefinition.ps1 create mode 100644 source/Public/Report/New-FabricReport.ps1 create mode 100644 source/Public/Report/Remove-FabricReport.ps1 create mode 100644 source/Public/Report/Update-FabricReport.ps1 create mode 100644 source/Public/Report/Update-FabricReportDefinition.ps1 create mode 100644 source/Public/SQL Database/Get-FabricSQLDatabase.ps1 create mode 100644 source/Public/SQL Database/New-FabricSQLDatabase.ps1 create mode 100644 source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 create mode 100644 source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 create mode 100644 source/Public/Semantic Model/Get-FabricSemanticModel.ps1 create mode 100644 source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 create mode 100644 source/Public/Semantic Model/New-FabricSemanticModel.ps1 create mode 100644 source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 create mode 100644 source/Public/Semantic Model/Update-FabricSemanticModel.ps1 create mode 100644 source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 create mode 100644 source/Public/Set-FabricAuthToken.ps1 create mode 100644 source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 create mode 100644 source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 create mode 100644 source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 create mode 100644 source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 create mode 100644 source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 create mode 100644 source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 create mode 100644 source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 create mode 100644 source/Public/Spark/Get-FabricSparkCustomPool.ps1 create mode 100644 source/Public/Spark/Get-FabricSparkSettings.ps1 create mode 100644 source/Public/Spark/New-FabricSparkCustomPool.ps1 create mode 100644 source/Public/Spark/Remove-FabricSparkCustomPool.ps1 create mode 100644 source/Public/Spark/Update-FabricSparkCustomPool.ps1 create mode 100644 source/Public/Spark/Update-FabricSparkSettings.ps1 create mode 100644 source/Public/Tenant copy/Get-FabricCapacityTenantSettingOverrides.ps1 create mode 100644 source/Public/Tenant copy/Get-FabricDomainTenantSettingOverrides.ps1 create mode 100644 source/Public/Tenant copy/Get-FabricTenantSetting.ps1 create mode 100644 source/Public/Tenant copy/Get-FabricWorkspaceTenantSettingOverrides.ps1 create mode 100644 source/Public/Tenant copy/Revoke-FabricCapacityTenantSettingOverrides.ps1 create mode 100644 source/Public/Tenant copy/Update-FabricCapacityTenantSettingOverrides.ps1 create mode 100644 source/Public/Tenant copy/Update-FabricTenantSetting.ps1 create mode 100644 source/Public/Tenant/Get-FabricTenantSettings.ps1 create mode 100644 source/Public/Users/Get-FabricUserListAccessEntities.ps1 create mode 100644 source/Public/Utils/Convert-FromBase64.ps1 create mode 100644 source/Public/Utils/Convert-ToBase64.ps1 create mode 100644 source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 create mode 100644 source/Public/Utils/Get-FabricLongRunningOperation.ps1 create mode 100644 source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 create mode 100644 source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 create mode 100644 source/Public/Utils/Invoke-FabricAPIRequest.ps1 create mode 100644 source/Public/Utils/Set-FabricApiHeaders copy.ps1 create mode 100644 source/Public/Utils/Set-FabricApiHeaders.ps1 create mode 100644 source/Public/Utils/Test-FabricApiResponse.ps1 create mode 100644 source/Public/Warehouse/Get-FabricWarehouse.ps1 create mode 100644 source/Public/Warehouse/New-FabricWarehouse.ps1 create mode 100644 source/Public/Warehouse/Remove-FabricWarehouse.ps1 create mode 100644 source/Public/Warehouse/Update-FabricWarehouse.ps1 create mode 100644 source/Public/Workspace copy/Add-FabricWorkspaceIdentity.ps1 create mode 100644 source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Workspace copy/Assign-FabricWorkspaceCapacity.ps1 create mode 100644 source/Public/Workspace copy/Get-FabricWorkspace.ps1 create mode 100644 source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Workspace copy/New-FabricWorkspace.ps1 create mode 100644 source/Public/Workspace copy/Remove-FabricWorkspace.ps1 create mode 100644 source/Public/Workspace copy/Remove-FabricWorkspaceIdentity.ps1 create mode 100644 source/Public/Workspace copy/Remove-FabricWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Workspace copy/Unassign-FabricWorkspaceCapacity.ps1 create mode 100644 source/Public/Workspace copy/Update-FabricWorkspace.ps1 create mode 100644 source/Public/Workspace copy/Update-FabricWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Workspace/Get-FabricWorkspace.ps1 create mode 100644 source/Public/Workspace/Get-FabricWorkspace2.ps1 create mode 100644 source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 create mode 100644 source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 create mode 100644 source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 create mode 100644 source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 create mode 100644 source/Public/Workspace/New-FabricWorkspace.ps1 create mode 100644 source/Public/Workspace/New-FabricWorkspace2.ps1 create mode 100644 source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 create mode 100644 source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 create mode 100644 source/Public/Workspace/Remove-FabricWorkspace.ps1 create mode 100644 source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 delete mode 100644 tests/Unit/Classes/class1.tests.ps1 delete mode 100644 tests/Unit/Classes/class11.tests.ps1 delete mode 100644 tests/Unit/Classes/class12.tests.ps1 delete mode 100644 tests/Unit/Classes/class2.tests.ps1 delete mode 100644 tests/Unit/Private/Get-PrivateFunction.tests.ps1 delete mode 100644 tests/Unit/Public/Get-Something.tests.ps1 diff --git a/GitVersion.yml b/GitVersion.yml index cfbd9d96..21fc5dab 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,5 +1,5 @@ mode: ContinuousDelivery -next-version: 0.0.1 +next-version: 0.11.0 major-version-bump-message: '(breaking\schange|breaking|major)\b' minor-version-bump-message: '(adds?|features?|minor)\b' patch-version-bump-message: '\s?(fix|patch)' @@ -37,4 +37,3 @@ merge-message-formats: {} # increment: Patch # regex: (hot)?fix(es)?[/-] # source-branches: ['master'] - diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3ce21bc7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Data Platform Community + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 5c3024f1..db956b79 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -20,10 +20,8 @@ Sampler = 'latest' 'Sampler.GitHubTasks' = 'latest' MarkdownLinkCheck = 'latest' - 'DscResource.Common' = 'latest' - 'DscResource.Test' = 'latest' - 'DscResource.AnalyzerRules' = 'latest' - xDscResourceDesigner = 'latest' - 'DscResource.DocGenerator' = 'latest' + PSFramework = 'latest' + 'Az.Accounts' = 'latest' + 'Az.Resources' = 'latest' + 'MicrosoftPowerBIMgmt' = 'latest' } - diff --git a/images/FabricToolsLogo.ico b/images/FabricToolsLogo.ico new file mode 100644 index 0000000000000000000000000000000000000000..91f0fc5f38b08bce67df599583e37083f2d6ffc5 GIT binary patch literal 195867 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
WVYac9?bXlwjiuHR7ZopEt*E(0eH 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 Cdgcd@PPNXWmNRuW-5JYLxK~z9Nkd6vS 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 B>qY + + + \ No newline at end of file diff --git a/images/Fabtools.ico b/images/Fabtools.ico new file mode 100644 index 0000000000000000000000000000000000000000..52f46acc4c7340a54b049fca83b06468d374d451 GIT binary patch literal 90222 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

m%s(q1)$v5?L)Z2|n`t`$N> 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
aaeAhX8=r!FcZZ90fMg40i%W1I?= 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

M?L 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 z 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ţ|<|#R^ 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 RJ`TVSjpsU$3Kg5T{{w8(Z(0BV literal 0 HcmV?d00001 diff --git a/images/Fabtools50.png b/images/Fabtools50.png new file mode 100644 index 0000000000000000000000000000000000000000..bd3393bff2bf0dacb75cacb005295de52c685714 GIT binary patch literal 180186 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%0DS65-z2@^L|?(U)7 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%0A!rDA0suf=Ga_qkUJ{U24eZ!O%jNEiH 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� zFX`TEGG^Y1?{Ba(q!=S7V2@zm0X4w>Ml5^6){AJ`TN#poRwnM4h9W8LdCF=1Umw}t zgfXWrlDQ7Deh8X{q4Qfk)FdfLf#hl`#Q@>E9LEVpY<}|$uG5Q;*EVM& z)@-!qRd;10mbGpzRr7BZ=V^sKzj6^>VI6&O%!yn`f%Kx*YsV48D}ya*|A4 zK7WvYzZlrh<}?_@NjE+DuOQ|St$#AXx=dk`TXZ$fJ|bs>=kt{YuNOIAMth*=m>DV`iJ-G5t-q>iGo`wJ3OEO?oHljk~A@fl+Zj#UgPblAjZ zwVX3>&hdA5F}^SS6mRq zKJi_ed|gc!{&_8Kb`txaJZH*CP@1<`%PMA)V zPG)L({T_*bKJ}Z=hts8;=l6XjlY7l+vVH0U7CeP*V)#;KXKz=%e1?JFe0|(b2e>xD z>nEQ%7fprR1b%{93bZkXl!Z~fFh?u* z0J(2H+x54HOq@67ZR|u5T2pK$7eiC49eX?dx;Ljo0cS2Xvo`gL{7>{DXKyUeH7}1Y zXJ$TkA#7U~kGy0yh+0>Ty6iFn_K2+-{4w48i6)(z5e3#14}TxO4c`O^OIUCA6>yal4p@oKwTw!1YZekG3Jx7c6W~ zNbnFZ;0%r3{Tnu~%tm{kh~Kjg?J-^~`@=KhGN$F}6>Uu?a-?w^@at5rpHZ*mXV z?g=AU_V#=_0pQlaM>IN$)yv^eUTxOv4R#w6=BsXQ2SAAT%Z1)b%7_SMjO4dZA!7ee fx8?v|`^~n3XjRQ~VH4N?UN0$ed9kvezkUA)C{r=t literal 0 HcmV?d00001 diff --git a/source/Classes/1.class1.ps1 b/source/Classes/1.class1.ps1 deleted file mode 100644 index 22cc7dae..00000000 --- a/source/Classes/1.class1.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -class Class1 -{ - [string]$Name = 'Class1' - - Class1() - { - #default Constructor - } - - [String] ToString() - { - # Typo "calss" is intentional - return ( 'This calss is {0}' -f $this.Name) - } -} diff --git a/source/Classes/2.class2.ps1 b/source/Classes/2.class2.ps1 deleted file mode 100644 index 801c626d..00000000 --- a/source/Classes/2.class2.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -class Class2 -{ - [string]$Name = 'Class2' - - Class2() - { - #default constructor - } - - [String] ToString() - { - return ( 'This calss is {0}' -f $this.Name) - } -} diff --git a/source/Classes/3.class11.ps1 b/source/Classes/3.class11.ps1 deleted file mode 100644 index a8202a35..00000000 --- a/source/Classes/3.class11.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -class Class11 : Class1 -{ - [string]$Name = 'Class11' - - Class11 () - { - } - - [String] ToString() - { - return ( 'This calss is {0}:{1}' -f $this.Name,'class1') - } -} diff --git a/source/Classes/4.class12.ps1 b/source/Classes/4.class12.ps1 deleted file mode 100644 index c04a82d1..00000000 --- a/source/Classes/4.class12.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -class Class12 : Class1 -{ - [string]$Name = 'Class12' - - Class12 () - { - } - - [String] ToString() - { - return ( 'This calss is {0}:{1}' -f $this.Name,'class1') - } -} diff --git a/source/Private/Get-PrivateFunction.ps1 b/source/Private/Get-PrivateFunction.ps1 deleted file mode 100644 index b4ef195a..00000000 --- a/source/Private/Get-PrivateFunction.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -function Get-PrivateFunction -{ - <# - .SYNOPSIS - This is a sample Private function only visible within the module. - - .DESCRIPTION - This sample function is not exported to the module and only return the data passed as parameter. - - .EXAMPLE - $null = Get-PrivateFunction -PrivateData 'NOTHING TO SEE HERE' - - .PARAMETER PrivateData - The PrivateData parameter is what will be returned without transformation. - - #> - [cmdletBinding()] - [OutputType([string])] - param - ( - [Parameter()] - [String] - $PrivateData - ) - - process - { - Write-Output $PrivateData - } - -} diff --git a/source/Private/Test-TokenExpired.ps1 b/source/Private/Test-TokenExpired.ps1 new file mode 100644 index 00000000..1f3da68f --- /dev/null +++ b/source/Private/Test-TokenExpired.ps1 @@ -0,0 +1,66 @@ +<# +.SYNOPSIS +Checks if the Fabric token is expired and logs appropriate messages. + +.DESCRIPTION +The `Test-TokenExpired` function checks the expiration status of the Fabric token stored in the `$FabricConfig.TokenExpiresOn` variable. +If the token is expired, it logs an error message and provides guidance for refreshing the token. +Otherwise, it logs that the token is still valid. + +.PARAMETER FabricConfig +The configuration object containing the token expiration details. + +.EXAMPLE +Test-TokenExpired -FabricConfig $config + +Checks the token expiration status using the provided `$config` object. + +.NOTES +- Ensure the `FabricConfig` object includes a valid `TokenExpiresOn` property of type `DateTimeOffset`. +- Requires the `Write-Message` function for logging. + +.AUTHOR +Tiago Balabuch +#> +function Test-TokenExpired { + [CmdletBinding()] + param () + + Confirm-FabricAuthToken | Out-Null + + Write-Message -Message "Validating token..." -Level Debug + + try { + # Ensure required properties have valid values + if ([string]::IsNullOrWhiteSpace($FabricConfig.TenantIdGlobal) -or + [string]::IsNullOrWhiteSpace($FabricConfig.TokenExpiresOn)) { + Write-Message -Message "Token details are missing. Please run 'Set-FabricApiHeaders' to configure them." -Level Error + throw "MissingTokenDetailsException: Token details are missing." + } + + # Convert the TokenExpiresOn value to a DateTime object + if ($FabricConfig.TokenExpiresOn.GetType() -eq [datetimeoffset]) { + $tokenExpiryDate = $FabricConfig.TokenExpiresOn + } else { + $tokenExpiryDate = [datetimeoffset]::Parse($FabricConfig.TokenExpiresOn) + } + + # Check if the token is expired + if ($tokenExpiryDate -le [datetimeoffset]::Now) { + Write-Message -Message "Your authentication token has expired. Please sign in again to refresh your session." -Level Warning + #throw "TokenExpiredException: Token has expired." + #Set-FabricApiHeaders -tenantId $FabricConfig.TenantIdGlobal + } + + # Log valid token status + Write-Message -Message "Token is still valid. Expiry time: $($tokenExpiryDate.ToString("u"))" -Level Debug + } catch [System.FormatException] { + Write-Message -Message "Invalid 'TokenExpiresOn' format in the FabricConfig object. Ensure it is a valid datetime string." -Level Error + throw "FormatException: Invalid TokenExpiresOn value." + } catch { + # Log unexpected errors with details + Write-Message -Message "An unexpected error occurred: $_" -Level Error + throw $_ + } + Write-Message -Message "Token validation completed." -Level Debug +} diff --git a/source/Public/Capacity/Get-AllFabricCapacities.ps1 b/source/Public/Capacity/Get-AllFabricCapacities.ps1 new file mode 100644 index 00000000..1e5434db --- /dev/null +++ b/source/Public/Capacity/Get-AllFabricCapacities.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS + This function retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. + +.DESCRIPTION + The Get-AllFabricCapacities function is used to retrieve all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. It uses the Az module to interact with Azure. + +.PARAMETER subscriptionID + An optional parameter that specifies the subscription ID. If this parameter is not provided, the function will retrieve resources from all subscriptions. + +.EXAMPLE + Get-AllFabricCapacities -subscriptionID "12345678-1234-1234-1234-123456789012" + + This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in the subscription with the ID "12345678-1234-1234-1234-123456789012". + +.EXAMPLE + Get-AllFabricCapacities + + This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in all subscriptions. + +.NOTES + Alias: Get-AllFabCapacities +#> +function Get-AllFabricCapacities { + # Define aliases for the function for flexibility. + + # Define parameters for the function + Param ( + # Optional parameter for subscription ID + [Parameter(Mandatory = $false)] + [string]$subscriptionID + ) + + # Initialize an array to store the results + $res = @() + + Get-FabricAuthToken | Out-Null + + # If a subscription ID is provided + if ($subscriptionID) { + # Set the context to the provided subscription ID + Set-AzContext -SubscriptionId $subscriptionID | Out-Null + + # Get all resource groups in the subscription + $rgs = Get-AzResourceGroup + + # For each resource group, get all resources of type "Microsoft.Fabric/capacities" + foreach ($r in $rgs) { + # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array + $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue + } + } + else { + # If no subscription ID is provided, get all subscriptions + $subscriptions = Get-AzSubscription + + # For each subscription, set the context to the subscription ID + foreach ($sub in $subscriptions) { + # Set the context to the subscription ID + Set-AzContext -SubscriptionId $sub.id | Out-Null + + # Get all resource groups in the subscription + $rgs = Get-AzResourceGroup + + # For each resource group, get all resources of type "Microsoft.Fabric/capacities" + foreach ($r in $rgs) { + # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array + $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue + } + } + } + + # Return the results + return $res +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacity copy.ps1 b/source/Public/Capacity/Get-FabricCapacity copy.ps1 new file mode 100644 index 00000000..a36c88ca --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacity copy.ps1 @@ -0,0 +1,92 @@ + +<# +.SYNOPSIS + Retrieves capacity details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves capacity details from a specified workspace using either the provided capacityId or capacityName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER capacityId + The unique identifier of the capacity to retrieve. This parameter is optional. + +.PARAMETER capacityName + The name of the capacity to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricCapacity -capacityId "capacity-12345" + This example retrieves the capacity details for the capacity with ID "capacity-12345". + +.EXAMPLE + Get-FabricCapacity -capacityName "MyCapacity" + This example retrieves the capacity details for the capacity named "MyCapacity". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Get-FabricCapacity { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$capacityId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$capacityName + ) + try { + # Handle ambiguous input + if ($capacityId -and $capacityName) { + Write-Message -Message "Both 'capacityId' and 'capacityName' were provided. Please specify only one." -Level Error + return $null + } + + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API endpoint URL + $apiEndpointURI = "{0}/capacities" -f $FabricConfig.BaseUrl + + # Invoke the Fabric API to retrieve capacity details + $capacities = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + + # Filter results based on provided parameters + $response = if ($capacityId) { + $capacities | Where-Object { $_.Id -eq $capacityId } + } + elseif ($capacityName) { + $capacities | Where-Object { $_.DisplayName -eq $capacityName } + } + else { + # No filter, return all capacities + Write-Message -Message "No filter specified. Returning all capacities." -Level Debug + return $capacities + } + + # Handle results + if ($response) { + Write-Message -Message "Capacity found matching the specified criteria." -Level Debug + return $response + } + else { + Write-Message -Message "No capacity found matching the specified criteria." -Level Warning + return $null + } + } + catch { + # Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error + return $null + } +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacity.ps1 b/source/Public/Capacity/Get-FabricCapacity.ps1 new file mode 100644 index 00000000..78fa93d9 --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacity.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS +Retrieves the fabric capacity information. + +.DESCRIPTION +This function makes a GET request to the Fabric API to retrieve the tenant settings. + +.PARAMETER capacity +Specifies the capacity to retrieve information for. If not provided, all capacities will be retrieved. + +.EXAMPLE +Get-FabricCapacity -capacity "exampleCapacity" +Retrieves the fabric capacity information for the specified capacity. + +Get-FabricCapacity +Retrieves the fabric capacity information for all capacities. + +#> + +function Get-FabricCapacity { + # Define aliases for the function for flexibility. + [Alias("Get-FabCapacity")] + + Param( + [Parameter(Mandatory=$false)] + [string]$capacity + ) + + Confirm-FabricAuthToken | Out-Null + + if ($capacity) { + $result = Invoke-FabricAPIRequest -uri "capacities/$capacity" -Method GET + } else { + $result = Invoke-FabricAPIRequest -uri "capacities" -Method GET + } + + return $result.value + +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 b/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 new file mode 100644 index 00000000..fa21ff63 --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 @@ -0,0 +1,36 @@ +<# +.SYNOPSIS +Retrieves the top refreshable capacities for the tenant. + +.DESCRIPTION +The Get-FabricCapacityRefreshables function retrieves the top refreshable capacities for the tenant. It supports multiple aliases for flexibility. + +.PARAMETER top +The number of top refreshable capacities to retrieve. This is a mandatory parameter. + +.EXAMPLE +Get-FabricCapacityRefreshables -top 5 + +This example retrieves the top 5 refreshable capacities for the tenant. + +.NOTES +The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the top refreshable capacities. It then returns the 'value' property of the response, which contains the capacities. +#> + +# This function retrieves the top refreshable capacities for the tenant. +function Get-FabricCapacityRefreshables { + # Define aliases for the function for flexibility. + [Alias("Get-FabCapacityRefreshables")] + + # Define a mandatory parameter for the number of top refreshable capacities to retrieve. + Param ( + [Parameter(Mandatory=$false)] + [string]$top = 5 + ) + + Confirm-FabricAuthToken | Out-Null + + # Make a GET request to the PowerBI API to retrieve the top refreshable capacities. + # The function returns the 'value' property of the response. + return (Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/capacities/refreshables?`$top=$top" -Headers $FabricSession.HeaderParams -Method GET).value +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacitySkus.ps1 b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 new file mode 100644 index 00000000..06e27a5f --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 @@ -0,0 +1,37 @@ +<# +.SYNOPSIS +Retrieves the fabric capacity information. + +.DESCRIPTION +This function makes a GET request to the Fabric API to retrieve the tenant settings. + +.PARAMETER capacity +Specifies the capacity to retrieve information for. If not provided, all capacities will be retrieved. + +.EXAMPLE +Get-FabricCapacitySkus -capacity "exampleCapacity" +Retrieves the fabric capacity information for the specified capacity. + +#> + +function Get-FabricCapacitySkus { + # Define aliases for the function for flexibility. + + Param( + [Parameter(Mandatory = $true)] + [string]$subscriptionID, + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + [Parameter(Mandatory=$true)] + [string]$capacity + ) + + Confirm-FabricAuthToken | Out-Null + + #GET https://management.azure.com/subscriptions/548B7FB7-3B2A-4F46-BB02-66473F1FC22C/resourceGroups/TestRG/providers/Microsoft.Fabric/capacities/azsdktest/skus?api-version=2023-11-01 + $uri = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.Fabric/capacities/$capacity/skus?api-version=2023-11-01" + $result = Invoke-RestMethod -Headers $AzureSession.HeaderParams -Uri $uri -Method GET + + return $result.value + +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityState.ps1 b/source/Public/Capacity/Get-FabricCapacityState.ps1 new file mode 100644 index 00000000..7b8f5307 --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacityState.ps1 @@ -0,0 +1,48 @@ +<# +.SYNOPSIS +Retrieves the state of a specific capacity. + +.DESCRIPTION +The Get-FabricCapacityState function retrieves the state of a specific capacity. It supports multiple aliases for flexibility. + +.PARAMETER subscriptionID +The ID of the subscription. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.PARAMETER resourcegroup +The resource group. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.PARAMETER capacity +The capacity. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.EXAMPLE +Get-FabricCapacityState -subscriptionID "your-subscription-id" -resourcegroupID "your-resource-group" -capacityID "your-capacity" + +This example retrieves the state of a specific capacity given the subscription ID, resource group, and capacity. + +.NOTES +The function checks if the Azure token is null. If it is, it connects to the Azure account and retrieves the token. It then defines the headers for the GET request and the URL for the GET request. Finally, it makes the GET request and returns the response. +#> + +# This function retrieves the state of a specific capacity. +function Get-FabricCapacityState { + # Define aliases for the function for flexibility. + [Alias("Get-FabCapacityState")] + + # Define mandatory parameters for the subscription ID, resource group, and capacity. + Param ( + [Parameter(Mandatory=$true)] + [string]$subscriptionID, + [Parameter(Mandatory=$true)] + [string]$resourcegroup, + [Parameter(Mandatory=$true)] + [string]$capacity + ) + + Confirm-FabricAuthToken | Out-Null + + # Define the URL for the GET request. + $getCapacityState = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$resourcegroup/providers/Microsoft.Fabric/capacities/$capacity/?api-version=2022-07-01-preview" + + # Make the GET request and return the response. + return Invoke-RestMethod -Method GET -Uri $getCapacityState -Headers $script:AzureSession.HeaderParams -ErrorAction Stop +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 b/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 new file mode 100644 index 00000000..ac6862bd --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 @@ -0,0 +1,33 @@ +<# +.SYNOPSIS +Retrieves the tenant overrides for all capacities. + +.DESCRIPTION +The Get-FabricCapacityTenantOverrides function retrieves the tenant overrides for all capacities. It supports multiple aliases for flexibility. + +.PARAMETER authToken +The authentication token used to authorize the request. If not provided, the function will retrieve the token using the Get-FabricAuthToken function. + +.EXAMPLE +Get-FabricCapacityTenantOverrides + +This example retrieves the tenant overrides for all capacities. + +.NOTES +The function retrieves the PowerBI access token and makes a GET request to the Fabric API to retrieve the tenant overrides for all capacities. It then returns the response of the GET request. +#> + +# This function retrieves the tenant overrides for all capacities. +function Get-FabricCapacityTenantOverrides { + # Define aliases for the function for flexibility. + [Alias("Get-FabCapacityTenantOverrides")] + + Param ( + ) + + Confirm-FabricAuthToken | Out-Null + + # Make a GET request to the Fabric API to retrieve the tenant overrides for all capacities. + # The function returns the response of the GET request. + return Invoke-RestMethod -uri "$($FabricSession.BaseApiUrl)/admin/capacities/delegatedTenantSettingOverrides" -Headers $FabricSession.HeaderParams -Method GET +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 b/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 new file mode 100644 index 00000000..f7036ae3 --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 @@ -0,0 +1,42 @@ +<# +.SYNOPSIS +Retrieves the workloads for a specific capacity. + +.DESCRIPTION +The Get-FabricCapacityWorkload function retrieves the workloads for a specific capacity. It supports multiple aliases for flexibility. + +.PARAMETER capacityID +The ID of the capacity. This is a mandatory parameter. + +.PARAMETER authToken +The authentication token to access the PowerBI API. If not provided, the function will retrieve the token using the Get-FabricAuthToken function. + +.EXAMPLE +Get-FabricCapacityWorkload -capacityID "your-capacity-id" -authToken "your-auth-token" + +This example retrieves the workloads for a specific capacity given the capacity ID and authentication token. + +.NOTES +The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the workloads for the specified capacity. It then returns the 'value' property of the response, which contains the workloads. +#> + +# This function retrieves the workloads for a specific capacity. +function Get-FabricCapacityWorkload { + # Define aliases for the function for flexibility. + [Alias("Get-FabCapacityWorkload")] + + # Define a mandatory parameter for the capacity ID. + Param ( + [Parameter(Mandatory=$true)] + [string]$capacityID + ) + + Confirm-FabricAuthToken | Out-Null + + # Make a GET request to the PowerBI API to retrieve the workloads for the specified capacity. + # The function returns the 'value' property of the response. + return (Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/capacities/$capacityID/Workloads" -Headers $FabricSession.HeaderParams -Method GET).value +} + + +#https://learn.microsoft.com/en-us/rest/api/power-bi/capacities/get-workloads \ No newline at end of file diff --git a/source/Public/Capacity/Resume-FabricCapacity.ps1 b/source/Public/Capacity/Resume-FabricCapacity.ps1 new file mode 100644 index 00000000..edf0d2f0 --- /dev/null +++ b/source/Public/Capacity/Resume-FabricCapacity.ps1 @@ -0,0 +1,51 @@ +<# +.SYNOPSIS +Resumes a capacity. + +.DESCRIPTION +The Resume-FabricCapacity function resumes a capacity. It supports multiple aliases for flexibility. + +.PARAMETER subscriptionID +The the ID of the subscription. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.PARAMETER resourcegroup +The resource group. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.PARAMETER capacity +The capacity. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.EXAMPLE +Resume-FabricCapacity -subscriptionID "your-subscription-id" -resourcegroupID "your-resource-group" -capacityID "your-capacity" + +This example resumes a capacity given the subscription ID, resource group, and capacity. + +.NOTES +The function defines parameters for the subscription ID, resource group, and capacity. If the 'azToken' environment variable is null, it connects to the Azure account and sets the 'azToken' environment variable. It then defines the headers for the request, defines the URI for the request, and makes a GET request to the URI. +#> + +# This function resumes a capacity. +function Resume-FabricCapacity { + # Define aliases for the function for flexibility. + [Alias("Resume-FabCapacity")] + [CmdletBinding(SupportsShouldProcess)] + + # Define parameters for the subscription ID, resource group, and capacity. + Param ( + [Parameter(Mandatory = $true)] + [string]$subscriptionID, + [Parameter(Mandatory = $true)] + [string]$resourcegroup, + [Parameter(Mandatory = $true)] + [string]$capacity + ) + + Confirm-FabricAuthToken | Out-Null + + # Define the URI for the request. + $resumeCapacity = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$resourcegroup/providers/Microsoft.Fabric/capacities/$capacity/resume?api-version=2022-07-01-preview" + + # Make a GET request to the URI and return the response. + if ($PSCmdlet.ShouldProcess("Resume capacity $capacity")) { + return Invoke-RestMethod -Method POST -Uri $resumeCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop + } +} \ No newline at end of file diff --git a/source/Public/Capacity/Suspend-FabricCapacity.ps1 b/source/Public/Capacity/Suspend-FabricCapacity.ps1 new file mode 100644 index 00000000..d7c3fe94 --- /dev/null +++ b/source/Public/Capacity/Suspend-FabricCapacity.ps1 @@ -0,0 +1,52 @@ +<# +.SYNOPSIS +Suspends a capacity. + +.DESCRIPTION +The Suspend-FabricCapacity function suspends a capacity. It supports multiple aliases for flexibility. + +.PARAMETER subscriptionID +The ID of the subscription. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.PARAMETER resourcegroup +The resource group. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.PARAMETER capacity +The the capacity. This is a mandatory parameter. This is a parameter found in Azure, not Fabric. + +.EXAMPLE +Suspend-FabricCapacity -subscriptionID "your-subscription-id" -resourcegroupID "your-resource-group" -capacityID "your-capacity" + +This example suspends a capacity given the subscription ID, resource group, and capacity. + +.NOTES +The function defines parameters for the subscription ID, resource group, and capacity. If the 'azToken' environment variable is null, it connects to the Azure account and sets the 'azToken' environment variable. It then defines the headers for the request, defines the URI for the request, and makes a GET request to the URI. +#> + +# This function suspends a capacity. +function Suspend-FabricCapacity { + # Define aliases for the function for flexibility. + [Alias("Suspend-PowerBICapacity", "Suspend-FabCapacity")] + [CmdletBinding(SupportsShouldProcess)] + + # Define parameters for the subscription ID, resource group, and capacity. + Param ( + [Parameter(Mandatory = $true)] + [string]$subscriptionID, + [Parameter(Mandatory = $true)] + [string]$resourcegroup, + [Parameter(Mandatory = $true)] + [string]$capacity + ) + + Confirm-FabricAuthToken | Out-Null + + # Define the URI for the request. + $suspendCapacity = "$($AzureSession.BaseApiUrl)/subscriptions/$subscriptionID/resourceGroups/$resourcegroup/providers/Microsoft.Fabric/capacities/$capacity/suspend?api-version=2023-11-01" + + # Make a GET request to the URI and return the response. + if ($PSCmdlet.ShouldProcess("Suspend capacity $capacity")) { + return Invoke-RestMethod -Method POST -Uri $suspendCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop + } + +} \ No newline at end of file diff --git a/source/Public/Confirm-FabricAuthToken.ps1 b/source/Public/Confirm-FabricAuthToken.ps1 new file mode 100644 index 00000000..14c7933b --- /dev/null +++ b/source/Public/Confirm-FabricAuthToken.ps1 @@ -0,0 +1,50 @@ +<# +.SYNOPSIS + Check whether the Fabric API authentication token is set and not expired and reset it if necessary. + +.DESCRIPTION + The Confirm-FabricAuthToken function retrieves the Fabric API authentication token. If the token is not already set, it calls the Set-FabricAuthToken function to set it. It then outputs the token. + +.EXAMPLE + Confirm-FabricAuthToken + + This command retrieves the Fabric API authentication token. + +.INPUTS + None. You cannot pipe inputs to this function. + +.OUTPUTS + Returns object as Get-FabricDebugInfo function + +.NOTES + +#> + +function Confirm-FabricAuthToken { + [CmdletBinding()] + param ( ) + + Write-Verbose "Check if session is established and token not expired." + + # Check if the Fabric token is already set + if (!$FabricSession.FabricToken -or !$AzureSession.AccessToken) { + Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken" + Set-FabricAuthToken | Out-Null + } + + $now = (Get-Date) + $s = Get-FabricDebugInfo + if ($FabricSession.AccessToken.ExpiresOn -lt $now ) { + Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#1" + Set-FabricAuthToken -reset | Out-Null + } + + if ($s.AzureSession.AccessToken.ExpiresOn -lt $now ) { + Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#2" + Set-FabricAuthToken -reset | Out-Null + } + + $s = Get-FabricDebugInfo + return $s + +} \ No newline at end of file diff --git a/source/Public/Connect-FabricAccount.ps1 b/source/Public/Connect-FabricAccount.ps1 new file mode 100644 index 00000000..a96a1cec --- /dev/null +++ b/source/Public/Connect-FabricAccount.ps1 @@ -0,0 +1,56 @@ +function Connect-FabricAccount { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Connects to the Fabric WebAPI. + +.DESCRIPTION + Connects to the Fabric WebAPI by using the cmdlet Connect-AzAccount. + This function retrieves the authentication token for the Fabric API and sets up the headers for API calls. + +.PARAMETER TenantId + The TenantId of the Azure Active Directory tenant you want to connect to + and in which your Fabric Capacity is. + +.EXAMPLE + Connect-FabricAccount ` + -TenantID '12345678-1234-1234-1234-123456789012' + +.NOTES + + Revsion History: + + - 2024-12-22 - FGE: Added Verbose Output + +.LINK + Connect-AzAccount https://learn.microsoft.com/de-de/powershell/module/az.accounts/connect-azaccount?view=azps-12.4.0 + +#> + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$TenantId + ) + +begin { +} + +process { + Write-Verbose "Connect to Azure Account" + Connect-AzAccount -TenantId $TenantId | Out-Null + + Write-Verbose "Get authentication token" + $FabricSession.FabricToken = (Get-AzAccessToken -ResourceUrl $FabricSession.BaseApiUrl).Token + Write-Verbose "Token: $($FabricSession.FabricToken)" + + Write-Verbose "Setup headers for API calls" + $FabricSession.HeaderParams = @{'Authorization'="Bearer {0}" -f $FabricSession.FabricToken} + Write-Verbose "HeaderParams: $($FabricSession.HeaderParams)" +} + +end { +} + +} \ No newline at end of file diff --git a/source/Public/Copy Job/Get-FabricCopyJob.ps1 b/source/Public/Copy Job/Get-FabricCopyJob.ps1 new file mode 100644 index 00000000..043ff8de --- /dev/null +++ b/source/Public/Copy Job/Get-FabricCopyJob.ps1 @@ -0,0 +1,100 @@ +<# +.SYNOPSIS + Retrieves CopyJob details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves CopyJob details from a specified workspace using either the provided CopyJobId or CopyJobName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the CopyJob exists. This parameter is mandatory. + +.PARAMETER CopyJobId + The unique identifier of the CopyJob to retrieve. This parameter is optional. + +.PARAMETER CopyJobName + The name of the CopyJob to retrieve. This parameter is optional. + +.EXAMPLE + FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "CopyJob-67890" + This example retrieves the CopyJob details for the CopyJob with ID "CopyJob-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobName "My CopyJob" + This example retrieves the CopyJob details for the CopyJob named "My CopyJob" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function FabricCopyJob { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$CopyJob + ) + + try { + # Handle ambiguous input + if ($CopyJobId -and $CopyJobName) { + Write-Message -Message "Both 'CopyJobId' and 'CopyJobName' were provided. Please specify only one." -Level Error + return $null + } + + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + + # Construct the API endpoint URL + $apiEndpointURI = "{0}/workspaces/{1}/copyJobs" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Invoke the Fabric API to retrieve capacity details + $copyJobs = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + # Filter results based on provided parameters + $response = if ($CopyJobId) { + $copyJobs | Where-Object { $_.Id -eq $CopyJobId } + } + elseif ($CopyJobName) { + $copyJobs | Where-Object { $_.DisplayName -eq $CopyJobName } + } + else { + # Return all CopyJobs if no filter is provided + Write-Message -Message "No filter provided. Returning all CopyJobs." -Level Debug + $copyJobs + } + + # Step 9: Handle results + if ($response) { + Write-Message -Message "CopyJob found matching the specified criteria." -Level Debug + return $response + } + else { + Write-Message -Message "No CopyJob found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve CopyJob. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 b/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 new file mode 100644 index 00000000..bfc2fcff --- /dev/null +++ b/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS +Retrieves the definition of a Copy Job from a specific workspace in Microsoft Fabric. + +.DESCRIPTION +This function fetches the Copy Job's content or metadata from a workspace. +It supports both synchronous and asynchronous operations, with detailed logging and error handling. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace from which the Copy Job definition is to be retrieved. + +.PARAMETER CopyJobId +(Mandatory) The unique identifier of the Copy Job whose definition needs to be retrieved. + +.PARAMETER CopyJobFormat +(Optional) Specifies the format of the Copy Job definition. For example, 'json' or 'xml'. + +.EXAMPLE +Get-FabricCopyJobDefinition -WorkspaceId "12345" -CopyJobId "67890" + +Retrieves the definition of the Copy Job with ID `67890` from the workspace with ID `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. +- Logs detailed information for debugging purposes. + +#> +function Get-FabricCopyJobDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobFormat + ) + + try { + # Step 1: Validate authentication token before proceeding. + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Step 2: Construct the API endpoint URL for retrieving the Copy Job definition. + $apiEndpointUrl = "{0}/workspaces/{1}/copyJobs/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId + + # Step 3: Append the format query parameter if specified by the user. + if ($CopyJobFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $CopyJobFormat + } + Write-Message -Message "Constructed API Endpoint URL: $apiEndpointUrl" -Level Debug + + # Step 4: Execute the API request to retrieve the Copy Job definition. + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointUrl ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post + + # Step 5: Return the API response containing the Copy Job definition. + return $response + } + catch { + # Step 6: Capture and log detailed error information for troubleshooting. + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Copy Job definition. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Copy Job/New-FabricCopyJob.ps1 b/source/Public/Copy Job/New-FabricCopyJob.ps1 new file mode 100644 index 00000000..7ac76e1c --- /dev/null +++ b/source/Public/Copy Job/New-FabricCopyJob.ps1 @@ -0,0 +1,145 @@ +<# +.SYNOPSIS + Creates a new copy job in a specified Microsoft Fabric workspace. + +.DESCRIPTION + Sends a POST request to the Microsoft Fabric API to create a new copy job in the specified workspace. + Supports optional parameters for description and definition files. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the copy job will be created. Mandatory. + +.PARAMETER CopyJobName + The name of the copy job to be created. Mandatory. + +.PARAMETER CopyJobDescription + Optional description for the copy job. + +.PARAMETER CopyJobPathDefinition + Optional file path to the copy job definition JSON file. + +.PARAMETER CopyJobPathPlatformDefinition + Optional file path to the platform definition file. + +.EXAMPLE + New-FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobName "New Copy Job" -CopyJobDescription "Description of the new copy job" + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function New-FabricCopyJob { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$CopyJobName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $CopyJobName + } + + if ($CopyJobDescription) { + $body.description = $CopyJobDescription + } + + # Step 4: Add copy job definition file content if provided + if ($CopyJobPathDefinition) { + $CopyJobEncodedContent = Convert-ToBase64 -filePath $CopyJobPathDefinition + + if (-not [string]::IsNullOrEmpty($CopyJobEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "copyjob-content.json" + payload = $CopyJobEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Copy Job definition." -Level Error + return $null + } + } + #Step 5: Add platform definition file content if provided + if ($CopyJobPathPlatformDefinition) { + $CopyJobEncodedPlatformContent = Convert-ToBase64 -filePath $CopyJobPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($CopyJobEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $CopyJobEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 6: Make the API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post ` + -Body $bodyJson + + Write-Message -Message "Copy Job created successfully!" -Level Info + return $response + + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Copy Job. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Copy Job/Remove-FabricCopyJob.ps1 b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 new file mode 100644 index 00000000..d1613966 --- /dev/null +++ b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 @@ -0,0 +1,61 @@ +<# +.SYNOPSIS + Deletes a Copy Job from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function performs a DELETE operation on the Microsoft Fabric API to remove a Copy Job + from the specified workspace using the provided WorkspaceId and CopyJobId parameters. + +.PARAMETER WorkspaceId + The unique identifier of the workspace containing the Copy Job to be deleted. + +.PARAMETER CopyJobId + The unique identifier of the Copy Job to delete. + +.EXAMPLE + Remove-FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "copyjob-67890" + Deletes the Copy Job with ID "copyjob-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires the `$FabricConfig` global configuration, which must include `BaseUrl` and `FabricHeaders`. + - Ensures token validity by invoking `Test-TokenExpired` before making the API request. + + Author: Tiago Balabuch +#> +function Remove-FabricCopyJob { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobId + ) + try { + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API endpoint URI + $apiEndpointURI = "{0}/workspaces/{1}/copyJobs/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -Method Delete + + Write-Message -Message "Copy Job '$CopyJobId' deleted successfully from workspace '$WorkspaceId'." -Level Info + return $response + + } + catch { + # Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Copy Job '$CopyJobId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Copy Job/Update-FabricCopyJob.ps1 b/source/Public/Copy Job/Update-FabricCopyJob.ps1 new file mode 100644 index 00000000..0db436dc --- /dev/null +++ b/source/Public/Copy Job/Update-FabricCopyJob.ps1 @@ -0,0 +1,90 @@ +<# +.SYNOPSIS + Updates an existing Copy Job in a specified Microsoft Fabric workspace. + +.DESCRIPTION + Sends a PATCH request to the Microsoft Fabric API to update an existing Copy Job + in the specified workspace. Allows updating the Copy Job's name and optionally its description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace containing the Copy Job. This parameter is mandatory. + +.PARAMETER CopyJobId + The unique identifier of the Copy Job to be updated. This parameter is mandatory. + +.PARAMETER CopyJobName + The new name for the Copy Job. This parameter is mandatory. + +.PARAMETER CopyJobDescription + An optional new description for the Copy Job. + +.EXAMPLE + Update-FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "copyjob-67890" -CopyJobName "Updated Copy Job" -CopyJobDescription "Updated description" + Updates the Copy Job with ID "copyjob-67890" in the workspace "workspace-12345" with a new name and description. + +.NOTES + - Requires the `$FabricConfig` global configuration, which includes `BaseUrl` and `FabricHeaders`. + - Ensures token validity by calling `Test-TokenExpired` before making the API request. + + Author: Tiago Balabuch +#> +function Update-FabricCopyJob { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$CopyJobName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobDescription + ) + + try { + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API endpoint URI + $apiEndpointURI = "{0}/workspaces/{1}/copyJobs/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Construct the request body + $body = @{ + displayName = $CopyJobName + } + + if ($CopyJobDescription) { + $body.description = $CopyJobDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -Method Patch ` + -Body $bodyJson + + Write-Message -Message "Copy Job '$CopyJobName' updated successfully!" -Level Info + return $response + } + catch { + # Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 b/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 new file mode 100644 index 00000000..4fd57f0f --- /dev/null +++ b/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 @@ -0,0 +1,134 @@ +<# +.SYNOPSIS +Updates the definition of a Copy Job in a Microsoft Fabric workspace. + +.DESCRIPTION +This function updates the content or metadata of a Copy Job within a Microsoft Fabric workspace. +The Copy Job content and platform-specific definitions can be provided as file paths, which will be encoded as Base64 and sent in the request. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace containing the Copy Job. + +.PARAMETER CopyJobId +(Mandatory) The unique identifier of the Copy Job to be updated. + +.PARAMETER CopyJobPathDefinition +(Mandatory) The file path to the Copy Job content definition file. The file content will be encoded as Base64. + +.PARAMETER CopyJobPathPlatformDefinition +(Optional) The file path to the platform-specific definition file for the Copy Job. The file content will be encoded as Base64. + +.EXAMPLE +Update-FabricCopyJobDefinition -WorkspaceId "12345" -CopyJobId "67890" -CopyJobPathDefinition "C:\CopyJobs\CopyJob.ipynb" + +Updates the content of the Copy Job with ID `67890` in the workspace `12345` using the specified Copy Job file. + +.EXAMPLE +Update-FabricCopyJobDefinition -WorkspaceId "12345" -CopyJobId "67890" -CopyJobPathDefinition "C:\CopyJobs\CopyJob.ipynb" -CopyJobPathPlatformDefinition "C:\CopyJobs\Platform.json" + +Updates both the content and platform-specific definition of the Copy Job with ID `67890` in the workspace `12345`. + +.NOTES +- Requires the `$FabricConfig` global configuration, which must include `BaseUrl` and `FabricHeaders`. +- Validates token expiration using `Test-TokenExpired` before making the API request. +- Encodes file content as Base64 before sending it to the Fabric API. +- Logs detailed messages for debugging and error handling. + +Author: Tiago Balabuch +#> + +function Update-FabricCopyJobDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CopyJobPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/copyJobs/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId + + if ($CopyJobPathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + if ($CopyJobPathDefinition) { + $CopyJobEncodedContent = Convert-ToBase64 -filePath $CopyJobPathDefinition + + if (-not [string]::IsNullOrEmpty($CopyJobEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "copyjob-content.json" + payload = $CopyJobEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Copy Job definition." -Level Error + return $null + } + } + + if ($CopyJobPathPlatformDefinition) { + $CopyJobEncodedPlatformContent = Convert-ToBase64 -filePath $CopyJobPathPlatformDefinition + if (-not [string]::IsNullOrEmpty($CopyJobEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $CopyJobEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post ` + -Body $bodyJson + + Write-Message -Message "Copy Job updated successfully!" -Level Info + return $response + + + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Dashboard/Get-FabricDashboard.ps1 b/source/Public/Dashboard/Get-FabricDashboard.ps1 new file mode 100644 index 00000000..ad267a30 --- /dev/null +++ b/source/Public/Dashboard/Get-FabricDashboard.ps1 @@ -0,0 +1,54 @@ +<# +.SYNOPSIS + Retrieves dashboards from a specified workspace. + +.DESCRIPTION + This function retrieves all dashboards from a specified workspace using the provided WorkspaceId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The ID of the workspace from which to retrieve dashboards. This parameter is mandatory. + +.EXAMPLE + Get-FabricDashboard -WorkspaceId "12345" + This example retrieves all dashboards from the workspace with ID "12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> + +function Get-FabricDashboard { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId + ) + + try { + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API endpoint URL + $apiEndpointURI = "{0}/workspaces/{1}/dashboards" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Invoke the Fabric API to retrieve capacity details + $Dashboards = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + return $Dashboards + + } + catch { + # Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Dashboard. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 new file mode 100644 index 00000000..6f944615 --- /dev/null +++ b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 @@ -0,0 +1,99 @@ +<# +.SYNOPSIS + Retrieves data pipelines from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves all data pipelines from a specified workspace using either the provided Data PipelineId or Data PipelineName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Data Pipeline exists. This parameter is mandatory. + +.PARAMETER Data PipelineId + The unique identifier of the Data Pipeline to retrieve. This parameter is optional. + +.PARAMETER Data PipelineName + The name of the Data Pipeline to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineId "Data Pipeline-67890" + This example retrieves the Data Pipeline details for the Data Pipeline with ID "Data Pipeline-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineName "My Data Pipeline" + This example retrieves the Data Pipeline details for the Data Pipeline named "My Data Pipeline" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Get-FabricDataPipeline { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$DataPipelineId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$DataPipelineName + ) + + try { + # Step 1: Handle ambiguous input + if ($DataPipelineId -and $DataPipelineName) { + Write-Message -Message "Both 'DataPipelineId' and 'DataPipelineName' were provided. Please specify only one." -Level Error + return $null + } + + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API endpoint URL + $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Invoke the Fabric API to retrieve capacity details + $DataPipelines = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + # Filter results based on provided parameters + $response = if ($DataPipelineId) { + $DataPipelines | Where-Object { $_.Id -eq $DataPipelineId } + } + elseif ($DataPipelineName) { + $DataPipelines | Where-Object { $_.DisplayName -eq $DataPipelineName } + } + else { + # Return all DataPipelines if no filter is provided + Write-Message -Message "No filter provided. Returning all DataPipelines." -Level Debug + $DataPipelines + } + + # Handle results + if ($response) { + Write-Message -Message "DataPipeline found matching the specified criteria." -Level Debug + return $response + } + else { + Write-Message -Message "No DataPipeline found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve DataPipeline. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 new file mode 100644 index 00000000..5daa2ff3 --- /dev/null +++ b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 @@ -0,0 +1,84 @@ +<# +.SYNOPSIS + Creates a new DataPipeline in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new DataPipeline + in the specified workspace. It supports optional parameters for DataPipeline description + and path definitions for the DataPipeline content. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the DataPipeline will be created. + +.PARAMETER DataPipelineName + The name of the DataPipeline to be created. + +.PARAMETER DataPipelineDescription + An optional description for the DataPipeline. + +.EXAMPLE + New-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineName "New DataPipeline" + This example creates a new DataPipeline named "New DataPipeline" in the workspace with ID "workspace-12345" and uploads the definition file from the specified path. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> + +function New-FabricDataPipeline { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$DataPipelineName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$DataPipelineDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $DataPipelineName + } + + if ($DataPipelineDescription) { + $body.description = $DataPipelineDescription + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post ` + -Body $bodyJson + + Write-Message -Message "Data Pipeline created successfully!" -Level Info + return $response + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create DataPipeline. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 new file mode 100644 index 00000000..f0ef3f1d --- /dev/null +++ b/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 @@ -0,0 +1,60 @@ +<# +.SYNOPSIS + Removes a DataPipeline from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove a DataPipeline + from the specified workspace using the provided WorkspaceId and DataPipelineId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the DataPipeline will be removed. + +.PARAMETER DataPipelineId + The unique identifier of the DataPipeline to be removed. + +.EXAMPLE + Remove-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineId "pipeline-67890" + This example removes the DataPipeline with ID "pipeline-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> + +function Remove-FabricDataPipeline { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DataPipelineId + ) + try { + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API URI + $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $DataPipelineId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -Method Delete + Write-Message -Message "DataPipeline '$DataPipelineId' deleted successfully from workspace '$WorkspaceId'." -Level Info + return $response + } + catch { + # Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete DataPipeline '$DataPipelineId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 new file mode 100644 index 00000000..557fcf6a --- /dev/null +++ b/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 @@ -0,0 +1,90 @@ +<# +.SYNOPSIS + Updates an existing DataPipeline in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing DataPipeline + in the specified workspace. It supports optional parameters for DataPipeline description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the DataPipeline exists. This parameter is optional. + +.PARAMETER DataPipelineId + The unique identifier of the DataPipeline to be updated. This parameter is mandatory. + +.PARAMETER DataPipelineName + The new name of the DataPipeline. This parameter is mandatory. + +.PARAMETER DataPipelineDescription + An optional new description for the DataPipeline. + +.EXAMPLE + Update-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineId "pipeline-67890" -DataPipelineName "Updated DataPipeline" -DataPipelineDescription "Updated description" + This example updates the DataPipeline with ID "pipeline-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Update-FabricDataPipeline { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DataPipelineId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$DataPipelineName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$DataPipelineDescription + ) + + try { + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API URL + $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $DataPipelineId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Construct the request body + $body = @{ + displayName = $DataPipelineName + } + + if ($DataPipelineDescription) { + $body.description = $DataPipelineDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -Method Patch ` + -Body $bodyJson + + Write-Message -Message "DataPipeline '$DataPipelineName' updated successfully!" -Level Info + return $response + } + catch { + # Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update DataPipeline. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Datamart/Get-FabricDatamart.ps1 b/source/Public/Datamart/Get-FabricDatamart.ps1 new file mode 100644 index 00000000..55de12a3 --- /dev/null +++ b/source/Public/Datamart/Get-FabricDatamart.ps1 @@ -0,0 +1,80 @@ +<# +.SYNOPSIS + Retrieves datamarts from a specified workspace. + +.DESCRIPTION + This function retrieves all datamarts from a specified workspace using the provided WorkspaceId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The ID of the workspace from which to retrieve datamarts. This parameter is mandatory. + +.EXAMPLE + Get-FabricDatamart -WorkspaceId "12345" + This example retrieves all datamarts from the workspace with ID "12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Get-FabricDatamart { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$datamartId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$datamartName + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + + $apiEndpointURI = "{0}/workspaces/{1}/Datamarts" -f $FabricConfig.BaseUrl, $WorkspaceId + + $Datamarts = = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + # Step 9: Filter results based on provided parameters + + $response = if ($datamartId) { + $Datamarts | Where-Object { $_.Id -eq $datamartId } + } + elseif ($datamartName) { + $Datamarts | Where-Object { $_.DisplayName -eq $datamartName } + } + else { + # No filter, return all datamarts + Write-Message -Message "No filter specified. Returning all datamarts." -Level Debug + return $Datamarts + } + + # Step 10: Handle results + if ($response) { + Write-Message -Message "Datamart found matching the specified criteria." -Level Debug + return $response + } + else { + Write-Message -Message "No Datamart found matching the specified criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Datamart. Error: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 new file mode 100644 index 00000000..5669c142 --- /dev/null +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 @@ -0,0 +1,113 @@ +<# +.SYNOPSIS +Assigns workspaces to a Fabric domain based on specified capacities. + +.DESCRIPTION +The `Assign-FabricDomainWorkspaceByCapacity` function assigns workspaces to a Fabric domain using a list of capacity IDs by making a POST request to the relevant API endpoint. + +.PARAMETER DomainId +The unique identifier of the Fabric domain to which the workspaces will be assigned. + +.PARAMETER CapacitiesIds +An array of capacity IDs used to assign workspaces to the domain. + +.EXAMPLE +Assign-FabricDomainWorkspaceByCapacity -DomainId "12345" -CapacitiesIds @("capacity1", "capacity2") + +Assigns workspaces to the domain with ID "12345" based on the specified capacities. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Assign-FabricDomainWorkspaceByCapacity { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [array]$CapacitiesIds + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspacesByCapacities" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + capacitiesIds = $CapacitiesIds + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Assigning domain workspaces by capacity completed successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Assigning domain workspaces by capacity is in progress for domain '$DomainId'." -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + return $operationStatus + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Error occurred while assigning workspaces by capacity for domain '$DomainId'. Details: $errorDetails" -Level Error + } +} diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 new file mode 100644 index 00000000..7c20edb6 --- /dev/null +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 @@ -0,0 +1,83 @@ +<# +.SYNOPSIS +Assigns workspaces to a specified domain in Microsoft Fabric by their IDs. + +.DESCRIPTION +The `Assign-FabricDomainWorkspaceById` function sends a request to assign multiple workspaces to a specified domain using the provided domain ID and an array of workspace IDs. + +.PARAMETER DomainId +The ID of the domain to which workspaces will be assigned. This parameter is mandatory. + +.PARAMETER WorkspaceIds +An array of workspace IDs to be assigned to the domain. This parameter is mandatory. + +.EXAMPLE +Assign-FabricDomainWorkspaceById -DomainId "12345" -WorkspaceIds @("ws1", "ws2", "ws3") + +Assigns the workspaces with IDs "ws1", "ws2", and "ws3" to the domain with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Assign-FabricDomainWorkspaceById { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [array]$WorkspaceIds + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspaces" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + workspacesIds = $WorkspaceIds + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Successfully assigned workspaces to the domain with ID '$DomainId'." -Level Info + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to assign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 new file mode 100644 index 00000000..52f868e8 --- /dev/null +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 @@ -0,0 +1,119 @@ +<# +.SYNOPSIS +Assigns workspaces to a domain based on principal IDs in Microsoft Fabric. + +.DESCRIPTION +The `Assign-FabricDomainWorkspaceByPrincipal` function sends a request to assign workspaces to a specified domain using a JSON object of principal IDs and types. + +.PARAMETER DomainId +The ID of the domain to which workspaces will be assigned. This parameter is mandatory. + +.PARAMETER PrincipalIds +An array representing the principals with their `id` and `type` properties. Must contain a `principals` key with an array of objects. + +.EXAMPLE +$PrincipalIds = @( + @{id = "813abb4a-414c-4ac0-9c2c-bd17036fd58c"; type = "User"}, + @{id = "b5b9495c-685a-447a-b4d3-2d8e963e6288"; type = "User"} + ) + +Assign-FabricDomainWorkspaceByPrincipal -DomainId "12345" -PrincipalIds $principals + +Assigns the workspaces based on the provided principal IDs and types. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Assign-FabricDomainWorkspaceByPrincipal { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + #[hashtable]$PrincipalIds # Must contain a JSON array of principals with 'id' and 'type' properties + [System.Object]$PrincipalIds + ) + + try { + # Step 1: Ensure each principal contains 'id' and 'type' + foreach ($principal in $PrincipalIds) { + if (-not ($principal.ContainsKey('id') -and $principal.ContainsKey('type'))) { + throw "Each principal object must contain 'id' and 'type' properties." + } + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspacesByPrincipals" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Message + + # Step 4: Construct the request body + $body = @{ + principals = $PrincipalIds + } + + # Convert the PrincipalIds to JSON + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 5: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 6: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Assigning domain workspaces by principal completed successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Assigning domain workspaces by principal is in progress for domain '$DomainId'." -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + return $operationStatus + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to assign domain workspaces by principals. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..da306f7d --- /dev/null +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 @@ -0,0 +1,102 @@ +<# +.SYNOPSIS +Bulk assigns roles to principals for workspaces in a Fabric domain. + +.DESCRIPTION +The `AssignFabricDomainWorkspaceRoleAssignment` function performs bulk role assignments for principals in a specific Fabric domain. It sends a POST request to the relevant API endpoint. + +.PARAMETER DomainId +The unique identifier of the Fabric domain where roles will be assigned. + +.PARAMETER DomainRole +The role to assign to the principals. Must be one of the following: +- `Admins` +- `Contributors` + +.PARAMETER PrincipalIds +An array of principals to assign roles to. Each principal must include: +- `id`: The identifier of the principal. +- `type`: The type of the principal (e.g., `User`, `Group`). + +.EXAMPLE +AssignFabricDomainWorkspaceRoleAssignment -DomainId "12345" -DomainRole "Admins" -PrincipalIds @(@{id="user1"; type="User"}, @{id="group1"; type="Group"}) + +Assigns the `Admins` role to the specified principals in the domain with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Assign-FabricDomainWorkspaceRoleAssignment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Admins', 'Contributors')] + [string]$DomainRole, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [array]$PrincipalIds # Array with 'id' and 'type' + ) + + try { + # Step 1: Validate PrincipalIds structure + foreach ($principal in $PrincipalIds) { + if (-not ($principal.id -and $principal.type)) { + throw "Invalid principal detected: Each principal must include 'id' and 'type' properties. Found: $principal" + } + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}/roleAssignments/bulkAssign" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Construct the request body + $body = @{ + type = $DomainRole + principals = $PrincipalIds + } + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 5: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 6: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Bulk role assignment for domain '$DomainId' completed successfully!" -Level Info + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Domain/Get-FabricDomain.ps1 b/source/Public/Domain/Get-FabricDomain.ps1 new file mode 100644 index 00000000..31c8c1c7 --- /dev/null +++ b/source/Public/Domain/Get-FabricDomain.ps1 @@ -0,0 +1,121 @@ +<# +.SYNOPSIS +Retrieves domain information from Microsoft Fabric, optionally filtering by domain ID, domain name, or only non-empty domains. + +.DESCRIPTION +The `Get-FabricDomain` function allows retrieval of domains in Microsoft Fabric, with optional filtering by domain ID or name. Additionally, it can filter to return only non-empty domains. + +.PARAMETER DomainId +(Optional) The ID of the domain to retrieve. + +.PARAMETER DomainName +(Optional) The display name of the domain to retrieve. + +.PARAMETER NonEmptyDomainsOnly +(Optional) If set to `$true`, only domains containing workspaces will be returned. + +.EXAMPLE +Get-FabricDomain -DomainId "12345" + +Fetches the domain with ID "12345". + +.EXAMPLE +Get-FabricDomain -DomainName "Finance" + +Fetches the domain with the display name "Finance". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> +function Get-FabricDomain { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$DomainName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$NonEmptyDomainsOnly = $false + ) + + try { + # Step 1: Handle ambiguous input + if ($DomainId -and $DomainName) { + Write-Message -Message "Both 'DomainId' and 'DomainName' were provided. Please specify only one." -Level Error + return @() + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL with filtering logic + $apiEndpointUrl = "{0}/admin/domains" -f $FabricConfig.BaseUrl + if ($NonEmptyDomainsOnly) { + $apiEndpointUrl = "{0}?nonEmptyOnly=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle empty response + if (-not $response) { + Write-Message -Message "No data returned from the API." -Level Warning + return $null + } + + + # Step 7: Filter results based on provided parameters + $domains = if ($DomainId) { + $response.domains | Where-Object { $_.Id -eq $DomainId } + } + elseif ($DomainName) { + $response.domains | Where-Object { $_.DisplayName -eq $DomainName } + } + else { + # Return all domains if no filter is provided + Write-Message -Message "No filter provided. Returning all domains." -Level Debug + return $response.domains + } + + # Step 8: Handle results + if ($domains) { + return $domains + } + else { + Write-Message -Message "No domain found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Domain/Get-FabricDomainWorkspace.ps1 b/source/Public/Domain/Get-FabricDomainWorkspace.ps1 new file mode 100644 index 00000000..4094ab30 --- /dev/null +++ b/source/Public/Domain/Get-FabricDomainWorkspace.ps1 @@ -0,0 +1,80 @@ +<# +.SYNOPSIS +Retrieves the workspaces associated with a specific domain in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricDomainWorkspace` function fetches the workspaces for the given domain ID. + +.PARAMETER DomainId +The ID of the domain for which to retrieve workspaces. + +.EXAMPLE +Get-FabricDomainWorkspace -DomainId "12345" + +Fetches workspaces for the domain with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricDomainWorkspace { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}/workspaces" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 5: Handle empty response + if (-not $response) { + Write-Message -Message "No data returned from the API." -Level Warning + return $null + } + # Step 6: Handle results + if ($response) { + return $response.value + } + else { + Write-Message -Message "No workspace found for the '$DomainId'." -Level Warning + return $null + } + + } + catch { + # Step 7: Capture and log error details + $errorDetails = Get-ErrorResponse($_.Exception) + Write-Message -Message "Failed to retrieve domain workspaces. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Domain/New-FabricDomain.ps1 b/source/Public/Domain/New-FabricDomain.ps1 new file mode 100644 index 00000000..e60c9048 --- /dev/null +++ b/source/Public/Domain/New-FabricDomain.ps1 @@ -0,0 +1,128 @@ +<# +.SYNOPSIS +Creates a new Fabric domain. + +.DESCRIPTION +The `Add-FabricDomain` function creates a new domain in Microsoft Fabric by making a POST request to the relevant API endpoint. + +.PARAMETER DomainName +The name of the domain to be created. Must only contain alphanumeric characters, underscores, and spaces. + +.PARAMETER DomainDescription +A description of the domain to be created. + +.PARAMETER ParentDomainId +(Optional) The ID of the parent domain, if applicable. + +.EXAMPLE +Add-FabricDomain -DomainName "Finance" -DomainDescription "Finance data domain" -ParentDomainId "12345" + +Creates a "Finance" domain under the parent domain with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricDomain { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$DomainName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$DomainDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ParentDomainId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the request body + $apiEndpointUrl = "{0}/admin/domains" -f $FabricConfig.BaseUrl + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Construct the request body + $body = @{ + displayName = $DomainName + } + + if ($DomainDescription) { + $body.description = $DomainDescription + } + + if ($ParentDomainId) { + $body.parentDomainId = $ParentDomainId + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Domain '$DomainName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Domain '$DomainName' creation accepted. Provisioning in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create domain. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Domain/Remove-FabricDomain.ps1 b/source/Public/Domain/Remove-FabricDomain.ps1 new file mode 100644 index 00000000..c7103158 --- /dev/null +++ b/source/Public/Domain/Remove-FabricDomain.ps1 @@ -0,0 +1,68 @@ +<# +.SYNOPSIS +Deletes a Fabric domain by its ID. + +.DESCRIPTION +The `Remove-FabricDomain` function removes a specified domain from Microsoft Fabric by making a DELETE request to the relevant API endpoint. + +.PARAMETER DomainId +The unique identifier of the domain to be deleted. + +.EXAMPLE +Remove-FabricDomain -DomainId "12345" + +Deletes the domain with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricDomain { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Domain '$DomainId' deleted successfully!" -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete domain '$DomainId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 b/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 new file mode 100644 index 00000000..d86afbdc --- /dev/null +++ b/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 @@ -0,0 +1,96 @@ + +<# +.SYNOPSIS +Unassign workspaces from a specified Fabric domain. + +.DESCRIPTION +The `Unassign -FabricDomainWorkspace` function allows you to Unassign specific workspaces from a given Fabric domain or unassign all workspaces if no workspace IDs are specified. +It makes a POST request to the relevant API endpoint for this operation. + +.PARAMETER DomainId +The unique identifier of the Fabric domain. + +.PARAMETER WorkspaceIds +(Optional) An array of workspace IDs to unassign. If not provided, all workspaces will be unassigned. + +.EXAMPLE +Unassign-FabricDomainWorkspace -DomainId "12345" + +Unassigns all workspaces from the domain with ID "12345". + +.EXAMPLE +Unassign-FabricDomainWorkspace -DomainId "12345" -WorkspaceIds @("workspace1", "workspace2") + +Unassigns the specified workspaces from the domain with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + + +Author: Tiago Balabuch + +#> +function Unassign-FabricDomainWorkspace { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [array]$WorkspaceIds + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + # Determine the API endpoint URL based on the presence of WorkspaceIds + $endpointSuffix = if ($WorkspaceIds) { "unassignWorkspaces" } else { "unassignAllWorkspaces" } + + $apiEndpointUrl = "{0}/admin/domains/{1}/{2}" -f $FabricConfig.BaseUrl, $DomainId, $endpointSuffix + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + + # Step 3: Construct the request body (if needed) + $bodyJson = if ($WorkspaceIds) { + $body = @{ workspacesIds = $WorkspaceIds } + $body | ConvertTo-Json -Depth 2 + } + else { + $null + } + + Write-Message -Message "Request Body: $bodyJson" -Level Debug + # Step 4: Make the API request to unassign specific workspaces + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Successfully unassigned workspaces to the domain with ID '$DomainId'." -Level Info + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to unassign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 b/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..c818fbb7 --- /dev/null +++ b/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 @@ -0,0 +1,103 @@ +<# +.SYNOPSIS +Bulk unUnassign roles to principals for workspaces in a Fabric domain. + +.DESCRIPTION +The `AssignFabricDomainWorkspaceRoleAssignment` function performs bulk role assignments for principals in a specific Fabric domain. It sends a POST request to the relevant API endpoint. + +.PARAMETER DomainId +The unique identifier of the Fabric domain where roles will be assigned. + +.PARAMETER DomainRole +The role to assign to the principals. Must be one of the following: +- `Admins` +- `Contributors` + +.PARAMETER PrincipalIds +An array of principals to assign roles to. Each principal must include: +- `id`: The identifier of the principal. +- `type`: The type of the principal (e.g., `User`, `Group`). + +.EXAMPLE +AssignFabricDomainWorkspaceRoleAssignment -DomainId "12345" -DomainRole "Admins" -PrincipalIds @(@{id="user1"; type="User"}, @{id="group1"; type="Group"}) + +Unassign the `Admins` role to the specified principals in the domain with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Unassign-FabricDomainWorkspaceRoleAssignment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Admins', 'Contributors')] + [string]$DomainRole, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [array]$PrincipalIds # Array with 'id' and 'type' + ) + + try { + # Step 1: Validate PrincipalIds structure + foreach ($principal in $PrincipalIds) { + if (-not ($principal.id -and $principal.type)) { + throw "Invalid principal detected: Each principal must include 'id' and 'type' properties. Found: $principal" + } + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}/roleAssignments/bulkUnassign" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Construct the request body + $body = @{ + type = $DomainRole + principals = $PrincipalIds + } + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 5: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 6: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Bulk role unassignment for domain '$DomainId' completed successfully!" -Level Info + + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Domain/Update-FabricDomain.ps1 b/source/Public/Domain/Update-FabricDomain.ps1 new file mode 100644 index 00000000..0ffad315 --- /dev/null +++ b/source/Public/Domain/Update-FabricDomain.ps1 @@ -0,0 +1,112 @@ +<# +.SYNOPSIS +Updates a Fabric domain by its ID. + +.DESCRIPTION +The `Update-FabricDomain` function modifies a specified domain in Microsoft Fabric using the provided parameters. + +.PARAMETER DomainId +The unique identifier of the domain to be updated. + +.PARAMETER DomainName +The new name for the domain. Must be alphanumeric. + +.PARAMETER DomainDescription +(Optional) A new description for the domain. + +.PARAMETER DomainContributorsScope +(Optional) The contributors' scope for the domain. Accepted values: 'AdminsOnly', 'AllTenant', 'SpecificUsersAndGroups'. + +.EXAMPLE +Update-FabricDomain -DomainId "12345" -DomainName "NewDomain" -DomainDescription "Updated description" -DomainContributorsScope "AdminsOnly" + +Updates the domain with ID "12345" with a new name, description, and contributors' scope. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricDomain { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DomainId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$DomainName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$DomainDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('AdminsOnly', 'AllTenant', 'SpecificUsersAndGroups')] + [string]$DomainContributorsScope + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/admin/domains/{1}" -f $FabricConfig.BaseUrl, $DomainId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $DomainName + } + + if ($DomainDescription) { + $body.description = $DomainDescription + } + + if ($DomainContributorsScope) { + $body.contributorsScope = $DomainContributorsScope + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Domain '$DomainName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update domain '$DomainId'. Error: $errorDetails" -Level Error + } +} + \ No newline at end of file diff --git a/source/Public/Environment/Get-FabricEnvironment.ps1 b/source/Public/Environment/Get-FabricEnvironment.ps1 new file mode 100644 index 00000000..48e497c8 --- /dev/null +++ b/source/Public/Environment/Get-FabricEnvironment.ps1 @@ -0,0 +1,157 @@ +<# +.SYNOPSIS +Retrieves an environment or a list of environments from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricEnvironment` function sends a GET request to the Fabric API to retrieve environment details for a given workspace. It can filter the results by `EnvironmentName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query environments. + +.PARAMETER EnvironmentName +(Optional) The name of the specific environment to retrieve. + +.EXAMPLE +Get-FabricEnvironment -WorkspaceId "12345" -EnvironmentName "Development" + +Retrieves the "Development" environment from workspace "12345". + +.EXAMPLE +Get-FabricEnvironment -WorkspaceId "12345" + +Retrieves all environments in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Returns the matching environment details or all environments if no filter is provided. + +Author: Tiago Balabuch + +#> + +function Get-FabricEnvironment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EnvironmentName + ) + + try { + # Step 1: Handle ambiguous input + if ($EnvironmentId -and $EnvironmentName) { + Write-Message -Message "Both 'EnvironmentId' and 'EnvironmentName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $environments = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + $baseApiEndpointUrl = "{0}/workspaces/{1}/environments" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Step 4: Loop to retrieve data with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $environments += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $environment = if ($EnvironmentId) { + $environments | Where-Object { $_.Id -eq $EnvironmentId } + } + elseif ($EnvironmentName) { + $environments | Where-Object { $_.DisplayName -eq $EnvironmentName } + } + else { + # Return all workspaces if no filter is provided + Write-Message -Message "No filter provided. Returning all environments." -Level Debug + $environments + } + + # Step 9: Handle results + if ($environment) { + Write-Message -Message "Environment found in the Workspace '$WorkspaceId'." -Level Debug + return $environment + } + else { + Write-Message -Message "No environment found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 b/source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 new file mode 100644 index 00000000..75a650ab --- /dev/null +++ b/source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 @@ -0,0 +1,76 @@ +<# +.SYNOPSIS +Retrieves the list of libraries associated with a specific environment in a Microsoft Fabric workspace. + +.DESCRIPTION +The Get-FabricEnvironmentLibrary function fetches library information for a given workspace and environment +using the Microsoft Fabric API. It ensures the authentication token is valid and validates the response +to handle errors gracefully. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the environment is located. + +.PARAMETER EnvironmentId +The unique identifier of the environment whose libraries are being queried. + +.EXAMPLE +Get-FabricEnvironmentLibrary -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" + +Retrieves the libraries associated with the specified environment in the given workspace. + +.NOTES +- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. +- Uses `Test-TokenExpired` to validate the token before making API calls. + +Author: Tiago Balabuch +#> +function Get-FabricEnvironmentLibrary { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/libraries" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 5: Handle results + return $response + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve environment libraries. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 b/source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 new file mode 100644 index 00000000..65cf74de --- /dev/null +++ b/source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 @@ -0,0 +1,76 @@ +<# +.SYNOPSIS +Retrieves the Spark compute details for a specific environment in a Microsoft Fabric workspace. + +.DESCRIPTION +The Get-FabricEnvironmentSparkCompute function communicates with the Microsoft Fabric API to fetch information +about Spark compute resources associated with a specified environment. It ensures that the API token is valid +and gracefully handles errors during the API call. + +.PARAMETER WorkspaceId +The unique identifier of the workspace containing the target environment. + +.PARAMETER EnvironmentId +The unique identifier of the environment whose Spark compute details are being retrieved. + +.EXAMPLE +Get-FabricEnvironmentSparkCompute -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" + +Retrieves Spark compute details for the specified environment in the given workspace. + +.NOTES +- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. +- Uses `Test-TokenExpired` to validate the token before making API calls. + +Author: Tiago Balabuch +#> +function Get-FabricEnvironmentSparkCompute { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/sparkcompute" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 5: Handle results + return $response + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve environment Spark compute. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 new file mode 100644 index 00000000..0a8a5505 --- /dev/null +++ b/source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 @@ -0,0 +1,76 @@ +<# +.SYNOPSIS +Retrieves the staging library details for a specific environment in a Microsoft Fabric workspace. + +.DESCRIPTION +The Get-FabricEnvironmentStagingLibrary function interacts with the Microsoft Fabric API to fetch information +about staging libraries associated with a specified environment. It ensures token validity and handles API errors gracefully. + +.PARAMETER WorkspaceId +The unique identifier of the workspace containing the target environment. + +.PARAMETER EnvironmentId +The unique identifier of the environment for which staging library details are being retrieved. + +.EXAMPLE + Get-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" + +Retrieves the staging libraries for the specified environment in the given workspace. + +.NOTES +- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. +- Uses `Test-TokenExpired` to validate the token before making API calls. + +Author: Tiago Balabuch +#> +function Get-FabricEnvironmentStagingLibrary { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/libraries" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 5: Handle results + return $response.customLibraries + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve environment spark compute. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 b/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 new file mode 100644 index 00000000..cbc94d55 --- /dev/null +++ b/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 @@ -0,0 +1,78 @@ +<# +.SYNOPSIS +Retrieves staging Spark compute details for a specific environment in a Microsoft Fabric workspace. + +.DESCRIPTION +The Get-FabricEnvironmentStagingSparkCompute function interacts with the Microsoft Fabric API to fetch information +about staging Spark compute configurations for a specified environment. It ensures token validity and handles API errors gracefully. + +.PARAMETER WorkspaceId +The unique identifier of the workspace containing the target environment. + +.PARAMETER EnvironmentId +The unique identifier of the environment for which staging Spark compute details are being retrieved. + +.EXAMPLE +Get-FabricEnvironmentStagingSparkCompute -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" + +Retrieves the staging Spark compute configurations for the specified environment in the given workspace. + +.NOTES +- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. +- Uses `Test-TokenExpired` to validate the token before making API calls. + +Author: Tiago Balabuch +#> + + +function Get-FabricEnvironmentStagingSparkCompute { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/sparkcompute" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 5: Handle results + return $response + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve environment spark compute. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Environment/New-FabricEnvironment.ps1 b/source/Public/Environment/New-FabricEnvironment.ps1 new file mode 100644 index 00000000..009a5aeb --- /dev/null +++ b/source/Public/Environment/New-FabricEnvironment.ps1 @@ -0,0 +1,123 @@ +<# +.SYNOPSIS +Creates a new environment in a specified workspace. + +.DESCRIPTION +The `Add-FabricEnvironment` function creates a new environment within a given workspace by making a POST request to the Fabric API. The environment can optionally include a description. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace where the environment will be created. + +.PARAMETER EnvironmentName +(Mandatory) The name of the environment to be created. Only alphanumeric characters, spaces, and underscores are allowed. + +.PARAMETER EnvironmentDescription +(Optional) A description of the environment. + +.EXAMPLE +Add-FabricEnvironment -WorkspaceId "12345" -EnvironmentName "DevEnv" -EnvironmentDescription "Development Environment" + +Creates an environment named "DevEnv" in workspace "12345" with the specified description. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricEnvironment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EnvironmentName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EnvironmentName + } + + if ($EnvironmentDescription) { + $body.description = $EnvironmentDescription + } + + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Environment '$EnvironmentName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Environment '$EnvironmentName' creation accepted. Provisioning in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create environment. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Environment/Publish-FabricEnvironment.ps1 b/source/Public/Environment/Publish-FabricEnvironment.ps1 new file mode 100644 index 00000000..bf9fffbc --- /dev/null +++ b/source/Public/Environment/Publish-FabricEnvironment.ps1 @@ -0,0 +1,104 @@ +<# +.SYNOPSIS +Publishes a staging environment in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function interacts with the Microsoft Fabric API to initiate the publishing process for a staging environment. +It validates the authentication token, constructs the API request, and handles both immediate and long-running operations. + + +.PARAMETER WorkspaceId +The unique identifier of the workspace containing the staging environment. + +.PARAMETER EnvironmentId +The unique identifier of the staging environment to be published. + +.EXAMPLE +Publish-FabricEnvironment -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" + +Initiates the publishing process for the specified staging environment. + +.NOTES +- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. +- Uses `Test-TokenExpired` to validate the token before making API calls. + +Author: Tiago Balabuch +#> + +function Publish-FabricEnvironment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/publish" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Publish operation request has been submitted successfully for the environment '$EnvironmentId'!" -Level Info + return $response.publishDetails + } + 202 { + Write-Message -Message "Publish operation accepted. Publishing in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create environment. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Environment/Remove-FabricEnvironment.ps1 b/source/Public/Environment/Remove-FabricEnvironment.ps1 new file mode 100644 index 00000000..4e1f734e --- /dev/null +++ b/source/Public/Environment/Remove-FabricEnvironment.ps1 @@ -0,0 +1,74 @@ +<# +.SYNOPSIS +Deletes an environment from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricEnvironment` function sends a DELETE request to the Fabric API to remove a specified environment from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the environment to delete. + +.PARAMETER EnvironmentId +(Mandatory) The ID of the environment to be deleted. + +.EXAMPLE +Remove-FabricEnvironment -WorkspaceId "12345" -EnvironmentId "67890" + +Deletes the environment with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricEnvironment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 new file mode 100644 index 00000000..ebfd16f5 --- /dev/null +++ b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 @@ -0,0 +1,85 @@ + +<# +.SYNOPSIS +Deletes a specified library from the staging environment in a Microsoft Fabric workspace. + +.DESCRIPTION +This function allows for the deletion of a library from the staging environment, one file at a time. +It ensures token validity, constructs the appropriate API request, and handles both success and failure responses. + +.PARAMETER WorkspaceId +The unique identifier of the workspace from which the library is to be deleted. + +.PARAMETER EnvironmentId +The unique identifier of the staging environment containing the library. + +.PARAMETER LibraryName +The name of the library to be deleted from the environment. + +.EXAMPLE +Remove-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" -LibraryName "library-to-delete" + +Deletes the specified library from the staging environment in the specified workspace. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. +- This function currently supports deleting one library at a time. +Author: Tiago Balabuch + +#> + + +function Remove-FabricEnvironmentStagingLibrary { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$LibraryName + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/libraries?libraryToDelete={3}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId, $LibraryName + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Staging library $LibraryName for the Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 b/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 new file mode 100644 index 00000000..9276ea0a --- /dev/null +++ b/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 @@ -0,0 +1,76 @@ +<# +.SYNOPSIS +Cancels the publish operation for a specified environment in Microsoft Fabric. + +.DESCRIPTION +This function sends a cancel publish request to the Microsoft Fabric API for a given environment. +It ensures that the token is valid before making the request and handles both successful and error responses. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the environment exists. + +.PARAMETER EnvironmentId +The unique identifier of the environment for which the publish operation is to be canceled. + +.EXAMPLE +Stop-FabricEnvironmentPublish -WorkspaceId "workspace-12345" -EnvironmentId "environment-67890" + +Cancels the publish operation for the specified environment. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> +function Stop-FabricEnvironmentPublish{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/cancelPublish" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Publication for environment '$EnvironmentId' has been successfully canceled." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to cancel publication for environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Environment/Update-FabricEnvironment.ps1 b/source/Public/Environment/Update-FabricEnvironment.ps1 new file mode 100644 index 00000000..2c9364b0 --- /dev/null +++ b/source/Public/Environment/Update-FabricEnvironment.ps1 @@ -0,0 +1,108 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric Environment. + +.DESCRIPTION +The `Update-FabricEnvironment` function updates the name and/or description of a specified Fabric Environment by making a PATCH request to the API. + +.PARAMETER EnvironmentId +The unique identifier of the Environment to be updated. + +.PARAMETER EnvironmentName +The new name for the Environment. + +.PARAMETER EnvironmentDescription +(Optional) The new description for the Environment. + +.EXAMPLE +Update-FabricEnvironment -EnvironmentId "Environment123" -EnvironmentName "NewEnvironmentName" + +Updates the name of the Environment with the ID "Environment123" to "NewEnvironmentName". + +.EXAMPLE +Update-FabricEnvironment -EnvironmentId "Environment123" -EnvironmentName "NewName" -EnvironmentDescription "Updated description" + +Updates both the name and description of the Environment "Environment123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricEnvironment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EnvironmentName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EnvironmentName + } + + if ($EnvironmentDescription) { + $body.description = $EnvironmentDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Environment '$EnvironmentName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Environment. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 b/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 new file mode 100644 index 00000000..bd542623 --- /dev/null +++ b/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 @@ -0,0 +1,177 @@ +<# +.SYNOPSIS +Updates the Spark compute configuration in the staging environment for a given workspace. + +.DESCRIPTION +This function sends a PATCH request to the Microsoft Fabric API to update the Spark compute settings +for a specified environment, including instance pool, driver and executor configurations, and dynamic allocation settings. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the environment exists. + +.PARAMETER EnvironmentId +The unique identifier of the environment where the Spark compute settings will be updated. + +.PARAMETER InstancePoolName +The name of the instance pool to be used for Spark compute. + +.PARAMETER InstancePoolType +The type of instance pool (either 'Workspace' or 'Capacity'). + +.PARAMETER DriverCores +The number of cores to allocate to the driver. + +.PARAMETER DriverMemory +The amount of memory to allocate to the driver. + +.PARAMETER ExecutorCores +The number of cores to allocate to each executor. + +.PARAMETER ExecutorMemory +The amount of memory to allocate to each executor. + +.PARAMETER DynamicExecutorAllocationEnabled +Boolean flag to enable or disable dynamic executor allocation. + +.PARAMETER DynamicExecutorAllocationMinExecutors +The minimum number of executors when dynamic allocation is enabled. + +.PARAMETER DynamicExecutorAllocationMaxExecutors +The maximum number of executors when dynamic allocation is enabled. + +.PARAMETER RuntimeVersion +The Spark runtime version to use. + +.PARAMETER SparkProperties +A hashtable of additional Spark properties to configure. + +.EXAMPLE +Update-FabricEnvironmentStagingSparkCompute -WorkspaceId "workspace-12345" -EnvironmentId "env-67890" -InstancePoolName "pool1" -InstancePoolType "Workspace" -DriverCores 4 -DriverMemory "16GB" -ExecutorCores 8 -ExecutorMemory "32GB" -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 2 -DynamicExecutorAllocationMaxExecutors 10 -RuntimeVersion "3.1" -SparkProperties @{ "spark.executor.memoryOverhead"="4GB" } + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> +function Update-FabricEnvironmentStagingSparkCompute { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$InstancePoolName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Workspace', 'Capacity')] + [string]$InstancePoolType, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$DriverCores, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DriverMemory, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$ExecutorCores, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ExecutorMemory, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [bool]$DynamicExecutorAllocationEnabled, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$DynamicExecutorAllocationMinExecutors, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$DynamicExecutorAllocationMaxExecutors, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$RuntimeVersion, + + [Parameter(Mandatory = $true)] + [System.Object]$SparkProperties + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/sparkcompute" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + instancePool = @{ + name = $InstancePoolName + type = $InstancePoolType + } + driverCores = $DriverCores + driverMemory = $DriverMemory + executorCores = $ExecutorCores + executorMemory = $ExecutorMemory + dynamicExecutorAllocation = @{ + enabled = $DynamicExecutorAllocationEnabled + minExecutors = $DynamicExecutorAllocationMinExecutors + maxExecutors = $DynamicExecutorAllocationMaxExecutors + } + runtimeVersion = $RuntimeVersion + sparkProperties = $SparkProperties + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 4 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Environment staging Spark compute updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update environment staging Spark compute. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 new file mode 100644 index 00000000..d8b09960 --- /dev/null +++ b/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 @@ -0,0 +1,79 @@ +<# +.SYNOPSIS +Uploads a library to the staging environment in a Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to upload a library to the specified +environment staging area for the given workspace. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the environment exists. + +.PARAMETER EnvironmentId +The unique identifier of the environment where the library will be uploaded. + +.EXAMPLE +Upload-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "env-67890" + +.NOTES +- This is not working code. It is a placeholder for future development. Fabric documentation is missing some important details on how to upload libraries. +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> +function Upload-FabricEnvironmentStagingLibrary { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/libraries" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Environment staging library uploaded successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to upload environment staging library. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 b/source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 new file mode 100644 index 00000000..dabafa46 --- /dev/null +++ b/source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Retrieves Eventhouse details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves Eventhouse details from a specified workspace using either the provided EventhouseId or EventhouseName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Eventhouse exists. This parameter is mandatory. + +.PARAMETER EventhouseId + The unique identifier of the Eventhouse to retrieve. This parameter is optional. + +.PARAMETER EventhouseName + The name of the Eventhouse to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" + This example retrieves the Eventhouse details for the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseName "My Eventhouse" + This example retrieves the Eventhouse details for the Eventhouse named "My Eventhouse" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricEventhouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventhouseName + ) + try { + + # Step 1: Handle ambiguous input + if ($EventhouseId -and $EventhouseName) { + Write-Message -Message "Both 'EventhouseId' and 'EventhouseName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $eventhouses = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId + # Step 3: Loop to retrieve data with continuation token + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $eventhouses += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $eventhouse = if ($EventhouseId) { + $eventhouses | Where-Object { $_.Id -eq $EventhouseId } + } + elseif ($EventhouseName) { + $eventhouses | Where-Object { $_.DisplayName -eq $EventhouseName } + } + else { + # Return all eventhouses if no filter is provided + Write-Message -Message "No filter provided. Returning all Eventhouses." -Level Debug + $eventhouses + } + + # Step 9: Handle results + if ($eventhouse) { + Write-Message -Message "Eventhouse found in the Workspace '$WorkspaceId'." -Level Debug + return $eventhouse + } + else { + Write-Message -Message "No Eventhouse found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Eventhouse/Get-FabricEventhouse.ps1 b/source/Public/Eventhouse/Get-FabricEventhouse.ps1 new file mode 100644 index 00000000..4d3bc871 --- /dev/null +++ b/source/Public/Eventhouse/Get-FabricEventhouse.ps1 @@ -0,0 +1,213 @@ +function Get-FabricEventhouse { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric Eventhouses + +.DESCRIPTION + Retrieves Fabric Eventhouses. Without the EventhouseName or EventhouseID parameter, all Eventhouses are returned. + If you want to retrieve a specific Eventhouse, you can use the EventhouseName or EventhouseID parameter. These + parameters cannot be used together. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Eventhouses should be retrieved. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventhouseName + The name of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseID. + +.PARAMETER EventhouseId + The Id of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseName. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' + + This example will give you all Eventhouses in the Workspace. + +.EXAMPLE + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseName 'MyEventhouse' + + This example will give you all Information about the Eventhouse with the name 'MyEventhouse'. + +.EXAMPLE + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' + + This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. + + .EXAMPLE + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' ` + -Verbose + + This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. + It will also give you verbose output which is useful for debugging. + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/list-eventhouses?tabs=HTTP + +.NOTES + TODO: Add functionality to list all Eventhouses in the subscription. To do so fetch all workspaces + and then all eventhouses in each workspace. + + Revsion History: + + - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName + - 2024-11-16 - FGE: Added Verbose Output + - 2024-11-27 - FGE: Added more Verbose Output +#> + +# + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Name","DisplayName")] + [string]$EventhouseName, + + [Alias("Id")] + [string]$EventhouseId + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + # You can either use Name or WorkspaceID + Write-Verbose "Checking if EventhouseName and EventhouseID are used together. This is not allowed" + if ($PSBoundParameters.ContainsKey("EventhouseName") -and $PSBoundParameters.ContainsKey("EventhouseID")) { + throw "Parameters EventhouseName and EventhouseID cannot be used together" + } + + # Create Eventhouse API + $eventhouseAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses" + Write-Verbose "Creating the URL for the Eventhouse API: $eventhouseAPI" + + $eventhouseAPIEventhouseId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" + Write-Verbose "Creating the URL for the Eventhouse API when the Id is used: $eventhouseAPIEventhouseId" + +} + +process { + + if ($PSBoundParameters.ContainsKey("EventhouseId")) { + Write-Verbose "Calling Eventhouse API with EventhouseId" + Write-Verbose "----------------------------------------" + Write-Verbose "Sending the following values to the Eventhouse API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $eventhouseAPIEventhouseId" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $eventhouseAPIEventhouseId ` + -ContentType "application/json" + + Write-Verbose "Adding the member queryServiceUri: $($response.properties.queryServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'queryServiceUri' ` + -Value $response.properties.queryServiceUri ` + -InputObject $response ` + -Force + + Write-Verbose "Adding the member ingestionServiceUri: $($response.properties.ingestionServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'ingestionServiceUri' ` + -Value $response.properties.ingestionServiceUri ` + -InputObject $response ` + -Force + + Write-Verbose "Adding the member databasesItemIds: $($response.properties.databasesItemIds)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'databasesItemIds' ` + -Value $response.properties.databasesItemIds ` + -InputObject $response ` + -Force + + Write-Verbose "Adding the member minimumConsumptionUnits: $($response.properties.minimumConsumptionUnits)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'minimumConsumptionUnits' ` + -Value $response.properties.minimumConsumptionUnits ` + -InputObject $response ` + -Force + + $response + } + else { + Write-Verbose "Calling Eventhouse API without EventhouseId" + Write-Verbose "-------------------------------------------" + Write-Verbose "Sending the following values to the Eventhouse API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $eventhouseAPI" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $eventhouseAPI ` + -ContentType "application/json" + + foreach ($eventhouse in $response.value) { + Write-Verbose "Adding the member queryServiceUri: $($eventhouse.properties.queryServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'queryServiceUri' ` + -Value $eventhouse.properties.queryServiceUri ` + -InputObject $eventhouse ` + -Force + + Write-Verbose "Adding the member ingestionServiceUri: $($eventhouse.properties.ingestionServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'ingestionServiceUri' ` + -Value $eventhouse.properties.ingestionServiceUri ` + -InputObject $eventhouse ` + -Force + + Write-Verbose "Adding the member databasesItemIds: $($eventhouse.properties.databasesItemIds)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'databasesItemIds' ` + -Value $eventhouse.properties.databasesItemIds ` + -InputObject $eventhouse ` + -Force + + Write-Verbose "Adding the member minimumConsumptionUnits: $($eventhouse.properties.minimumConsumptionUnits)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'minimumConsumptionUnits' ` + -Value $eventhouse.properties.minimumConsumptionUnits ` + -InputObject $eventhouse ` + -Force + } + + if ($PSBoundParameters.ContainsKey("EventhouseName")) { + Write-Verbose "Filtering the Eventhouse by EventhouseName: $EventhouseName" + $response.value | ` + Where-Object { $_.displayName -eq $EventhouseName } + } + else { + Write-Verbose "Returning all Eventhouses" + $response.value + } + } + +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 b/source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 new file mode 100644 index 00000000..e42a8a17 --- /dev/null +++ b/source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 @@ -0,0 +1,124 @@ +<# +.SYNOPSIS + Retrieves the definition of an Eventhouse from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves the definition of an Eventhouse from a specified workspace using the provided EventhouseId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Eventhouse exists. This parameter is mandatory. + +.PARAMETER EventhouseId + The unique identifier of the Eventhouse to retrieve the definition for. This parameter is optional. + +.PARAMETER EventhouseFormat + The format in which to retrieve the Eventhouse definition. This parameter is optional. + +.EXAMPLE + Get-FabricEventhouseDefinition -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" + This example retrieves the definition of the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricEventhouseDefinition -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" -EventhouseFormat "json" + This example retrieves the definition of the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345" in JSON format. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricEventhouseDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseFormat + ) + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId + + if ($EventhouseFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $EventhouseFormat + } + + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "Eventhouse '$EventhouseId' definition retrieved successfully!" -Level Debug + return $response + } + 202 { + + Write-Message -Message "Getting Eventhouse '$EventhouseId' definition request accepted. Retrieving in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Eventhouse/New-FabricEventhouse copy.ps1 b/source/Public/Eventhouse/New-FabricEventhouse copy.ps1 new file mode 100644 index 00000000..de5979b9 --- /dev/null +++ b/source/Public/Eventhouse/New-FabricEventhouse copy.ps1 @@ -0,0 +1,193 @@ +<# +.SYNOPSIS + Creates a new Eventhouse in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new Eventhouse + in the specified workspace. It supports optional parameters for Eventhouse description and path definitions. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Eventhouse will be created. This parameter is mandatory. + +.PARAMETER EventhouseName + The name of the Eventhouse to be created. This parameter is mandatory. + +.PARAMETER EventhouseDescription + An optional description for the Eventhouse. + +.PARAMETER EventhousePathDefinition + An optional path to the Eventhouse definition file to upload. + +.PARAMETER EventhousePathPlatformDefinition + An optional path to the platform-specific definition file to upload. + +.EXAMPLE + New-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseName "New Eventhouse" -EventhouseDescription "Description of the new Eventhouse" + This example creates a new Eventhouse named "New Eventhouse" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function New-FabricEventhouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventhouseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhousePathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhousePathPlatformDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EventhouseName + } + + if ($EventhouseDescription) { + $body.description = $EventhouseDescription + } + if ($EventhousePathDefinition) { + $eventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition + + if (-not [string]::IsNullOrEmpty($eventhouseEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "EventhouseProperties.json" + payload = $eventhouseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error + return $null + } + } + + if ($EventhousePathPlatformDefinition) { + $eventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($eventhouseEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $eventhouseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Eventhouse '$EventhouseName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Eventhouse '$EventhouseName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Eventhouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventhouse/New-FabricEventhouse.ps1 b/source/Public/Eventhouse/New-FabricEventhouse.ps1 new file mode 100644 index 00000000..103be974 --- /dev/null +++ b/source/Public/Eventhouse/New-FabricEventhouse.ps1 @@ -0,0 +1,104 @@ +function New-FabricEventhouse { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Creates a new Fabric Eventhouse + +.DESCRIPTION + Creates a new Fabric Eventhouse + +.PARAMETER WorkspaceID + Id of the Fabric Workspace for which the Eventhouse should be created. The value for WorkspaceID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventhouseName + The name of the Eventhouse to create. + +.PARAMETER EventhouseDescription + The description of the Eventhouse to create. + +.EXAMPLE + New-FabricEventhouse ` + -WorkspaceID '12345678-1234-1234-1234-123456789012' ` + -EventhouseName 'MyEventhouse' ` + -EventhouseDescription 'This is my Eventhouse' + + This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. + +.EXAMPLE + New-FabricEventhouse ` + -WorkspaceID '12345678-1234-1234-1234-123456789012' ` + -EventhouseName 'MyEventhouse' ` + -EventhouseDescription 'This is my Eventhouse' ` + -Verbose + + This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. + It will also give you verbose output which is useful for debugging. + +.NOTES + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName + - 2024-11-27 - FGE: Added Verbose Output + + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceID, + + [Parameter(Mandatory=$true)] + [Alias("Name", "DisplayName")] + [string]$EventhouseName, + + [ValidateLength(0, 256)] + [Alias("Description")] + [string]$EventhouseDescription + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + # Create body of request + $body = @{ + 'displayName' = $EventhouseName + 'description' = $EventhouseDescription + } | ConvertTo-Json ` + -Depth 1 + + # Create Eventhouse API URL + $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses" + } + +process { + + Write-Verbose "Calling Eventhouse API" + Write-Verbose "----------------------" + Write-Verbose "Sending the following values to the Eventhouse API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $eventhouseApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + if($PSCmdlet.ShouldProcess($EventhouseName)) { + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $eventhouseApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 b/source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 new file mode 100644 index 00000000..0bdfd275 --- /dev/null +++ b/source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Removes an Eventhouse from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove an Eventhouse + from the specified workspace using the provided WorkspaceId and EventhouseId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the Eventhouse will be removed. + +.PARAMETER EventhouseId + The unique identifier of the Eventhouse to be removed. + +.EXAMPLE + Remove-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" + This example removes the Eventhouse with ID "eventhouse-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Remove-FabricEventhouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle response + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Eventhouse '$EventhouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Eventhouse '$EventhouseId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 new file mode 100644 index 00000000..cc4274e5 --- /dev/null +++ b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 @@ -0,0 +1,106 @@ +function Remove-FabricEventhouse { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Removes an existing Fabric Eventhouse + +.DESCRIPTION + Removes an existing Fabric Eventhouse + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Eventhouse should be deleted. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventhouseId + The Id of the Eventhouse to delete. The value for EventhouseId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. EventhouseId and EventhouseName cannot be used together. + +.PARAMETER EventhouseName + The name of the Eventhouse to delete. EventhouseId and EventhouseName cannot be used together. + +.EXAMPLE + Remove-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' + + This example will delete the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' from + the Workspace with the Id '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Remove-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseName 'MyEventhouse' + + This example will delete the Eventhouse with the name 'MyEventhouse' from the Workspace with the + Id '12345678-1234-1234-1234-123456789012'. +.NOTES + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName + - 2024-11-27 - FGE: Added Verbose Output + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/delete-eventhouse?tabs=HTTP +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Id")] + [string]$EventhouseId, + + [Alias("Name", "DisplayName")] + [string]$EventhouseName + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Check if EventhouseName and EventhouseID are used together. This is not allowed" + if ($PSBoundParameters.ContainsKey("EventhouseName") -and $PSBoundParameters.ContainsKey("EventhouseID")) { + throw "Parameters EventhouseName and EventhouseID cannot be used together" + } + + if ($PSBoundParameters.ContainsKey("EventhouseName")) { + Write-Verbose "Eventhouse Name $EventhouseName is used. Get Eventhouse ID from Eventhouse Name" + $eh = Get-FabricEventhouse ` + -WorkspaceId $WorkspaceId ` + -EventhouseName $EventhouseName + + $EventhouseId = $eh.id + Write-Verbose "Eventhouse ID is $EventhouseId" + } + + # Create Eventhouse API URL + $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" +} + +process { + + Write-Verbose "Calling Eventhouse API with EventhouseId" + Write-Verbose "----------------------------------------" + Write-Verbose "Sending the following values to the Eventhouse API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: DELETE" + Write-Verbose "URI: $eventhouseApiUrl" + Write-Verbose "ContentType: application/json" + if($PSCmdlet.ShouldProcess($EventhouseName)) { + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method DELETE ` + -Uri $eventhouseApiUrl ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Set-FabricEventhouse.ps1 b/source/Public/Eventhouse/Set-FabricEventhouse.ps1 new file mode 100644 index 00000000..701b19f4 --- /dev/null +++ b/source/Public/Eventhouse/Set-FabricEventhouse.ps1 @@ -0,0 +1,130 @@ +function Set-FabricEventhouse { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Updates Properties of an existing Fabric Eventhouse + +.DESCRIPTION + Updates Properties of an existing Fabric Eventhouse + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Eventhouse should be updated. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventhouseId + The Id of the Eventhouse to update. The value for EventhouseId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventhouseNewName + The new name of the Eventhouse. + +.PARAMETER EventhouseDescription + The new description of the Eventhouse. + +.EXAMPLE + Set-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' ` + -EventhouseNewName 'MyNewEventhouse' ` + -EventhouseDescription 'This is my new Eventhouse' + + This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' + in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to + have the name 'MyNewEventhouse' and the description + 'This is my new Eventhouse'. + +.EXAMPLE + Set-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' ` + -EventhouseNewName 'MyNewEventhouse' ` + -EventhouseDescription 'This is my new Eventhouse' ` + -Verbose + + This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' + in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to + have the name 'MyNewEventhouse' and the description 'This is my new Eventhouse'. + It will also give you verbose output which is useful for debugging. + +.NOTES + TODO: Add functionality to update Eventhouse properties using EventhouseName instead of EventhouseId + + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added NewDisplaName as Alias for EventhouseName + - 2024-11-27 - FGE: Added Verbose Output + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Parameter(Mandatory=$true)] + [Alias("Id")] + [string]$EventhouseId, + + [Alias("NewName", "NewDisplayName")] + [string]$EventhouseNewName, + + [ValidateLength(0, 256)] + [Alias("Description")] + [string]$EventhouseDescription + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + # Create body of request + $body = @{} + + if ($PSBoundParameters.ContainsKey("EventhouseNewName")) { + Write-Verbose "New name found for Eventhouse. New name is: $EventhouseNewName" + $body["displayName"] = $EventhouseNewName + } + + if ($PSBoundParameters.ContainsKey("EventhouseDescription")) { + Write-Verbose "Description found for Eventhouse. Description is: $EventhouseDescription" + $body["description"] = $EventhouseDescription + } + + $body = $body ` + | ConvertTo-Json ` + -Depth 1 + + # Create Eventhouse API URL + $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" +} + +process { + + Write-Verbose "Calling Eventhouse API with EventhouseId" + Write-Verbose "----------------------------------------" + Write-Verbose "Sending the following values to the Eventhouse API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: PATCH" + Write-Verbose "URI: $eventhouseApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + if($PSCmdlet.ShouldProcess($EventhouseId)) { + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method PATCH ` + -Uri $eventhouseApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Update-FabricEventhouse.ps1 b/source/Public/Eventhouse/Update-FabricEventhouse.ps1 new file mode 100644 index 00000000..6968c89c --- /dev/null +++ b/source/Public/Eventhouse/Update-FabricEventhouse.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + Updates an existing Eventhouse in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing Eventhouse + in the specified workspace. It supports optional parameters for Eventhouse description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Eventhouse exists. This parameter is optional. + +.PARAMETER EventhouseId + The unique identifier of the Eventhouse to be updated. This parameter is mandatory. + +.PARAMETER EventhouseName + The new name of the Eventhouse. This parameter is mandatory. + +.PARAMETER EventhouseDescription + An optional new description for the Eventhouse. + +.EXAMPLE + Update-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" -EventhouseName "Updated Eventhouse" -EventhouseDescription "Updated description" + This example updates the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricEventhouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventhouseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseDescription + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EventhouseName + } + + if ($EventhouseDescription) { + $body.description = $EventhouseDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Eventhouse '$EventhouseName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 b/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 new file mode 100644 index 00000000..aa1bf713 --- /dev/null +++ b/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 @@ -0,0 +1,168 @@ +<# +.SYNOPSIS + Updates the definition of an existing Eventhouse in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Eventhouse + in the specified workspace. It supports optional parameters for Eventhouse definition and platform-specific definition. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Eventhouse exists. This parameter is mandatory. + +.PARAMETER EventhouseId + The unique identifier of the Eventhouse to be updated. This parameter is mandatory. + +.PARAMETER EventhousePathDefinition + An optional path to the Eventhouse definition file to upload. + +.PARAMETER EventhousePathPlatformDefinition + An optional path to the platform-specific definition file to upload. + +.EXAMPLE + Update-FabricEventhouseDefinition -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" -EventhousePathDefinition "C:\Path\To\EventhouseDefinition.json" + This example updates the definition of the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345" using the provided definition file. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricEventhouseDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventhousePathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhousePathPlatformDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId + + #if ($UpdateMetadata -eq $true) { + if($EventhousePathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + if ($EventhousePathDefinition) { + $EventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition + + if (-not [string]::IsNullOrEmpty($EventhouseEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "EventhouseProperties.json" + payload = $EventhouseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error + return $null + } + } + + if ($EventhousePathPlatformDefinition) { + $EventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition + if (-not [string]::IsNullOrEmpty($EventhouseEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $EventhouseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for Eventhouse '$EventhouseId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for Eventhouse '$EventhouseId' accepted. Operation in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventstream/Get-FabricEventstream copy.ps1 b/source/Public/Eventstream/Get-FabricEventstream copy.ps1 new file mode 100644 index 00000000..28f6448a --- /dev/null +++ b/source/Public/Eventstream/Get-FabricEventstream copy.ps1 @@ -0,0 +1,159 @@ +<# +.SYNOPSIS +Retrieves an Eventstream or a list of Eventstreams from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricEventstream` function sends a GET request to the Fabric API to retrieve Eventstream details for a given workspace. It can filter the results by `EventstreamName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query Eventstreams. + +.PARAMETER EventstreamName +(Optional) The name of the specific Eventstream to retrieve. + +.EXAMPLE +Get-FabricEventstream -WorkspaceId "12345" -EventstreamName "Development" + +Retrieves the "Development" Eventstream from workspace "12345". + +.EXAMPLE +Get-FabricEventstream -WorkspaceId "12345" + +Retrieves all Eventstreams in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricEventstream { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventstreamName + ) + + try { + # Step 1: Handle ambiguous input + if ($EventstreamId -and $EventstreamName) { + Write-Message -Message "Both 'EventstreamId' and 'EventstreamName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + $continuationToken = $null + $eventstreams = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Step 3: Loop to retrieve data with continuation token + + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $eventstreams += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + + # Step 8: Filter results based on provided parameters + $eventstream = if ($EventstreamId) { + $eventstreams | Where-Object { $_.Id -eq $EventstreamId } + } + elseif ($EventstreamName) { + $eventstreams | Where-Object { $_.DisplayName -eq $EventstreamName } + } + else { + # Return all eventstreams if no filter is provided + Write-Message -Message "No filter provided. Returning all Eventstreams." -Level Debug + $eventstreams + } + + # Step 9: Handle results + if ($eventstream) { + Write-Message -Message "Eventstream found matching the specified criteria." -Level Debug + return $eventstream + } + else { + Write-Message -Message "No Eventstream found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Eventstream/Get-FabricEventstream.ps1 b/source/Public/Eventstream/Get-FabricEventstream.ps1 new file mode 100644 index 00000000..3189c52a --- /dev/null +++ b/source/Public/Eventstream/Get-FabricEventstream.ps1 @@ -0,0 +1,133 @@ +function Get-FabricEventstream { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric Eventstreams + +.DESCRIPTION + Retrieves Fabric Eventstreams. Without the EventstreamName or EventstreamID parameter, all Eventstreams are returned. + If you want to retrieve a specific Eventstream, you can use the EventstreamName or EventstreamID parameter. These + parameters cannot be used together. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Eventstreams should be retrieved. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventstreamName + The name of the Eventstream to retrieve. This parameter cannot be used together with EventstreamID. + +.PARAMETER EventstreamId + The Id of the Eventstream to retrieve. This parameter cannot be used together with EventstreamName. The value for EventstreamId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricEventstream ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' + + This example will give you all Eventstreams in the Workspace. + +.EXAMPLE + Get-FabricEventstream ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventstreamName 'MyEventstream' + + This example will give you all Information about the Eventstream with the name 'MyEventstream'. + +.EXAMPLE + Get-FabricEventstream ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventstreamId '12345678-1234-1234-1234-123456789012' + + This example will give you all Information about the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/get-eventstream?tabs=HTTP + +.NOTES + TODO: Add functionality to list all Eventhouses. To do so fetch all workspaces and + then all eventhouses in each workspace. + + Revision History: + - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName + - 2024-11-27 - FGE: Added Verbose Output +#> + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Name", "DisplayName")] + [string]$EventstreamName, + + [Alias("Id")] + [string]$EventstreamId + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "You can either use Name or WorkspaceID not both. If both are used throw error" + if ($PSBoundParameters.ContainsKey("EventstreamName") -and $PSBoundParameters.ContainsKey("EventstreamID")) { + throw "Parameters EventstreamName and EventstreamID cannot be used together" + } + + # Create Eventhouse API + $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams" + + $eventstreamAPIEventstreamIdUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams/$EventstreamId" + +} + +process { + + if ($PSBoundParameters.ContainsKey("EventstreamId")) { + Write-Verbose "Calling Eventstream API with EventstreamId" + Write-Verbose "------------------------------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: PATCH" + Write-Verbose "URI: $eventstreamAPIEventstreamIdUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $eventstreamAPIEventstreamIdUrl ` + -ContentType "application/json" + + $response + } + else { + Write-Verbose "Calling Eventstream API" + Write-Verbose "-----------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: PATCH" + Write-Verbose "URI: $eventstreamApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $eventstreamApiUrl ` + -ContentType "application/json" + + if ($PSBoundParameters.ContainsKey("EventstreamName")) { + Write-Verbose "Filtering Eventstream with name $EventstreamName" + $response.value | ` + Where-Object { $_.displayName -eq $EventstreamName } + } + else { + Write-Verbose "Returning all Eventstreams" + $response.value + } + } + +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 b/source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 new file mode 100644 index 00000000..eb070a7b --- /dev/null +++ b/source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 @@ -0,0 +1,121 @@ + +<# +.SYNOPSIS +Retrieves the definition of a Eventstream from a specific workspace in Microsoft Fabric. + +.DESCRIPTION +This function fetches the Eventstream's content or metadata from a workspace. +Handles both synchronous and asynchronous operations, with detailed logging and error handling. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace from which the Eventstream definition is to be retrieved. + +.PARAMETER EventstreamId +(Optional)The unique identifier of the Eventstream whose definition needs to be retrieved. + +.PARAMETER EventstreamFormat +Specifies the format of the Eventstream definition. Currently, only 'ipynb' is supported. +Default: 'ipynb'. + +.EXAMPLE +Get-FabricEventstreamDefinition -WorkspaceId "12345" -EventstreamId "67890" + +Retrieves the definition of the Eventstream with ID `67890` from the workspace with ID `12345` in the `ipynb` format. + +.EXAMPLE +Get-FabricEventstreamDefinition -WorkspaceId "12345" + +Retrieves the definitions of all Eventstreams in the workspace with ID `12345` in the `ipynb` format. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. + +#> +function Get-FabricEventstreamDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamFormat + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/Eventstreams/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId + + if ($EventstreamFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $EventstreamFormat + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "Eventstream '$EventstreamId' definition retrieved successfully!" -Level Info + return $response.definition.parts + } + 202 { + + Write-Message -Message "Getting Eventstream '$EventstreamId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Eventstream/New-FabricEventstream copy.ps1 b/source/Public/Eventstream/New-FabricEventstream copy.ps1 new file mode 100644 index 00000000..f5def5b5 --- /dev/null +++ b/source/Public/Eventstream/New-FabricEventstream copy.ps1 @@ -0,0 +1,188 @@ +<# +.SYNOPSIS +Creates a new Eventstream in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new Eventstream +in the specified workspace. It supports optional parameters for Eventstream description +and path definitions for the Eventstream content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Eventstream will be created. + +.PARAMETER EventstreamName +The name of the Eventstream to be created. + +.PARAMETER EventstreamDescription +An optional description for the Eventstream. + +.PARAMETER EventstreamPathDefinition +An optional path to the Eventstream definition file (e.g., .ipynb file) to upload. + +.PARAMETER EventstreamPathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. + +.EXAMPLE + Add-FabricEventstream -WorkspaceId "workspace-12345" -EventstreamName "New Eventstream" -EventstreamPathDefinition "C:\Eventstreams\example.ipynb" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricEventstream { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventstreamName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EventstreamName + } + + if ($EventstreamDescription) { + $body.description = $EventstreamDescription + } + + if ($EventstreamPathDefinition) { + $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition + + if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "eventstream" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "eventstream.json" + payload = $EventstreamEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error + return $null + } + } + + if ($EventstreamPathPlatformDefinition) { + $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "eventstream" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $EventstreamEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Eventstream '$EventstreamName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Eventstream '$EventstreamName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Eventstream. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventstream/New-FabricEventstream.ps1 b/source/Public/Eventstream/New-FabricEventstream.ps1 new file mode 100644 index 00000000..35f5f662 --- /dev/null +++ b/source/Public/Eventstream/New-FabricEventstream.ps1 @@ -0,0 +1,87 @@ +function New-FabricEventstream { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Creates a new Fabric Eventstream + +.DESCRIPTION + Creates a new Fabric Eventstream + +.PARAMETER WorkspaceID + Id of the Fabric Workspace for which the Eventstream should be created. The value for WorkspaceID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventstreamName + The name of the Eventstream to create. + +.PARAMETER EventstreamDescription + The description of the Eventstream to create. + +.EXAMPLE + New-FabricEventstream + -WorkspaceID '12345678-1234-1234-1234-123456789012' + -EventstreamName 'MyEventstream' + -EventstreamDescription 'This is my Eventstream' + + This example will create a new Eventstream with the name 'MyEventstream' and the description 'This is my Eventstream'. + +.NOTES + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName + - 2024-11-30 - FGE: Added Verbose Output + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/create-eventstream?tabs=HTTP +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceID, + + [Parameter(Mandatory=$true)] + [Alias("Name","DisplayName")] + [string]$EventstreamName, + + [ValidateLength(0, 256)] + [Alias("Description")] + [string]$EventstreamDescription + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create body of request" + $body = @{ + 'displayName' = $EventstreamName + 'description' = $EventstreamDescription + } | ConvertTo-Json ` + -Depth 1 + + # Create Eventhouse API URL + $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams" + } + +process { + + # Call Eventstream API + if($PSCmdlet.ShouldProcess($EventstreamName)) { + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $eventstreamApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 b/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 new file mode 100644 index 00000000..ef59265f --- /dev/null +++ b/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS +Deletes an Eventstream from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricEventstream` function sends a DELETE request to the Fabric API to remove a specified Eventstream from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the Eventstream to delete. + +.PARAMETER EventstreamId +(Mandatory) The ID of the Eventstream to be deleted. + +.EXAMPLE +Remove-FabricEventstream -WorkspaceId "12345" -EventstreamId "67890" + +Deletes the Eventstream with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricEventstream { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Eventstream '$EventstreamId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Eventstream '$EventstreamId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventstream/Remove-FabricEventstream.ps1 b/source/Public/Eventstream/Remove-FabricEventstream.ps1 new file mode 100644 index 00000000..25d85024 --- /dev/null +++ b/source/Public/Eventstream/Remove-FabricEventstream.ps1 @@ -0,0 +1,105 @@ +function Remove-FabricEventstream { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Removes an existing Fabric Eventstream + +.DESCRIPTION + Removes an existing Fabric Eventstream + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Eventstream should be deleted. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventstreamId + The Id of the Eventstream to delete. The value for Eventstream is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Remove-FabricEventstream ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventstreamId '12345678-1234-1234-1234-123456789012' + + This example will delete the Eventstream with the Id '12345678-1234-1234-1234-123456789012' from + the Workspace. + +.EXAMPLE + Remove-FabricEventstream ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventstreamName 'MyEventstream' + + This example will delete the Eventstream with the name 'MyEventstream' from the Workspace. + +.NOTES + + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName + - 2024-12-08 - FGE: Added Verbose Output +#> + + + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Id")] + [string]$EventstreamId, + + [Alias("Name","DisplayName")] + [string]$EventstreamName + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "You can either use Name or WorkspaceID not both. If both are used throw error" + if ($PSBoundParameters.ContainsKey("EventstreamId") -and $PSBoundParameters.ContainsKey("EventstreamName")) { + throw "Parameters EventstreamId and EventstreamName cannot be used together" + } + + if ($PSBoundParameters.ContainsKey("EventstreamName")) { + Write-Verbose "The name $EventstreamName was provided. Fetching EventstreamId." + + $eh = Get-FabricEventstream ` + -WorkspaceId $WorkspaceId ` + -EventstreamName $EventstreamName + + $EventstreamId = $eh.id + Write-Verbose "EventstreamId: $EventstreamId" + } + + $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams/$EventstreamId" + +} + +process { + + # Call Eventstream API + if($PSCmdlet.ShouldProcess($EventstreamName)) { + Write-Verbose "Calling Eventstream API with EventstreamId" + Write-Verbose "------------------------------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: DELETE" + Write-Verbose "URI: $eventstreamApiUrl" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method DELETE ` + -Uri $eventstreamApiUrl ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventstream/Set-FabricEventstream.ps1 b/source/Public/Eventstream/Set-FabricEventstream.ps1 new file mode 100644 index 00000000..c15a8805 --- /dev/null +++ b/source/Public/Eventstream/Set-FabricEventstream.ps1 @@ -0,0 +1,113 @@ +function Set-FabricEventstream { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Updates Properties of an existing Fabric Eventstream + +.DESCRIPTION + Updates Properties of an existing Fabric Eventstream + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Eventstream should be updated. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventstreamId + The Id of the Eventstream to update. The value for EventstreamId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventstreamNewName + The new name of the Eventstream. + +.PARAMETER EventstreamDescription + The new description of the Eventstream. + +.EXAMPLE + Set-FabricEventstream ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventstreamId '12345678-1234-1234-1234-123456789012' ` + -EventstreamNewName 'MyNewEventstream' ` + -EventstreamDescription 'This is my new Eventstream' + + This example will update the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. + +.NOTES + TODO: Add functionality to update Eventstream properties using EventstreamName instead of EventstreamId + + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamNewName + - 2024-12-08 - FGE: Added Verbose Output + Added Aliases for EventstreamNewName and EventstreamDescription + Corrected typo in EventstreamNewName Variable +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Parameter(Mandatory=$true)] + [Alias("Id")] + [string]$EventstreamId, + + [Alias("NewName","NewDisplayName")] + [string]$EventstreamNewName, + + [ValidateLength(0, 256)] + [Alias("Description","NewDescription", "EventstreamNewDescription")] + [string]$EventstreamDescription + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create body of request" + $body = @{} + + if ($PSBoundParameters.ContainsKey("EventstreamNewName")) { + Write-Verbose "Setting EventstreamNewName: $EventstreamNewName" + $body["displayName"] = $EventstreamNewName + } + + if ($PSBoundParameters.ContainsKey("EventstreamDescription")) { + Write-Verbose "Setting EventstreamDescription: $EventstreamDescription" + $body["description"] = $EventstreamDescription + } + + $body = $body ` + | ConvertTo-Json ` + -Depth 1 + + # Create Eventstream API URL + $EventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/Eventstreams/$EventstreamId" + } + +process { + + if($PSCmdlet.ShouldProcess($EventhouseName)) { + Write-Verbose "Calling Eventstream API with EventstreamId" + Write-Verbose "------------------------------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: PATCH" + Write-Verbose "URI: $EventstreamApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method PATCH ` + -Uri $EventstreamApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Eventstream/Update-FabricEventstream.ps1 b/source/Public/Eventstream/Update-FabricEventstream.ps1 new file mode 100644 index 00000000..28103bf3 --- /dev/null +++ b/source/Public/Eventstream/Update-FabricEventstream.ps1 @@ -0,0 +1,107 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric Eventstream. + +.DESCRIPTION +The `Update-FabricEventstream` function updates the name and/or description of a specified Fabric Eventstream by making a PATCH request to the API. + +.PARAMETER EventstreamId +The unique identifier of the Eventstream to be updated. + +.PARAMETER EventstreamName +The new name for the Eventstream. + +.PARAMETER EventstreamDescription +(Optional) The new description for the Eventstream. + +.EXAMPLE +Update-FabricEventstream -EventstreamId "Eventstream123" -EventstreamName "NewEventstreamName" + +Updates the name of the Eventstream with the ID "Eventstream123" to "NewEventstreamName". + +.EXAMPLE +Update-FabricEventstream -EventstreamId "Eventstream123" -EventstreamName "NewName" -EventstreamDescription "Updated description" + +Updates both the name and description of the Eventstream "Eventstream123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricEventstream { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventstreamName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EventstreamName + } + + if ($EventstreamDescription) { + $body.description = $EventstreamDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Eventstream '$EventstreamName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 b/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 new file mode 100644 index 00000000..0929734e --- /dev/null +++ b/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 @@ -0,0 +1,180 @@ +<# +.SYNOPSIS +Updates the definition of a Eventstream in a Microsoft Fabric workspace. + +.DESCRIPTION +This function allows updating the content or metadata of a Eventstream in a Microsoft Fabric workspace. +The Eventstream content can be provided as file paths, and metadata updates can optionally be enabled. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the Eventstream resides. + +.PARAMETER EventstreamId +(Mandatory) The unique identifier of the Eventstream to be updated. + +.PARAMETER EventstreamPathDefinition +(Mandatory) The file path to the Eventstream content definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER EventstreamPathPlatformDefinition +(Optional) The file path to the Eventstream's platform-specific definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER UpdateMetadata +(Optional)A boolean flag indicating whether to update the Eventstream's metadata. +Default: `$false`. + +.EXAMPLE +Update-FabricEventstreamDefinition -WorkspaceId "12345" -EventstreamId "67890" -EventstreamPathDefinition "C:\Eventstreams\Eventstream.ipynb" + +Updates the content of the Eventstream with ID `67890` in the workspace `12345` using the specified Eventstream file. + +.EXAMPLE +Update-FabricEventstreamDefinition -WorkspaceId "12345" -EventstreamId "67890" -EventstreamPathDefinition "C:\Eventstreams\Eventstream.ipynb" -UpdateMetadata $true + +Updates both the content and metadata of the Eventstream with ID `67890` in the workspace `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- The Eventstream content is encoded as Base64 before being sent to the Fabric API. +- This function handles asynchronous operations and retrieves operation results if required. + +Author: Tiago Balabuch + +#> + +function Update-FabricEventstreamDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId + + if($EventstreamPathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + if ($EventstreamPathDefinition) { + $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition + + if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "eventstream.json" + payload = $EventstreamEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error + return $null + } + } + + if ($EventstreamPathPlatformDefinition) { + $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition + if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $EventstreamEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for Eventstream '$EventstreamId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for Eventstream '$EventstreamId' accepted. Operation in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/External Data Share/Get-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Get-FabricExternalDataShares.ps1 new file mode 100644 index 00000000..d9434787 --- /dev/null +++ b/source/Public/External Data Share/Get-FabricExternalDataShares.ps1 @@ -0,0 +1,50 @@ +<# +.SYNOPSIS + Retrieves External Data Shares details from a specified Microsoft Fabric. + +.DESCRIPTION + This function retrieves External Data Shares details. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.EXAMPLE + Get-FabricExternalDataShares + This example retrieves the External Data Shares details + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Get-FabricExternalDataShares { + [CmdletBinding()] + param ( ) + + try { + + # Validate authentication token before proceeding + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API endpoint URI for retrieving external data shares + Write-Message -Message "Constructing API endpoint URI..." -Level Debug + $apiEndpointURI = "{0}/admin/items/externalDataShares" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Invoke the API request to retrieve external data shares + $externalDataShares = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + # Return the retrieved external data shares + Write-Message -Message "Successfully retrieved external data shares." -Level Debug + return $externalDataShares + } + catch { + # Capture and log detailed error information if the API request fails + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 new file mode 100644 index 00000000..0d7f4cb2 --- /dev/null +++ b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 @@ -0,0 +1,61 @@ +<# +.SYNOPSIS + Retrieves External Data Shares details from a specified Microsoft Fabric. + +.DESCRIPTION + This function retrieves External Data Shares details. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.EXAMPLE + Get-FabricExternalDataShares + This example retrieves the External Data Shares details + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Revoke-FabricExternalDataShares { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ItemId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ExternalDataShareId + ) + + try { + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Constructing API endpoint URI..." -Level Debug + $apiEndpointURI = "{0}/admin/workspaces/{1}/items/{2}/externalDataShares/{3}/revoke" -f $FabricConfig.BaseUrl, $WorkspaceId, $ItemId, $ExternalDataShareId + + $externalDataShares = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post + + # Step 4: Return retrieved data + Write-Message -Message "Successfully revoked external data shares." -Level Info + return $externalDataShares + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Get-AllFabricDatasetRefreshes.ps1 b/source/Public/Get-AllFabricDatasetRefreshes.ps1 new file mode 100644 index 00000000..94dab947 --- /dev/null +++ b/source/Public/Get-AllFabricDatasetRefreshes.ps1 @@ -0,0 +1,57 @@ +<# +.SYNOPSIS +Retrieves all refreshes for all datasets in all PowerBI workspaces. + +.DESCRIPTION +The Get-AllFabricDatasetRefreshes function retrieves all refreshes for all datasets in all PowerBI workspaces. It supports multiple aliases for flexibility. + +.EXAMPLE +Get-AllFabricDatasetRefreshes + +This example retrieves all refreshes for all datasets in all PowerBI workspaces. + +.NOTES +The function makes a GET request to the PowerBI API to retrieve the refreshes. It loops through each workspace and each dataset in each workspace. If a dataset is refreshable, it retrieves the refreshes for the dataset and selects the most recent one. It then creates a PSCustomObject with information about the refresh and adds it to an array of refreshes. Finally, it returns the array of refreshes. +#> + +# This function retrieves all refreshes for all datasets in all PowerBI workspaces. +function Get-AllFabricDatasetRefreshes { + # Define aliases for the function for flexibility. + + Confirm-FabricAuthToken | Out-Null + + # Retrieve all PowerBI workspaces. + $wsps = Get-FabricWorkspace + # Initialize an array to store the refreshes. + $refs = @() + # Loop through each workspace. + foreach ($w in $wsps) { + # Get a list of all the datasets in the workspace. + $d = Get-FabricDataset -workspaceid $w.Id -ErrorAction SilentlyContinue + # Loop through each dataset. + foreach ($di in $d) { + # Check if the dataset is refreshable. + if ($di.isrefreshable ) { + # Get a list of all the refreshes for the dataset. + $results = (Invoke-PowerBIRestMethod -Method get -Url ("datasets/" + $di.id + "/Refreshes") | ConvertFrom-Json) + # Select the most recent refresh. + $results.value[0] + # Create a PSCustomObject with the information about the refresh. + $refresh = [PSCustomObject] @{ + Clock = Get-Date # Current date and time. + Workspace = $w.name # Name of the workspace. + Dataset = $di.Name # Name of the dataset. + refreshtype = $results.value[0].refreshType # Type of the refresh. + startTime = $results.value[0].startTime # Start time of the refresh. + endTime = $results.value[0].endTime # End time of the refresh. + status = $results.value[0].status # Status of the refresh. + ErrorMessage = $results.value[0].serviceExceptionJson # Error message of the refresh, if any. + } + # Add the refresh to the array of refreshes. + $refs += $refresh + } + } + } + # Return the array of refreshes. + return $refs +} \ No newline at end of file diff --git a/source/Public/Get-FabricAPIClusterURI.ps1 b/source/Public/Get-FabricAPIClusterURI.ps1 new file mode 100644 index 00000000..73cdf5f2 --- /dev/null +++ b/source/Public/Get-FabricAPIClusterURI.ps1 @@ -0,0 +1,43 @@ +<# +.SYNOPSIS +Retrieves the cluster URI for the tenant. + +.DESCRIPTION +The Get-FabricAPIclusterURI function retrieves the cluster URI for the tenant. It supports multiple aliases for flexibility. + +.EXAMPLE +Get-FabricAPIclusterURI + +This example retrieves the cluster URI for the tenant. + +.NOTES +The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the datasets. It then extracts the '@odata.context' property from the response, splits it on the '/' character, and selects the third element. This element is used to construct the cluster URI, which is then returned by the function. + +#> + +#This function retrieves the cluster URI for the tenant. + + +function Get-FabricAPIclusterURI { + # Define aliases for the function for flexibility. + [Alias("Get-FabAPIClusterURI")] + Param ( + ) + + Confirm-FabricAuthToken | Out-Null + + # Make a GET request to the PowerBI API to retrieve the datasets. + $reply = Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/datasets" -Headers $FabricSession.HeaderParams -Method GET + + # Extract the '@odata.context' property from the response. + $unaltered = $reply.'@odata.context' + + # Split the '@odata.context' property on the '/' character and select the third element. + $stripped = $unaltered.split('/')[2] + + # Construct the cluster URI. + $clusterURI = "https://$stripped/beta/myorg/groups" + + # Return the cluster URI. + return $clusterURI +} \ No newline at end of file diff --git a/source/Public/Get-FabricAuthToken.ps1 b/source/Public/Get-FabricAuthToken.ps1 new file mode 100644 index 00000000..55707a69 --- /dev/null +++ b/source/Public/Get-FabricAuthToken.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + Retrieves the Fabric API authentication token. + +.DESCRIPTION + The Get-FabricAuthToken function retrieves the Fabric API authentication token. If the token is not already set, it calls the Set-FabricAuthToken function to set it. It then outputs the token. + +.EXAMPLE + Get-FabricAuthToken + + This command retrieves the Fabric API authentication token. + +.INPUTS + None. You cannot pipe inputs to this function. + +.OUTPUTS + String. This function returns the Fabric API authentication token. + +.NOTES + This function was originally written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip +#> + +function Get-FabricAuthToken { + [Alias("Get-FabAuthToken")] + [CmdletBinding()] + param + ( + ) + + # Check if the Fabric token is already set + if (!$FabricSession.FabricToken) { + # If not, set the Fabric token + Set-FabricAuthToken + } + + # Output the Fabric token + return $FabricSession.FabricToken +} \ No newline at end of file diff --git a/source/Public/Get-FabricConnection.ps1 b/source/Public/Get-FabricConnection.ps1 new file mode 100644 index 00000000..b621a862 --- /dev/null +++ b/source/Public/Get-FabricConnection.ps1 @@ -0,0 +1,49 @@ +<# +.SYNOPSIS +Retrieves Fabric connections. + +.DESCRIPTION +The Get-FabricConnection function retrieves Fabric connections. It can retrieve all connections or the specified one only. + +.PARAMETER connectionId +The ID of the connection to retrieve. + +.EXAMPLE +Get-FabricConnection + +This example retrieves all connections from Fabric + +.EXAMPLE +Get-FabricConnection -connectionId "12345" + +This example retrieves specific connection from Fabric with ID "12345". + +.NOTES +https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/get-connection?tabs=HTTP +https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/list-connections?tabs=HTTP +#> + + +Function Get-FabricConnection { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $false)] + [string]$connectionId + ) + + begin { + Confirm-FabricAuthToken | Out-Null + } + + process { + if ($connectionId) { + $result = Invoke-FabricAPIRequest -Uri "/connections/$($connectionId)" -Method GET + } + else { + $result = Invoke-FabricAPIRequest -Uri "/connections" -Method GET + } + + return $result.value + } +} diff --git a/source/Public/Get-FabricDatasetRefreshes.ps1 b/source/Public/Get-FabricDatasetRefreshes.ps1 new file mode 100644 index 00000000..7f979aae --- /dev/null +++ b/source/Public/Get-FabricDatasetRefreshes.ps1 @@ -0,0 +1,69 @@ +<# +.SYNOPSIS + Retrieves the refresh history of a specified dataset in a PowerBI workspace. + +.DESCRIPTION + The Get-FabricDatasetRefreshes function uses the PowerBI cmdlets to retrieve the refresh history of a specified dataset in a workspace. It uses the dataset ID and workspace ID to get the dataset and checks if it is refreshable. If it is, the function retrieves the refresh history. + +.PARAMETER DatasetID + The ID of the dataset. This is a mandatory parameter. + +.PARAMETER workspaceId + The ID of the workspace. This is a mandatory parameter. + +.EXAMPLE + Get-FabricDatasetRefreshes -DatasetID "12345678-90ab-cdef-1234-567890abcdef" -workspaceId "abcdef12-3456-7890-abcd-ef1234567890" + + This command retrieves the refresh history of the specified dataset in the specified workspace. + +.INPUTS + String. You can pipe two strings that contain the dataset ID and workspace ID to Get-FabricDatasetRefreshes. + +.OUTPUTS + Object. Get-FabricDatasetRefreshes returns an object that contains the refresh history. + +.NOTES + Alias: Get-PowerBIDatasetRefreshes, Get-FabricDatasetRefreshes +#> +function Get-FabricDatasetRefreshes { + # Define aliases for the function for flexibility. + [Alias("Get-FabDatasetRefreshes")] + + # Define a mandatory parameter for the dataset ID. + Param ( + [Parameter(Mandatory=$true)] + [string]$DatasetID, + [Parameter(Mandatory=$true)] + [string]$workspaceId + ) + + # Get the dataset information. + $di = get-powerbidataset -id $DatasetID -WorkspaceId $workspaceId + + # Check if the dataset is refreshable. + if ($di.isrefreshable -eq "True") { + # Get a list of all the refreshes for the dataset. + $results = (Invoke-PowerBIRestMethod -Method get -Url ("datasets/" + $di.id + "/Refreshes") | ConvertFrom-Json) + + # Create a PSCustomObject with the information about the refresh. + $refresh = [PSCustomObject]@{ + Clock = Get-Date + Workspace = $w.name + Dataset = $di.Name + refreshtype = $results.value[0].refreshType + startTime = $results.value[0].startTime + endTime = $results.value[0].endTime + status = $results.value[0].status + ErrorMessage = $results.value[0].serviceExceptionJson + } + + # Return the PSCustomObject. + return $refresh + } else { + # If the dataset is not refreshable, return null. + Write-Warning "Dataset is not refreshable." + return $null + } + + +} \ No newline at end of file diff --git a/source/Public/Get-FabricDebugInfo.ps1 b/source/Public/Get-FabricDebugInfo.ps1 new file mode 100644 index 00000000..1c7704a0 --- /dev/null +++ b/source/Public/Get-FabricDebugInfo.ps1 @@ -0,0 +1,46 @@ +function Get-FabricDebugInfo { + #Requires -Version 7.1 + +<# +.SYNOPSIS + Shows internal debug information about the current session. + +.DESCRIPTION + Shows internal debug information about the current session. It is useful for troubleshooting purposes. + It will show you the current session object. This includes the bearer token. This can be useful + for connecting to the REST API directly via Postman. + +.Example + Get-FabricDebugInfo + + This example shows the current session object. + +.NOTES + + Revsion History: + + - 2024-12-22 - FGE: Added Verbose Output + +#> + +[CmdletBinding()] + param ( + + ) + +begin {} + +process { + Write-Verbose "Show current session object" + + return @{ + FabricSession = $script:FabricSession + AzureSession = $script:AzureSession + FabricConfig = $script:FabricConfig + } + +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Get-FabricUsageMetricsQuery.ps1 b/source/Public/Get-FabricUsageMetricsQuery.ps1 new file mode 100644 index 00000000..14a57128 --- /dev/null +++ b/source/Public/Get-FabricUsageMetricsQuery.ps1 @@ -0,0 +1,84 @@ +<# +.SYNOPSIS +Retrieves usage metrics for a specific dataset. + +.DESCRIPTION +The Get-FabricUsageMetricsQuery function retrieves usage metrics for a specific dataset. It supports multiple aliases for flexibility. + +.PARAMETER DatasetID +The ID of the dataset. This is a mandatory parameter. + +.PARAMETER groupId +The ID of the group. This is a mandatory parameter. + +.PARAMETER reportname +The name of the report. This is a mandatory parameter. + +.PARAMETER token +The access token. This is a mandatory parameter. + +.PARAMETER ImpersonatedUser +The name of the impersonated user. This is an optional parameter. + +.PARAMETER authToken +The authentication token. This is an optional parameter. + +.EXAMPLE +Get-FabricUsageMetricsQuery -DatasetID "your-dataset-id" -groupId "your-group-id" -reportname "your-report-name" -token "your-token" + +This example retrieves the usage metrics for a specific dataset given the dataset ID, group ID, report name, and token. + +.NOTES +The function defines the headers and body for a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. It then makes the POST request and returns the response. +#> + +# This function retrieves usage metrics for a specific dataset. +function Get-FabricUsageMetricsQuery { + # Define aliases for the function for flexibility. + [Alias("Get-FabUsageMetricsQuery")] + + # Define parameters for the dataset ID, group ID, report name, token, and impersonated user. + param ( + [Parameter(Mandatory = $true)] + [string]$DatasetID, + [Parameter(Mandatory = $true)] + [string]$groupId, + [Parameter(Mandatory = $true)] + $reportname, + [Parameter(Mandatory = $false)] + [string]$ImpersonatedUser = "" + ) + + # Confirm the authentication token. + Confirm-FabricAuthToken | Out-Null + + # Define the body of the POST request. + if ($ImpersonatedUser -ne "") { + $reportbody = '{ + "queries": [ + { + "query": "EVALUATE VALUES(' + $reportname + ')" + } + ], + "serializerSettings": { + "includeNulls": true + }, + "impersonatedUserName": "'+ $ImpersonatedUser + '" + }' + } + else { + $reportbody = '{ + "queries": [ + { + "query": "EVALUATE VALUES(' + $reportname + ')" + } + ], + "serializerSettings": { + "includeNulls": true + } + }' + } + # Make a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. + # The function returns the response of the POST request. + return Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/groups/$groupId/datasets/$DatasetID/executeQueries" -Headers $FabricSession.HeaderParams -Body $reportbody -Method Post +} \ No newline at end of file diff --git a/source/Public/Get-SHA256.ps1 b/source/Public/Get-SHA256.ps1 new file mode 100644 index 00000000..905b291d --- /dev/null +++ b/source/Public/Get-SHA256.ps1 @@ -0,0 +1,36 @@ +<# +.SYNOPSIS +Calculates the SHA256 hash of a string. + +.DESCRIPTION +The Get-Sha256 function calculates the SHA256 hash of a string. + +.PARAMETER string +The string to hash. This is a mandatory parameter. + +.EXAMPLE +Get-Sha256 -string "your-string" + +This example calculates the SHA256 hash of a string. + +.NOTES +The function creates a new SHA256CryptoServiceProvider object, converts the string to a byte array using UTF8 encoding, computes the SHA256 hash of the byte array, converts the hash to a string and removes any hyphens, and returns the resulting hash. +#> + +# This function calculates the SHA256 hash of a string. +function Get-Sha256 ($string) { + # Create a new SHA256CryptoServiceProvider object. + $sha256 = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider + + # Convert the string to a byte array using UTF8 encoding. + $bytes = [System.Text.Encoding]::UTF8.GetBytes($string) + + # Compute the SHA256 hash of the byte array. + $hash = $sha256.ComputeHash($bytes) + + # Convert the hash to a string and remove any hyphens. + $result = [System.BitConverter]::ToString($hash) -replace '-' + + # Return the resulting hash. + return $result +} \ No newline at end of file diff --git a/source/Public/Get-Something.ps1 b/source/Public/Get-Something.ps1 deleted file mode 100644 index 1bc54b75..00000000 --- a/source/Public/Get-Something.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -function Get-Something -{ - <# - .SYNOPSIS - Sample Function to return input string. - - .DESCRIPTION - This function is only a sample Advanced function that returns the Data given via parameter Data. - - .EXAMPLE - Get-Something -Data 'Get me this text' - - - .PARAMETER Data - The Data parameter is the data that will be returned without transformation. - - #> - [cmdletBinding( - SupportsShouldProcess = $true, - ConfirmImpact = 'Low' - )] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [String] - $Data - ) - - process - { - if ($pscmdlet.ShouldProcess($Data)) - { - Write-Verbose ('Returning the data: {0}' -f $Data) - Get-PrivateFunction -PrivateData $Data - } - else - { - Write-Verbose 'oh dear' - } - } -} diff --git a/source/Public/Invoke-FabricAPIRequest.ps1 b/source/Public/Invoke-FabricAPIRequest.ps1 new file mode 100644 index 00000000..d54b46a9 --- /dev/null +++ b/source/Public/Invoke-FabricAPIRequest.ps1 @@ -0,0 +1,175 @@ + +<# + .SYNOPSIS + Sends an HTTP request to a Fabric API endpoint and retrieves the response. + Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response + + .DESCRIPTION + The Invoke-FabricAPIRequest function is used to send an HTTP request to a Fabric API endpoint and retrieve the response. It handles various aspects such as authentication, 429 throttling, and Long-Running-Operation (LRO) response. + + .PARAMETER authToken + The authentication token to be used for the request. If not provided, it will be obtained using the Get-FabricAuthToken function. + + .PARAMETER uri + The URI of the Fabric API endpoint to send the request to. + + .PARAMETER method + The HTTP method to be used for the request. Valid values are 'Get', 'Post', 'Delete', 'Put', and 'Patch'. The default value is 'Get'. + + .PARAMETER body + The body of the request, if applicable. + + .PARAMETER contentType + The content type of the request. The default value is 'application/json; charset=utf-8'. + + .PARAMETER timeoutSec + The timeout duration for the request in seconds. The default value is 240 seconds. + + .PARAMETER outFile + The file path to save the response content to, if applicable. + + .PARAMETER retryCount + The number of times to retry the request in case of a 429 (Too Many Requests) error. The default value is 0. + + .EXAMPLE + Invoke-FabricAPIRequest -uri "/api/resource" -method "Get" + + This example sends a GET request to the "/api/resource" endpoint of the Fabric API. + + .EXAMPLE + Invoke-FabricAPIRequest -authToken "abc123" -uri "/api/resource" -method "Post" -body $requestBody + + This example sends a POST request to the "/api/resource" endpoint of the Fabric API with a request body. + + .NOTES + This function requires the Get-FabricAuthToken function to be defined in the same script or module. + This function was originally written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip + #> + + +Function Invoke-FabricAPIRequest { + <# + .SYNOPSIS + Sends an HTTP request to a Fabric API endpoint and retrieves the response. + Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $false)] [string] $authToken, + [Parameter(Mandatory = $true)] [string] $uri, + [Parameter(Mandatory = $false)] [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] [string] $method = "Get", + [Parameter(Mandatory = $false)] $body, + [Parameter(Mandatory = $false)] [string] $contentType = "application/json; charset=utf-8", + [Parameter(Mandatory = $false)] [int] $timeoutSec = 240, + [Parameter(Mandatory = $false)] [int] $retryCount = 0 + ) + + Confirm-FabricAuthToken | Out-Null + $fabricHeaders = $FabricSession.HeaderParams + + try { + + $requestUrl = "$($FabricSession.BaseApiUrl)/$uri" + Write-Verbose "Calling $requestUrl" + $response = Invoke-WebRequest -Headers $fabricHeaders -Method $method -Uri $requestUrl -Body $body -TimeoutSec $timeoutSec + + if ($response.StatusCode -eq 202) { + if ($uri -match "jobType=Pipeline") { + write-output "Waiting for pipeline to complete. Please check the status in the Power BI Service." + } + else { + do { + $asyncUrl = [string]$response.Headers.Location + + Write-Output "Waiting for request to complete. Sleeping..." + Start-Sleep -Seconds 5 + $response2 = Invoke-WebRequest -Headers $fabricHeaders -Method Get -Uri $asyncUrl + $lroStatusContent = $response2.Content | ConvertFrom-Json + } + while ($lroStatusContent.status -ine "succeeded" -and $lroStatusContent.status -ine "failed") + + try { + $response = Invoke-WebRequest -Headers $fabricHeaders -Method Get -Uri "$asyncUrl/result" + } + catch { + write-output "No result URL" + } + } + } + + #if ($response.StatusCode -in @(200,201) -and $response.Content) + if ($response.Content) { + $contentBytes = $response.RawContentStream.ToArray() + + if ($contentBytes[0] -eq 0xef -and $contentBytes[1] -eq 0xbb -and $contentBytes[2] -eq 0xbf) { + $contentText = [System.Text.Encoding]::UTF8.GetString($contentBytes[3..$contentBytes.Length]) + } + else { + $contentText = $response.Content + } + $jsonResult = $contentText | ConvertFrom-Json + Write-Output $jsonResult -NoEnumerate + } + else { + Write-Output $response -NoEnumerate + } + } + catch { + $ex = $_.Exception + $message = $null + + if ($null -ne $ex.Response) { + + $responseStatusCode = [int]$ex.Response.StatusCode + + if ($responseStatusCode -in @(429)) { + if ($ex.Response.Headers.RetryAfter) { + $retryAfterSeconds = $ex.Response.Headers.RetryAfter.Delta.TotalSeconds + 5 + } + + if (!$retryAfterSeconds) { + $retryAfterSeconds = 60 + } + + Write-Output "Exceeded the amount of calls (TooManyRequests - 429), sleeping for $retryAfterSeconds seconds." + + Start-Sleep -Seconds $retryAfterSeconds + + $maxRetries = 3 + + if ($retryCount -le $maxRetries) { + Invoke-FabricAPIRequest -authToken $authToken -uri $uri -method $method -body $body -contentType $contentType -timeoutSec $timeoutSec -retryCount ($retryCount + 1) + } + else { + throw "Exceeded the amount of retries ($maxRetries) after 429 error." + } + + } + else { + $apiErrorObj = $ex.Response.Headers | Where-Object { $_.key -ieq "x-ms-public-api-error-code" } | Select-object -First 1 + + if ($apiErrorObj) { + $apiError = $apiErrorObj.Value[0] + } + + if ($apiError -ieq "ItemHasProtectedLabel") { + Write-Warning "Item has a protected label." + } + else { + throw + } + + # TODO: Investigate why response.Content is empty but powershell can read it on throw + #$errorContent = $ex.Response.Content.ReadAsStringAsync().Result; + #$message = "$($ex.Message) - StatusCode: '$($ex.Response.StatusCode)'; Content: '$errorContent'" + } + } + else { + $message = "$($ex.Message)" + } + if ($message) { + throw $message + } + } +} \ No newline at end of file diff --git a/source/Public/Invoke-FabricDatasetRefresh.ps1 b/source/Public/Invoke-FabricDatasetRefresh.ps1 new file mode 100644 index 00000000..be979475 --- /dev/null +++ b/source/Public/Invoke-FabricDatasetRefresh.ps1 @@ -0,0 +1,47 @@ +<# +.SYNOPSIS + This function invokes a refresh of a PowerBI dataset + +.DESCRIPTION + The Invoke-FabricDatasetRefresh function is used to refresh a PowerBI dataset. It first checks if the dataset is refreshable. If it is not, it writes an error. If it is, it invokes a PowerBI REST method to refresh the dataset. The URL for the request is constructed using the provided dataset ID. + + +.PARAMETER DatasetID + A mandatory parameter that specifies the dataset ID. + +.EXAMPLE + Invoke-FabricDatasetRefresh -DatasetID "12345678-1234-1234-1234-123456789012" + + This command invokes a refresh of the dataset with the ID "12345678-1234-1234-1234-123456789012" +.NOTES + Alias: Invoke-FabDatasetRetresh +#> +function Invoke-FabricDatasetRefresh { + # Define aliases for the function for flexibility. + [Alias("Invoke-FabDatasetRefresh")] + + # Define parameters for the workspace ID and dataset ID. + param( + # Mandatory parameter for the dataset ID + [Parameter(Mandatory = $true, ParameterSetName = "DatasetId")] + [string]$DatasetID + ) + + Confirm-FabricAuthToken | Out-Null + + # Check if the dataset is refreshable + if ((Get-FabricDataset -DatasetId $DatasetID).isrefreshable -eq $false) { + # If the dataset is not refreshable, write an error + Write-Error "Dataset is not refreshable" + } + else { + # If the dataset is refreshable, invoke a PowerBI REST method to refresh the dataset + # The URL for the request is constructed using the provided workspace ID and dataset ID. + + # Check if the dataset is refreshable + + # If the dataset is refreshable, invoke a PowerBI REST method to refresh the dataset + # The URL for the request is constructed using the provided workspace ID and dataset ID. + return Invoke-RestMethod -Method POST -uri ("$($PowerBI.BaseApiUrl)/datasets/$datasetid/refreshes") -Headers $FabricSession.HeaderParams + } +} \ No newline at end of file diff --git a/source/Public/Item/Export-FabricItem.ps1 b/source/Public/Item/Export-FabricItem.ps1 new file mode 100644 index 00000000..a760653c --- /dev/null +++ b/source/Public/Item/Export-FabricItem.ps1 @@ -0,0 +1,116 @@ +<# +.SYNOPSIS +Exports items from a Fabric workspace. Either all items in a workspace or a specific item. + +.DESCRIPTION +The Export-FabricItem function exports items from a Fabric workspace to a specified directory. It can export items of type "Report", "SemanticModel", "SparkJobDefinitionV1" or "Notebook". If a specific item ID is provided, only that item will be exported. Otherwise, all items in the workspace will be exported. + +.PARAMETER path +The path to the directory where the items will be exported. The default value is '.\pbipOutput'. + +.PARAMETER workspaceId +The ID of the Fabric workspace. + +.PARAMETER filter +A script block used to filter the items to be exported. Only items that match the filter will be exported. The default filter includes items of type "Report", "SemanticModel", "SparkJobDefinitionV1" or "Notebook". + +.PARAMETER itemID +The ID of the specific item to export. If provided, only that item will be exported. + +.EXAMPLE +Export-FabricItem -workspaceId "12345678-1234-1234-1234-1234567890AB" -path "C:\ExportedItems" + +This example exports all items from the Fabric workspace with the specified ID to the "C:\ExportedItems" directory. + +.EXAMPLE +Export-FabricItem -workspaceId "12345678-1234-1234-1234-1234567890AB" -itemID "98765432-4321-4321-4321-9876543210BA" -path "C:\ExportedItems" + +This example exports the item with the specified ID from the Fabric workspace with the specified ID to the "C:\ExportedItems" directory. + +.NOTES +This function is based on the Export-FabricItems function written by Rui Romano. +https://github.com/RuiRomano/fabricps-pbip + +#> +Function Export-FabricItem { + [Alias("Export-FabItem")] + [CmdletBinding()] + param + ( + [string]$path = '.\pbipOutput', + [string]$workspaceId = '', + [scriptblock]$filter = { $_.type -in @("Report", "SemanticModel", "Notebook", "SparkJobDefinitionV1") }, + [string]$itemID = '' + ) + if (![string]::IsNullOrEmpty($itemID)) { + # Invoke the Fabric API to get the specific item in the workspace + + $item = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemID/getDefinition" -Method POST + # If a filter is provided + + + $parts = $item.definition.parts + + if (!(test-path $path)) { + New-Item -ItemType Directory -Force -Path $path + } + + + foreach ($part in $parts) { + $filename = $part.path + $payload = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($part.payload)) + $payload | Out-File -FilePath "$path\$filename" + } + + # Display a message indicating the items were exported + write-output "Items exported to $path" + } else { + $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get + + if ($filter) { + $items = $items.value | Where-Object $filter + } + else { + $items = $items.value + } + + Write-Output "Existing items: $($items.Count)" + + foreach ($item in $items) { + $itemId = $item.id + $itemName = $item.displayName + $itemType = $item.type + $itemOutputPath = "$path\$workspaceId\$($itemName).$($itemType)" + + if ($itemType -in @("report", "semanticmodel", "notebook", "SparkJobDefinitionV1", "DataPipeline")) { + write-output "Getting definition of: $itemId / $itemName / $itemType" + + $response = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/getDefinition" -Method Post + + $partCount = $response.definition.parts.Count + + write-output "Parts: $partCount" + if ($partCount -gt 0) { + foreach ($part in $response.definition.parts) { + write-output "Saving part: $($part.path)" + $outputFilePath = "$itemOutputPath\$($part.path.Replace("/", "\"))" + + New-Item -ItemType Directory -Path (Split-Path $outputFilePath -Parent) -ErrorAction SilentlyContinue | Out-Null + $payload = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($part.payload)) + $payload | Out-File -FilePath "$outputFilePath" + + } + + @{ + "type" = $itemType + "displayName" = $itemName + + } | ConvertTo-Json | Out-File "$itemOutputPath\item.metadata.json" + } + } + else { + write-output "Type '$itemType' not available for export." + } + } + } +} \ No newline at end of file diff --git a/source/Public/Item/Get-FabricItem.ps1 b/source/Public/Item/Get-FabricItem.ps1 new file mode 100644 index 00000000..e9261449 --- /dev/null +++ b/source/Public/Item/Get-FabricItem.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS +Retrieves fabric items from a workspace. + +.DESCRIPTION +The Get-FabricItem function retrieves fabric items from a specified workspace. It can retrieve all items or filter them by item type. + +.PARAMETER workspaceId +The ID of the workspace from which to retrieve the fabric items. + +.PARAMETER type +(Optional) The type of the fabric items to retrieve. If not specified, all items will be retrieved. + +.EXAMPLE +Get-FabricItem -workspaceId "12345" -type "file" + +This example retrieves all fabric items of type "file" from the workspace with ID "12345". + +.NOTES +This function was originally written by Rui Romano. +https://github.com/RuiRomano/fabricps-pbip + +#> + + +Function Get-FabricItem { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] + [string]$workspaceId, + + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true )] + $Workspace, + + [Parameter(Mandatory = $false)] + [Alias("itemType")] + [string]$type, + + [Parameter(Mandatory = $false)] + [string]$itemID + ) + + begin { + Confirm-FabricAuthToken | Out-Null + } + + process { + if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { + $workspaceID = $Workspace.id + } + if ($itemID) { + $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Get + } + else { + if ($type) { + $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items?type=$type" -Method Get + } + else { + # Invoke the Fabric API to get the workspaces + $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items" -Method Get + } + # Output the result + return $result.value + } + } +} \ No newline at end of file diff --git a/source/Public/Item/Import-FabricItem.ps1 b/source/Public/Item/Import-FabricItem.ps1 new file mode 100644 index 00000000..b885fb21 --- /dev/null +++ b/source/Public/Item/Import-FabricItem.ps1 @@ -0,0 +1,239 @@ + +<# + .SYNOPSIS + Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source. + + .PARAMETER fileOverrides + This parameter let's you override a PBIP file without altering the local file. + + .PARAMETER path + The path to the PBIP files. Default value is '.\pbipOutput'. + + .PARAMETER workspaceId + The ID of the Fabric workspace. + + .PARAMETER filter + A filter to limit the search for PBIP files to specific folders. + + .EXAMPLE + Import-FabricItems -path 'C:\PBIPFiles' -workspaceId '12345' -filter 'C:\PBIPFiles\Reports' + + This example imports PBIP files from the 'C:\PBIPFiles' folder into the Fabric workspace with ID '12345'. It only searches for PBIP files in the 'C:\PBIPFiles\Reports' folder. + + .NOTES + This function requires the Invoke-FabricAPIRequest function to be available in the current session. + This function was originally written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip + #> + +Function Import-FabricItem { + <# + .SYNOPSIS + Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source. + + .PARAMETER fileOverrides + This parameter let's you override a PBIP file without altering the local file. + #> + [CmdletBinding()] + param + ( + [string] $path = '.\pbipOutput' + , [string] $workspaceId + , [string] $filter = $null + , [hashtable] $fileOverrides + ) + + # Search for folders with .pbir and .pbidataset in it + + Confirm-FabricAuthToken | Out-Null + + $itemsFolders = Get-ChildItem -Path $path -recurse -include *.pbir, *.pbidataset + + if ($filter) { + $itemsFolders = $itemsFolders | where-object { $_.Directory.FullName -like $filter } + } + + # Get existing items of the workspace + + $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get + + write-output "Existing items: $($items.Count)" + + # Datasets first + + $itemsFolders = $itemsFolders | Select-Object @{n = "Order"; e = { if ($_.Name -like "*.pbidataset") { 1 } else { 2 } } }, * | sort-object Order + + $datasetReferences = @{} + + foreach ($itemFolder in $itemsFolders) { + # Get the parent folder + + $itemPath = $itemFolder.Directory.FullName + + write-output "Processing item: '$itemPath'" + + $files = Get-ChildItem -Path $itemPath -Recurse -Attributes !Directory + + # Remove files not required for the API: item.*.json; cache.abf; .pbi folder + + $files = $files | Where-Object { $_.Name -notlike "item.*.json" -and $_.Name -notlike "*.abf" -and $_.Directory.Name -notlike ".pbi" } + + # There must be a item.metadata.json in the item folder containing the item type and displayname, necessary for the item creation + + $itemMetadataStr = Get-Content "$itemPath\item.metadata.json" + if ($fileOverrides) { + $fileOverrideMatch = $fileOverrides.GetEnumerator() | where-object { "$itemPath\item.metadata.json" -ilike $_.Name } | select-object -First 1 + + if ($fileOverrideMatch) { + $itemMetadataStr = $fileOverrideMatch.Value + } + } + $itemMetadata = $itemMetadataStr | ConvertFrom-Json + $itemType = $itemMetadata.type + + if ($itemType -ieq "dataset") { + $itemType = "SemanticModel" + } + + + $displayName = $itemMetadata.displayName + + $itemPathAbs = Resolve-Path $itemPath + + $parts = $files | ForEach-Object { + + #$fileName = $_.Name + $filePath = $_.FullName + if ($fileOverrides) { + $fileOverrideMatch = $fileOverrides.GetEnumerator() | Where-Object { $filePath -ilike $_.Name } | select-object -First 1 + } + if ($fileOverrideMatch) { + $fileContent = $fileOverrideMatch.Value + + # convert to byte array + + if ($fileContent -is [string]) { + $fileContent = [system.Text.Encoding]::UTF8.GetBytes($fileContent) + } + elseif (!($fileContent -is [byte[]])) { + throw "FileOverrides value type must be string or byte[]" + } + } + else { + if ($filePath -like "*.pbir") { + + $pbirJson = Get-Content -Path $filePath | ConvertFrom-Json + + if ($pbirJson.datasetReference.byPath -and $pbirJson.datasetReference.byPath.path) { + + # try to swap byPath to byConnection + + $reportDatasetPath = (Resolve-path (Join-Path $itemPath $pbirJson.datasetReference.byPath.path.Replace("/", "\"))).Path + + $datasetReference = $datasetReferences[$reportDatasetPath] + + if ($datasetReference) { + # $datasetName = $datasetReference.name + $datasetId = $datasetReference.id + + $newPBIR = @{ + "version" = "1.0" + "datasetReference" = @{ + "byConnection" = @{ + "connectionString" = $null + "pbiServiceModelId" = $null + "pbiModelVirtualServerName" = "sobe_wowvirtualserver" + "pbiModelDatabaseName" = "$datasetId" + "name" = "EntityDataSource" + "connectionType" = "pbiServiceXmlaStyleLive" + } + } + } | ConvertTo-Json + + $fileContent = [system.Text.Encoding]::UTF8.GetBytes($newPBIR) + + } + else { + throw "Item API dont support byPath connection, switch the connection in the *.pbir file to 'byConnection'." + } + } else { + $fileContent = Get-Content -Path $filePath -AsByteStream -Raw + } + } + else { + + $fileContent = Get-Content -Path $filePath -AsByteStream -Raw + } + } + + $partPath = $filePath.Replace($itemPathAbs, "").TrimStart("\").Replace("\", "/") + write-host "Processing part: '$partPath'" + $fileEncodedContent = [Convert]::ToBase64String($fileContent) + + Write-Output @{ + Path = $partPath + Payload = $fileEncodedContent + PayloadType = "InlineBase64" + } + } + + $itemId = $null + + # Check if there is already an item with same displayName and type + + $foundItem = $items | Where-Object { $_.type -ieq $itemType -and $_.displayName -ieq $displayName } + + if ($foundItem) { + if ($foundItem.Count -gt 1) { + throw "Found more than one item for displayName '$displayName'" + } + + Write-output "Item '$displayName' of type '$itemType' already exists." -ForegroundColor Yellow + + $itemId = $foundItem.id + } + + if ($null -eq $itemId) { + write-output "Creating a new item" + + # Prepare the request + + $itemRequest = @{ + displayName = $displayName + type = $itemType + definition = @{ + Parts = $parts + } + } | ConvertTo-Json -Depth 3 + + $createItemResult = Invoke-FabricAPIRequest -uri "workspaces/$workspaceId/items" -method Post -body $itemRequest + + $itemId = $createItemResult.id + + write-output "Created a new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green + + Write-Output $itemId + } + else { + write-output "Updating item definition" + + $itemRequest = @{ + definition = @{ + Parts = $parts + } + } | ConvertTo-Json -Depth 3 + Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/updateDefinition" -Method Post -Body $itemRequest + + write-output "Updated new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green + + Write-Output $itemId + } + + # Save dataset references to swap byPath to byConnection + + if ($itemType -ieq "semanticmodel") { + $datasetReferences[$itemPath] = @{"id" = $itemId; "name" = $displayName } + } + } + +} diff --git a/source/Public/Item/Remove-FabricItem.ps1 b/source/Public/Item/Remove-FabricItem.ps1 new file mode 100644 index 00000000..8659760b --- /dev/null +++ b/source/Public/Item/Remove-FabricItem.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS + Removes selected items from a Fabric workspace. + +.DESCRIPTION + The Remove-FabricItems function removes selected items from a specified Fabric workspace. It uses the workspace ID and an optional filter to select the items to remove. If a filter is provided, only items whose DisplayName matches the filter are removed. + +.PARAMETER WorkspaceID + The ID of the Fabric workspace. This is a mandatory parameter. + +.PARAMETER Filter + An optional filter to select items to remove. If provided, only items whose DisplayName matches the filter are removed. + +.EXAMPLE + Remove-FabricItems -WorkspaceID "12345678-90ab-cdef-1234-567890abcdef" -Filter "*test*" + + This command removes all items from the workspace with the specified ID whose DisplayName includes "test". + +.INPUTS + String. You can pipe two strings that contain the workspace ID and filter to Remove-FabricItems. + +.OUTPUTS + None. This function does not return any output. + +.NOTES + This function was written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip +#> + +Function Remove-FabricItem { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory = $true)] + [string]$workspaceId, + [Parameter(Mandatory = $false)] + [string]$filter, + [Parameter(Mandatory = $false)] + [string]$itemID + ) + + Confirm-FabricAuthToken | Out-Null + + # Invoke the Fabric API to get the items in the workspace + if ($PSCmdlet.ShouldProcess("Remove items from workspace $workspaceId")) { + if ($itemID) { + Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Delete + } else { + $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get + + # Display the count of existing items + Write-Output "Existing items: $($items.Count)" + + # If a filter is provided + if ($filter) { + # Filter the items whose DisplayName matches the filter + $items = $items | Where-Object { $_.DisplayName -like $filter } + } + + # For each item + foreach ($item in $items) { + # Remove the item + Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$($item.ID)" -Method Delete + } + } + } +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 new file mode 100644 index 00000000..66087294 --- /dev/null +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 @@ -0,0 +1,155 @@ +<# +.SYNOPSIS +Retrieves an KQLDashboard or a list of KQLDashboards from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricKQLDashboard` function sends a GET request to the Fabric API to retrieve KQLDashboard details for a given workspace. It can filter the results by `KQLDashboardName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query KQLDashboards. + +.PARAMETER KQLDashboardName +(Optional) The name of the specific KQLDashboard to retrieve. + +.EXAMPLE +Get-FabricKQLDashboard -WorkspaceId "12345" -KQLDashboardName "Development" + +Retrieves the "Development" KQLDashboard from workspace "12345". + +.EXAMPLE +Get-FabricKQLDashboard -WorkspaceId "12345" + +Retrieves all KQLDashboards in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricKQLDashboard { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDashboardName + ) + + try { + # Step 1: Handle ambiguous input + if ($KQLDashboardId -and $KQLDashboardName) { + Write-Message -Message "Both 'KQLDashboardId' and 'KQLDashboardName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $KQLDashboards = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $KQLDashboards += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $KQLDashboard = if ($KQLDashboardId) { + $KQLDashboards | Where-Object { $_.Id -eq $KQLDashboardId } + } + elseif ($KQLDashboardName) { + $KQLDashboards | Where-Object { $_.DisplayName -eq $KQLDashboardName } + } + else { + # Return all KQLDashboards if no filter is provided + Write-Message -Message "No filter provided. Returning all KQLDashboards." -Level Debug + $KQLDashboards + } + + # Step 9: Handle results + if ($KQLDashboard) { + Write-Message -Message "KQLDashboard found matching the specified criteria." -Level Debug + return $KQLDashboard + } + else { + Write-Message -Message "No KQLDashboard found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 new file mode 100644 index 00000000..74a10285 --- /dev/null +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 @@ -0,0 +1,112 @@ +function Get-FabricKQLDashboard { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric KQLDashboards + +.DESCRIPTION + Retrieves Fabric KQLDashboards. Without the KQLDashboardName or KQLDashboardID parameter, all KQLDashboards are returned. + If you want to retrieve a specific KQLDashboard, you can use the KQLDashboardName or KQLDashboardID parameter. These + parameters cannot be used together. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the KQLDashboards should be retrieved. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLDashboardName + The name of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardID. + +.PARAMETER KQLDashboardID + The Id of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardName. The value for KQLDashboardID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricKQLDashboard + +.NOTES + TODO: Add functionality to list all KQLDashboards. To do so fetch all workspaces and + then all KQLDashboards in each workspace. + + Revision History: + - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName + - 2024-12-08 - FGE: Added Verbose Output +#> + + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Name","DisplayName")] + [string]$KQLDashboardName, + + [Alias("Id")] + [string]$KQLDashboardId + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + Write-Verbode "You can either use Name or WorkspaceID" + if ($PSBoundParameters.ContainsKey("KQLDashboardName") -and $PSBoundParameters.ContainsKey("KQLDashboardId")) { + throw "Parameters KQLDashboardName and KQLDashboardId cannot be used together" + } + + # Create KQLDashboard API + $KQLDashboardAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards" + + $KQLDashboardAPIKQLDashboardId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards/$KQLDashboardId" + +} + +process { + + if ($PSBoundParameters.ContainsKey("KQLDashboardId")) { + Write-Verbose "Get KQLDashboard with ID $KQLDashboardId" + Write-Verbose "Calling KQLDashboard API with KQLDashboardId" + Write-Verbose "--------------------------------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $KQLDashboardAPIKQLDashboardId" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $KQLDashboardAPIKQLDashboardId ` + -ContentType "application/json" + + $response + } + else { + Write-Verbose "Calling KQLDashboard API" + Write-Verbose "------------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $KQLDashboardAPI" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $KQLDashboardAPI ` + -ContentType "application/json" + + if ($PSBoundParameters.ContainsKey("KQLDashboardName")) { + Write-Verbose "Filtering KQLDashboards by name. Name: $KQLDashboardName" + $response.value | ` + Where-Object { $_.displayName -eq $KQLDashboardName } + } + else { + Write-Verbose "Returning all KQLDashboards" + $response.value + } + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 new file mode 100644 index 00000000..4f0cf5d1 --- /dev/null +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 @@ -0,0 +1,120 @@ + +<# +.SYNOPSIS +Retrieves the definition of a KQLDashboard from a specific workspace in Microsoft Fabric. + +.DESCRIPTION +This function fetches the KQLDashboard's content or metadata from a workspace. +Handles both synchronous and asynchronous operations, with detailed logging and error handling. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace from which the KQLDashboard definition is to be retrieved. + +.PARAMETER KQLDashboardId +(Optional)The unique identifier of the KQLDashboard whose definition needs to be retrieved. + +.PARAMETER KQLDashboardFormat +Specifies the format of the KQLDashboard definition. + +.EXAMPLE +Get-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" + +Retrieves the definition of the KQLDashboard with ID `67890` from the workspace with ID `12345` in the `ipynb` format. + +.EXAMPLE +Get-FabricKQLDashboardDefinition -WorkspaceId "12345" + +Retrieves the definitions of all KQLDashboards in the workspace with ID `12345` in the `ipynb` format. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. + +#> +function Get-FabricKQLDashboardDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardFormat + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId + + if ($KQLDashboardFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLDashboardFormat + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "KQLDashboard '$KQLDashboardId' definition retrieved successfully!" -Level Debug + return $response.definition.parts + } + 202 { + + Write-Message -Message "Getting KQLDashboard '$KQLDashboardId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 new file mode 100644 index 00000000..c74147fe --- /dev/null +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 @@ -0,0 +1,119 @@ +function Get-FabricKQLDashboardDefinition { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric KQLDashboard Definitions for a given KQLDashboard. + +.DESCRIPTION + Retrieves the Definition of the Fabric KQLDashboard that is specified by the KQLDashboardName or KQLDashboardID. + The KQLDashboard Definition contains the parts of the KQLDashboard, which are the visualizations and their configuration. + This is provided as a JSON object. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace in which the KQLDashboard exists. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLDashboardName + The name of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardID. + +.PARAMETER KQLDashboardID + The Id of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardName. The value for KQLDashboardID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricKQLDashboardDefinition ` + -WorkspaceId "12345678-1234-1234-1234-123456789012" ` + -KQLDashboardName "MyKQLDashboard" + + This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the + Workspace with the ID "12345678-1234-1234-1234-123456789012". + +.EXAMPLE + $db = Get-FabricKQLDashboardDefinition ` + -WorkspaceId "12345678-1234-1234-1234-123456789012" ` + -KQLDashboardName "MyKQLDashboard" + + $db[0].payload | ` + Set-Content ` + -Path "C:\temp\mydashboard.json" + + This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the + Workspace with the ID "12345678-1234-1234-1234-123456789012". + The definition is saved to a file named "mydashboard.json". + + +.NOTES + + Revision History: + - 2024-11-16 - FGE: First version + - 2024-12-08 - FGE: Added Verbose Output +#> + + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Name","DisplayName")] + [string]$KQLDashboardName, + + [Alias("Id")] + [string]$KQLDashboardId, + + [string]$Format + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "You can either use Name or WorkspaceID" + if ($PSBoundParameters.ContainsKey("KQLDashboardName") -and $PSBoundParameters.ContainsKey("KQLDashboardId")) { + throw "Parameters KQLDashboardName and KQLDashboardId cannot be used together" + } + + # Create KQLDashboard API + + $KQLDashboardAPIKQLDashboardId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards/$KQLDashboardId/getDefinition" + +} + +process { + + if ($PSBoundParameters.ContainsKey("KQLDashboardId")) { + Write-Verbose "Get KQLDashboardDefinition with ID $KQLDashboardId" + Write-Verbose "Calling KQLDashboard API with KQLDashboardId" + Write-Verbose "--------------------------------------------" + Write-Verbose "Sending the following values to the KQLDashboard API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $KQLDashboardAPIKQLDashboardId" + Write-Verbose "Body of request: $$null" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $KQLDashboardAPIKQLDashboardId ` + -Body $null ` + -ContentType "application/json" + + $parts = $response.definition.parts + Write-Verbose "Decoding the payload of the parts: $parts" + + foreach ($part in $parts) { + $bytes = [System.Convert]::FromBase64String($part.payload) + Write-Verbose "Returned bytes for part $part.name: $bytes" + $decodedText = [System.Text.Encoding]::UTF8.GetString($bytes) + Write-Verbose "decodedText for part $part.name: $decodedText" + $part.payload = $decodedText + } + + $parts + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 b/source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 new file mode 100644 index 00000000..50694d70 --- /dev/null +++ b/source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 @@ -0,0 +1,188 @@ +<# +.SYNOPSIS +Creates a new KQLDashboard in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new KQLDashboard +in the specified workspace. It supports optional parameters for KQLDashboard description +and path definitions for the KQLDashboard content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLDashboard will be created. + +.PARAMETER KQLDashboardName +The name of the KQLDashboard to be created. + +.PARAMETER KQLDashboardDescription +An optional description for the KQLDashboard. + +.PARAMETER KQLDashboardPathDefinition +An optional path to the KQLDashboard definition file (e.g., .ipynb file) to upload. + +.PARAMETER KQLDashboardPathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. + +.EXAMPLE + Add-FabricKQLDashboard -WorkspaceId "workspace-12345" -KQLDashboardName "New KQLDashboard" -KQLDashboardPathDefinition "C:\KQLDashboards\example.ipynb" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricKQLDashboard { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDashboardName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $KQLDashboardName + } + + if ($KQLDashboardDescription) { + $body.description = $KQLDashboardDescription + } + + if ($KQLDashboardPathDefinition) { + $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition + + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "KQLDashboard" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "RealTimeDashboard.json" + payload = $KQLDashboardEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error + return $null + } + } + + if ($KQLDashboardPathPlatformDefinition) { + $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = $null + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLDashboardEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "KQLDashboard '$KQLDashboardName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "KQLDashboard '$KQLDashboardName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create KQLDashboard. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 new file mode 100644 index 00000000..e3e05f2e --- /dev/null +++ b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 @@ -0,0 +1,92 @@ +function New-FabricKQLDashboard { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Creates a new Fabric KQLDashboard + +.DESCRIPTION + Creates a new Fabric KQLDashboard + +.PARAMETER WorkspaceID + Id of the Fabric Workspace for which the KQLDashboard should be created. The value for WorkspaceID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLDashboardName + The name of the KQLDashboard to create. + +.PARAMETER KQLDashboardDescription + The description of the KQLDashboard to create. + +.EXAMPLE + New-FabricDashboard ` + -WorkspaceID '12345678-1234-1234-1234-123456789012' ` + -KQLDashboardName 'MyKQLDashboard' ` + -KQLDashboardDescription 'This is my KQLDashboard' + + This example will create a new KQLDashboard with the name 'MyKQLDashboard' and the description 'This is my KQLDashboard'. + +.NOTES + + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName + - 2024-12-08 - FGE: Added Verbose Output +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceID, + + [Parameter(Mandatory=$true)] + [Alias("Name", "DisplayName")] + [string]$KQLDashboardName, + + [ValidateLength(0, 256)] + [Alias("Description")] + [string]$KQLDashboardDescription + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create body of request" + $body = @{ + 'displayName' = $KQLDashboardName + 'description' = $KQLDashboardDescription + } | ConvertTo-Json ` + -Depth 1 + + # Create KQLDashboard API URL + $KQLDashboardApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards" + } + +process { + + if($PSCmdlet.ShouldProcess($KQLDashboardName)) { + Write-Verbose "Calling KQLDashboard API" + Write-Verbose "------------------------" + Write-Verbose "Sending the following values to the KQLDashboard API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $KQLDashboardApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $KQLDashboardApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 new file mode 100644 index 00000000..166e7635 --- /dev/null +++ b/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS +Deletes an KQLDashboard from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricKQLDashboard` function sends a DELETE request to the Fabric API to remove a specified KQLDashboard from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the KQLDashboard to delete. + +.PARAMETER KQLDashboardId +(Mandatory) The ID of the KQLDashboard to be deleted. + +.EXAMPLE +Remove-FabricKQLDashboard -WorkspaceId "12345" -KQLDashboardId "67890" + +Deletes the KQLDashboard with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricKQLDashboard { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "KQLDashboard '$KQLDashboardId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete KQLDashboard '$KQLDashboardId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 new file mode 100644 index 00000000..874a614b --- /dev/null +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 @@ -0,0 +1,107 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric KQLDashboard. + +.DESCRIPTION +The `Update-FabricKQLDashboard` function updates the name and/or description of a specified Fabric KQLDashboard by making a PATCH request to the API. + +.PARAMETER KQLDashboardId +The unique identifier of the KQLDashboard to be updated. + +.PARAMETER KQLDashboardName +The new name for the KQLDashboard. + +.PARAMETER KQLDashboardDescription +(Optional) The new description for the KQLDashboard. + +.EXAMPLE +Update-FabricKQLDashboard -KQLDashboardId "KQLDashboard123" -KQLDashboardName "NewKQLDashboardName" + +Updates the name of the KQLDashboard with the ID "KQLDashboard123" to "NewKQLDashboardName". + +.EXAMPLE +Update-FabricKQLDashboard -KQLDashboardId "KQLDashboard123" -KQLDashboardName "NewName" -KQLDashboardDescription "Updated description" + +Updates both the name and description of the KQLDashboard "KQLDashboard123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricKQLDashboard { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDashboardName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $KQLDashboardName + } + + if ($KQLDashboardDescription) { + $body.description = $KQLDashboardDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "KQLDashboard '$KQLDashboardName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 new file mode 100644 index 00000000..14b423fc --- /dev/null +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 @@ -0,0 +1,166 @@ +<# +.SYNOPSIS +Updates the definition of a KQLDashboard in a Microsoft Fabric workspace. + +.DESCRIPTION +This function allows updating the content or metadata of a KQLDashboard in a Microsoft Fabric workspace. +The KQLDashboard content can be provided as file paths, and metadata updates can optionally be enabled. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the KQLDashboard resides. + +.PARAMETER KQLDashboardId +(Mandatory) The unique identifier of the KQLDashboard to be updated. + +.PARAMETER KQLDashboardPathDefinition +(Mandatory) The file path to the KQLDashboard content definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER KQLDashboardPathPlatformDefinition +(Optional) The file path to the KQLDashboard's platform-specific definition file. The content will be encoded as Base64 and sent in the request. + + +.EXAMPLE +Update-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" -KQLDashboardPathDefinition "C:\KQLDashboards\KQLDashboard.ipynb" + +Updates the content of the KQLDashboard with ID `67890` in the workspace `12345` using the specified KQLDashboard file. + +.EXAMPLE +Update-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" -KQLDashboardPathDefinition "C:\KQLDashboards\KQLDashboard.ipynb" + +Updates both the content and metadata of the KQLDashboard with ID `67890` in the workspace `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- The KQLDashboard content is encoded as Base64 before being sent to the Fabric API. +- This function handles asynchronous operations and retrieves operation results if required. + +Author: Tiago Balabuch + +#> + +function Update-FabricKQLDashboardDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/KQLDashboards/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId + + if($KQLDashboardPathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + format = $null + parts = @() + } + } + + if ($KQLDashboardPathDefinition) { + $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition + + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "RealTimeDashboard.json" + payload = $KQLDashboardEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error + return $null + } + } + + if ($KQLDashboardPathPlatformDefinition) { + $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLDashboardEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for KQLDashboard '$KQLDashboardId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for KQLDashboard '$KQLDashboardId' accepted. Operation in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + $operationResult = Get-FabricLongRunningOperation -operationId $operationId + + # Handle operation result + if ($operationResult.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + + $result = Get-FabricLongRunningOperationResult -operationId $operationId + return $result.definition.parts + } + else { + Write-Message -Message "Operation Failed" -Level Debug + return $operationResult.definition.parts + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 new file mode 100644 index 00000000..94677fe6 --- /dev/null +++ b/source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 @@ -0,0 +1,153 @@ +<# +.SYNOPSIS +Retrieves an KQLDatabase or a list of KQLDatabases from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricKQLDatabase` function sends a GET request to the Fabric API to retrieve KQLDatabase details for a given workspace. It can filter the results by `KQLDatabaseName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query KQLDatabases. + +.PARAMETER KQLDatabaseName +(Optional) The name of the specific KQLDatabase to retrieve. + +.EXAMPLE +Get-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseName "Development" + +Retrieves the "Development" KQLDatabase from workspace "12345". + +.EXAMPLE +Get-FabricKQLDatabase -WorkspaceId "12345" + +Retrieves all KQLDatabases in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> +function Get-FabricKQLDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDatabaseName + ) + + try { + # Step 1: Handle ambiguous input + if ($KQLDatabaseId -and $KQLDatabaseName) { + Write-Message -Message "Both 'KQLDatabaseId' and 'KQLDatabaseName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $KQLDatabases = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $KQLDatabases += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $KQLDatabase = if ($KQLDatabaseId) { + $KQLDatabases | Where-Object { $_.Id -eq $KQLDatabaseId } + } + elseif ($KQLDatabaseName) { + $KQLDatabases | Where-Object { $_.DisplayName -eq $KQLDatabaseName } + } + else { + # Return all KQLDatabases if no filter is provided + Write-Message -Message "No filter provided. Returning all KQLDatabases." -Level Debug + $KQLDatabases + } + + # Step 9: Handle results + if ($KQLDatabase) { + Write-Message -Message "KQLDatabase found matching the specified criteria." -Level Debug + return $KQLDatabase + } + else { + Write-Message -Message "No KQLDatabase found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 new file mode 100644 index 00000000..701cd233 --- /dev/null +++ b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 @@ -0,0 +1,228 @@ +function Get-FabricKQLDatabase { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric KQLDatabases + +.DESCRIPTION + Retrieves Fabric KQLDatabases. Without the KQLDatabaseName or KQLDatabaseID parameter, + all KQLDatabases are returned. If you want to retrieve a specific KQLDatabase, you can + use the KQLDatabaseName or KQLDatabaseID parameter. These parameters cannot be used together. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the KQLDatabases should be retrieved. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLDatabaseName + The name of the KQLDatabase to retrieve. This parameter cannot be used together with KQLDatabaseID. + +.PARAMETER KQLDatabaseID + The Id of the KQLDatabase to retrieve. This parameter cannot be used together with KQLDatabaseName. + The value for KQLDatabaseID is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricKQLDatabase ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLDatabaseName 'MyKQLDatabase' + + This example will retrieve the KQLDatabase with the name 'MyKQLDatabase'. + +.EXAMPLE + Get-FabricKQLDatabase + + This example will retrieve all KQLDatabases in the workspace that is specified + by the WorkspaceId. + +.EXAMPLE + Get-FabricKQLDatabase ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLDatabaseId '12345678-1234-1234-1234-123456789012' + + This example will retrieve the KQLDatabase with the ID '12345678-1234-1234-1234-123456789012'. + +.NOTES + TODO: Add functionality to list all KQLDatabases. To do so fetch all workspaces and + then all KQLDatabases in each workspace. + + Revision History: + - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName + - 2024-12-08 - FGE: Added Verbose Output + +#> + + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Name","DisplayName")] + [string]$KQLDatabaseName, + + [Alias("Id")] + [string]$KQLDatabaseId + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "You can either use KQLDatabaseName or KQLDatabaseID not both. If both are used throw error" + if ($PSBoundParameters.ContainsKey("KQLDatabaseName") -and $PSBoundParameters.ContainsKey("KQLDatabaseId")) { + throw "Parameters KQLDatabaseName and KQLDatabaseId cannot be used together" + } + + # Create KQLDatabase API + $KQLDatabaseAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/kqldatabases" + + $KQLDatabaseAPIKQLDatabaseId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/kqldatabases/$KQLDatabaseId" + +} + +process { + + if ($PSBoundParameters.ContainsKey("KQLDatabaseId")) { + Write-Verbose "Calling KQLDatabase API with KQLDatabaseId : $KQLDatabaseId" + Write-Verbose "-------------------------------------------------------------------------" + Write-Verbose "Sending the following values to the KQLDatabase API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $KQLDatabaseAPIKQLDatabaseId" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $KQLDatabaseAPIKQLDatabaseId ` + -ContentType "application/json" + + Write-Verbose "Adding Members to the Output object for convenience" + Write-Verbose "Adding Member parentEventhouseItemId with value $($response.properties.parentEventhouseItemId)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'parentEventhouseItemId' ` + -Value $response.properties.parentEventhouseItemId ` + -InputObject $response ` + -Force + + Write-Verbose "Adding Member queryServiceUri with value $($response.properties.queryServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'queryServiceUri' ` + -Value $response.properties.queryServiceUri ` + -InputObject $response ` + -Force + + Write-Verbose "Adding Member ingestionServiceUri with value $($response.properties.ingestionServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'ingestionServiceUri' ` + -Value $response.properties.ingestionServiceUri ` + -InputObject $response ` + -Force + + Write-Verbose "Adding Member databaseType with value $($response.properties.databaseType)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'databaseType' ` + -Value $response.properties.databaseType ` + -InputObject $response ` + -Force + + Write-Verbose "Adding Member oneLakeStandardStoragePeriod with value $($response.properties.oneLakeStandardStoragePeriod)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'oneLakeStandardStoragePeriod' ` + -Value $response.properties.oneLakeStandardStoragePeriod ` + -InputObject $response ` + -Force + + Write-Verbose "Adding Member oneLakeCachingPeriod with value $($response.properties.oneLakeCachingPeriod)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'oneLakeCachingPeriod' ` + -Value $response.properties.oneLakeCachingPeriod ` + -InputObject $response ` + -Force + + $response + } + else { + Write-Verbose "Calling KQLDatabase API" + Write-Verbose "-----------------------" + Write-Verbose "Sending the following values to the KQLDatabase API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $KQLDatabaseAPI" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $KQLDatabaseAPI ` + -ContentType "application/json" + + Write-Verbose "Adding Members to the Output object for convenience" + foreach ($kqlDatabase in $response.value) { + Write-Verbose "Adding Member parentEventhouseItemId with value $($response.properties.parentEventhouseItemId)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'parentEventhouseItemId' ` + -Value $response.properties.parentEventhouseItemId ` + -InputObject $response ` + -Force + + Write-Verbose "Adding Member queryServiceUri with value $($kqlDatabase.properties.queryServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'queryServiceUri' ` + -Value $kqlDatabase.properties.queryServiceUri ` + -InputObject $kqlDatabase ` + -Force + + Write-Verbose "Adding Member ingestionServiceUri with value $($kqlDatabase.properties.ingestionServiceUri)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'ingestionServiceUri' ` + -Value $kqlDatabase.properties.ingestionServiceUri ` + -InputObject $kqlDatabase ` + -Force + Write-Verbose "Adding Member databaseType with value $($kqlDatabase.properties.databaseType)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'databaseType' ` + -Value $kqlDatabase.properties.databaseType ` + -InputObject $kqlDatabase ` + -Force + + Write-Verbose "Adding Member oneLakeStandardStoragePeriod with value $($kqlDatabase.properties.oneLakeStandardStoragePeriod)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'oneLakeStandardStoragePeriod' ` + -Value $kqlDatabase.properties.oneLakeStandardStoragePeriod ` + -InputObject $kqlDatabase ` + -Force + + Write-Verbose "Adding Member oneLakeCachingPeriod with value $($kqlDatabase.properties.oneLakeCachingPeriod)" + Add-Member ` + -MemberType NoteProperty ` + -Name 'oneLakeCachingPeriod' ` + -Value $kqlDatabase.properties.oneLakeCachingPeriod ` + -InputObject $kqlDatabase ` + -Force + } + + if ($PSBoundParameters.ContainsKey("KQLDatabaseName")) { + Write-Verbose "Filtering KQLDatabases by name. Name: $KQLDatabaseName" + $response.value | ` + Where-Object { $_.displayName -eq $KQLDatabaseName } + } + else { + Write-Verbose "Returning all KQLDatabases" + $response.value + } + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 new file mode 100644 index 00000000..a3d56115 --- /dev/null +++ b/source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 @@ -0,0 +1,129 @@ + +<# +.SYNOPSIS +Retrieves the definition of a KQLDatabase from a specific workspace in Microsoft Fabric. + +.DESCRIPTION +This function fetches the KQLDatabase's content or metadata from a workspace. +It supports retrieving KQLDatabase definitions in the Jupyter KQLDatabase (`ipynb`) format. +Handles both synchronous and asynchronous operations, with detailed logging and error handling. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace from which the KQLDatabase definition is to be retrieved. + +.PARAMETER KQLDatabaseId +(Optional)The unique identifier of the KQLDatabase whose definition needs to be retrieved. + +.PARAMETER KQLDatabaseFormat +Specifies the format of the KQLDatabase definition. Currently, only 'ipynb' is supported. + + +.EXAMPLE +Get-FabricKQLDatabaseDefinition -WorkspaceId "12345" -KQLDatabaseId "67890" + +Retrieves the definition of the KQLDatabase with ID `67890` from the workspace with ID `12345` in the `ipynb` format. + +.EXAMPLE +Get-FabricKQLDatabaseDefinition -WorkspaceId "12345" + +Retrieves the definitions of all KQLDatabases in the workspace with ID `12345` in the `ipynb` format. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. + +#> +function Get-FabricKQLDatabaseDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseFormat + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/KQLDatabases/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId + + if ($KQLDatabaseFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLDatabaseFormat + } + + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "KQLDatabase '$KQLDatabaseId' definition retrieved successfully!" -Level Debug + return $response + } + 202 { + + Write-Message -Message "Getting KQLDatabase '$KQLDatabaseId' definition request accepted. Retrieving in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 new file mode 100644 index 00000000..21098872 --- /dev/null +++ b/source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 @@ -0,0 +1,288 @@ +<# +.SYNOPSIS +Creates a new KQLDatabase in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new KQLDatabase +in the specified workspace. It supports optional parameters for KQLDatabase description +and path definitions for the KQLDatabase content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLDatabase will be created. + +.PARAMETER KQLDatabaseName +The name of the KQLDatabase to be created. + +.PARAMETER KQLDatabaseDescription +An optional description for the KQLDatabase. + +.PARAMETER KQLDatabasePathDefinition +An optional path to the KQLDatabase definition file (e.g., .ipynb file) to upload. + +.PARAMETER KQLDatabasePathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. + +.EXAMPLE + Add-FabricKQLDatabase -WorkspaceId "workspace-12345" -KQLDatabaseName "New KQLDatabase" -KQLDatabasePathDefinition "C:\KQLDatabases\example.ipynb" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Precedent Request Body + - Definition file high priority. + - CreationPayload is evaluate only if Definition file is not provided. + - invitationToken has priority over all other payload fields. + +Author: Tiago Balabuch + +#> + +function New-FabricKQLDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDatabaseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$parentEventhouseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet("ReadWrite", "Shortcut")] + [string]$KQLDatabaseType, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLInvitationToken, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLSourceClusterUri, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLSourceDatabaseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathPlatformDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathSchemaDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body ### This is working + $body = @{ + displayName = $KQLDatabaseName + } + + if ($KQLDatabaseDescription) { + $body.description = $KQLDatabaseDescription + } + + if ($KQLDatabasePathDefinition) { + $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition + + $body.definition = @{ + parts = @() + } + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { + + + # Add new part to the parts array + $body.definition.parts += @{ + path = "DatabaseProperties.json" + payload = $KQLDatabaseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error + return $null + } + + if ($KQLDatabasePathPlatformDefinition) { + $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLDatabaseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + + } + if ($KQLDatabasePathSchemaDefinition) { + $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { + + # Add new part to the parts array + $body.definition.parts += @{ + path = "DatabaseSchema.kql" + payload = $KQLDatabaseEncodedSchemaContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in schema definition." -Level Error + return $null + } + } + + } + else { + if ($KQLDatabaseType -eq "Shortcut") { + if (-not $parentEventhouseId) { + Write-Message -Message "Error: 'parentEventhouseId' is required for Shortcut type." -Level Error + return $null + } + if (-not ($KQLInvitationToken -or $KQLSourceClusterUri -or $KQLSourceDatabaseName)) { + Write-Message -Message "Error: Provide either 'KQLInvitationToken', 'KQLSourceClusterUri', or 'KQLSourceDatabaseName'." -Level Error + return $null + } + if ($KQLInvitationToken) { + Write-Message -Message "Info: 'KQLInvitationToken' is provided." -Level Warning + + if ($KQLSourceClusterUri) { + Write-Message -Message "Warning: 'KQLSourceClusterUri' is ignored when 'KQLInvitationToken' is provided." -Level Warning + #$KQLSourceClusterUri = $null + } + if ($KQLSourceDatabaseName) { + Write-Message -Message "Warning: 'KQLSourceDatabaseName' is ignored when 'KQLInvitationToken' is provided." -Level Warning + #$KQLSourceDatabaseName = $null + } + } + if ($KQLSourceClusterUri -and -not $KQLSourceDatabaseName) { + Write-Message -Message "Error: 'KQLSourceDatabaseName' is required when 'KQLSourceClusterUri' is provided." -Level Error + return $null + } + } + + # Validate ReadWrite type database + if ($KQLDatabaseType -eq "ReadWrite" -and -not $parentEventhouseId) { + Write-Message -Message "Error: 'parentEventhouseId' is required for ReadWrite type." -Level Error + return $null + } + + $body.creationPayload = @{ + databaseType = $KQLDatabaseType + parentEventhouseItemId = $parentEventhouseId + } + + if ($KQLDatabaseType -eq "Shortcut") { + if ($KQLInvitationToken) { + + $body.creationPayload.invitationToken = $KQLInvitationToken + } + if ($KQLSourceClusterUri -and -not $KQLInvitationToken) { + $body.creationPayload.sourceClusterUri = $KQLSourceClusterUri + } + if ($KQLSourceDatabaseName -and -not $KQLInvitationToken) { + $body.creationPayload.sourceDatabaseName = $KQLSourceDatabaseName + } + } + + + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "KQLDatabase '$KQLDatabaseName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "KQLDatabase '$KQLDatabaseName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create KQLDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 new file mode 100644 index 00000000..e019acd7 --- /dev/null +++ b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 @@ -0,0 +1,106 @@ +function New-FabricKQLDatabase { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Creates a new Fabric KQLDatabase + +.DESCRIPTION + Creates a new Fabric KQLDatabase. The KQLDatabase is created in the specified Workspace and Eventhouse. + It will be created with the specified name and description. + +.PARAMETER WorkspaceID + Id of the Fabric Workspace for which the KQLDatabase should be created. The value for WorkspaceID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventhouseID + Id of the Fabric Eventhouse for which the KQLDatabase should be created. The value for EventhouseID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLDatabaseName + The name of the KQLDatabase to create. The name must be unique within the eventhouse and is a + mandatory parameter. + +.PARAMETER KQLDatabaseDescription + The description of the KQLDatabase to create. + +.EXAMPLE + New-FabricKQLDatabase ` + -WorkspaceID '12345678-1234-1234-1234-123456789012' ` + -EventhouseID '12345678-1234-1234-1234-123456789012' ` + -KQLDatabaseName 'MyKQLDatabase' ` + -KQLDatabaseDescription 'This is my KQLDatabase' + + This example will create a new KQLDatabase with the name 'MyKQLDatabase' and the description 'This is my KQLDatabase'. + +.NOTES + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName + - 2024-12-08 - FGE: Added Verbose Output +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceID, + + [Parameter(Mandatory=$true)] + [string]$EventhouseID, + + [Parameter(Mandatory=$true)] + [Alias("Name", "DisplayName")] + [string]$KQLDatabaseName, + + [ValidateLength(0, 256)] + [Alias("Description")] + [string]$KQLDatabaseDescription + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create body for the request" + $body = @{ + 'displayName' = $KQLDatabaseName + 'description' = $KQLDatabaseDescription + 'creationPayload'= @{ + 'databaseType' = "ReadWrite"; + 'parentEventhouseItemId' = $EventhouseId} + } | ConvertTo-Json ` + -Depth 1 + + # Create KQLDatabase API URL + $KQLDatabaseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases" + } + +process { + + if($PSCmdlet.ShouldProcess($EventhouseName)) { + Write-Verbose "Calling KQLDatabase API" + Write-Verbose "-----------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $KQLDatabaseApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $KQLDatabaseApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end { + +} + +} \ No newline at end of file diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 new file mode 100644 index 00000000..2b5d69f2 --- /dev/null +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 @@ -0,0 +1,74 @@ +<# +.SYNOPSIS +Deletes an KQLDatabase from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricKQLDatabase` function sends a DELETE request to the Fabric API to remove a specified KQLDatabase from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the KQLDatabase to delete. + +.PARAMETER KQLDatabaseId +(Mandatory) The ID of the KQLDatabase to be deleted. + +.EXAMPLE +Remove-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseId "67890" + +Deletes the KQLDatabase with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricKQLDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "KQLDatabase '$KQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete KQLDatabase '$KQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 new file mode 100644 index 00000000..411cec73 --- /dev/null +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 @@ -0,0 +1,77 @@ +function Remove-FabricKQLDatabase { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Removes an existing Fabric Eventhouse + +.DESCRIPTION + Removes an existing Fabric Eventhouse. The Eventhouse is removed from the specified Workspace. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace from which the Eventhouse should be removed. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER EventhouseId + The Id of the Eventhouse to remove. The value for EventhouseId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Remove-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' + + This example will remove the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. + +.NOTES + TODO: Add functionality to remove Eventhouse by name. + + Revsion History: + + - 2024-12-08 - FGE: Added Verbose Output + +#> + + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Parameter(Mandatory=$true)] + [Alias("Id")] + [string]$KQLDatabaseId + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create Eventhouse API URL" + $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases/$KQLDatabaseId" + +} + +process { + + if($PSCmdlet.ShouldProcess($KQLDatabaseId)) { + Write-Verbose "Calling KQLDatabase API" + Write-Verbose "-----------------------" + Write-Verbose "Sending the following values to the Eventstream API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: DELETE" + Write-Verbose "URI: $eventhouseApiUrl" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method DELETE ` + -Uri $eventhouseApiUrl ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Database/Set-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Set-FabricKQLDatabase.ps1 new file mode 100644 index 00000000..ddeade86 --- /dev/null +++ b/source/Public/KQL Database/Set-FabricKQLDatabase.ps1 @@ -0,0 +1,114 @@ +function Set-FabricKQLDatabase { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Updates Properties of an existing Fabric KQLDatabase + +.DESCRIPTION + Updates Properties of an existing Fabric KQLDatabase. The KQLDatabase is updated + in the specified Workspace. The KQLDatabaseId is used to identify the KQLDatabase + that should be updated. The KQLDatabaseNewName and KQLDatabaseDescription are the + properties that can be updated. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the KQLDatabase should be updated. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLDatabaseId + The Id of the KQLDatabase to update. The value for KQLDatabaseId is a GUID. + An example of a GUID is '12345678-1234-123-1234-123456789012'. + +.PARAMETER NewKQLDatabaseName + The new name of the KQLDatabase. + +.PARAMETER KQLDatabaseDescription + The new description of the KQLDatabase. The description can be up to 256 characters long. + +.EXAMPLE + Set-FabricKQLDatabase ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLDatabaseId '12345678-1234-1234-1234-123456789012' ` + -NewKQLDatabaseNewName 'MyNewKQLDatabase' ` + -KQLDatabaseDescription 'This is my new KQLDatabase' + + This example will update the KQLDatabase with the Id '12345678-1234-1234-1234-123456789012'. + It will update the name to 'MyNewKQLDatabase' and the description to 'This is my new KQLDatabase'. + +.NOTES + + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName + - 2024-12-08 - FGE: Added Verbose Output + Renamed Parameter KQLDatabaseName to NewKQLDatabaseNewName + +.LINK + +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Parameter(Mandatory=$true)] + [Alias("Id")] + [string]$KQLDatabaseId, + + [Alias("NewName", "NewDisplayName")] + [string]$NewKQLDatabaseName, + + [Alias("Description")] + [ValidateLength(0, 256)] + [string]$KQLDatabaseDescription + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create body of request" + $body = @{} + + if ($PSBoundParameters.ContainsKey("NewKQLDatabaseName")) { + $body["displayName"] = $NewKQLDatabaseName + } + + if ($PSBoundParameters.ContainsKey("KQLDatabaseDescription")) { + $body["description"] = $KQLDatabaseDescription + } + + $body = $body | ConvertTo-Json -Depth 1 + + # Create KQLDatabase API URL + $KQLDatabaseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases/$KQLDatabaseId" + } + +process { + + if($PSCmdlet.ShouldProcess($KQLDatabaseId)) { + Write-Verbose "Calling KQLDatabase API with KQLDatabaseId $KQLDatabaseId" + Write-Verbose "------------------------------------------------------------------------------------" + Write-Verbose "Sending the following values to the KQLDatabase API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: PATCH" + Write-Verbose "URI: $KQLDatabaseApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method PATCH ` + -Uri $KQLDatabaseApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 new file mode 100644 index 00000000..4c15f3a2 --- /dev/null +++ b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 @@ -0,0 +1,108 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric KQLDatabase. + +.DESCRIPTION +The `Update-FabricKQLDatabase` function updates the name and/or description of a specified Fabric KQLDatabase by making a PATCH request to the API. + +.PARAMETER KQLDatabaseId +The unique identifier of the KQLDatabase to be updated. + +.PARAMETER KQLDatabaseName +The new name for the KQLDatabase. + +.PARAMETER KQLDatabaseDescription +(Optional) The new description for the KQLDatabase. + +.EXAMPLE +Update-FabricKQLDatabase -KQLDatabaseId "KQLDatabase123" -KQLDatabaseName "NewKQLDatabaseName" + +Updates the name of the KQLDatabase with the ID "KQLDatabase123" to "NewKQLDatabaseName". + +.EXAMPLE +Update-FabricKQLDatabase -KQLDatabaseId "KQLDatabase123" -KQLDatabaseName "NewName" -KQLDatabaseDescription "Updated description" + +Updates both the name and description of the KQLDatabase "KQLDatabase123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricKQLDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDatabaseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $KQLDatabaseName + } + + if ($KQLDatabaseDescription) { + $body.description = $KQLDatabaseDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "KQLDatabase '$KQLDatabaseName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 new file mode 100644 index 00000000..35df15fe --- /dev/null +++ b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 @@ -0,0 +1,202 @@ +<# +.SYNOPSIS +Updates the definition of a KQLDatabase in a Microsoft Fabric workspace. + +.DESCRIPTION +This function allows updating the content or metadata of a KQLDatabase in a Microsoft Fabric workspace. +The KQLDatabase content can be provided as file paths, and metadata updates can optionally be enabled. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the KQLDatabase resides. + +.PARAMETER KQLDatabaseId +(Mandatory) The unique identifier of the KQLDatabase to be updated. + +.PARAMETER KQLDatabasePathDefinition +(Mandatory) The file path to the KQLDatabase content definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER KQLDatabasePathPlatformDefinition +(Optional) The file path to the KQLDatabase's platform-specific definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER UpdateMetadata +(Optional)A boolean flag indicating whether to update the KQLDatabase's metadata. +Default: `$false`. + +.EXAMPLE +Update-FabricKQLDatabaseDefinition -WorkspaceId "12345" -KQLDatabaseId "67890" -KQLDatabasePathDefinition "C:\KQLDatabases\KQLDatabase.ipynb" + +Updates the content of the KQLDatabase with ID `67890` in the workspace `12345` using the specified KQLDatabase file. + +.EXAMPLE +Update-FabricKQLDatabaseDefinition -WorkspaceId "12345" -KQLDatabaseId "67890" -KQLDatabasePathDefinition "C:\KQLDatabases\KQLDatabase.ipynb" -UpdateMetadata $true + +Updates both the content and metadata of the KQLDatabase with ID `67890` in the workspace `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- The KQLDatabase content is encoded as Base64 before being sent to the Fabric API. +- This function handles asynchronous operations and retrieves operation results if required. + +Author: Tiago Balabuch + +#> + +function Update-FabricKQLDatabaseDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathPlatformDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathSchemaDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId + + if($KQLDatabasePathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + if ($KQLDatabasePathDefinition) { + $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "DatabaseProperties.json" + payload = $KQLDatabaseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error + return $null + } + } + + if ($KQLDatabasePathPlatformDefinition) { + $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLDatabaseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + if ($KQLDatabasePathSchemaDefinition) { + $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { + + # Add new part to the parts array + $body.definition.parts += @{ + path = "DatabaseSchema.kql" + payload = $KQLDatabaseEncodedSchemaContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in schema definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for KQLDatabase '$KQLDatabaseId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for KQLDatabase '$KQLDatabaseId' accepted. Operation in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + Write-Message -Message "Operation completed successfully." -Level Info + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 new file mode 100644 index 00000000..b1c06d8b --- /dev/null +++ b/source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 @@ -0,0 +1,154 @@ +<# +.SYNOPSIS +Retrieves an KQLQueryset or a list of KQLQuerysets from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricKQLQueryset` function sends a GET request to the Fabric API to retrieve KQLQueryset details for a given workspace. It can filter the results by `KQLQuerysetName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query KQLQuerysets. + +.PARAMETER KQLQuerysetName +(Optional) The name of the specific KQLQueryset to retrieve. + +.EXAMPLE +Get-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetName "Development" + +Retrieves the "Development" KQLQueryset from workspace "12345". + +.EXAMPLE +Get-FabricKQLQueryset -WorkspaceId "12345" + +Retrieves all KQLQuerysets in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricKQLQueryset { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLQuerysetName + ) + + try { + # Step 1: Handle ambiguous input + if ($KQLQuerysetId -and $KQLQuerysetName) { + Write-Message -Message "Both 'KQLQuerysetId' and 'KQLQuerysetName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $KQLQuerysets = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets" -f $FabricConfig.BaseUrl, $WorkspaceId + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $KQLQuerysets += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $KQLQueryset = if ($KQLQuerysetId) { + $KQLQuerysets | Where-Object { $_.Id -eq $KQLQuerysetId } + } + elseif ($KQLQuerysetName) { + $KQLQuerysets | Where-Object { $_.DisplayName -eq $KQLQuerysetName } + } + else { + # Return all KQLQuerysets if no filter is provided + Write-Message -Message "No filter provided. Returning all KQLQuerysets." -Level Debug + $KQLQuerysets + } + + # Step 9: Handle results + if ($KQLQueryset) { + Write-Message -Message "KQLQueryset found matching the specified criteria." -Level Debug + return $KQLQueryset + } + else { + Write-Message -Message "No KQLQueryset found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 new file mode 100644 index 00000000..ce7629a4 --- /dev/null +++ b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 @@ -0,0 +1,130 @@ +function Get-FabricKQLQueryset { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric KQLQuerysets + +.DESCRIPTION + Retrieves Fabric KQLQuerysets. Without the KQLQuerysetName or KQLQuerysetId parameter, + all KQLQuerysets are returned in the given Workspace. If you want to retrieve a specific + KQLQueryset, you can use the KQLQuerysetName or KQLQuerysetId parameter. These parameters + cannot be used together. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the KQLQuerysets should be retrieved. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. + +.PARAMETER KQLQuerysetName + The name of the KQLQueryset to retrieve. This parameter cannot be used together with KQLQuerysetId. + +.PARAMETER KQLQuerysetId + The Id of the KQLQueryset to retrieve. This parameter cannot be used together with KQLQuerysetName. + The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricKQLQueryset ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLQuerysetName 'MyKQLQueryset' + + This example will retrieve the KQLQueryset with the name 'MyKQLQueryset'. + +.EXAMPLE + Get-FabricKQLQueryset ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' + + This example will retrieve all KQLQuerysets in the workspace that is specified + by the WorkspaceId. + +.EXAMPLE + Get-FabricKQLQueryset ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLQuerysetId '12345678-1234-1234-1234-123456789012' + + This example will retrieve the KQLQueryset with the ID '12345678-1234-1234-1234-123456789012'. + +.NOTES + TODO: Add functionality to list all KQLQuerysets. To do so fetch all workspaces and + then all KQLQuerysets in each workspace. + + Revision History: + - 2024-11-09 - FGE: Added DisplaName as Alias for KQLQuerysetName + - 2024-12-22 - FGE: Added Verbose Output +#> + + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Name","DisplayName")] + [string]$KQLQuerysetName, + + [Alias("Id")] + [string]$KQLQuerysetId + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "You can either use Name or WorkspaceID" + if ($PSBoundParameters.ContainsKey("KQLQuerysetName") -and $PSBoundParameters.ContainsKey("KQLQuerysetId")) { + throw "Parameters KQLQuerysetName and KQLQuerysetId cannot be used together" + } + + # Create KQLQueryset API + $KQLQuerysetAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets" + + $KQLQuerysetAPIKQLQuerysetId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" + +} + +process { + + if ($PSBoundParameters.ContainsKey("KQLQuerysetId")) { + Write-Verbose "Calling KQLQueryset API with KQLQuerysetId $KQLQuerysetId" + Write-Verbose "------------------------------------------------------------------------------------" + Write-Verbose "Sending the following values to the KQLQueryset API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $KQLQuerysetAPIKQLQuerysetId" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $KQLQuerysetAPIKQLQuerysetId ` + -ContentType "application/json" + + $response + } + else { + Write-Verbose "Calling KQLQueryset API" + Write-Verbose "------------------------------------------------------------------------------------" + Write-Verbose "Sending the following values to the KQLQueryset API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $KQLQuerysetAPI" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $KQLQuerysetAPI ` + -ContentType "application/json" + + if ($PSBoundParameters.ContainsKey("KQLQuerysetName")) { + Write-Verbose "Filtering KQLQuerysets by name. Name: $KQLQuerysetName" + $response.value | ` + Where-Object { $_.displayName -eq $KQLQuerysetName } + } + else { + Write-Verbose "Returning all KQLQuerysets" + $response.value + } + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 new file mode 100644 index 00000000..79e88196 --- /dev/null +++ b/source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 @@ -0,0 +1,120 @@ + +<# +.SYNOPSIS +Retrieves the definition of a KQLQueryset from a specific workspace in Microsoft Fabric. + +.DESCRIPTION +This function fetches the KQLQueryset's content or metadata from a workspace. +Handles both synchronous and asynchronous operations, with detailed logging and error handling. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace from which the KQLQueryset definition is to be retrieved. + +.PARAMETER KQLQuerysetId +(Optional)The unique identifier of the KQLQueryset whose definition needs to be retrieved. + +.PARAMETER KQLQuerysetFormat +Specifies the format of the KQLQueryset definition. + +.EXAMPLE +Get-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" + +Retrieves the definition of the KQLQueryset with ID `67890` from the workspace with ID `12345` in the `ipynb` format. + +.EXAMPLE +Get-FabricKQLQuerysetDefinition -WorkspaceId "12345" + +Retrieves the definitions of all KQLQuerysets in the workspace with ID `12345` in the `ipynb` format. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. + +#> +function Get-FabricKQLQuerysetDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetFormat + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId + + if ($KQLQuerysetFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLQuerysetFormat + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "KQLQueryset '$KQLQuerysetId' definition retrieved successfully!" -Level Debug + return $response.definition.parts + } + 202 { + + Write-Message -Message "Getting KQLQueryset '$KQLQuerysetId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 b/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 new file mode 100644 index 00000000..34720f2c --- /dev/null +++ b/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 @@ -0,0 +1,230 @@ +function Invoke-FabricKQLCommand { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Executes a KQL command in a Kusto Database. + +.DESCRIPTION + Executes a KQL command in a Kusto Database. The KQL command is executed in the Kusto Database + that is specified by the KQLDatabaseName or KQLDatabaseId parameter. The KQL command is executed + in the context of the Fabric Real-Time Intelligence session that is established by the + Connect-RTISession cmdlet. The cmdlet distinguishes between management commands and query commands. + Management commands are executed in the management API, while query commands are executed in the query API. + The distinction is made by checking if the KQL command starts with a dot. If it does, it is a management command + else it is a query command. If the KQL command is a management command, it is crucial to have the + .execute database script <| in the beginning, otherwise the Kusto API will not execute the script. This cmdlet + will automatically add the .execute database script <| in the beginning of the KQL command if it is a management command + and if it is not already present. + If the KQL Command is a query command, the result is returned as an array of PowerShell objects by default. If the + parameter -ReturnRawResult is used, the raw result of the KQL query is returned which is a JSON object. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the KQL command should be executed. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLDatabaseName + The name of the KQLDatabase in which the KQL command should be executed. This parameter cannot be used together with KQLDatabaseId. + +.PARAMETER KQLDatabaseId + The Id of the KQLDatabase in which the KQL command should be executed. This parameter cannot be used together with KQLDatabaseName. + The value for KQLDatabaseId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER KQLCommand + The KQL command that should be executed in the Kusto Database. + The KQL command is a string. An example of a string is '.create table MyTable (MyColumn: string)'. + +.PARAMETER ReturnRawResult + When this switch is used, the raw result of the KQL command is returned. By default, the result is returned as + a PowerShell object. + +.EXAMPLE + Invoke-FabricKQLCommand ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLDatabaseName 'MyKQLDatabase' ` + -KQLCommand '.create table MyTable (MyColumn: string)' + + This example will create a table named 'MyTable' with a column named 'MyColumn' in + the KQLDatabase 'MyKQLDatabase'. + +.EXAMPLE + Invoke-FabricKQLCommand ` + -WorkspaceId '2c4ccbb5-9b13-4495-9ab3-ba41152733d9' ` + -KQLDatabaseName 'MyEventhouse2' ` + -KQLCommand 'productcategory + | take 100' + + This example will Execute the Query 'productcategory | take 100' in the KQLDatabase 'MyEventhouse2' + and it will return the result as an array of PowerShell objects. + +.EXAMPLE + Invoke-FabricKQLCommand ` + -WorkspaceId '2c4ccbb5-9b13-4495-9ab3-ba41152733d9' ` + -KQLDatabaseName 'MyEventhouse2' ` + -ReturnRawResult ` + -KQLCommand 'productcategory + | take 100' + + This example will Execute the Query 'productcategory | take 100' in the KQLDatabase 'MyEventhouse2' + and it will return the result as the raw result of the KQL command, which is a JSON object. + +.NOTES + + Revsion History: + + - 2024-12-22 - FGE: Added Verbose Output + - 2024-12-27 - FGE: Major Update to support KQL Queries and Management Commands + +#> + +[CmdletBinding()] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [string]$KQLDatabaseName, + + [string]$KQLDatabaseId, + + [Parameter(Mandatory=$true)] + [string]$KQLCommand, + + [switch]$ReturnRawResult + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Check if KQLDatabaseName and KQLDatabaseId are used together" + if ($PSBoundParameters.ContainsKey("KQLDatabaseName") -and $PSBoundParameters.ContainsKey("KQLDatabaseId")) { + throw "Parameters KQLDatabaseName and KQLDatabaseId cannot be used together" + } + + Write-Verbose "Get Kusto Database" + if ($PSBoundParameters.ContainsKey("KQLDatabaseName")) { + Write-Verbose "Getting Kusto Database by Name: $KQLDatabaseName" + $kustDB = Get-FabricKQLDatabase ` + -WorkspaceId $WorkspaceId ` + -KQLDatabaseName $KQLDatabaseName + } + + if ($PSBoundParameters.ContainsKey("KQLDatabaseId")) { + Write-Verbose "Getting Kusto Database by Id: $KQLDatabaseId" + $kustDB = Get-FabricKQLDatabase ` + -WorkspaceId $WorkspaceId ` + -KQLDatabaseId $KQLDatabaseId + } + + Write-Verbose "Check if Kusto Database was found" + if ($null -eq $kustDB) { + throw "Kusto Database not found" + } + + Write-Verbose "Generate the Management API URL" + $mgmtAPI = "$($kustDB.queryServiceUri)/v1/rest/mgmt" + + Write-Verbose "Generate the query API URL" + $queryAPI = "$($kustDB.queryServiceUri)/v1/rest/query" + + + $KQLCommand = $KQLCommand | Out-String + + Write-Verbose "Check if the KQL command starts with a dot so it is a management command. Otherwise it is a query command" + if (-not ($KQLCommand -match "^\.")) { + $isManamgentCommand = $false + Write-Verbose "The command is a query command." + } + else { + $isManamgentCommand = $true + Write-Verbose "The command is a management command. It is crucial to have the .execute database script <| in the beginning, otherwise the Kusto API will not execute the script." + if (-not ($KQLCommand -match "\.execute database script <\|")) { + $KQLCommand = ".execute database script <| $KQLCommand" + } + } +} + +process { + + Write-Verbose "The KQL-Command is: $KQLCommand" + + Write-Verbose "Create body of the request" + $body = @{ + 'csl' = $KQLCommand; + 'db'= $kustDB.displayName + } | ConvertTo-Json -Depth 1 + + + if ($isManamgentCommand) { + Write-Verbose "Calling Management API" + Write-Verbose "----------------------" + Write-Verbose "Sending the following values to the Query API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $mgmtAPI" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + + $result = Invoke-RestMethod ` + -Headers $headerParams ` + -Method POST ` + -Uri $mgmtAPI ` + -Body ($body) ` + -ContentType "application/json; charset=utf-8" + + Write-Verbose "Result of the Management API: $($result | ` + ConvertTo-Json ` + -Depth 10)" + $result + } + else { + Write-Verbose "Calling Query API" + Write-Verbose "-----------------" + Write-Verbose "Sending the following values to the Query API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $queryAPI" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + + $result = Invoke-RestMethod ` + -Headers $headerParams ` + -Method POST ` + -Uri $queryAPI ` + -Body ($body) ` + -ContentType "application/json; charset=utf-8" + Write-Verbose "Result of the Query API: $($result | ` + ConvertTo-Json ` + -Depth 10)" + + + + if ($ReturnRawResult) { + $result + } + else { + $myRecords = @() + + for ($j = 0; $j -lt $Result.tables[0].rows.Count; $j++) { + $myTableRow = [PSCustomObject]@{} + + for ($i = 0; $i -lt $Result.tables[0].rows[0].Count; $i++) { + $myTableRow | ` + Add-Member ` + -MemberType NoteProperty ` + -Name $Result.Tables[0].Columns[$i].ColumnName ` + -Value $Result.Tables[0].rows[$j][$i] + } + $myRecords += $myTableRow + } + + $myRecords + } + + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 new file mode 100644 index 00000000..fea1665c --- /dev/null +++ b/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 @@ -0,0 +1,188 @@ +<# +.SYNOPSIS +Creates a new KQLQueryset in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new KQLQueryset +in the specified workspace. It supports optional parameters for KQLQueryset description +and path definitions for the KQLQueryset content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLQueryset will be created. + +.PARAMETER KQLQuerysetName +The name of the KQLQueryset to be created. + +.PARAMETER KQLQuerysetDescription +An optional description for the KQLQueryset. + +.PARAMETER KQLQuerysetPathDefinition +An optional path to the KQLQueryset definition file (e.g., .ipynb file) to upload. + +.PARAMETER KQLQuerysetPathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. + +.EXAMPLE + Add-FabricKQLQueryset -WorkspaceId "workspace-12345" -KQLQuerysetName "New KQLQueryset" -KQLQuerysetPathDefinition "C:\KQLQuerysets\example.ipynb" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricKQLQueryset { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLQuerysetName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $KQLQuerysetName + } + + if ($KQLQuerysetDescription) { + $body.description = $KQLQuerysetDescription + } + + if ($KQLQuerysetPathDefinition) { + $KQLQuerysetEncodedContent = Convert-ToBase64 -filePath $KQLQuerysetPathDefinition + + if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = $null + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "RealTimeQueryset.json" + payload = $KQLQuerysetEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLQueryset definition." -Level Error + return $null + } + } + + if ($KQLQuerysetPathPlatformDefinition) { + $KQLQuerysetEncodedPlatformContent = Convert-ToBase64 -filePath $KQLQuerysetPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = $null + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLQuerysetEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "KQLQueryset '$KQLQuerysetName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "KQLQueryset '$KQLQuerysetName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create KQLQueryset. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 b/source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 new file mode 100644 index 00000000..d9a48c5d --- /dev/null +++ b/source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS +Deletes an KQLQueryset from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricKQLQueryset` function sends a DELETE request to the Fabric API to remove a specified KQLQueryset from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the KQLQueryset to delete. + +.PARAMETER KQLQuerysetId +(Mandatory) The ID of the KQLQueryset to be deleted. + +.EXAMPLE +Remove-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetId "67890" + +Deletes the KQLQueryset with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricKQLQueryset { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "KQLQueryset '$KQLQuerysetId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete KQLQueryset '$KQLQuerysetId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 new file mode 100644 index 00000000..68d2b3df --- /dev/null +++ b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 @@ -0,0 +1,77 @@ +function Remove-FabricKQLQueryset { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Removes an existing Fabric KQLQueryset. + +.DESCRIPTION + Removes an existing Fabric KQLQueryset. The Eventhouse is identified by the WorkspaceId and KQLQuerysetId. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the KQLQueryset should be removed. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. + +.PARAMETER KQLQuerysetId + The Id of the KQLQueryset to remove. The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + This parameter is mandatory. + +.EXAMPLE + Remove-FabricKQLQueryset ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLQuerysetId '12345678-1234-1234-1234-123456789012' + +.NOTES + TODO: Add functionality to remove KQLQueryset by name. + + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-12-22 - FGE: Added Verbose Output + +#> + + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Parameter(Mandatory=$true)] + [Alias("Id")] + [string]$KQLQuerysetId + + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + # Create KQLQueryset API URL + $querysetApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" +} + +process { + + # Call KQL Queryset API + if($PSCmdlet.ShouldProcess($KQLQuerysetId)) { + Write-Verbose "Calling KQLQueryset API" + Write-Verbose "-----------------------" + Write-Verbose "Sending the following values to the KQLQueryset API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: DELETE" + Write-Verbose "URI: $querysetApiUrl" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method DELETE ` + -Uri $querysetApiUrl ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 new file mode 100644 index 00000000..f571d379 --- /dev/null +++ b/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 @@ -0,0 +1,111 @@ +function Set-FabricKQLQueryset { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Updates Properties of an existing Fabric KQLQueryset + +.DESCRIPTION + Updates Properties of an existing Fabric KQLQueryset. The KQLQueryset is identified by + the WorkspaceId and KQLQuerysetId. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the KQLQueryset should be updated. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. + +.PARAMETER KQLQuerysetId + The Id of the KQLQueryset to update. The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + This parameter is mandatory. + +.PARAMETER KQLQuerysetName + The new name of the KQLQueryset. This parameter is optional. + +.PARAMETER KQLQuerysetDescription + The new description of the KQLQueryset. This parameter is optional. + +.EXAMPLE + Set-FabricKQLQueryset ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -KQLQuerysetId '12345678-1234-1234-1234-123456789012' ` + -KQLQuerysetNewName 'MyKQLQueryset' ` + -KQLQuerysetDescription 'This is my KQLQueryset' + + This example will update the KQLQueryset. The KQLQueryset will have the name 'MyKQLQueryset' + and the description 'This is my KQLQueryset'. + +.NOTES + + Revsion History: + + - 2024-11-07 - FGE: Implemented SupportShouldProcess + - 2024-11-09 - FGE: Added NewDisplaName as Alias for KQLQuerysetNewName + - 2024-12-22 - FGE: Added Verbose Output + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/KQLQueryset/items/create-KQLQueryset?tabs=HTTP +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Parameter(Mandatory=$true)] + [Alias("Id")] + [string]$KQLQuerysetId, + + [Alias("NewName", "NewDisplayName")] + [string]$KQLQuerysetNewName, + + [ValidateLength(0, 256)] + [Alias("Description")] + [string]$KQLQuerysetDescription + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create body of request" + $body = @{} + + if ($PSBoundParameters.ContainsKey("KQLQuerysetName")) { + $body["displayName"] = $KQLQuerysetNName + } + + if ($PSBoundParameters.ContainsKey("KQLQuerysetDescription")) { + $body["description"] = $KQLQuerysetDescription + } + + $body = $body | ConvertTo-Json -Depth 1 + + # Create KQLQueryset API URL + $KQLQuerysetApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" + } + +process { + + # Call KQLQueryset API + if($PSCmdlet.ShouldProcess($KQLQuerysetId)) { + Write-Verbose "Calling KQLQueryset API with KQLQuerysetId $KQLQuerysetId" + Write-Verbose "---------------------------------------------------------" + Write-Verbose "Sending the following values to the KQLQueryset API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: PATCH" + Write-Verbose "URI: $KQLQuerysetApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method PATCH ` + -Uri $KQLQuerysetApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 new file mode 100644 index 00000000..4377448c --- /dev/null +++ b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 @@ -0,0 +1,107 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric KQLQueryset. + +.DESCRIPTION +The `Update-FabricKQLQueryset` function updates the name and/or description of a specified Fabric KQLQueryset by making a PATCH request to the API. + +.PARAMETER KQLQuerysetId +The unique identifier of the KQLQueryset to be updated. + +.PARAMETER KQLQuerysetName +The new name for the KQLQueryset. + +.PARAMETER KQLQuerysetDescription +(Optional) The new description for the KQLQueryset. + +.EXAMPLE +Update-FabricKQLQueryset -KQLQuerysetId "KQLQueryset123" -KQLQuerysetName "NewKQLQuerysetName" + +Updates the name of the KQLQueryset with the ID "KQLQueryset123" to "NewKQLQuerysetName". + +.EXAMPLE +Update-FabricKQLQueryset -KQLQuerysetId "KQLQueryset123" -KQLQuerysetName "NewName" -KQLQuerysetDescription "Updated description" + +Updates both the name and description of the KQLQueryset "KQLQueryset123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricKQLQueryset { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLQuerysetName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $KQLQuerysetName + } + + if ($KQLQuerysetDescription) { + $body.description = $KQLQuerysetDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "KQLQueryset '$KQLQuerysetName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 b/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 new file mode 100644 index 00000000..6189f659 --- /dev/null +++ b/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 @@ -0,0 +1,166 @@ +<# +.SYNOPSIS +Updates the definition of a KQLQueryset in a Microsoft Fabric workspace. + +.DESCRIPTION +This function allows updating the content or metadata of a KQLQueryset in a Microsoft Fabric workspace. +The KQLQueryset content can be provided as file paths, and metadata updates can optionally be enabled. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the KQLQueryset resides. + +.PARAMETER KQLQuerysetId +(Mandatory) The unique identifier of the KQLQueryset to be updated. + +.PARAMETER KQLQuerysetPathDefinition +(Mandatory) The file path to the KQLQueryset content definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER KQLQuerysetPathPlatformDefinition +(Optional) The file path to the KQLQueryset's platform-specific definition file. The content will be encoded as Base64 and sent in the request. + + +.EXAMPLE +Update-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" -KQLQuerysetPathDefinition "C:\KQLQuerysets\KQLQueryset.ipynb" + +Updates the content of the KQLQueryset with ID `67890` in the workspace `12345` using the specified KQLQueryset file. + +.EXAMPLE +Update-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" -KQLQuerysetPathDefinition "C:\KQLQuerysets\KQLQueryset.ipynb" + +Updates both the content and metadata of the KQLQueryset with ID `67890` in the workspace `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- The KQLQueryset content is encoded as Base64 before being sent to the Fabric API. +- This function handles asynchronous operations and retrieves operation results if required. + +Author: Tiago Balabuch + +#> + +function Update-FabricKQLQuerysetDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId + + if($KQLQuerysetPathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + format = $null + parts = @() + } + } + + if ($KQLQuerysetPathDefinition) { + $KQLQuerysetEncodedContent = Convert-ToBase64 -filePath $KQLQuerysetPathDefinition + + if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "RealTimeQueryset.json" + payload = $KQLQuerysetEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLQueryset definition." -Level Error + return $null + } + } + + if ($KQLQuerysetPathPlatformDefinition) { + $KQLQuerysetEncodedPlatformContent = Convert-ToBase64 -filePath $KQLQuerysetPathPlatformDefinition + if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLQuerysetEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for KQLQueryset '$KQLQuerysetId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for KQLQueryset '$KQLQuerysetId' accepted. Operation in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + $operationResult = Get-FabricLongRunningOperation -operationId $operationId + + # Handle operation result + if ($operationResult.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + + $result = Get-FabricLongRunningOperationResult -operationId $operationId + return $result.definition.parts + } + else { + Write-Message -Message "Operation Failed" -Level Debug + return $operationResult.definition.parts + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Lakehouse/Get-FabricLakehouse.ps1 b/source/Public/Lakehouse/Get-FabricLakehouse.ps1 new file mode 100644 index 00000000..ecdeb17f --- /dev/null +++ b/source/Public/Lakehouse/Get-FabricLakehouse.ps1 @@ -0,0 +1,154 @@ +<# +.SYNOPSIS +Retrieves an Lakehouse or a list of Lakehouses from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricLakehouse` function sends a GET request to the Fabric API to retrieve Lakehouse details for a given workspace. It can filter the results by `LakehouseName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query Lakehouses. + +.PARAMETER LakehouseName +(Optional) The name of the specific Lakehouse to retrieve. + +.EXAMPLE +Get-FabricLakehouse -WorkspaceId "12345" -LakehouseName "Development" + +Retrieves the "Development" Lakehouse from workspace "12345". + +.EXAMPLE +Get-FabricLakehouse -WorkspaceId "12345" + +Retrieves all Lakehouses in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricLakehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$LakehouseName + ) + + try { + # Step 1: Handle ambiguous input + if ($LakehouseId -and $LakehouseName) { + Write-Message -Message "Both 'LakehouseId' and 'LakehouseName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $lakehouses = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/lakehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $lakehouses += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $lakehouse = if ($LakehouseId) { + $lakehouses | Where-Object { $_.Id -eq $LakehouseId } + } + elseif ($LakehouseName) { + $lakehouses | Where-Object { $_.DisplayName -eq $LakehouseName } + } + else { + # Return all lakehouses if no filter is provided + Write-Message -Message "No filter provided. Returning all Lakehouses." -Level Debug + $lakehouses + } + + # Step 9: Handle results + if ($Lakehouse) { + Write-Message -Message "Lakehouse found matching the specified criteria." -Level Debug + return $Lakehouse + } + else { + Write-Message -Message "No Lakehouse found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 new file mode 100644 index 00000000..e35ce0dd --- /dev/null +++ b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 @@ -0,0 +1,104 @@ + + +function Get-FabricLakehouseTable { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + + + # Step 2: Initialize variables + $continuationToken = $null + $tables = @() + $maxResults = 100 + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + $baseApiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/tables?maxResults={3}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId, $maxResults + + # Step 3: Loop to retrieve data with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + do { + # Step 4: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}&continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 5: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "API response code: $statusCode" -Level Debug + # Step 6: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 7: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $tables += $response.data + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + # Step 9: Handle results + if ($tables) { + Write-Message -Message "Tables found in the Lakehouse '$LakehouseId'." -Level Debug + return $tables + } + else { + Write-Message -Message "No tables found matching in the Lakehouse '$LakehouseId'." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 new file mode 100644 index 00000000..b1ca0822 --- /dev/null +++ b/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 @@ -0,0 +1,139 @@ +function Load-FabricLakehouseTable { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$TableName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('File', 'Folder')] + [string]$PathType, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$RelativePath, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('CSV', 'Parquet')] + [string]$FileFormat, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CsvDelimiter = ",", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$CsvHeader = $false, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('append', 'overwrite')] + [string]$Mode = "append", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$Recursive = $false + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/tables/{3}/load" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId, $TableName + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + relativePath = $RelativePath + pathType = $PathType + mode = $Mode + recursive = $Recursive + formatOptions = @{ + format = $FileFormat + } + } + + if ($FileFormat -eq "CSV") { + $body.formatOptions.delimiter = $CsvDelimiter + $body.formatOptions.hasHeader = $CsvHeader + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 202) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 5: Handle and log the response + switch ($statusCode) { + 202 { + Write-Message -Message "Load table '$TableName' request accepted. Load table operation in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Load table '$TableName' operation complete successfully!" -Level Info + return $operationStatus + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + throw "API request failed with status code $statusCode." + } + } + + # Step 6: Handle results + + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Lakehouse/New-FabricLakehouse copy.ps1 b/source/Public/Lakehouse/New-FabricLakehouse copy.ps1 new file mode 100644 index 00000000..f24c8feb --- /dev/null +++ b/source/Public/Lakehouse/New-FabricLakehouse copy.ps1 @@ -0,0 +1,136 @@ +<# +.SYNOPSIS +Creates a new Lakehouse in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new Lakehouse +in the specified workspace. It supports optional parameters for Lakehouse description +and path definitions for the Lakehouse content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Lakehouse will be created. + +.PARAMETER LakehouseName +The name of the Lakehouse to be created. + +.PARAMETER LakehouseDescription +An optional description for the Lakehouse. + +.PARAMETER LakehouseEnableSchemas +An optional path to enable schemas in the Lakehouse + +.EXAMPLE + Add-FabricLakehouse -WorkspaceId "workspace-12345" -LakehouseName "New Lakehouse" -LakehouseEnableSchemas $true + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricLakehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$LakehouseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$LakehouseEnableSchemas = $false + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $LakehouseName + } + + if ($LakehouseDescription) { + $body.description = $LakehouseDescription + } + + if ($true -eq $LakehouseEnableSchemas) { + $body.creationPayload = @{ + enableSchemas = $LakehouseEnableSchemas + } + } + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Lakehouse '$LakehouseName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Lakehouse '$LakehouseName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Lakehouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Lakehouse/New-FabricLakehouse.ps1 b/source/Public/Lakehouse/New-FabricLakehouse.ps1 new file mode 100644 index 00000000..37e622fa --- /dev/null +++ b/source/Public/Lakehouse/New-FabricLakehouse.ps1 @@ -0,0 +1,113 @@ +function New-FabricLakehouse { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Creates a new Fabric Lakehouse + +.DESCRIPTION + Creates a new Fabric Lakehouse + +.PARAMETER WorkspaceID + Id of the Fabric Workspace for which the Lakehouse should be created. The value for WorkspaceID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER LakehouseName + The name of the Lakehouse to create. + +.PARAMETER LakehouseDescription + The description of the Lakehouse to create. + +.EXAMPLE + New-FabricLakehouse ` + -WorkspaceID '12345678-1234-1234-1234-123456789012' ` + -LakehouseName 'MyLakehouse' ` + -LakehouseSchemaEnabled $true ` + -LakehouseDescription 'This is my Lakehouse' + + This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my Lakehouse'. + + .EXAMPLE + New-FabricLakehouse ` + -WorkspaceID '12345678-1234-1234-1234-123456789012' ` + -LakehouseName 'MyLakehouse' ` + -LakehouseSchemaEnabled $true ` + -LakehouseDescription 'This is my Lakehouse' + -Verbose + + This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my Lakehouse'. + It will also give you verbose output which is useful for debugging. + +.NOTES + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/lakehouse/items/create-lakehouse?tabs=HTTP +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$WorkspaceID, + + [Parameter(Mandatory=$true)] + [Alias("Name", "DisplayName")] + [string]$LakehouseName, + + [Parameter(Mandatory=$true)] + [Alias("LakehouseSchemaEnabled")] + [bool]$SchemaEnabled, + + [ValidateLength(0, 256)] + [Alias("Description")] + [string]$LakehouseDescription + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + #create payload + $body = [ordered]@{ + 'displayName' = $LakehouseName + 'description' = $LakehouseDescription + } + + #add enableSchema element if $LakehouseSchemaEnabled is true + if ($LakehouseSchemaEnabled) { + $body['creationPayload'] = @{ + 'enableSchemas' = $LakehouseSchemaEnabled + } + } + + #format payload + $body = $body | ConvertTo-Json -Depth 1 + + # Create Eventhouse API URL + $lakehouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/lakehouses" +} + +process { + + Write-Verbose "Calling Lakehouse API" + Write-Verbose "----------------------" + Write-Verbose "Sending the following values to the Lakehouse API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $lakehouseApiUrl" + Write-Verbose "Body of request: $bodyJson" + Write-Verbose "ContentType: application/json" + if($PSCmdlet.ShouldProcess($LakehouseName)) { + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $lakehouseApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} diff --git a/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 b/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 new file mode 100644 index 00000000..254bdde6 --- /dev/null +++ b/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS +Deletes an Lakehouse from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricLakehouse` function sends a DELETE request to the Fabric API to remove a specified Lakehouse from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the Lakehouse to delete. + +.PARAMETER LakehouseId +(Mandatory) The ID of the Lakehouse to be deleted. + +.EXAMPLE +Remove-FabricLakehouse -WorkspaceId "12345" -LakehouseId "67890" + +Deletes the Lakehouse with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricLakehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Lakehouse '$LakehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Lakehouse '$LakehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 new file mode 100644 index 00000000..c680beba --- /dev/null +++ b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 @@ -0,0 +1,160 @@ +function Start-FabricLakehouseTableMaintenance { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('TableMaintenance')] + [string]$JobType = "TableMaintenance", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SchemaName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$TableName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$IsVOrder, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [array]$ColumnsZOrderBy, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern("^\d+:[0-1][0-9]|2[0-3]:[0-5][0-9]:[0-5][0-9]$")] + [string]$retentionPeriod, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$waitForCompletion = $false + + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + + $lakehouse = Get-FabricLakehouse -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId + if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and -not $SchemaName) { + Write-Error "The Lakehouse '$lakehouse.displayName' has schema enabled, but no schema name was provided. Please specify the 'SchemaName' parameter to proceed." + return + } + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/jobs/instances?jobType={3}" -f $FabricConfig.BaseUrl, $WorkspaceId , $LakehouseId, $JobType + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + executionData = @{ + tableName = $TableName + optimizeSettings = @{} + } + } + if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and $SchemaName) { + $body.executionData.schemaName = $SchemaName + } + + if ($IsVOrder) { + $body.executionData.optimizeSettings.vOrder = $IsVOrder + } + + if ($ColumnsZOrderBy) { + # Ensure $ColumnsZOrderBy is an array + if (-not ($ColumnsZOrderBy -is [array])) { + $ColumnsZOrderBy = $ColumnsZOrderBy -split "," + } + # Add it to the optimizeSettings in the request body + $body.executionData.optimizeSettings.zOrderBy = $ColumnsZOrderBy + } + + + + if ($retentionPeriod) { + + if (-not $body.executionData.PSObject.Properties['vacuumSettings']) { + $body.executionData.vacuumSettings = @{ + retentionPeriod = @() + } + } + $body.executionData.vacuumSettings.retentionPeriod = $retentionPeriod + + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Table maintenance job successfully initiated for Lakehouse '$lakehouse.displayName'." -Level Info + return $response + } + 202 { + Write-Message -Message "Table maintenance job accepted and is now running in the background. Job execution is in progress." -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + + if ($waitForCompletion -eq $true) { + Write-Message -Message "Getting Long Running Operation status" -Level Debug + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + return $operationStatus + } + else { + Write-Message -Message "The operation is running asynchronously." -Level Info + Write-Message -Message "Use the returned details to check the operation status." -Level Info + Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info + $operationDetails = [PSCustomObject]@{ + OperationId = $operationId + Location = $location + RetryAfter = $retryAfter + } + return $operationDetails + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to start table maintenance job. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Lakehouse/Update-FabricLakehouse.ps1 b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 new file mode 100644 index 00000000..c4b9879b --- /dev/null +++ b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 @@ -0,0 +1,108 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric Lakehouse. + +.DESCRIPTION +The `Update-FabricLakehouse` function updates the name and/or description of a specified Fabric Lakehouse by making a PATCH request to the API. + +.PARAMETER LakehouseId +The unique identifier of the Lakehouse to be updated. + +.PARAMETER LakehouseName +The new name for the Lakehouse. + +.PARAMETER LakehouseDescription +(Optional) The new description for the Lakehouse. + +.EXAMPLE +Update-FabricLakehouse -LakehouseId "Lakehouse123" -LakehouseName "NewLakehouseName" + +Updates the name of the Lakehouse with the ID "Lakehouse123" to "NewLakehouseName". + +.EXAMPLE +Update-FabricLakehouse -LakehouseId "Lakehouse123" -LakehouseName "NewName" -LakehouseDescription "Updated description" + +Updates both the name and description of the Lakehouse "Lakehouse123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricLakehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$LakehouseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $LakehouseName + } + + if ($LakehouseDescription) { + $body.description = $LakehouseDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Lakehouse '$LakehouseName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/ML Experiment/Get-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Get-FabricMLExperiment.ps1 new file mode 100644 index 00000000..52e57f8e --- /dev/null +++ b/source/Public/ML Experiment/Get-FabricMLExperiment.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Retrieves ML Experiment details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves ML Experiment details from a specified workspace using either the provided MLExperimentId or MLExperimentName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the ML Experiment exists. This parameter is mandatory. + +.PARAMETER MLExperimentId + The unique identifier of the ML Experiment to retrieve. This parameter is optional. + +.PARAMETER MLExperimentName + The name of the ML Experiment to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentId "experiment-67890" + This example retrieves the ML Experiment details for the experiment with ID "experiment-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentName "My ML Experiment" + This example retrieves the ML Experiment details for the experiment named "My ML Experiment" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricMLExperiment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MLExperimentId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$MLExperimentName + ) + + try { + # Step 1: Handle ambiguous input + if ($MLExperimentId -and $MLExperimentName) { + Write-Message -Message "Both 'MLExperimentId' and 'MLExperimentName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $MLExperiments = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/mlExperiments" -f $FabricConfig.BaseUrl, $WorkspaceId + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $MLExperiments += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $MLExperiment = if ($MLExperimentId) { + $MLExperiments | Where-Object { $_.Id -eq $MLExperimentId } + } + elseif ($MLExperimentName) { + $MLExperiments | Where-Object { $_.DisplayName -eq $MLExperimentName } + } + else { + # Return all MLExperiments if no filter is provided + Write-Message -Message "No filter provided. Returning all MLExperiments." -Level Debug + $MLExperiments + } + + # Step 9: Handle results + if ($MLExperiment) { + Write-Message -Message "ML Experiment found matching the specified criteria." -Level Debug + return $MLExperiment + } + else { + Write-Message -Message "No ML Experiment found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve ML Experiment. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/ML Experiment/New-FabricMLExperiment.ps1 b/source/Public/ML Experiment/New-FabricMLExperiment.ps1 new file mode 100644 index 00000000..0a4d28be --- /dev/null +++ b/source/Public/ML Experiment/New-FabricMLExperiment.ps1 @@ -0,0 +1,130 @@ +<# +.SYNOPSIS + Creates a new ML Experiment in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new ML Experiment + in the specified workspace. It supports optional parameters for ML Experiment description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the ML Experiment will be created. This parameter is mandatory. + +.PARAMETER MLExperimentName + The name of the ML Experiment to be created. This parameter is mandatory. + +.PARAMETER MLExperimentDescription + An optional description for the ML Experiment. + +.EXAMPLE + New-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentName "New ML Experiment" -MLExperimentDescription "Description of the new ML Experiment" + This example creates a new ML Experiment named "New ML Experiment" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function New-FabricMLExperiment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$MLExperimentName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MLExperimentDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mlExperiments" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $MLExperimentName + } + + if ($MLExperimentDescription) { + $body.description = $MLExperimentDescription + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "ML Experiment '$MLExperimentName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "ML Experiment '$MLExperimentName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create ML Experiment. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 new file mode 100644 index 00000000..7c8cbb20 --- /dev/null +++ b/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Removes an ML Experiment from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove an ML Experiment + from the specified workspace using the provided WorkspaceId and MLExperimentId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the MLExperiment will be removed. + +.PARAMETER MLExperimentId + The unique identifier of the MLExperiment to be removed. + +.EXAMPLE + Remove-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentId "experiment-67890" + This example removes the MLExperiment with ID "experiment-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Remove-FabricMLExperiment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MLExperimentId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mlExperiments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLExperimentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "ML Experiment '$MLExperimentId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete ML Experiment '$MLExperimentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 new file mode 100644 index 00000000..f9c0a9e5 --- /dev/null +++ b/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + Updates an existing ML Experiment in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Experiment + in the specified workspace. It supports optional parameters for ML Experiment description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the ML Experiment exists. This parameter is optional. + +.PARAMETER MLExperimentId + The unique identifier of the ML Experiment to be updated. This parameter is mandatory. + +.PARAMETER MLExperimentName + The new name of the ML Experiment. This parameter is mandatory. + +.PARAMETER MLExperimentDescription + An optional new description for the ML Experiment. + +.EXAMPLE + Update-FabricMLExperiment -WorkspaceId "workspace-12345" -MLExperimentId "experiment-67890" -MLExperimentName "Updated ML Experiment" -MLExperimentDescription "Updated description" + This example updates the ML Experiment with ID "experiment-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricMLExperiment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MLExperimentId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$MLExperimentName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MLExperimentDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mlExperiments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLExperimentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $MLExperimentName + } + + if ($MLExperimentDescription) { + $body.description = $MLExperimentDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "ML Experiment '$MLExperimentName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update ML Experiment. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/ML Model/Get-FabricMLModel.ps1 b/source/Public/ML Model/Get-FabricMLModel.ps1 new file mode 100644 index 00000000..7d15c5e8 --- /dev/null +++ b/source/Public/ML Model/Get-FabricMLModel.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Retrieves ML Model details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves ML Model details from a specified workspace using either the provided MLModelId or MLModelName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the ML Model exists. This parameter is mandatory. + +.PARAMETER MLModelId + The unique identifier of the ML Model to retrieve. This parameter is optional. + +.PARAMETER MLModelName + The name of the ML Model to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricMLModel -WorkspaceId "workspace-12345" -MLModelId "model-67890" + This example retrieves the ML Model details for the model with ID "model-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricMLModel -WorkspaceId "workspace-12345" -MLModelName "My ML Model" + This example retrieves the ML Model details for the model named "My ML Model" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricMLModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MLModelId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$MLModelName + ) + + try { + # Step 1: Handle ambiguous input + if ($MLModelId -and $MLModelName) { + Write-Message -Message "Both 'MLModelId' and 'MLModelName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $MLModels = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/mlModels" -f $FabricConfig.BaseUrl, $WorkspaceId + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $MLModels += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $MLModel = if ($MLModelId) { + $MLModels | Where-Object { $_.Id -eq $MLModelId } + } + elseif ($MLModelName) { + $MLModels | Where-Object { $_.DisplayName -eq $MLModelName } + } + else { + # Return all MLModels if no filter is provided + Write-Message -Message "No filter provided. Returning all MLModels." -Level Debug + $MLModels + } + + # Step 9: Handle results + if ($MLModel) { + Write-Message -Message "ML Model found matching the specified criteria." -Level Debug + return $MLModel + } + else { + Write-Message -Message "No ML Model found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve ML Model. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/ML Model/New-FabricMLModel.ps1 b/source/Public/ML Model/New-FabricMLModel.ps1 new file mode 100644 index 00000000..cf4ffac0 --- /dev/null +++ b/source/Public/ML Model/New-FabricMLModel.ps1 @@ -0,0 +1,130 @@ +<# +.SYNOPSIS + Creates a new ML Model in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new ML Model + in the specified workspace. It supports optional parameters for ML Model description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the ML Model will be created. This parameter is mandatory. + +.PARAMETER MLModelName + The name of the ML Model to be created. This parameter is mandatory. + +.PARAMETER MLModelDescription + An optional description for the ML Model. + +.EXAMPLE + New-FabricMLModel -WorkspaceId "workspace-12345" -MLModelName "New ML Model" -MLModelDescription "Description of the new ML Model" + This example creates a new ML Model named "New ML Model" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function New-FabricMLModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$MLModelName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MLModelDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mlModels" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $MLModelName + } + + if ($MLModelDescription) { + $body.description = $MLModelDescription + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "ML Model '$MLModelName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "ML Model '$MLModelName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create ML Model. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/ML Model/Remove-FabricMLModel.ps1 b/source/Public/ML Model/Remove-FabricMLModel.ps1 new file mode 100644 index 00000000..f1491d73 --- /dev/null +++ b/source/Public/ML Model/Remove-FabricMLModel.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Removes an ML Model from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove an ML Model + from the specified workspace using the provided WorkspaceId and MLModelId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the ML Model will be removed. + +.PARAMETER MLModelId + The unique identifier of the ML Model to be removed. + +.EXAMPLE + Remove-FabricMLModel -WorkspaceId "workspace-12345" -MLModelId "model-67890" + This example removes the ML Model with ID "model-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Remove-FabricMLModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MLModelId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mlModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLModelId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "ML Model '$MLModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete ML Model '$MLModelId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/ML Model/Update-FabricMLModel.ps1 b/source/Public/ML Model/Update-FabricMLModel.ps1 new file mode 100644 index 00000000..50e2d6a0 --- /dev/null +++ b/source/Public/ML Model/Update-FabricMLModel.ps1 @@ -0,0 +1,93 @@ +<# +.SYNOPSIS + Updates an existing ML Model in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Model + in the specified workspace. It supports optional parameters for ML Model description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the ML Model exists. This parameter is optional. + +.PARAMETER MLModelId + The unique identifier of the ML Model to be updated. This parameter is mandatory. + +.PARAMETER MLModelDescription + New description for the ML Model. + +.EXAMPLE + Update-FabricMLModel -WorkspaceId "workspace-12345" -MLModelId "model-67890" -MLModelName "Updated ML Model" -MLModelDescription "Updated description" + This example updates the ML Model with ID "model-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricMLModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MLModelId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MLModelDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mlModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLModelId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + description = $MLModelDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "ML Model '$MLModelId' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update ML Model. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 new file mode 100644 index 00000000..e5d93177 --- /dev/null +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 @@ -0,0 +1,157 @@ +<# +.SYNOPSIS +Retrieves an MirroredDatabase or a list of MirroredDatabases from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricMirroredDatabase` function sends a GET request to the Fabric API to retrieve MirroredDatabase details for a given workspace. It can filter the results by `MirroredDatabaseName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query MirroredDatabases. + +.PARAMETER MirroredDatabaseName +(Optional) The name of the specific MirroredDatabase to retrieve. + +.EXAMPLE +Get-FabricMirroredDatabase -WorkspaceId "12345" -MirroredDatabaseName "Development" + +Retrieves the "Development" MirroredDatabase from workspace "12345". + +.EXAMPLE +Get-FabricMirroredDatabase -WorkspaceId "12345" + +Retrieves all MirroredDatabases in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricMirroredDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$MirroredDatabaseName + ) + + try { + # Step 1: Handle ambiguous input + if ($MirroredDatabaseId -and $MirroredDatabaseName) { + Write-Message -Message "Both 'MirroredDatabaseId' and 'MirroredDatabaseName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + $continuationToken = $null + $MirroredDatabases = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Step 3: Loop to retrieve data with continuation token + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $MirroredDatabases += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + + # Step 8: Filter results based on provided parameters + $MirroredDatabase = if ($MirroredDatabaseId) { + $MirroredDatabases | Where-Object { $_.Id -eq $MirroredDatabaseId } + } + elseif ($MirroredDatabaseName) { + $MirroredDatabases | Where-Object { $_.DisplayName -eq $MirroredDatabaseName } + } + else { + # Return all MirroredDatabases if no filter is provided + Write-Message -Message "No filter provided. Returning all MirroredDatabases." -Level Debug + $MirroredDatabases + } + + # Step 9: Handle results + if ($MirroredDatabase) { + Write-Message -Message "MirroredDatabase found matching the specified criteria." -Level Debug + return $MirroredDatabase + } + else { + Write-Message -Message "No MirroredDatabase found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 new file mode 100644 index 00000000..2a7700cf --- /dev/null +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 @@ -0,0 +1,109 @@ + +<# +.SYNOPSIS +Retrieves the definition of a MirroredDatabase from a specific workspace in Microsoft Fabric. + +.DESCRIPTION +This function fetches the MirroredDatabase's content or metadata from a workspace. +Handles both synchronous and asynchronous operations, with detailed logging and error handling. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace from which the MirroredDatabase definition is to be retrieved. + +.PARAMETER MirroredDatabaseId +(Optional)The unique identifier of the MirroredDatabase whose definition needs to be retrieved. + +.EXAMPLE +Get-FabricMirroredDatabaseDefinition -WorkspaceId "12345" -MirroredDatabaseId "67890" + +Retrieves the definition of the MirroredDatabase with ID `67890` from the workspace with ID `12345`. + +.EXAMPLE +Get-FabricMirroredDatabaseDefinition -WorkspaceId "12345" + +Retrieves the definitions of all MirroredDatabases in the workspace with ID `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. + +#> +function Get-FabricMirroredDatabaseDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "MirroredDatabase '$MirroredDatabaseId' definition retrieved successfully!" -Level Debug + return $response.definition.parts + } + 202 { + + Write-Message -Message "Getting MirroredDatabase '$MirroredDatabaseId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 new file mode 100644 index 00000000..200788c3 --- /dev/null +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 @@ -0,0 +1,52 @@ +function Get-FabricMirroredDatabaseStatus { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getMirroringStatus" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 9: Handle results + + Write-Message -Message "Returning status of MirroredDatabases." -Level Debug + return $response + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 new file mode 100644 index 00000000..f37c4cfe --- /dev/null +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 @@ -0,0 +1,97 @@ +function Get-FabricMirroredDatabaseTableStatus { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId + ) + + try { + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + $continuationToken = $null + $MirroredDatabaseTableStatus = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getTablesMirroringStatus" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + + # Step 3: Loop to retrieve data with continuation token + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $MirroredDatabaseTableStatus += $response.data + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 9: Handle results + # Return all Mirrored Database Table Status + Write-Message -Message "No filter provided. Returning all MirroredDatabases." -Level Debug + $MirroredDatabaseTableStatus + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 new file mode 100644 index 00000000..70b4710d --- /dev/null +++ b/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 @@ -0,0 +1,186 @@ +<# +.SYNOPSIS +Creates a new MirroredDatabase in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new MirroredDatabase +in the specified workspace. It supports optional parameters for MirroredDatabase description +and path definitions for the MirroredDatabase content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the MirroredDatabase will be created. + +.PARAMETER MirroredDatabaseName +The name of the MirroredDatabase to be created. + +.PARAMETER MirroredDatabaseDescription +An optional description for the MirroredDatabase. + +.PARAMETER MirroredDatabasePathDefinition +An optional path to the MirroredDatabase definition file to upload. + +.PARAMETER MirroredDatabasePathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. + +.EXAMPLE + Add-FabricMirroredDatabase -WorkspaceId "workspace-12345" -MirroredDatabaseName "New MirroredDatabase" -MirroredDatabasePathDefinition "C:\MirroredDatabases\example.json" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricMirroredDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$MirroredDatabaseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabasePathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabasePathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $MirroredDatabaseName + } + + if ($MirroredDatabaseDescription) { + $body.description = $MirroredDatabaseDescription + } + + if ($MirroredDatabasePathDefinition) { + $MirroredDatabaseEncodedContent = Convert-ToBase64 -filePath $MirroredDatabasePathDefinition + + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "mirroredDatabase.json" + payload = $MirroredDatabaseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error + return $null + } + } + + if ($MirroredDatabasePathPlatformDefinition) { + $MirroredDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $MirroredDatabasePathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "MirroredDatabase" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $MirroredDatabaseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation Failed" -Level Debug + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create MirroredDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 new file mode 100644 index 00000000..701e513d --- /dev/null +++ b/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS +Deletes an MirroredDatabase from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricMirroredDatabase` function sends a DELETE request to the Fabric API to remove a specified MirroredDatabase from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the MirroredDatabase to delete. + +.PARAMETER MirroredDatabaseId +(Mandatory) The ID of the MirroredDatabase to be deleted. + +.EXAMPLE +Remove-FabricMirroredDatabase -WorkspaceId "12345" -MirroredDatabaseId "67890" + +Deletes the MirroredDatabase with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch +#> + +function Remove-FabricMirroredDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "MirroredDatabase '$MirroredDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete MirroredDatabase '$MirroredDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 new file mode 100644 index 00000000..19f3bce3 --- /dev/null +++ b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 @@ -0,0 +1,51 @@ +function Start-FabricMirroredDatabaseMirroring{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/startMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 9: Handle results + Write-Message -Message "Database mirroring started successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info + return + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to start MirroredDatabase. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 new file mode 100644 index 00000000..77d7404e --- /dev/null +++ b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 @@ -0,0 +1,53 @@ + + +function Stop-FabricMirroredDatabaseMirroring{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/stopMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 9: Handle results + Write-Message -Message "Database mirroring stopped successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info + return + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to stop MirroredDatabase. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 new file mode 100644 index 00000000..09691385 --- /dev/null +++ b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 @@ -0,0 +1,107 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric MirroredDatabase. + +.DESCRIPTION +The `Update-FabricMirroredDatabase` function updates the name and/or description of a specified Fabric MirroredDatabase by making a PATCH request to the API. + +.PARAMETER MirroredDatabaseId +The unique identifier of the MirroredDatabase to be updated. + +.PARAMETER MirroredDatabaseName +The new name for the MirroredDatabase. + +.PARAMETER MirroredDatabaseDescription +(Optional) The new description for the MirroredDatabase. + +.EXAMPLE +Update-FabricMirroredDatabase -MirroredDatabaseId "MirroredDatabase123" -MirroredDatabaseName "NewMirroredDatabaseName" + +Updates the name of the MirroredDatabase with the ID "MirroredDatabase123" to "NewMirroredDatabaseName". + +.EXAMPLE +Update-FabricMirroredDatabase -MirroredDatabaseId "MirroredDatabase123" -MirroredDatabaseName "NewName" -MirroredDatabaseDescription "Updated description" + +Updates both the name and description of the MirroredDatabase "MirroredDatabase123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricMirroredDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$MirroredDatabaseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $MirroredDatabaseName + } + + if ($MirroredDatabaseDescription) { + $body.description = $MirroredDatabaseDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 b/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 new file mode 100644 index 00000000..5fc7fb71 --- /dev/null +++ b/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 @@ -0,0 +1,168 @@ +<# +.SYNOPSIS +Updates the definition of a MirroredDatabase in a Microsoft Fabric workspace. + +.DESCRIPTION +This function allows updating the content or metadata of a MirroredDatabase in a Microsoft Fabric workspace. +The MirroredDatabase content can be provided as file paths, and metadata updates can optionally be enabled. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the MirroredDatabase resides. + +.PARAMETER MirroredDatabaseId +(Mandatory) The unique identifier of the MirroredDatabase to be updated. + +.PARAMETER MirroredDatabasePathDefinition +(Mandatory) The file path to the MirroredDatabase content definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER MirroredDatabasePathPlatformDefinition +(Optional) The file path to the MirroredDatabase's platform-specific definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER UpdateMetadata +(Optional)A boolean flag indicating whether to update the MirroredDatabase's metadata. +Default: `$false`. + +.EXAMPLE +Update-FabricMirroredDatabaseDefinition -WorkspaceId "12345" -MirroredDatabaseId "67890" -MirroredDatabasePathDefinition "C:\MirroredDatabases\MirroredDatabase.json" + +Updates the content of the MirroredDatabase with ID `67890` in the workspace `12345` using the specified MirroredDatabase file. + +.EXAMPLE +Update-FabricMirroredDatabaseDefinition -WorkspaceId "12345" -MirroredDatabaseId "67890" -MirroredDatabasePathDefinition "C:\MirroredDatabases\MirroredDatabase.json" -UpdateMetadata $true + +Updates both the content and metadata of the MirroredDatabase with ID `67890` in the workspace `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- The MirroredDatabase content is encoded as Base64 before being sent to the Fabric API. +- This function handles asynchronous operations and retrieves operation results if required. + +Author: Tiago Balabuch + +#> + +function Update-FabricMirroredDatabaseDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabaseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabasePathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredDatabasePathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId + + if($MirroredDatabasePathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + if ($MirroredDatabasePathDefinition) { + $MirroredDatabaseEncodedContent = Convert-ToBase64 -filePath $MirroredDatabasePathDefinition + + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "MirroredDatabase.json" + payload = $MirroredDatabaseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error + return $null + } + } + + if ($MirroredDatabasePathPlatformDefinition) { + $MirroredDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $MirroredDatabasePathPlatformDefinition + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $MirroredDatabaseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for MirroredDatabase '$MirroredDatabaseId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for MirroredDatabase '$MirroredDatabaseId' accepted. Operation in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + $operationResult = Get-FabricLongRunningOperation -operationId $operationId + + # Handle operation result + if ($operationResult.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + + $result = Get-FabricLongRunningOperationResult -operationId $operationId + return $result.definition.parts + } + else { + Write-Message -Message "Operation Failed" -Level Debug + return $operationResult.definition.parts + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 b/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 new file mode 100644 index 00000000..a5775d3f --- /dev/null +++ b/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 @@ -0,0 +1,157 @@ +<# +.SYNOPSIS +Retrieves an MirroredWarehouse or a list of MirroredWarehouses from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricMirroredWarehouse` function sends a GET request to the Fabric API to retrieve MirroredWarehouse details for a given workspace. It can filter the results by `MirroredWarehouseName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query MirroredWarehouses. + +.PARAMETER MirroredWarehouseName +(Optional) The name of the specific MirroredWarehouse to retrieve. + +.EXAMPLE +Get-FabricMirroredWarehouse -WorkspaceId "12345" -MirroredWarehouseName "Development" + +Retrieves the "Development" MirroredWarehouse from workspace "12345". + +.EXAMPLE +Get-FabricMirroredWarehouse -WorkspaceId "12345" + +Retrieves all MirroredWarehouses in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricMirroredWarehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$MirroredWarehouseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$MirroredWarehouseName + ) + + try { + # Step 1: Handle ambiguous input + if ($MirroredWarehouseId -and $MirroredWarehouseName) { + Write-Message -Message "Both 'MirroredWarehouseId' and 'MirroredWarehouseName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + $continuationToken = $null + $MirroredWarehouses = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/MirroredWarehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + + # Step 3: Loop to retrieve data with continuation token + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $MirroredWarehouses += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + + # Step 8: Filter results based on provided parameters + $MirroredWarehouse = if ($MirroredWarehouseId) { + $MirroredWarehouses | Where-Object { $_.Id -eq $MirroredWarehouseId } + } + elseif ($MirroredWarehouseName) { + $MirroredWarehouses | Where-Object { $_.DisplayName -eq $MirroredWarehouseName } + } + else { + # Return all MirroredWarehouses if no filter is provided + Write-Message -Message "No filter provided. Returning all MirroredWarehouses." -Level Debug + $MirroredWarehouses + } + + # Step 9: Handle results + if ($MirroredWarehouse) { + Write-Message -Message "MirroredWarehouse found matching the specified criteria." -Level Debug + return $MirroredWarehouse + } + else { + Write-Message -Message "No MirroredWarehouse found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve MirroredWarehouse. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Notebook/Get-FabricNotebook.ps1 b/source/Public/Notebook/Get-FabricNotebook.ps1 new file mode 100644 index 00000000..bd759f9a --- /dev/null +++ b/source/Public/Notebook/Get-FabricNotebook.ps1 @@ -0,0 +1,155 @@ +<# +.SYNOPSIS +Retrieves an Notebook or a list of Notebooks from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Get-FabricNotebook` function sends a GET request to the Fabric API to retrieve Notebook details for a given workspace. It can filter the results by `NotebookName`. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace to query Notebooks. + +.PARAMETER NotebookName +(Optional) The name of the specific Notebook to retrieve. + +.EXAMPLE +Get-FabricNotebook -WorkspaceId "12345" -NotebookName "Development" + +Retrieves the "Development" Notebook from workspace "12345". + +.EXAMPLE +Get-FabricNotebook -WorkspaceId "12345" + +Retrieves all Notebooks in workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricNotebook { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$NotebookName + ) + + try { + # Step 1: Handle ambiguous input + if ($NotebookId -and $NotebookName) { + Write-Message -Message "Both 'NotebookId' and 'NotebookName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $notebooks = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/notebooks" -f $FabricConfig.BaseUrl, $WorkspaceId + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $notebooks += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $notebook = if ($NotebookId) { + $notebooks | Where-Object { $_.Id -eq $NotebookId } + } + elseif ($NotebookName) { + $notebooks | Where-Object { $_.DisplayName -eq $NotebookName } + } + else { + # Return all notebooks if no filter is provided + Write-Message -Message "No filter provided. Returning all Notebooks." -Level Debug + $notebooks + } + + # Step 9: Handle results + if ($notebook) { + Write-Message -Message "Notebook found matching the specified criteria." -Level Debug + return $notebook + } + else { + Write-Message -Message "No notebook found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Notebook. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Notebook/Get-FabricNotebookDefinition.ps1 b/source/Public/Notebook/Get-FabricNotebookDefinition.ps1 new file mode 100644 index 00000000..009eb88d --- /dev/null +++ b/source/Public/Notebook/Get-FabricNotebookDefinition.ps1 @@ -0,0 +1,131 @@ + +<# +.SYNOPSIS +Retrieves the definition of a notebook from a specific workspace in Microsoft Fabric. + +.DESCRIPTION +This function fetches the notebook's content or metadata from a workspace. +It supports retrieving notebook definitions in the Jupyter Notebook (`ipynb`) format. +Handles both synchronous and asynchronous operations, with detailed logging and error handling. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace from which the notebook definition is to be retrieved. + +.PARAMETER NotebookId +(Optional)The unique identifier of the notebook whose definition needs to be retrieved. + +.PARAMETER NotebookFormat +Specifies the format of the notebook definition. Currently, only 'ipynb' is supported. +Default: 'ipynb'. + +.EXAMPLE +Get-FabricNotebookDefinition -WorkspaceId "12345" -NotebookId "67890" + +Retrieves the definition of the notebook with ID `67890` from the workspace with ID `12345` in the `ipynb` format. + +.EXAMPLE +Get-FabricNotebookDefinition -WorkspaceId "12345" + +Retrieves the definitions of all notebooks in the workspace with ID `12345` in the `ipynb` format. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. + +#> +function Get-FabricNotebookDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('ipynb')] + [string]$NotebookFormat = 'ipynb' + ) + + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId + + if ($NotebookFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $NotebookFormat + } + + + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "Notebook '$NotebookId' definition retrieved successfully!" -Level Debug + return $response + } + 202 { + + Write-Message -Message "Getting notebook '$NotebookId' definition request accepted. Retrieving in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + #[string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Notebook. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Notebook/New-FabricNotebook.ps1 b/source/Public/Notebook/New-FabricNotebook.ps1 new file mode 100644 index 00000000..eec94a4e --- /dev/null +++ b/source/Public/Notebook/New-FabricNotebook.ps1 @@ -0,0 +1,193 @@ +<# +.SYNOPSIS +Creates a new notebook in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new notebook +in the specified workspace. It supports optional parameters for notebook description +and path definitions for the notebook content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the notebook will be created. + +.PARAMETER NotebookName +The name of the notebook to be created. + +.PARAMETER NotebookDescription +An optional description for the notebook. + +.PARAMETER NotebookPathDefinition +An optional path to the notebook definition file (e.g., .ipynb file) to upload. + +.PARAMETER NotebookPathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. + +.EXAMPLE + Add-FabricNotebook -WorkspaceId "workspace-12345" -NotebookName "New Notebook" -NotebookPathDefinition "C:\notebooks\example.ipynb" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricNotebook { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$NotebookName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/notebooks" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $NotebookName + } + + if ($NotebookDescription) { + $body.description = $NotebookDescription + } + + if ($NotebookPathDefinition) { + $notebookEncodedContent = Convert-ToBase64 -filePath $NotebookPathDefinition + + if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "ipynb" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "notebook-content.py" + payload = $notebookEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in notebook definition." -Level Error + return $null + } + } + + if ($NotebookPathPlatformDefinition) { + $notebookEncodedPlatformContent = Convert-ToBase64 -filePath $NotebookPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "ipynb" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $notebookEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Notebook '$NotebookName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Notebook/New-FabricNotebookNEW.ps1 b/source/Public/Notebook/New-FabricNotebookNEW.ps1 new file mode 100644 index 00000000..c29b71f7 --- /dev/null +++ b/source/Public/Notebook/New-FabricNotebookNEW.ps1 @@ -0,0 +1,158 @@ +<# +.SYNOPSIS +Creates a new notebook in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new notebook +in the specified workspace. It supports optional parameters for notebook description +and path definitions for the notebook content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the notebook will be created. + +.PARAMETER NotebookName +The name of the notebook to be created. + +.PARAMETER NotebookDescription +An optional description for the notebook. + +.PARAMETER NotebookPathDefinition +An optional path to the notebook definition file (e.g., .ipynb file) to upload. + +.PARAMETER NotebookPathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. + +.EXAMPLE + Add-FabricNotebook -WorkspaceId "workspace-12345" -NotebookName "New Notebook" -NotebookPathDefinition "C:\notebooks\example.ipynb" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function New-FabricNotebookNEW { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$NotebookName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookPathDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/notebooks" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $NotebookName + } + + if ($NotebookDescription) { + $body.description = $NotebookDescription + } + + if ($NotebookPathDefinition) { + if (-not $body.definition) { + $body.definition = @{ + format = "ipynb" + parts = @() + } + } + $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $NotebookPathDefinition + # Add new part to the parts array + $body.definition.parts = $jsonObjectParts.parts + } + # Check if any path is .platform + foreach ($part in $jsonObjectParts.parts) { + if ($part.path -eq ".platform") { + $hasPlatformFile = $true + Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Notebook '$NotebookName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Notebook/Remove-FabricNotebook.ps1 b/source/Public/Notebook/Remove-FabricNotebook.ps1 new file mode 100644 index 00000000..ec22fad3 --- /dev/null +++ b/source/Public/Notebook/Remove-FabricNotebook.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS +Deletes an Notebook from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricNotebook` function sends a DELETE request to the Fabric API to remove a specified Notebook from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the Notebook to delete. + +.PARAMETER NotebookId +(Mandatory) The ID of the Notebook to be deleted. + +.EXAMPLE +Remove-FabricNotebook -WorkspaceId "12345" -NotebookId "67890" + +Deletes the Notebook with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricNotebook { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$NotebookId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Notebook '$NotebookId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete notebook '$NotebookId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Notebook/Update-FabricNotebook.ps1 b/source/Public/Notebook/Update-FabricNotebook.ps1 new file mode 100644 index 00000000..c5cc8203 --- /dev/null +++ b/source/Public/Notebook/Update-FabricNotebook.ps1 @@ -0,0 +1,107 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric Notebook. + +.DESCRIPTION +The `Update-FabricNotebook` function updates the name and/or description of a specified Fabric Notebook by making a PATCH request to the API. + +.PARAMETER NotebookId +The unique identifier of the Notebook to be updated. + +.PARAMETER NotebookName +The new name for the Notebook. + +.PARAMETER NotebookDescription +(Optional) The new description for the Notebook. + +.EXAMPLE +Update-FabricNotebook -NotebookId "Notebook123" -NotebookName "NewNotebookName" + +Updates the name of the Notebook with the ID "Notebook123" to "NewNotebookName". + +.EXAMPLE +Update-FabricNotebook -NotebookId "Notebook123" -NotebookName "NewName" -NotebookDescription "Updated description" + +Updates both the name and description of the Notebook "Notebook123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricNotebook { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$NotebookId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$NotebookName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $NotebookName + } + + if ($NotebookDescription) { + $body.description = $NotebookDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Notebook '$NotebookName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 b/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 new file mode 100644 index 00000000..e6272644 --- /dev/null +++ b/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 @@ -0,0 +1,179 @@ +<# +.SYNOPSIS +Updates the definition of a notebook in a Microsoft Fabric workspace. + +.DESCRIPTION +This function allows updating the content or metadata of a notebook in a Microsoft Fabric workspace. +The notebook content can be provided as file paths, and metadata updates can optionally be enabled. + +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the notebook resides. + +.PARAMETER NotebookId +(Mandatory) The unique identifier of the notebook to be updated. + +.PARAMETER NotebookPathDefinition +(Mandatory) The file path to the notebook content definition file. The content will be encoded as Base64 and sent in the request. + +.PARAMETER NotebookPathPlatformDefinition +(Optional) The file path to the notebook's platform-specific definition file. The content will be encoded as Base64 and sent in the request. + +.EXAMPLE +Update-FabricNotebookDefinition -WorkspaceId "12345" -NotebookId "67890" -NotebookPathDefinition "C:\Notebooks\Notebook.ipynb" + +Updates the content of the notebook with ID `67890` in the workspace `12345` using the specified notebook file. + +.EXAMPLE +Update-FabricNotebookDefinition -WorkspaceId "12345" -NotebookId "67890" -NotebookPathDefinition "C:\Notebooks\Notebook.ipynb" -NotebookPathPlatformDefinition "C:\Notebooks\.platform" + +Updates both the content and metadata of the notebook with ID `67890` in the workspace `12345`. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- The notebook content is encoded as Base64 before being sent to the Fabric API. +- This function handles asynchronous operations and retrieves operation results if required. + +Author: Tiago Balabuch + +#> + +function Update-FabricNotebookDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$NotebookId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$NotebookPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$NotebookPathPlatformDefinition + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId + + if ($NotebookPathPlatformDefinition) { + $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + format = "ipynb" + parts = @() + } + } + + if ($NotebookPathDefinition) { + $notebookEncodedContent = Convert-ToBase64 -filePath $NotebookPathDefinition + + if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "notebook-content.py" + payload = $notebookEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in notebook definition." -Level Error + return $null + } + } + + if ($NotebookPathPlatformDefinition) { + $notebookEncodedPlatformContent = Convert-ToBase64 -filePath $NotebookPathPlatformDefinition + if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $notebookEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for notebook '$NotebookId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for notebook '$NotebookId' accepted. Operation in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 b/source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 new file mode 100644 index 00000000..fc52257d --- /dev/null +++ b/source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 @@ -0,0 +1,155 @@ +<# +.SYNOPSIS + Retrieves paginated report details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves paginated report details from a specified workspace using either the provided PaginatedReportId or PaginatedReportName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the paginated reports exist. This parameter is mandatory. + +.PARAMETER PaginatedReportId + The unique identifier of the paginated report to retrieve. This parameter is optional. + +.PARAMETER PaginatedReportName + The name of the paginated report to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricPaginatedReports -WorkspaceId "workspace-12345" -PaginatedReportId "report-67890" + This example retrieves the paginated report details for the report with ID "report-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricPaginatedReports -WorkspaceId "workspace-12345" -PaginatedReportName "My Paginated Report" + This example retrieves the paginated report details for the report named "My Paginated Report" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricPaginatedReport { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$PaginatedReportId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$PaginatedReportName + ) + + try { + # Step 1: Handle ambiguous input + if ($PaginatedReportId -and $PaginatedReportName) { + Write-Message -Message "Both 'PaginatedReportId' and 'PaginatedReportName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $PaginatedReports = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/paginatedReports" -f $FabricConfig.BaseUrl, $WorkspaceId + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $PaginatedReports += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $PaginatedReport = if ($PaginatedReportId) { + $PaginatedReports | Where-Object { $_.Id -eq $PaginatedReportId } + } + elseif ($PaginatedReportName) { + $PaginatedReports | Where-Object { $_.DisplayName -eq $PaginatedReportName } + } + else { + # Return all PaginatedReports if no filter is provided + Write-Message -Message "No filter provided. Returning all Paginated Reports." -Level Debug + $PaginatedReports + } + + # Step 9: Handle results + if ($PaginatedReport) { + Write-Message -Message "Paginated Report found matching the specified criteria." -Level Debug + return $PaginatedReport + } + else { + Write-Message -Message "No Paginated Report found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Paginated Report. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 b/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 new file mode 100644 index 00000000..00d0c59d --- /dev/null +++ b/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + Updates an existing paginated report in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing paginated report + in the specified workspace. It supports optional parameters for paginated report description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the paginated report exists. This parameter is optional. + +.PARAMETER PaginatedReportId + The unique identifier of the paginated report to be updated. This parameter is mandatory. + +.PARAMETER PaginatedReportName + The new name of the paginated report. This parameter is mandatory. + +.PARAMETER PaginatedReportDescription + An optional new description for the paginated report. + +.EXAMPLE + Update-FabricPaginatedReport -WorkspaceId "workspace-12345" -PaginatedReportId "report-67890" -PaginatedReportName "Updated Paginated Report" -PaginatedReportDescription "Updated description" + This example updates the paginated report with ID "report-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricPaginatedReport { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$PaginatedReportId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$PaginatedReportName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$PaginatedReportDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/paginatedReports/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $PaginatedReportId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $PaginatedReportName + } + + if ($PaginatedReportDescription) { + $body.description = $PaginatedReportDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Paginated Report '$PaginatedReportName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Paginated Report. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Reflex/Get-FabricReflex.ps1 b/source/Public/Reflex/Get-FabricReflex.ps1 new file mode 100644 index 00000000..9e274d3c --- /dev/null +++ b/source/Public/Reflex/Get-FabricReflex.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Retrieves Reflex details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves Reflex details from a specified workspace using either the provided ReflexId or ReflexName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Reflex exists. This parameter is mandatory. + +.PARAMETER ReflexId + The unique identifier of the Reflex to retrieve. This parameter is optional. + +.PARAMETER ReflexName + The name of the Reflex to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricReflex -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" + This example retrieves the Reflex details for the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricReflex -WorkspaceId "workspace-12345" -ReflexName "My Reflex" + This example retrieves the Reflex details for the Reflex named "My Reflex" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricReflex { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$ReflexName + ) + try { + + # Step 1: Handle ambiguous input + if ($ReflexId -and $ReflexName) { + Write-Message -Message "Both 'ReflexId' and 'ReflexName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $Reflexes = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/reflexes" -f $FabricConfig.BaseUrl, $WorkspaceId + # Step 3: Loop to retrieve data with continuation token + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $Reflexes += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $Reflex = if ($ReflexId) { + $Reflexes | Where-Object { $_.Id -eq $ReflexId } + } + elseif ($ReflexName) { + $Reflexes | Where-Object { $_.DisplayName -eq $ReflexName } + } + else { + # Return all Reflexes if no filter is provided + Write-Message -Message "No filter provided. Returning all Reflexes." -Level Debug + $Reflexes + } + + # Step 9: Handle results + if ($Reflex) { + Write-Message -Message "Reflex found in the Workspace '$WorkspaceId'." -Level Debug + return $Reflex + } + else { + Write-Message -Message "No Reflex found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Reflex. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Reflex/Get-FabricReflexDefinition.ps1 b/source/Public/Reflex/Get-FabricReflexDefinition.ps1 new file mode 100644 index 00000000..121677e4 --- /dev/null +++ b/source/Public/Reflex/Get-FabricReflexDefinition.ps1 @@ -0,0 +1,124 @@ +<# +.SYNOPSIS + Retrieves the definition of an Reflex from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves the definition of an Reflex from a specified workspace using the provided ReflexId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Reflex exists. This parameter is mandatory. + +.PARAMETER ReflexId + The unique identifier of the Reflex to retrieve the definition for. This parameter is optional. + +.PARAMETER ReflexFormat + The format in which to retrieve the Reflex definition. This parameter is optional. + +.EXAMPLE + Get-FabricReflexDefinition -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" + This example retrieves the definition of the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricReflexDefinition -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" -ReflexFormat "json" + This example retrieves the definition of the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345" in JSON format. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricReflexDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexFormat + ) + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId + + if ($ReflexFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $ReflexFormat + } + + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "Reflex '$ReflexId' definition retrieved successfully!" -Level Debug + return $response.definition.parts + } + 202 { + + Write-Message -Message "Getting Reflex '$ReflexId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Reflex. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Reflex/New-FabricReflex.ps1 b/source/Public/Reflex/New-FabricReflex.ps1 new file mode 100644 index 00000000..e71b6502 --- /dev/null +++ b/source/Public/Reflex/New-FabricReflex.ps1 @@ -0,0 +1,193 @@ +<# +.SYNOPSIS + Creates a new Reflex in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new Reflex + in the specified workspace. It supports optional parameters for Reflex description and path definitions. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Reflex will be created. This parameter is mandatory. + +.PARAMETER ReflexName + The name of the Reflex to be created. This parameter is mandatory. + +.PARAMETER ReflexDescription + An optional description for the Reflex. + +.PARAMETER ReflexPathDefinition + An optional path to the Reflex definition file to upload. + +.PARAMETER ReflexPathPlatformDefinition + An optional path to the platform-specific definition file to upload. + +.EXAMPLE + New-FabricReflex -WorkspaceId "workspace-12345" -ReflexName "New Reflex" -ReflexDescription "Description of the new Reflex" + This example creates a new Reflex named "New Reflex" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function New-FabricReflex { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$ReflexName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexPathPlatformDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reflexes" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $ReflexName + } + + if ($ReflexDescription) { + $body.description = $ReflexDescription + } + if ($ReflexPathDefinition) { + $ReflexEncodedContent = Convert-ToBase64 -filePath $ReflexPathDefinition + + if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "ReflexEntities.json" + payload = $ReflexEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error + return $null + } + } + + if ($ReflexPathPlatformDefinition) { + $ReflexEncodedPlatformContent = Convert-ToBase64 -filePath $ReflexPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $ReflexEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Reflex '$ReflexName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Reflex '$ReflexName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Reflex. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Reflex/Remove-FabricReflex.ps1 b/source/Public/Reflex/Remove-FabricReflex.ps1 new file mode 100644 index 00000000..884f4ab5 --- /dev/null +++ b/source/Public/Reflex/Remove-FabricReflex.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Removes an Reflex from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove an Reflex + from the specified workspace using the provided WorkspaceId and ReflexId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the Reflex will be removed. + +.PARAMETER ReflexId + The unique identifier of the Reflex to be removed. + +.EXAMPLE + Remove-FabricReflex -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" + This example removes the Reflex with ID "Reflex-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Remove-FabricReflex { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReflexId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle response + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Reflex '$ReflexId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Reflex '$ReflexId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Reflex/Update-FabricReflex.ps1 b/source/Public/Reflex/Update-FabricReflex.ps1 new file mode 100644 index 00000000..a600d2fc --- /dev/null +++ b/source/Public/Reflex/Update-FabricReflex.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + Updates an existing Reflex in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing Reflex + in the specified workspace. It supports optional parameters for Reflex description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Reflex exists. This parameter is optional. + +.PARAMETER ReflexId + The unique identifier of the Reflex to be updated. This parameter is mandatory. + +.PARAMETER ReflexName + The new name of the Reflex. This parameter is mandatory. + +.PARAMETER ReflexDescription + An optional new description for the Reflex. + +.EXAMPLE + Update-FabricReflex -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" -ReflexName "Updated Reflex" -ReflexDescription "Updated description" + This example updates the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricReflex { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReflexId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$ReflexName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexDescription + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $ReflexName + } + + if ($ReflexDescription) { + $body.description = $ReflexDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Reflex '$ReflexName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Reflex/Update-FabricReflexDefinition.ps1 b/source/Public/Reflex/Update-FabricReflexDefinition.ps1 new file mode 100644 index 00000000..45582d9a --- /dev/null +++ b/source/Public/Reflex/Update-FabricReflexDefinition.ps1 @@ -0,0 +1,168 @@ +<# +.SYNOPSIS + Updates the definition of an existing Reflex in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Reflex + in the specified workspace. It supports optional parameters for Reflex definition and platform-specific definition. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Reflex exists. This parameter is mandatory. + +.PARAMETER ReflexId + The unique identifier of the Reflex to be updated. This parameter is mandatory. + +.PARAMETER ReflexPathDefinition + An optional path to the Reflex definition file to upload. + +.PARAMETER ReflexPathPlatformDefinition + An optional path to the platform-specific definition file to upload. + +.EXAMPLE + Update-FabricReflexDefinition -WorkspaceId "workspace-12345" -ReflexId "Reflex-67890" -ReflexPathDefinition "C:\Path\To\ReflexDefinition.json" + This example updates the definition of the Reflex with ID "Reflex-67890" in the workspace with ID "workspace-12345" using the provided definition file. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricReflexDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReflexId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReflexPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReflexPathPlatformDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId + + #if ($UpdateMetadata -eq $true) { + if($ReflexPathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + if ($ReflexPathDefinition) { + $ReflexEncodedContent = Convert-ToBase64 -filePath $ReflexPathDefinition + + if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "ReflexEntities.json" + payload = $ReflexEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error + return $null + } + } + + if ($ReflexPathPlatformDefinition) { + $ReflexEncodedPlatformContent = Convert-ToBase64 -filePath $ReflexPathPlatformDefinition + if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $ReflexEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for Reflex '$ReflexId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for Reflex '$ReflexId' accepted. Operation in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Report/Get-FabricReport.ps1 b/source/Public/Report/Get-FabricReport.ps1 new file mode 100644 index 00000000..0f655be2 --- /dev/null +++ b/source/Public/Report/Get-FabricReport.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Retrieves Report details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves Report details from a specified workspace using either the provided ReportId or ReportName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Report exists. This parameter is mandatory. + +.PARAMETER ReportId + The unique identifier of the Report to retrieve. This parameter is optional. + +.PARAMETER ReportName + The name of the Report to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricReport -WorkspaceId "workspace-12345" -ReportId "Report-67890" + This example retrieves the Report details for the Report with ID "Report-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricReport -WorkspaceId "workspace-12345" -ReportName "My Report" + This example retrieves the Report details for the Report named "My Report" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricReport { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReportId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$ReportName + ) + try { + + # Step 1: Handle ambiguous input + if ($ReportId -and $ReportName) { + Write-Message -Message "Both 'ReportId' and 'ReportName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $Reports = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/reports" -f $FabricConfig.BaseUrl, $WorkspaceId + # Step 3: Loop to retrieve data with continuation token + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $Reports += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $Report = if ($ReportId) { + $Reports | Where-Object { $_.Id -eq $ReportId } + } + elseif ($ReportName) { + $Reports | Where-Object { $_.DisplayName -eq $ReportName } + } + else { + # Return all Reports if no filter is provided + Write-Message -Message "No filter provided. Returning all Reports." -Level Debug + $Reports + } + + # Step 9: Handle results + if ($Report) { + Write-Message -Message "Report found in the Workspace '$WorkspaceId'." -Level Debug + return $Report + } + else { + Write-Message -Message "No Report found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Report. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Report/Get-FabricReportDefinition.ps1 b/source/Public/Report/Get-FabricReportDefinition.ps1 new file mode 100644 index 00000000..ef06367d --- /dev/null +++ b/source/Public/Report/Get-FabricReportDefinition.ps1 @@ -0,0 +1,124 @@ +<# +.SYNOPSIS + Retrieves the definition of an Report from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves the definition of an Report from a specified workspace using the provided ReportId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Report exists. This parameter is mandatory. + +.PARAMETER ReportId + The unique identifier of the Report to retrieve the definition for. This parameter is optional. + +.PARAMETER ReportFormat + The format in which to retrieve the Report definition. This parameter is optional. + +.EXAMPLE + Get-FabricReportDefinition -WorkspaceId "workspace-12345" -ReportId "Report-67890" + This example retrieves the definition of the Report with ID "Report-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricReportDefinition -WorkspaceId "workspace-12345" -ReportId "Report-67890" -ReportFormat "json" + This example retrieves the definition of the Report with ID "Report-67890" in the workspace with ID "workspace-12345" in JSON format. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricReportDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReportId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReportFormat + ) + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId + + if ($ReportFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $ReportFormat + } + + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "Report '$ReportId' definition retrieved successfully!" -Level Debug + return $response + } + 202 { + + Write-Message -Message "Getting Report '$ReportId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Report. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Report/New-FabricReport.ps1 b/source/Public/Report/New-FabricReport.ps1 new file mode 100644 index 00000000..9e61167c --- /dev/null +++ b/source/Public/Report/New-FabricReport.ps1 @@ -0,0 +1,150 @@ +<# +.SYNOPSIS + Creates a new Report in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new Report + in the specified workspace. It supports optional parameters for Report description and path definitions. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Report will be created. This parameter is mandatory. + +.PARAMETER ReportName + The name of the Report to be created. This parameter is mandatory. + +.PARAMETER ReportDescription + An optional description for the Report. + +.PARAMETER ReportPathDefinition + A mandatory path to the folder that contains Report definition files to upload. + + +.EXAMPLE + New-FabricReport -WorkspaceId "workspace-12345" -ReportName "New Report" -ReportDescription "Description of the new Report" + This example creates a new Report named "New Report" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function New-FabricReport { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$ReportName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReportDescription, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReportPathDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reports" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $ReportName + } + + if ($ReportDescription) { + $body.description = $ReportDescription + } + if ($ReportPathDefinition) { + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $ReportPathDefinition + # Add new part to the parts array + $body.definition.parts = $jsonObjectParts.parts + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Report '$ReportName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Report '$ReportName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Report. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Report/Remove-FabricReport.ps1 b/source/Public/Report/Remove-FabricReport.ps1 new file mode 100644 index 00000000..3ab69774 --- /dev/null +++ b/source/Public/Report/Remove-FabricReport.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Removes an Report from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove an Report + from the specified workspace using the provided WorkspaceId and ReportId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the Report will be removed. + +.PARAMETER ReportId + The unique identifier of the Report to be removed. + +.EXAMPLE + Remove-FabricReport -WorkspaceId "workspace-12345" -ReportId "Report-67890" + This example removes the Report with ID "Report-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Remove-FabricReport { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReportId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle response + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Report '$ReportId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Report '$ReportId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Report/Update-FabricReport.ps1 b/source/Public/Report/Update-FabricReport.ps1 new file mode 100644 index 00000000..08b852ea --- /dev/null +++ b/source/Public/Report/Update-FabricReport.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + Updates an existing Report in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing Report + in the specified workspace. It supports optional parameters for Report description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Report exists. This parameter is optional. + +.PARAMETER ReportId + The unique identifier of the Report to be updated. This parameter is mandatory. + +.PARAMETER ReportName + The new name of the Report. This parameter is mandatory. + +.PARAMETER ReportDescription + An optional new description for the Report. + +.EXAMPLE + Update-FabricReport -WorkspaceId "workspace-12345" -ReportId "Report-67890" -ReportName "Updated Report" -ReportDescription "Updated description" + This example updates the Report with ID "Report-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricReport { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReportId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$ReportName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ReportDescription + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $ReportName + } + + if ($ReportDescription) { + $body.description = $ReportDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Report '$ReportName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Report/Update-FabricReportDefinition.ps1 b/source/Public/Report/Update-FabricReportDefinition.ps1 new file mode 100644 index 00000000..1a7bb73f --- /dev/null +++ b/source/Public/Report/Update-FabricReportDefinition.ps1 @@ -0,0 +1,145 @@ +<# +.SYNOPSIS + Updates the definition of an existing Report in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Report + in the specified workspace. It supports optional parameters for Report definition and platform-specific definition. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Report exists. This parameter is mandatory. + +.PARAMETER ReportId + The unique identifier of the Report to be updated. This parameter is mandatory. + +.PARAMETER ReportPathDefinition + A mandatory path to the Report definition file to upload. + +.EXAMPLE + Update-FabricReportDefinition -WorkspaceId "workspace-12345" -ReportId "Report-67890" -ReportPathDefinition "C:\Path\To\ReportDefinition.json" + This example updates the definition of the Report with ID "Report-67890" in the workspace with ID "workspace-12345" using the provided definition file. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricReportDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReportId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$ReportPathDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/Reports/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId + + #if ($UpdateMetadata -eq $true) { + + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + if ($ReportPathDefinition) { + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $ReportPathDefinition + # Add new part to the parts array + $body.definition.parts = $jsonObjectParts.parts + } + # Check if any path is .platform + foreach ($part in $jsonObjectParts.parts) { + if ($part.path -eq ".platform") { + $hasPlatformFile = $true + Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug + } + } + + if($hasPlatformFile -eq $true) { + $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for Report '$ReportId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for Report '$ReportId' accepted. Operation in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Update definition operation for Report '$ReportId' succeeded!" -Level Info + return $operationStatus + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/SQL Database/Get-FabricSQLDatabase.ps1 b/source/Public/SQL Database/Get-FabricSQLDatabase.ps1 new file mode 100644 index 00000000..6ba60a10 --- /dev/null +++ b/source/Public/SQL Database/Get-FabricSQLDatabase.ps1 @@ -0,0 +1,78 @@ +function Get-FabricSQLDatabase { + +<# +.SYNOPSIS + Retrieves Fabric SQLDatabases + +.DESCRIPTION + Retrieves Fabric SQLDatabases. Without the SQLDatabaseName or SQLDatabaseID parameter, + all SQLDatabases are returned. If you want to retrieve a specific SQLDatabase, you can + use the SQLDatabaseName or SQLDatabaseID parameter. These parameters cannot be used together. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the SQLDatabases should be retrieved. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER SQLDatabaseName + The name of the KQLDatabase to retrieve. This parameter cannot be used together with SQLDatabaseID. + +.PARAMETER SQLDatabaseID + The Id of the SQLDatabase to retrieve. This parameter cannot be used together with SQLDatabaseName. + The value for SQLDatabaseID is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricSQLDatabase ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -SQLDatabaseName 'MySQLDatabase' + + This example will retrieve the SQLDatabase with the name 'MySQLDatabase'. + +.EXAMPLE + Get-FabricSQLDatabase + + This example will retrieve all SQLDatabases in the workspace that is specified + by the WorkspaceId. + +.EXAMPLE + Get-FabricSQLDatabase ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -SQLDatabaseId '12345678-1234-1234-1234-123456789012' + + This example will retrieve the SQLDatabase with the ID '12345678-1234-1234-1234-123456789012'. + +.NOTES + Revision History: + - 2025-03-06 - KNO: Init version of the function +#> + + +[CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Alias("Name","DisplayName")] + [string]$SQLDatabaseName, + + [Alias("Id")] + [string]$SQLDatabaseId + ) + + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "You can either use SQLDatabaseName or SQLDatabaseID not both. If both are used throw error" + if ($PSBoundParameters.ContainsKey("SQLDatabaseName") -and $PSBoundParameters.ContainsKey("SQLDatabaseId")) { + throw "Parameters SQLDatabaseName and SQLDatabaseId cannot be used together" + } + + # Create SQLDatabase API + $uri = "$($FabricSession.BaseApiUrl)/workspaces/$workspaceId/SqlDatabases" + if ($SQLDatabaseId) { + $uri = "$uri/$SQLDatabaseId" + } + $result = Invoke-RestMethod -Headers $FabricSession.HeaderParams -Uri $uri + ##$databases.Where({$_.displayName -eq $body.displayName}).id + + return $result.value + +} \ No newline at end of file diff --git a/source/Public/SQL Database/New-FabricSQLDatabase.ps1 b/source/Public/SQL Database/New-FabricSQLDatabase.ps1 new file mode 100644 index 00000000..e980f04e --- /dev/null +++ b/source/Public/SQL Database/New-FabricSQLDatabase.ps1 @@ -0,0 +1,89 @@ +<# +.SYNOPSIS +Creates a new SQL Database in a specified Microsoft Fabric workspace. + +.DESCRIPTION +This function sends a POST request to the Microsoft Fabric API to create a new SQL Database +in the specified workspace. It supports optional parameters for SQL Database description +and path definitions for the SQL Database content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the SQL Database will be created. + +.PARAMETER Name +The name of the SQL Database to be created. + +.PARAMETER Description +An optional description for the SQL Database. + + +.EXAMPLE + New-FabricSQLDatabase -WorkspaceId "workspace-12345" -Name "NewDatabase" + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Kamil Nowinski + +#> + +function New-FabricSQLDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$Name, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$Description + ) + + try { + # Step 1: Ensure token validity + Test-TokenExpired + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $Name + } + + if ($Description) { + $body.description = $Description + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + Write-Message "RESPONSE: $response" -Level Debug + Test-FabricApiResponse -response $response -responseHeader $responseHeader -statusCode $statusCode -Name $Name -TypeName 'SQL Database' + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create SQL Database. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 new file mode 100644 index 00000000..799f07d4 --- /dev/null +++ b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS +Deletes a SQL Database from a specified workspace in Microsoft Fabric. + +.DESCRIPTION +The `Remove-FabricSQLDatabase` function sends a DELETE request to the Fabric API to remove a specified SQLDatabase from a given workspace. + +.PARAMETER WorkspaceId +(Mandatory) The ID of the workspace containing the SQLDatabase to delete. + +.PARAMETER SQLDatabaseId +(Mandatory) The ID of the SQL Database to be deleted. + +.EXAMPLE +Remove-FabricSQLDatabas -WorkspaceId "12345" -SQLDatabaseId "67890" + +Deletes the SQL Database with ID "67890" from workspace "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. + +Author: Kamil Nowinski + +#> + +function Remove-FabricSQLDatabase { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SQLDatabaseId + ) + + try { + # Step 1: Ensure token validity + Confirm-FabricAuthToken | Out-Null + Test-TokenExpired + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SQLDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "SQL Database '$SQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete SQL Database '$SQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 b/source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 new file mode 100644 index 00000000..6b760c91 --- /dev/null +++ b/source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 @@ -0,0 +1,154 @@ +<# +.SYNOPSIS +Retrieves SQL Endpoints from a specified workspace in Fabric. + +.DESCRIPTION +The Get-FabricSQLEndpoint function retrieves SQL Endpoints from a specified workspace in Fabric. +It supports filtering by SQL Endpoint ID or SQL Endpoint Name. If both filters are provided, +an error message is returned. The function handles token validation, API requests with continuation +tokens, and processes the response to return the desired SQL Endpoint(s). + +.PARAMETER WorkspaceId +The ID of the workspace from which to retrieve SQL Endpoints. This parameter is mandatory. + +.PARAMETER SQLEndpointId +The ID of the SQL Endpoint to retrieve. This parameter is optional but cannot be used together with SQLEndpointName. + +.PARAMETER SQLEndpointName +The name of the SQL Endpoint to retrieve. This parameter is optional but cannot be used together with SQLEndpointId. + +.EXAMPLE +Get-FabricSQLEndpoint -WorkspaceId "workspace123" -SQLEndpointId "endpoint456" + +.EXAMPLE +Get-FabricSQLEndpoint -WorkspaceId "workspace123" -SQLEndpointName "MySQLEndpoint" + +.NOTES +- This function requires the FabricConfig object to be properly configured with BaseUrl and FabricHeaders. +- The function uses continuation tokens to handle paginated API responses. +- If no filter parameters are provided, all SQL Endpoints in the specified workspace are returned. + +#> +function Get-FabricSQLEndpoint { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SQLEndpointId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SQLEndpointName + ) + + try { + # Step 1: Handle ambiguous input + if ($SQLEndpointId -and $SQLEndpointName) { + Write-Message -Message "Both 'SQLEndpointId' and 'SQLEndpointName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $SQLEndpoints = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/SQLEndpoints" -f $FabricConfig.BaseUrl, $WorkspaceId + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $SQLEndpoints += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $SQLEndpoint = if ($SQLEndpointId) { + $SQLEndpoints | Where-Object { $_.Id -eq $SQLEndpointId } + } + elseif ($SQLEndpointName) { + $SQLEndpoints | Where-Object { $_.DisplayName -eq $SQLEndpointName } + } + else { + # Return all SQLEndpoints if no filter is provided + Write-Message -Message "No filter provided. Returning all Paginated Reports." -Level Debug + $SQLEndpoints + } + + # Step 9: Handle results + if ($SQLEndpoint) { + Write-Message -Message "Paginated Report found matching the specified criteria." -Level Debug + return $SQLEndpoint + } + else { + Write-Message -Message "No Paginated Report found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Paginated Report. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Semantic Model/Get-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Get-FabricSemanticModel.ps1 new file mode 100644 index 00000000..f0611bdf --- /dev/null +++ b/source/Public/Semantic Model/Get-FabricSemanticModel.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Retrieves SemanticModel details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves SemanticModel details from a specified workspace using either the provided SemanticModelId or SemanticModelName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SemanticModel exists. This parameter is mandatory. + +.PARAMETER SemanticModelId + The unique identifier of the SemanticModel to retrieve. This parameter is optional. + +.PARAMETER SemanticModelName + The name of the SemanticModel to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" + This example retrieves the SemanticModel details for the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelName "My SemanticModel" + This example retrieves the SemanticModel details for the SemanticModel named "My SemanticModel" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricSemanticModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SemanticModelName + ) + try { + + # Step 1: Handle ambiguous input + if ($SemanticModelId -and $SemanticModelName) { + Write-Message -Message "Both 'SemanticModelId' and 'SemanticModelName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $SemanticModels = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/semanticModels" -f $FabricConfig.BaseUrl, $WorkspaceId + # Step 3: Loop to retrieve data with continuation token + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $SemanticModels += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $SemanticModel = if ($SemanticModelId) { + $SemanticModels | Where-Object { $_.Id -eq $SemanticModelId } + } + elseif ($SemanticModelName) { + $SemanticModels | Where-Object { $_.DisplayName -eq $SemanticModelName } + } + else { + # Return all SemanticModels if no filter is provided + Write-Message -Message "No filter provided. Returning all SemanticModels." -Level Debug + $SemanticModels + } + + # Step 9: Handle results + if ($SemanticModel) { + Write-Message -Message "SemanticModel found in the Workspace '$WorkspaceId'." -Level Debug + return $SemanticModel + } + else { + Write-Message -Message "No SemanticModel found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve SemanticModel. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 b/source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 new file mode 100644 index 00000000..fa38d546 --- /dev/null +++ b/source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 @@ -0,0 +1,125 @@ +<# +.SYNOPSIS + Retrieves the definition of an SemanticModel from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves the definition of an SemanticModel from a specified workspace using the provided SemanticModelId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SemanticModel exists. This parameter is mandatory. + +.PARAMETER SemanticModelId + The unique identifier of the SemanticModel to retrieve the definition for. This parameter is optional. + +.PARAMETER SemanticModelFormat + The format in which to retrieve the SemanticModel definition. This parameter is optional. + +.EXAMPLE + Get-FabricSemanticModelDefinition -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" + This example retrieves the definition of the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricSemanticModelDefinition -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" -SemanticModelFormat "json" + This example retrieves the definition of the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345" in JSON format. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricSemanticModelDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('TMDL', 'TMSL')] + [string]$SemanticModelFormat = "TMDL" + ) + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId + + if ($SemanticModelFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $SemanticModelFormat + } + + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "SemanticModel '$SemanticModelId' definition retrieved successfully!" -Level Debug + return $response + } + 202 { + + Write-Message -Message "Getting SemanticModel '$SemanticModelId' definition request accepted. Retrieving in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve SemanticModel. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Semantic Model/New-FabricSemanticModel.ps1 b/source/Public/Semantic Model/New-FabricSemanticModel.ps1 new file mode 100644 index 00000000..4ff05a02 --- /dev/null +++ b/source/Public/Semantic Model/New-FabricSemanticModel.ps1 @@ -0,0 +1,145 @@ +<# +.SYNOPSIS + Creates a new SemanticModel in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new SemanticModel + in the specified workspace. It supports optional parameters for SemanticModel description and path definitions. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SemanticModel will be created. This parameter is mandatory. + +.PARAMETER SemanticModelName + The name of the SemanticModel to be created. This parameter is mandatory. + +.PARAMETER SemanticModelDescription + An optional description for the SemanticModel. + +.PARAMETER SemanticModelPathDefinition + An optional path to the SemanticModel definition file to upload. + +.EXAMPLE + New-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelName "New SemanticModel" -SemanticModelDescription "Description of the new SemanticModel" + This example creates a new SemanticModel named "New SemanticModel" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function New-FabricSemanticModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SemanticModelName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelDescription, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelPathDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $SemanticModelName + definition = @{ + parts = @() + }} + + $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $SemanticModelPathDefinition + # Add new part to the parts array + $body.definition.parts = $jsonObjectParts.parts + + if ($SemanticModelDescription) { + $body.description = $SemanticModelDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "SemanticModel '$SemanticModelName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "SemanticModel '$SemanticModelName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create SemanticModel. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 new file mode 100644 index 00000000..4e12b229 --- /dev/null +++ b/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Removes an SemanticModel from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove an SemanticModel + from the specified workspace using the provided WorkspaceId and SemanticModelId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the SemanticModel will be removed. + +.PARAMETER SemanticModelId + The unique identifier of the SemanticModel to be removed. + +.EXAMPLE + Remove-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" + This example removes the SemanticModel with ID "SemanticModel-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Remove-FabricSemanticModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle response + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "SemanticModel '$SemanticModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete SemanticModel '$SemanticModelId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 new file mode 100644 index 00000000..12974b58 --- /dev/null +++ b/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + Updates an existing SemanticModel in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing SemanticModel + in the specified workspace. It supports optional parameters for SemanticModel description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SemanticModel exists. This parameter is optional. + +.PARAMETER SemanticModelId + The unique identifier of the SemanticModel to be updated. This parameter is mandatory. + +.PARAMETER SemanticModelName + The new name of the SemanticModel. This parameter is mandatory. + +.PARAMETER SemanticModelDescription + An optional new description for the SemanticModel. + +.EXAMPLE + Update-FabricSemanticModel -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" -SemanticModelName "Updated SemanticModel" -SemanticModelDescription "Updated description" + This example updates the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricSemanticModel { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SemanticModelName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelDescription + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $SemanticModelName + } + + if ($SemanticModelDescription) { + $body.description = $SemanticModelDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "SemanticModel '$SemanticModelName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 b/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 new file mode 100644 index 00000000..67e146ea --- /dev/null +++ b/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 @@ -0,0 +1,135 @@ +<# +.SYNOPSIS + Updates the definition of an existing SemanticModel in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SemanticModel + in the specified workspace. It supports optional parameters for SemanticModel definition and platform-specific definition. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SemanticModel exists. This parameter is mandatory. + +.PARAMETER SemanticModelId + The unique identifier of the SemanticModel to be updated. This parameter is mandatory. + +.PARAMETER SemanticModelPathDefinition + An optional path to the SemanticModel definition file to upload. + +.EXAMPLE + Update-FabricSemanticModelDefinition -WorkspaceId "workspace-12345" -SemanticModelId "SemanticModel-67890" -SemanticModelPathDefinition "C:\Path\To\SemanticModelDefinition.json" + This example updates the definition of the SemanticModel with ID "SemanticModel-67890" in the workspace with ID "workspace-12345" using the provided definition file. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricSemanticModelDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SemanticModelPathDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/SemanticModels/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId + + # Step 3: Construct the request body + $body = @{ + definition = @{ + parts = @() + } + } + + $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $SemanticModelPathDefinition + # Add new part to the parts array + $body.definition.parts = $jsonObjectParts.parts + # Check if any path is .platform + foreach ($part in $jsonObjectParts.parts) { + if ($part.path -eq ".platform") { + $hasPlatformFile = $true + Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug + } + } + + if ($hasPlatformFile -eq $true) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for SemanticModel '$SemanticModelId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for SemanticModel '$SemanticModelId' accepted. Operation in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Update definition operation for Semantic Model '$SemanticModelId' succeeded!" -Level Info + return $operationStatus + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Set-FabricAuthToken.ps1 b/source/Public/Set-FabricAuthToken.ps1 new file mode 100644 index 00000000..591f8ec9 --- /dev/null +++ b/source/Public/Set-FabricAuthToken.ps1 @@ -0,0 +1,104 @@ +<# +.SYNOPSIS + Sets the Fabric authentication token. + +.DESCRIPTION + The Set-FabricAuthToken function sets the Fabric authentication token. It checks if an Azure context is already available. If not, it connects to the Azure account using either a service principal ID and secret, a provided credential, or interactive login. It then gets the Azure context and sets the Fabric authentication token. + +.PARAMETER azContext + The Azure context. If not provided, the function connects to the Azure account and gets the context. + +.PARAMETER servicePrincipalId + The service principal ID. If provided, the function uses this ID and the service principal secret to connect to the Azure account. + +.PARAMETER servicePrincipalSecret + The service principal secret. Used with the service principal ID to connect to the Azure account. + +.PARAMETER tenantId + The tenant ID. Used with the service principal ID and secret or the credential to connect to the Azure account. + +.PARAMETER credential + The credential. If provided, the function uses this credential to connect to the Azure account. + +.EXAMPLE + Set-FabricAuthToken -servicePrincipalId "12345678-90ab-cdef-1234-567890abcdef" -servicePrincipalSecret "secret" -tenantId "12345678-90ab-cdef-1234-567890abcdef" + + This command sets the Fabric authentication token using the provided service principal ID, secret, and tenant ID. + +.INPUTS + String, SecureString, PSCredential. You can pipe a string that contains the service principal ID, a secure string that contains the service principal secret, and a PSCredential object that contains the credential to Set-FabricAuthToken. + +.OUTPUTS + None. This function does not return any output. + +.NOTES + This function was originally written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip +#> + +function Set-FabricAuthToken { + <# + .SYNOPSIS + Set authentication token for the Fabric service + #> + [CmdletBinding(SupportsShouldProcess)] + param + ( + [string] $servicePrincipalId + , [string] $servicePrincipalSecret + , [PSCredential] $credential + , [string] $tenantId + , [switch] $reset + , [string] $apiUrl + ) + + if (!$reset) { + $azContext = Get-AzContext + } + + if ($apiUrl) { + $FabricSession.BaseApiUrl = $apiUrl + } + + if (!$azContext) { + Write-Output "Getting authentication token" + if ($servicePrincipalId) { + $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $servicePrincipalId, ($servicePrincipalSecret | ConvertTo-SecureString -AsPlainText -Force) + + Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential | Out-Null + + Set-AzContext -Tenant $tenantId | Out-Null + } + elseif ($null -ne $credential) { + Connect-AzAccount -Credential $credential -Tenant $tenantId | Out-Null + } + else { + Connect-AzAccount | Out-Null + } + $azContext = Get-AzContext + } + if ($PSCmdlet.ShouldProcess("Setting Fabric authentication token for $($azContext.Account)")) { + Write-Output "Connnected: $($azContext.Account)" + Write-Output "Fabric ResourceUrl: $($FabricSession.ResourceUrl)" + + $FabricSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $FabricSession.ResourceUrl) + $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($FabricSession.AccessToken.Token) + $FabricSession.FabricToken = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) + Write-Verbose "Setup headers for API calls" + $FabricSession.HeaderParams = @{ Authorization = $FabricSession.AccessToken.Type + ' ' + $FabricSession.FabricToken } + + Write-Output "Azure BaseApiUrl: $($FabricSession.ResourceUrl)" + $script:AzureSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $AzureSession.BaseApiUrl) + $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzureSession.AccessToken.Token) + $script:AzureSession.Token = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) + $AzureSession.HeaderParams = @{ Authorization = $AzureSession.AccessToken.Type + ' ' + $AzureSession.Token } + + # Copy session values to exposed $FabricConfig + $FabricConfig.TenantIdGlobal = $FabricSession.AccessToken.TenantId + $FabricConfig.TokenExpiresOn = $FabricSession.AccessToken.ExpiresOn + $FabricConfig.FabricHeaders = $FabricSession.HeaderParams + + return $($FabricSession.FabricToken) + + } +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 new file mode 100644 index 00000000..09524fb4 --- /dev/null +++ b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Retrieves Spark Job Definition details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves SparkJobDefinition details from a specified workspace using either the provided SparkJobDefinitionId or SparkJobDefinitionName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionId + The unique identifier of the SparkJobDefinition to retrieve. This parameter is optional. + +.PARAMETER SparkJobDefinitionName + The name of the SparkJobDefinition to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" + This example retrieves the SparkJobDefinition details for the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionName "My SparkJobDefinition" + This example retrieves the SparkJobDefinition details for the SparkJobDefinition named "My SparkJobDefinition" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricSparkJobDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SparkJobDefinitionName + ) + try { + + # Step 1: Handle ambiguous input + if ($SparkJobDefinitionId -and $SparkJobDefinitionName) { + Write-Message -Message "Both 'SparkJobDefinitionId' and 'SparkJobDefinitionName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $SparkJobDefinitions = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions" -f $FabricConfig.BaseUrl, $WorkspaceId + # Step 3: Loop to retrieve data with continuation token + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $SparkJobDefinitions += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $SparkJobDefinition = if ($SparkJobDefinitionId) { + $SparkJobDefinitions | Where-Object { $_.Id -eq $SparkJobDefinitionId } + } + elseif ($SparkJobDefinitionName) { + $SparkJobDefinitions | Where-Object { $_.DisplayName -eq $SparkJobDefinitionName } + } + else { + # Return all SparkJobDefinitions if no filter is provided + Write-Message -Message "No filter provided. Returning all SparkJobDefinitions." -Level Debug + $SparkJobDefinitions + } + + # Step 9: Handle results + if ($SparkJobDefinition) { + Write-Message -Message "Spark Job Definition found in the Workspace '$WorkspaceId'." -Level Debug + return $SparkJobDefinition + } + else { + Write-Message -Message "No Spark Job Definition found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve SparkJobDefinition. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 new file mode 100644 index 00000000..b1db8a91 --- /dev/null +++ b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 @@ -0,0 +1,124 @@ +<# +.SYNOPSIS + Retrieves the definition of an SparkJobDefinition from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves the definition of an SparkJobDefinition from a specified workspace using the provided SparkJobDefinitionId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionId + The unique identifier of the SparkJobDefinition to retrieve the definition for. This parameter is optional. + +.PARAMETER SparkJobDefinitionFormat + The format in which to retrieve the SparkJobDefinition definition. This parameter is optional. + +.EXAMPLE + Get-FabricSparkJobDefinitionDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" + This example retrieves the definition of the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricSparkJobDefinitionDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" -SparkJobDefinitionFormat "json" + This example retrieves the definition of the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345" in JSON format. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Get-FabricSparkJobDefinitionDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('SparkJobDefinitionV1')] + [string]$SparkJobDefinitionFormat = "SparkJobDefinitionV1" + ) + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId + + if ($SparkJobDefinitionFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $SparkJobDefinitionFormat + } + + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "Spark Job Definition '$SparkJobDefinitionId' definition retrieved successfully!" -Level Debug + return $response.definition.parts + } + 202 { + + Write-Message -Message "Getting Spark Job Definition '$SparkJobDefinitionId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Spark Job Definition. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 new file mode 100644 index 00000000..99d992ef --- /dev/null +++ b/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 @@ -0,0 +1,194 @@ +<# +.SYNOPSIS + Creates a new SparkJobDefinition in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new SparkJobDefinition + in the specified workspace. It supports optional parameters for SparkJobDefinition description and path definitions. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SparkJobDefinition will be created. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionName + The name of the SparkJobDefinition to be created. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionDescription + An optional description for the SparkJobDefinition. + +.PARAMETER SparkJobDefinitionPathDefinition + An optional path to the SparkJobDefinition definition file to upload. + +.PARAMETER SparkJobDefinitionPathPlatformDefinition + An optional path to the platform-specific definition file to upload. + +.EXAMPLE + New-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionName "New SparkJobDefinition" -SparkJobDefinitionDescription "Description of the new SparkJobDefinition" + This example creates a new SparkJobDefinition named "New SparkJobDefinition" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function New-FabricSparkJobDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SparkJobDefinitionName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionPathPlatformDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $SparkJobDefinitionName + } + + if ($SparkJobDefinitionDescription) { + $body.description = $SparkJobDefinitionDescription + } + if ($SparkJobDefinitionPathDefinition) { + $SparkJobDefinitionEncodedContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathDefinition + + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "SparkJobDefinitionV1" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "SparkJobDefinitionProperties.json" + payload = $SparkJobDefinitionEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error + return $null + } + } + + if ($SparkJobDefinitionPathPlatformDefinition) { + $SparkJobDefinitionEncodedPlatformContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "SparkJobDefinitionV1" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $SparkJobDefinitionEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Spark Job Definition. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 new file mode 100644 index 00000000..68f8e184 --- /dev/null +++ b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Removes an SparkJobDefinition from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove an SparkJobDefinition + from the specified workspace using the provided WorkspaceId and SparkJobDefinitionId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the SparkJobDefinition will be removed. + +.PARAMETER SparkJobDefinitionId + The unique identifier of the SparkJobDefinition to be removed. + +.EXAMPLE + Remove-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" + This example removes the SparkJobDefinition with ID "SparkJobDefinition-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Remove-FabricSparkJobDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle response + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Spark Job Definition '$SparkJobDefinitionId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete SparkJobDefinition '$SparkJobDefinitionId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 b/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 new file mode 100644 index 00000000..43bb3dac --- /dev/null +++ b/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 @@ -0,0 +1,118 @@ +<# +.SYNOPSIS + Starts a Fabric Spark Job Definition on demand. + +.DESCRIPTION + This function initiates a Spark Job Definition on demand within a specified workspace. + It constructs the appropriate API endpoint URL and makes a POST request to start the job. + The function can optionally wait for the job to complete based on the 'waitForCompletion' parameter. + +.PARAMETER WorkspaceId + The ID of the workspace where the Spark Job Definition is located. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionId + The ID of the Spark Job Definition to be started. This parameter is mandatory. + +.PARAMETER JobType + The type of job to be started. The default value is 'sparkjob'. This parameter is optional. + +.PARAMETER waitForCompletion + A boolean flag indicating whether to wait for the job to complete. The default value is $false. This parameter is optional. + +.EXAMPLE + Start-FabricSparkJobDefinitionOnDemand -WorkspaceId "workspace123" -SparkJobDefinitionId "jobdef456" -waitForCompletion $true + +.NOTES + Ensure that the necessary authentication tokens are valid before running this function. + The function logs detailed messages for debugging and informational purposes. +#> +function Start-FabricSparkJobDefinitionOnDemand { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('sparkjob')] + [string]$JobType = "sparkjob", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$waitForCompletion = $false + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/jobs/instances?jobType={3}" -f $FabricConfig.BaseUrl, $WorkspaceId , $SparkJobDefinitionId, $JobType + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Spark Job Definition on demand successfully initiated for SparkJobDefinition '$SparkJobDefinition.displayName'." -Level Info + return $response + } + 202 { + Write-Message -Message "Spark Job Definition on demand accepted and is now running in the background. Job execution is in progress." -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + + if ($waitForCompletion -eq $true) { + Write-Message -Message "Getting Long Running Operation status" -Level Debug + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + return $operationStatus + } + else { + Write-Message -Message "The operation is running asynchronously." -Level Info + Write-Message -Message "Use the returned details to check the operation status." -Level Info + Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info + $operationDetails = [PSCustomObject]@{ + OperationId = $operationId + Location = $location + RetryAfter = $retryAfter + } + return $operationDetails + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to start Spark Job Definition on demand. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 new file mode 100644 index 00000000..9b2c641a --- /dev/null +++ b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 @@ -0,0 +1,104 @@ +<# +.SYNOPSIS + Updates an existing SparkJobDefinition in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing SparkJobDefinition + in the specified workspace. It supports optional parameters for SparkJobDefinition description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is optional. + +.PARAMETER SparkJobDefinitionId + The unique identifier of the SparkJobDefinition to be updated. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionName + The new name of the SparkJobDefinition. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionDescription + An optional new description for the SparkJobDefinition. + +.EXAMPLE + Update-FabricSparkJobDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" -SparkJobDefinitionName "Updated SparkJobDefinition" -SparkJobDefinitionDescription "Updated description" + This example updates the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Update-FabricSparkJobDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SparkJobDefinitionName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionDescription + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $SparkJobDefinitionName + } + + if ($SparkJobDefinitionDescription) { + $body.description = $SparkJobDefinitionDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update SparkJobDefinition. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 new file mode 100644 index 00000000..cca084d7 --- /dev/null +++ b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 @@ -0,0 +1,168 @@ +<# +.SYNOPSIS + Updates the definition of an existing SparkJobDefinition in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SparkJobDefinition + in the specified workspace. It supports optional parameters for SparkJobDefinition definition and platform-specific definition. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the SparkJobDefinition exists. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionId + The unique identifier of the SparkJobDefinition to be updated. This parameter is mandatory. + +.PARAMETER SparkJobDefinitionPathDefinition + An optional path to the SparkJobDefinition definition file to upload. + +.PARAMETER SparkJobDefinitionPathPlatformDefinition + An optional path to the platform-specific definition file to upload. + +.EXAMPLE + Update-FabricSparkJobDefinitionDefinition -WorkspaceId "workspace-12345" -SparkJobDefinitionId "SparkJobDefinition-67890" -SparkJobDefinitionPathDefinition "C:\Path\To\SparkJobDefinitionDefinition.json" + This example updates the definition of the SparkJobDefinition with ID "SparkJobDefinition-67890" in the workspace with ID "workspace-12345" using the provided definition file. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Update-FabricSparkJobDefinitionDefinition { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkJobDefinitionPathPlatformDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId + + #if ($UpdateMetadata -eq $true) { + if($SparkJobDefinitionPathPlatformDefinition){ + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + definition = @{ + format = "SparkJobDefinitionV1" + parts = @() + } + } + + if ($SparkJobDefinitionPathDefinition) { + $SparkJobDefinitionEncodedContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathDefinition + + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = "SparkJobDefinitionV1.json" + payload = $SparkJobDefinitionEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error + return $null + } + } + + if ($SparkJobDefinitionPathPlatformDefinition) { + $SparkJobDefinitionEncodedPlatformContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathPlatformDefinition + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) { + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $SparkJobDefinitionEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Update definition for Spark Job Definition '$SparkJobDefinitionId' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Update definition for Spark Job Definition '$SparkJobDefinitionId' accepted. Operation in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Spark Job Definition. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark/Get-FabricSparkCustomPool.ps1 b/source/Public/Spark/Get-FabricSparkCustomPool.ps1 new file mode 100644 index 00000000..6bf0441e --- /dev/null +++ b/source/Public/Spark/Get-FabricSparkCustomPool.ps1 @@ -0,0 +1,161 @@ +<# +.SYNOPSIS + Retrieves Spark custom pools from a specified workspace. + +.DESCRIPTION + This function retrieves all Spark custom pools from a specified workspace using the provided WorkspaceId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + The function supports filtering by SparkCustomPoolId or SparkCustomPoolName, but not both simultaneously. + +.PARAMETER WorkspaceId + The ID of the workspace from which to retrieve Spark custom pools. This parameter is mandatory. + +.PARAMETER SparkCustomPoolId + The ID of the specific Spark custom pool to retrieve. This parameter is optional. + +.PARAMETER SparkCustomPoolName + The name of the specific Spark custom pool to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricSparkCustomPool -WorkspaceId "12345" + This example retrieves all Spark custom pools from the workspace with ID "12345". + +.EXAMPLE + Get-FabricSparkCustomPool -WorkspaceId "12345" -SparkCustomPoolId "pool1" + This example retrieves the Spark custom pool with ID "pool1" from the workspace with ID "12345". + +.EXAMPLE + Get-FabricSparkCustomPool -WorkspaceId "12345" -SparkCustomPoolName "MyPool" + This example retrieves the Spark custom pool with name "MyPool" from the workspace with ID "12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + - Handles continuation tokens to retrieve all Spark custom pools if there are multiple pages of results. + + Author: Tiago Balabuch +#> +function Get-FabricSparkCustomPool { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SparkCustomPoolId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SparkCustomPoolName + ) + + try { + # Step 1: Handle ambiguous input + if ($SparkCustomPoolId -and $SparkCustomPoolName) { + Write-Message -Message "Both 'SparkCustomPoolId' and 'SparkCustomPoolName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $SparkCustomPools = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/spark/pools" -f $FabricConfig.BaseUrl, $WorkspaceId + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $SparkCustomPools += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $SparkCustomPool = if ($SparkCustomPoolId) { + $SparkCustomPools | Where-Object { $_.id -eq $SparkCustomPoolId } + } + elseif ($SparkCustomPoolName) { + $SparkCustomPools | Where-Object { $_.name -eq $SparkCustomPoolName } + } + else { + # Return all SparkCustomPools if no filter is provided + Write-Message -Message "No filter provided. Returning all SparkCustomPools." -Level Debug + $SparkCustomPools + } + + # Step 9: Handle results + if ($SparkCustomPool) { + Write-Message -Message "SparkCustomPool found matching the specified criteria." -Level Debug + return $SparkCustomPool + } + else { + Write-Message -Message "No SparkCustomPool found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve SparkCustomPool. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Spark/Get-FabricSparkSettings.ps1 b/source/Public/Spark/Get-FabricSparkSettings.ps1 new file mode 100644 index 00000000..11c5c51f --- /dev/null +++ b/source/Public/Spark/Get-FabricSparkSettings.ps1 @@ -0,0 +1,119 @@ +<# +.SYNOPSIS + Retrieves Spark settings from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves Spark settings from a specified workspace using the provided WorkspaceId. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which to retrieve Spark settings. This parameter is mandatory. + +.EXAMPLE + Get-FabricSparkSettings -WorkspaceId "workspace-12345" + This example retrieves the Spark settings for the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Get-FabricSparkSettings { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId + ) + + try { + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $SparkSettings = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/spark/settings" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $SparkSettings += $response + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 9: Handle results + if ($SparkSettings) { + Write-Message -Message " Returning all Spark Settings." -Level Debug + # Return all Spark Settings + return $SparkSettings + } + else { + Write-Message -Message "No SparkSettings found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve SparkSettings. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Spark/New-FabricSparkCustomPool.ps1 b/source/Public/Spark/New-FabricSparkCustomPool.ps1 new file mode 100644 index 00000000..848677ce --- /dev/null +++ b/source/Public/Spark/New-FabricSparkCustomPool.ps1 @@ -0,0 +1,190 @@ +<# +.SYNOPSIS + Creates a new Spark custom pool in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new Spark custom pool + in the specified workspace. It supports various parameters for Spark custom pool configuration. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Spark custom pool will be created. This parameter is mandatory. + +.PARAMETER SparkCustomPoolName + The name of the Spark custom pool to be created. This parameter is mandatory. + +.PARAMETER NodeFamily + The family of nodes to be used in the Spark custom pool. This parameter is mandatory and must be 'MemoryOptimized'. + +.PARAMETER NodeSize + The size of the nodes to be used in the Spark custom pool. This parameter is mandatory and must be one of 'Large', 'Medium', 'Small', 'XLarge', 'XXLarge'. + +.PARAMETER AutoScaleEnabled + Specifies whether auto-scaling is enabled for the Spark custom pool. This parameter is mandatory. + +.PARAMETER AutoScaleMinNodeCount + The minimum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. + +.PARAMETER AutoScaleMaxNodeCount + The maximum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationEnabled + Specifies whether dynamic executor allocation is enabled for the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationMinExecutors + The minimum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationMaxExecutors + The maximum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. + +.EXAMPLE + New-FabricSparkCustomPool -WorkspaceId "workspace-12345" -SparkCustomPoolName "New Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 + This example creates a new Spark custom pool named "New Spark Pool" in the workspace with ID "workspace-12345" with the specified configuration. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> + +function New-FabricSparkCustomPool { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$SparkCustomPoolName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('MemoryOptimized')] + [string]$NodeFamily, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Large', 'Medium', 'Small', 'XLarge', 'XXLarge')] + [string]$NodeSize, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [bool]$AutoScaleEnabled, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$AutoScaleMinNodeCount, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$AutoScaleMaxNodeCount, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [bool]$DynamicExecutorAllocationEnabled, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$DynamicExecutorAllocationMinExecutors, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$DynamicExecutorAllocationMaxExecutors + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/spark/pools" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + name = $SparkCustomPoolName + nodeFamily = $NodeFamily + nodeSize = $NodeSize + autoScale = @{ + enabled = $AutoScaleEnabled + minNodeCount = $AutoScaleMinNodeCount + maxNodeCount = $AutoScaleMaxNodeCount + } + dynamicExecutorAllocation = @{ + enabled = $DynamicExecutorAllocationEnabled + minExecutors = $DynamicExecutorAllocationMinExecutors + maxExecutors = $DynamicExecutorAllocationMaxExecutors + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "SparkCustomPool '$SparkCustomPoolName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "SparkCustomPool '$SparkCustomPoolName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create SparkCustomPool. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 new file mode 100644 index 00000000..2b748e65 --- /dev/null +++ b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Removes a Spark custom pool from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove a Spark custom pool + from the specified workspace using the provided WorkspaceId and SparkCustomPoolId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the Spark custom pool will be removed. + +.PARAMETER SparkCustomPoolId + The unique identifier of the Spark custom pool to be removed. + +.EXAMPLE + Remove-FabricSparkCustomPool -WorkspaceId "workspace-12345" -SparkCustomPoolId "pool-67890" + This example removes the Spark custom pool with ID "pool-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Remove-FabricSparkCustomPool { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SparkCustomPoolId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/spark/pools/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkCustomPoolId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Spark Custom Pool '$SparkCustomPoolId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete SparkCustomPool '$SparkCustomPoolId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark/Update-FabricSparkCustomPool.ps1 b/source/Public/Spark/Update-FabricSparkCustomPool.ps1 new file mode 100644 index 00000000..9e4bcfd5 --- /dev/null +++ b/source/Public/Spark/Update-FabricSparkCustomPool.ps1 @@ -0,0 +1,164 @@ +<# +.SYNOPSIS + Updates an existing Spark custom pool in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool + in the specified workspace. It supports various parameters for Spark custom pool configuration. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Spark custom pool exists. This parameter is mandatory. + +.PARAMETER SparkCustomPoolId + The unique identifier of the Spark custom pool to be updated. This parameter is mandatory. + +.PARAMETER InstancePoolName + The new name of the Spark custom pool. This parameter is mandatory. + +.PARAMETER NodeFamily + The family of nodes to be used in the Spark custom pool. This parameter is mandatory and must be 'MemoryOptimized'. + +.PARAMETER NodeSize + The size of the nodes to be used in the Spark custom pool. This parameter is mandatory and must be one of 'Large', 'Medium', 'Small', 'XLarge', 'XXLarge'. + +.PARAMETER AutoScaleEnabled + Specifies whether auto-scaling is enabled for the Spark custom pool. This parameter is mandatory. + +.PARAMETER AutoScaleMinNodeCount + The minimum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. + +.PARAMETER AutoScaleMaxNodeCount + The maximum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationEnabled + Specifies whether dynamic executor allocation is enabled for the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationMinExecutors + The minimum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationMaxExecutors + The maximum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. + +.EXAMPLE + Update-FabricSparkCustomPool -WorkspaceId "workspace-12345" -SparkCustomPoolId "pool-67890" -InstancePoolName "Updated Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 + This example updates the Spark custom pool with ID "pool-67890" in the workspace with ID "workspace-12345" with a new name and configuration. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricSparkCustomPool { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SparkCustomPoolId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$InstancePoolName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('MemoryOptimized')] + [string]$NodeFamily, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Large', 'Medium', 'Small', 'XLarge', 'XXLarge')] + [string]$NodeSize, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [bool]$AutoScaleEnabled, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$AutoScaleMinNodeCount, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$AutoScaleMaxNodeCount, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [bool]$DynamicExecutorAllocationEnabled, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$DynamicExecutorAllocationMinExecutors, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [int]$DynamicExecutorAllocationMaxExecutors + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/spark/pools/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkCustomPoolId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + name = $InstancePoolName + nodeFamily = $NodeFamily + nodeSize = $NodeSize + autoScale = @{ + enabled = $AutoScaleEnabled + minNodeCount = $AutoScaleMinNodeCount + maxNodeCount = $AutoScaleMaxNodeCount + } + dynamicExecutorAllocation = @{ + enabled = $DynamicExecutorAllocationEnabled + minExecutors = $DynamicExecutorAllocationMinExecutors + maxExecutors = $DynamicExecutorAllocationMaxExecutors + } + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Spark Custom Pool '$SparkCustomPoolName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update SparkCustomPool. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Spark/Update-FabricSparkSettings.ps1 b/source/Public/Spark/Update-FabricSparkSettings.ps1 new file mode 100644 index 00000000..9320af81 --- /dev/null +++ b/source/Public/Spark/Update-FabricSparkSettings.ps1 @@ -0,0 +1,189 @@ +<# +.SYNOPSIS + Updates an existing Spark custom pool in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool + in the specified workspace. It supports various parameters for Spark custom pool configuration. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Spark custom pool exists. This parameter is mandatory. + +.PARAMETER SparkSettingsId + The unique identifier of the Spark custom pool to be updated. This parameter is mandatory. + +.PARAMETER InstancePoolName + The new name of the Spark custom pool. This parameter is mandatory. + +.PARAMETER NodeFamily + The family of nodes to be used in the Spark custom pool. This parameter is mandatory and must be 'MemoryOptimized'. + +.PARAMETER NodeSize + The size of the nodes to be used in the Spark custom pool. This parameter is mandatory and must be one of 'Large', 'Medium', 'Small', 'XLarge', 'XXLarge'. + +.PARAMETER AutoScaleEnabled + Specifies whether auto-scaling is enabled for the Spark custom pool. This parameter is mandatory. + +.PARAMETER AutoScaleMinNodeCount + The minimum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. + +.PARAMETER AutoScaleMaxNodeCount + The maximum number of nodes for auto-scaling in the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationEnabled + Specifies whether dynamic executor allocation is enabled for the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationMinExecutors + The minimum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. + +.PARAMETER DynamicExecutorAllocationMaxExecutors + The maximum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. + +.EXAMPLE + Update-FabricSparkSettings -WorkspaceId "workspace-12345" -SparkSettingsId "pool-67890" -InstancePoolName "Updated Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 + This example updates the Spark custom pool with ID "pool-67890" in the workspace with ID "workspace-12345" with a new name and configuration. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricSparkSettings { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$automaticLogEnabled, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$notebookInteractiveRunEnabled, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$customizeComputeEnabled, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$defaultPoolName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Workspace', 'Capacity')] + [string]$defaultPoolType, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [int]$starterPoolMaxNode, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [int]$starterPoolMaxExecutors, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EnvironmentRuntimeVersion + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/spark/settings" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkSettingsId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + # Construct the request body with optional properties + + $body = @{} + + if ($PSBoundParameters.ContainsKey('automaticLogEnabled')) { + $body.automaticLog = @{ + enabled = $automaticLogEnabled + } + } + + if ($PSBoundParameters.ContainsKey('notebookInteractiveRunEnabled')) { + $body.highConcurrency = @{ + notebookInteractiveRunEnabled = $notebookInteractiveRunEnabled + } + } + + if ($PSBoundParameters.ContainsKey('customizeComputeEnabled') ) { + $body.pool = @{ + customizeComputeEnabled = $customizeComputeEnabled + } + } + if ($PSBoundParameters.ContainsKey('defaultPoolName') -or $PSBoundParameters.ContainsKey('defaultPoolType')) { + if ($PSBoundParameters.ContainsKey('defaultPoolName') -and $PSBoundParameters.ContainsKey('defaultPoolType')) { + $body.pool = @{ + defaultPool = @{ + name = $defaultPoolName + type = $defaultPoolType + } + } + } else { + Write-Message -Message "Both 'defaultPoolName' and 'defaultPoolType' must be provided together." -Level Error + throw + } + } + + if ($PSBoundParameters.ContainsKey('EnvironmentName') -or $PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) { + $body.environment = @{ + name = $EnvironmentName + } + } + if ($PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) { + $body.environment = @{ + runtimeVersion = $EnvironmentRuntimeVersion + } + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Spark Custom Pool '$SparkSettingsName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update SparkSettings. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Tenant copy/Get-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant copy/Get-FabricCapacityTenantSettingOverrides.ps1 new file mode 100644 index 00000000..e36f1f3a --- /dev/null +++ b/source/Public/Tenant copy/Get-FabricCapacityTenantSettingOverrides.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS +Retrieves tenant setting overrides for a specific capacity or all capacities in the Fabric tenant. + +.DESCRIPTION +The `Get-FabricCapacityTenantSettingOverrides` function retrieves tenant setting overrides for a specific capacity or all capacities in the Fabric tenant by making a GET request to the appropriate API endpoint. If a `capacityId` is provided, the function retrieves overrides for that specific capacity. Otherwise, it retrieves overrides for all capacities. + +.PARAMETER capacityId +The ID of the capacity for which tenant setting overrides should be retrieved. If not provided, overrides for all capacities will be retrieved. + +.EXAMPLE +Get-FabricCapacityTenantSettingOverrides + +Returns all capacities tenant setting overrides. + +.EXAMPLE +Get-FabricCapacityTenantSettingOverrides -capacityId "12345" + +Returns tenant setting overrides for the capacity with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> +function Get-FabricCapacityTenantSettingOverrides { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$capacityId + ) + + try { + # Step 1: Validate authentication token before making API requests + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Step 2: Construct the API endpoint URL for retrieving capacity tenant setting overrides + if ($capacityId) { + $apiEndpointURI = "{0}/admin/capacities/{1}/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl, $capacityId + $message = "Successfully retrieved tenant setting overrides for capacity ID: $capacityId." + } else { + $apiEndpointURI = "{0}/admin/capacities/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl + $message = "Successfully retrieved capacity tenant setting overrides." + } + Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Invoke the Fabric API to retrieve capacity tenant setting overrides + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + # Step 4: Check if any capacity tenant setting overrides were retrieved and handle results accordingly + if ($response) { + Write-Message -Message $message -Level Debug + return $response + } + else { + Write-Message -Message "No capacity tenant setting overrides found." -Level Warning + return $null + } + } + catch { + # Step 5: Log detailed error information if the API request fails + $errorDetails = $_.Exception.Message + Write-Message -Message "Error retrieving capacity tenant setting overrides: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Tenant copy/Get-FabricDomainTenantSettingOverrides.ps1 b/source/Public/Tenant copy/Get-FabricDomainTenantSettingOverrides.ps1 new file mode 100644 index 00000000..22ce3d3a --- /dev/null +++ b/source/Public/Tenant copy/Get-FabricDomainTenantSettingOverrides.ps1 @@ -0,0 +1,55 @@ +<# +.SYNOPSIS +Retrieves tenant setting overrides for a specific domain or all capacities in the Fabric tenant. + +.DESCRIPTION +The `Get-FabricDomainTenantSettingOverrides` function retrieves tenant setting overrides for all domains in the Fabric tenant by making a GET request to the designated API endpoint. The function ensures token validity before making the request and handles the response appropriately. + +.EXAMPLE +Get-FabricDomainTenantSettingOverrides + +Fetches tenant setting overrides for all domains in the Fabric tenant. + +.NOTES +- Requires the `$FabricConfig` global configuration, which must include `BaseUrl` and `FabricHeaders`. +- Ensures token validity by invoking `Test-TokenExpired` before making the API request. +- Logs detailed messages for debugging and error handling. + +Author: Tiago Balabuch +#> +function Get-FabricDomainTenantSettingOverrides { + [CmdletBinding()] + param ( ) + + try { + # Step 1: Validate authentication token before making API requests + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Step 2: Construct the API endpoint URL for retrieving domain tenant setting overrides + $apiEndpointURI = "{0}/admin/domains/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl + Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Invoke the Fabric API to retrieve domain tenant setting overrides + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + # Step 4: Check if any domain tenant setting overrides were retrieved and handle results accordingly + if ($response) { + Write-Message -Message "Successfully retrieved domain tenant setting overrides." -Level Debug + return $response + } + else { + Write-Message -Message "No domain tenant setting overrides found." -Level Warning + return $null + } + } + catch { + # Step 5: Log detailed error information if the API request fails + $errorDetails = $_.Exception.Message + Write-Message -Message "Error retrieving domain tenant setting overrides: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Tenant copy/Get-FabricTenantSetting.ps1 b/source/Public/Tenant copy/Get-FabricTenantSetting.ps1 new file mode 100644 index 00000000..655e1c9c --- /dev/null +++ b/source/Public/Tenant copy/Get-FabricTenantSetting.ps1 @@ -0,0 +1,78 @@ +<# +.SYNOPSIS +Retrieves tenant settings from the Fabric environment. + +.DESCRIPTION +The `Get-FabricTenantSetting` function retrieves tenant settings for a Fabric environment by making a GET request to the appropriate API endpoint. Optionally, it filters the results by the `SettingTitle` parameter. + +.PARAMETER SettingTitle +(Optional) The title of a specific tenant setting to filter the results. + +.EXAMPLE +Get-FabricTenantSetting + +Returns all tenant settings. + +.EXAMPLE +Get-FabricTenantSetting -SettingTitle "SomeSetting" + +Returns the tenant setting with the title "SomeSetting". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Is-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Get-FabricTenantSetting { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$SettingTitle + ) + + try { + # Step 1: Validate authentication token before making API requests + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Step 2: Construct the API endpoint URL for retrieving tenant settings + $apiEndpointURI = "{0}/admin/tenantsettings" -f $FabricConfig.BaseUrl + Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Invoke the Fabric API to retrieve tenant settings + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + # Step 4: Filter tenant settings based on the provided SettingTitle parameter (if specified) + $settings = if ($SettingTitle) { + Write-Message -Message "Filtering tenant settings by title: '$SettingTitle'" -Level Debug + $response.tenantSettings | Where-Object { $_.title -eq $SettingTitle } + } + else { + Write-Message -Message "No filter specified. Retrieving all tenant settings." -Level Debug + $response.tenantSettings + } + + # Step 5: Check if any tenant settings were found and return results accordingly + if ($settings) { + Write-Message -Message "Tenant settings successfully retrieved." -Level Debug + return $settings + } + else { + Write-Message -Message "No tenant settings found matching the specified criteria." -Level Warning + return $null + } + } + catch { + # Step 6: Log detailed error information if the API request fails + $errorDetails = $_.Exception.Message + Write-Message -Message "Error retrieving tenant settings: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Tenant copy/Get-FabricWorkspaceTenantSettingOverrides.ps1 b/source/Public/Tenant copy/Get-FabricWorkspaceTenantSettingOverrides.ps1 new file mode 100644 index 00000000..c04e8ed1 --- /dev/null +++ b/source/Public/Tenant copy/Get-FabricWorkspaceTenantSettingOverrides.ps1 @@ -0,0 +1,54 @@ +<# +.SYNOPSIS +Retrieves tenant setting overrides for all workspaces in the Fabric tenant. + +.DESCRIPTION +The `Get-FabricWorkspaceTenantSettingOverrides` function retrieves tenant setting overrides for all workspaces in the Fabric tenant by making a GET request to the appropriate API endpoint. The function validates the authentication token before making the request and handles the response accordingly. + +.EXAMPLE +Get-FabricWorkspaceTenantSettingOverrides + +Returns all workspaces tenant setting overrides. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> +function Get-FabricWorkspaceTenantSettingOverrides { + [CmdletBinding()] + param ( ) + + try { + # Step 1: Validate authentication token before making API requests + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Step 2: Construct the API endpoint URL for retrieving workspaces tenant setting overrides + $apiEndpointURI = "{0}/admin/workspaces/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl + Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Invoke the Fabric API to retrieve workspaces tenant setting overrides + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + # Step 4: Check if any workspaces tenant setting overrides were retrieved and handle results accordingly + if ($response) { + Write-Message -Message "Successfully retrieved workspaces tenant setting overrides." -Level Debug + return $response + } + else { + Write-Message -Message "No workspaces tenant setting overrides found." -Level Warning + return $null + } + } + catch { + # Step 5: Log detailed error information if the API request fails + $errorDetails = $_.Exception.Message + Write-Message -Message "Error retrieving workspaces tenant setting overrides: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Tenant copy/Revoke-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant copy/Revoke-FabricCapacityTenantSettingOverrides.ps1 new file mode 100644 index 00000000..7665ddea --- /dev/null +++ b/source/Public/Tenant copy/Revoke-FabricCapacityTenantSettingOverrides.ps1 @@ -0,0 +1,60 @@ +<# +.SYNOPSIS +Removes a tenant setting override from a specific capacity in the Fabric tenant. + +.DESCRIPTION +The `Revoke-FabricCapacityTenantSettingOverrides` function deletes a specific tenant setting override for a given capacity in the Fabric tenant by making a DELETE request to the appropriate API endpoint. + +.PARAMETER capacityId +The unique identifier of the capacity from which the tenant setting override will be removed. + +.PARAMETER tenantSettingName +The name of the tenant setting override to be removed. + +.EXAMPLE +Revoke-FabricCapacityTenantSettingOverrides -capacityId "12345" -tenantSettingName "ExampleSetting" + +Removes the tenant setting override named "ExampleSetting" from the capacity with ID "12345". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> +function Revoke-FabricCapacityTenantSettingOverrides { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$capacityId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$tenantSettingName + ) + try { + # Step 1: Validate authentication token before making API requests + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Step 2: Construct the API endpoint URL for retrieving capacity tenant setting overrides + $apiEndpointURI = "{0}/admin/capacities/{1}/delegatedTenantSettingOverrides/{2}" -f $FabricConfig.BaseUrl, $capacityId, $tenantSettingName + Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Invoke the Fabric API to retrieve capacity tenant setting overrides + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Delete + + Write-Message -Message "Successfully removed the tenant setting override '$tenantSettingName' from the capacity with ID '$capacityId'." -Level Info + return $response + } + catch { + # Step 5: Log detailed error information if the API request fails + $errorDetails = $_.Exception.Message + Write-Message -Message "Error retrieving capacity tenant setting overrides: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Tenant copy/Update-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant copy/Update-FabricCapacityTenantSettingOverrides.ps1 new file mode 100644 index 00000000..f352b771 --- /dev/null +++ b/source/Public/Tenant copy/Update-FabricCapacityTenantSettingOverrides.ps1 @@ -0,0 +1,136 @@ +<# +.SYNOPSIS +Updates tenant setting overrides for a specified capacity ID. + +.DESCRIPTION +The `Update-FabricCapacityTenantSettingOverrides` function updates tenant setting overrides in a Fabric environment by making a POST request to the appropriate API endpoint. It allows specifying settings such as enabling tenant settings, delegating to a workspace, and including or excluding security groups. + +.PARAMETER CapacityId +(Mandatory) The ID of the capacity for which the tenant setting overrides are being updated. + +.PARAMETER SettingTitle +(Mandatory) The title of the tenant setting to be updated. + +.PARAMETER EnableTenantSetting +(Mandatory) Indicates whether the tenant setting should be enabled. + +.PARAMETER DelegateToWorkspace +(Optional) Specifies the workspace to which the setting should be delegated. + +.PARAMETER EnabledSecurityGroups +(Optional) A JSON array of security groups to be enabled, each containing `graphId` and `name` properties. + +.PARAMETER ExcludedSecurityGroups +(Optional) A JSON array of security groups to be excluded, each containing `graphId` and `name` properties. + +.EXAMPLE +Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" + +Updates the tenant setting "SomeSetting" for the capacity with ID "12345" and enables it. + +.EXAMPLE +Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" -EnabledSecurityGroups @(@{graphId="1";name="Group1"},@{graphId="2";name="Group2"}) + +Updates the tenant setting "SomeSetting" for the capacity with ID "12345", enables it, and specifies security groups to include. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricCapacityTenantSettingOverrides { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$CapacityId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$SettingTitle, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [bool]$EnableTenantSetting, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$DelegateToWorkspace, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [System.Object]$EnabledSecurityGroups, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [System.Object]$ExcludedSecurityGroups + ) + + try { + # Validate authentication token + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Validate Security Groups if provided + if ($EnabledSecurityGroups) { + foreach ($enabledGroup in $EnabledSecurityGroups) { + if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) { + throw "Each enabled security group must contain 'graphId' and 'name' properties." + } + } + } + + if ($ExcludedSecurityGroups) { + foreach ($excludedGroup in $ExcludedSecurityGroups) { + if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) { + throw "Each excluded security group must contain 'graphId' and 'name' properties." + } + } + } + + # Construct API endpoint URL + $apiEndpointURI = "{0}/admin/capacities/{1}/delegatedTenantSettingOverrides" -f $FabricConfig.BaseUrl, $CapacityId + Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + + # Construct request body + $body = @{ + EnableTenantSetting = $EnableTenantSetting + SettingTitle = $SettingTitle + } + + if ($DelegateToWorkspace) { + $body.delegateToWorkspace = $DelegateToWorkspace + } + + if ($EnabledSecurityGroups) { + $body.enabledSecurityGroups = $EnabledSecurityGroups + } + + if ($ExcludedSecurityGroups) { + $body.excludedSecurityGroups = $ExcludedSecurityGroups + } + + # Convert body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 4 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Invoke Fabric API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post ` + -Body $bodyJson + + Write-Message -Message "Successfully updated capacity tenant setting overrides for CapacityId: $CapacityId and SettingTitle: $SettingTitle." -Level Info + return $response + } + catch { + $errorDetails = $_.Exception.Message + Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error + } +} + diff --git a/source/Public/Tenant copy/Update-FabricTenantSetting.ps1 b/source/Public/Tenant copy/Update-FabricTenantSetting.ps1 new file mode 100644 index 00000000..0a701151 --- /dev/null +++ b/source/Public/Tenant copy/Update-FabricTenantSetting.ps1 @@ -0,0 +1,164 @@ +<# +.SYNOPSIS +Updates tenant setting overrides for a specified capacity ID. + +.DESCRIPTION +The `Update-FabricCapacityTenantSettingOverrides` function updates tenant setting overrides in a Fabric environment by making a POST request to the appropriate API endpoint. It allows specifying settings such as enabling tenant settings, delegating to a workspace, and including or excluding security groups. + +.PARAMETER CapacityId +(Mandatory) The ID of the capacity for which the tenant setting overrides are being updated. + +.PARAMETER SettingTitle +(Mandatory) The title of the tenant setting to be updated. + +.PARAMETER EnableTenantSetting +(Mandatory) Indicates whether the tenant setting should be enabled. + +.PARAMETER DelegateToWorkspace +(Optional) Specifies the workspace to which the setting should be delegated. + +.PARAMETER EnabledSecurityGroups +(Optional) A JSON array of security groups to be enabled, each containing `graphId` and `name` properties. + +.PARAMETER ExcludedSecurityGroups +(Optional) A JSON array of security groups to be excluded, each containing `graphId` and `name` properties. + +.EXAMPLE +Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" + +Updates the tenant setting "SomeSetting" for the capacity with ID "12345" and enables it. + +.EXAMPLE +Update-FabricCapacityTenantSettingOverrides -CapacityId "12345" -SettingTitle "SomeSetting" -EnableTenantSetting "true" -EnabledSecurityGroups @(@{graphId="1";name="Group1"},@{graphId="2";name="Group2"}) + +Updates the tenant setting "SomeSetting" for the capacity with ID "12345", enables it, and specifies security groups to include. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Update-FabricCapacityTenantSettingOverrides { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$TenantSettingName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [bool]$EnableTenantSetting, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$DelegateToCapacity, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$DelegateToDomain, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$DelegateToWorkspace, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [System.Object]$EnabledSecurityGroups, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [System.Object]$ExcludedSecurityGroups, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [System.Object]$Properties + ) + + try { + # Validate authentication token + Write-Message -Message "Validating authentication token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Authentication token is valid." -Level Debug + + # Validate Security Groups if provided + if ($EnabledSecurityGroups) { + foreach ($enabledGroup in $EnabledSecurityGroups) { + if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) { + throw "Each enabled security group must contain 'graphId' and 'name' properties." + } + } + } + + if ($ExcludedSecurityGroups) { + foreach ($excludedGroup in $ExcludedSecurityGroups) { + if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) { + throw "Each excluded security group must contain 'graphId' and 'name' properties." + } + } + } + + # Validate Security Groups if provided + if ($Properties) { + foreach ($property in $Properties) { + if (-not ($property.PSObject.Properties.Name -contains 'name' -and $property.PSObject.Properties.Name -contains 'type' -and $property.PSObject.Properties.Name -contains 'value')) { + throw "Each property object must include 'name', 'type', and 'value' properties to be valid." + } + } + } + + # Construct API endpoint URL + $apiEndpointURI = "{0}/admin/tenantsettings/{1}/update" -f $FabricConfig.BaseUrl, $TenantSettingName + Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + + # Construct request body + $body = @{ + EnableTenantSetting = $EnableTenantSetting + } + + if ($DelegateToCapacity) { + $body.delegateToCapacity = $DelegateToCapacity + } + + if ($DelegateToDomain) { + $body.delegateToDomain = $DelegateToDomain + } + + if ($DelegateToWorkspace) { + $body.delegateToWorkspace = $DelegateToWorkspace + } + + if ($EnabledSecurityGroups) { + $body.enabledSecurityGroups = $EnabledSecurityGroups + } + + if ($ExcludedSecurityGroups) { + $body.excludedSecurityGroups = $ExcludedSecurityGroups + } + + if ($Properties) { + $body.properties = $Properties + } + + # Convert body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 5 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Invoke Fabric API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post ` + -Body $bodyJson + + Write-Message -Message "Successfully updated tenant setting." -Level Info + return $response + } + catch { + $errorDetails = $_.Exception.Message + Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error + } +} + diff --git a/source/Public/Tenant/Get-FabricTenantSettings.ps1 b/source/Public/Tenant/Get-FabricTenantSettings.ps1 new file mode 100644 index 00000000..51f88e64 --- /dev/null +++ b/source/Public/Tenant/Get-FabricTenantSettings.ps1 @@ -0,0 +1,27 @@ + +<# +.SYNOPSIS +Retrieves the tenant settings from the Fabric API. + +.DESCRIPTION +The Get-FabricTenantSettings function makes a GET request to the Fabric API to retrieve the tenant settings. It returns the 'tenantSettings' property of the first item in the response. + +.PARAMETER None +This function does not have any parameters. + +.EXAMPLE +Get-FabricTenantSettings +Retrieves the tenant settings from the Fabric API. + +#> + +function Get-FabricTenantSettings { + [Alias("Get-FabTenantSettings")] + Param () + + Confirm-FabricAuthToken | Out-Null + + $result = Invoke-FabricAPIRequest -uri "admin/tenantsettings" -Method GET + + return $result.tenantSettings +} \ No newline at end of file diff --git a/source/Public/Users/Get-FabricUserListAccessEntities.ps1 b/source/Public/Users/Get-FabricUserListAccessEntities.ps1 new file mode 100644 index 00000000..8d15cdaa --- /dev/null +++ b/source/Public/Users/Get-FabricUserListAccessEntities.ps1 @@ -0,0 +1,68 @@ +<# +.SYNOPSIS + Retrieves access entities for a specified user in Microsoft Fabric. + +.DESCRIPTION + This function retrieves a list of access entities associated with a specified user in Microsoft Fabric. + It supports filtering by entity type and handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER UserId + The unique identifier of the user whose access entities are to be retrieved. This parameter is mandatory. + +.PARAMETER Type + The type of access entity to filter the results by. This parameter is optional and supports predefined values such as 'CopyJob', 'Dashboard', 'DataPipeline', etc. + +.EXAMPLE + Get-FabricUserListAccessEntities -UserId "user-12345" + This example retrieves all access entities associated with the user having ID "user-12345". + +.EXAMPLE + Get-FabricUserListAccessEntities -UserId "user-12345" -Type "Dashboard" + This example retrieves only the 'Dashboard' access entities associated with the user having ID "user-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Get-FabricUserListAccessEntities { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$UserId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet('CopyJob', ' Dashboard', 'DataPipeline', 'Datamart', 'Environment', 'Eventhouse', 'Eventstream', 'GraphQLApi', 'KQLDashboard', 'KQLDatabase', 'KQLQueryset', 'Lakehouse', 'MLExperiment', 'MLModel', 'MirroredDatabase', 'MountedDataFactory', 'Notebook', 'PaginatedReport', 'Reflex', 'Report', 'SQLDatabase', 'SQLEndpoint', 'SemanticModel', 'SparkJobDefinition', 'VariableLibrary', 'Warehouse')] + [string]$Type + ) + + try { + + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + + # Step 4: Loop to retrieve all capacities with continuation token + $apiEndpointURI = "{0}admin/users/{1}/access" -f $FabricConfig.BaseUrl, $UserId + if ($Type) { + $apiEndpointURI += "?type=$Type" + } + + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get + + return $response + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Warehouse. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Utils/Convert-FromBase64.ps1 b/source/Public/Utils/Convert-FromBase64.ps1 new file mode 100644 index 00000000..5aa99e13 --- /dev/null +++ b/source/Public/Utils/Convert-FromBase64.ps1 @@ -0,0 +1,55 @@ +<# +.SYNOPSIS + Decodes a Base64-encoded string into its original text representation. + +.DESCRIPTION + The Convert-FromBase64 function takes a Base64-encoded string as input, decodes it into a byte array, + and converts it back into a UTF-8 encoded string. It is useful for reversing Base64 encoding applied + to text or other data. + +.PARAMETER Base64String + The Base64-encoded string that you want to decode. + +.EXAMPLE + Convert-FromBase64 -Base64String "SGVsbG8sIFdvcmxkIQ==" + + Output: + Hello, World! + +.EXAMPLE + $encodedString = "U29tZSBlbmNvZGVkIHRleHQ=" + Convert-FromBase64 -Base64String $encodedString + + Output: + Some encoded text + +.NOTES + - This function assumes the Base64 input is a valid UTF-8 encoded string. + - Any decoding errors will throw a descriptive error message. + +.AUTHOR +Tiago Balabuch +#> +function Convert-FromBase64 { + param ( + [Parameter(Mandatory = $true)] + [string]$Base64String + ) + + try { + # Step 1: Convert the Base64 string to a byte array + $bytes = [Convert]::FromBase64String($Base64String) + + # Step 2: Convert the byte array back to a UTF-8 string + $decodedString = [System.Text.Encoding]::UTF8.GetString($bytes) + + # Step 3: Return the decoded string + return $decodedString + } + catch { + # Step 4: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "An error occurred while decoding from Base64: $errorDetails" -Level Error + throw "An error occurred while decoding from Base64: $_" + } +} \ No newline at end of file diff --git a/source/Public/Utils/Convert-ToBase64.ps1 b/source/Public/Utils/Convert-ToBase64.ps1 new file mode 100644 index 00000000..8b152567 --- /dev/null +++ b/source/Public/Utils/Convert-ToBase64.ps1 @@ -0,0 +1,60 @@ +<# +.SYNOPSIS + Encodes the content of a file into a Base64-encoded string. + +.DESCRIPTION + The Convert-ToBase64 function takes a file path as input, reads the file's content as a byte array, + and converts it into a Base64-encoded string. This is useful for embedding binary data (e.g., images, + documents) in text-based formats such as JSON or XML. + +.PARAMETER filePath + The full path to the file whose contents you want to encode into Base64. + +.EXAMPLE + Convert-ToBase64 -filePath "C:\Path\To\File.txt" + + Output: + VGhpcyBpcyBhbiBlbmNvZGVkIGZpbGUu + +.EXAMPLE + $encodedContent = Convert-ToBase64 -filePath "C:\Path\To\Image.jpg" + $encodedContent | Set-Content -Path "C:\Path\To\EncodedImage.txt" + + This saves the Base64-encoded content of the image to a text file. + +.NOTES + - Ensure the file exists at the specified path before running this function. + - Large files may cause memory constraints due to full loading into memory. + +.AUTHOR +Tiago Balabuch +#> +function Convert-ToBase64 { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$filePath + ) + try { + + # Step 1: Reading all the bytes from the file + #$bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString) + Write-Message -Message "Reading all the bytes from the file specified: $filePath" -Level Debug + $fileBytes = [System.IO.File]::ReadAllBytes($filePath) + + # Step 2: Convert the byte array to Base64 string + Write-Message -Message "Convert the byte array to Base64 string" -Level Debug + $base64String = [Convert]::ToBase64String($fileBytes) + + # Step 3: Return the encoded string + Write-Message -Message "Return the encoded string for the file: $filePath" -Level Debug + return $base64String + } + catch { + # Step 4: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "An error occurred while encoding to Base64: $errorDetails" -Level Error + throw "An error occurred while encoding to Base64: $_" + } +} \ No newline at end of file diff --git a/source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 b/source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 new file mode 100644 index 00000000..4fa2cc07 --- /dev/null +++ b/source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 @@ -0,0 +1,88 @@ +<# +.SYNOPSIS +Monitors the status of a long-running operation in Microsoft Fabric. + +.DESCRIPTION +The Get-FabricLongRunningOperation function queries the Microsoft Fabric API to check the status of a +long-running operation. It periodically polls the operation until it reaches a terminal state (Succeeded or Failed). + +.PARAMETER operationId +The unique identifier of the long-running operation to be monitored. + +.PARAMETER retryAfter +The interval (in seconds) to wait between polling the operation status. The default is 5 seconds. + +.EXAMPLE +Get-FabricLongRunningOperation -operationId "12345-abcd-67890-efgh" -retryAfter 10 + +This command polls the status of the operation with the given operationId every 10 seconds until it completes. + +.NOTES +- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. + +.AUTHOR +Tiago Balabuch + +#> +function Get-FabricLongRunningOperation { + param ( + [Parameter(Mandatory = $false)] + [string]$operationId, + + [Parameter(Mandatory = $false)] + [string]$location, + + [Parameter(Mandatory = $false)] + [int]$retryAfter = 5 + ) + + # Step 1: Construct the API URL + if ($location) { + # Use the Location header to define the operationUrl + $apiEndpointUrl = $location + } + else { + $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}" -f $operationId + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + try { + do { + + # Step 2: Wait before the next request + if ($retryAfter) { + Start-Sleep -Seconds $retryAfter + } + else { + Start-Sleep -Seconds 5 # Default retry interval if no Retry-After header + } + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -ResponseHeadersVariable responseHeader ` + -StatusCodeVariable statusCode + + # Step 3: Parse the response + $jsonOperation = $response | ConvertTo-Json + $operation = $jsonOperation | ConvertFrom-Json + + # Log status for debugging + Write-Message -Message "Operation Status: $($operation.status)" -Level Debug + + + } while ($operation.status -notin @("Succeeded", "Completed", "Failed")) + + # Step 5: Return the operation result + return $operation + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "An error occurred while checking the operation: $errorDetails" -Level Error + throw + } +} \ No newline at end of file diff --git a/source/Public/Utils/Get-FabricLongRunningOperation.ps1 b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 new file mode 100644 index 00000000..4fa2cc07 --- /dev/null +++ b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 @@ -0,0 +1,88 @@ +<# +.SYNOPSIS +Monitors the status of a long-running operation in Microsoft Fabric. + +.DESCRIPTION +The Get-FabricLongRunningOperation function queries the Microsoft Fabric API to check the status of a +long-running operation. It periodically polls the operation until it reaches a terminal state (Succeeded or Failed). + +.PARAMETER operationId +The unique identifier of the long-running operation to be monitored. + +.PARAMETER retryAfter +The interval (in seconds) to wait between polling the operation status. The default is 5 seconds. + +.EXAMPLE +Get-FabricLongRunningOperation -operationId "12345-abcd-67890-efgh" -retryAfter 10 + +This command polls the status of the operation with the given operationId every 10 seconds until it completes. + +.NOTES +- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. + +.AUTHOR +Tiago Balabuch + +#> +function Get-FabricLongRunningOperation { + param ( + [Parameter(Mandatory = $false)] + [string]$operationId, + + [Parameter(Mandatory = $false)] + [string]$location, + + [Parameter(Mandatory = $false)] + [int]$retryAfter = 5 + ) + + # Step 1: Construct the API URL + if ($location) { + # Use the Location header to define the operationUrl + $apiEndpointUrl = $location + } + else { + $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}" -f $operationId + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + try { + do { + + # Step 2: Wait before the next request + if ($retryAfter) { + Start-Sleep -Seconds $retryAfter + } + else { + Start-Sleep -Seconds 5 # Default retry interval if no Retry-After header + } + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -ResponseHeadersVariable responseHeader ` + -StatusCodeVariable statusCode + + # Step 3: Parse the response + $jsonOperation = $response | ConvertTo-Json + $operation = $jsonOperation | ConvertFrom-Json + + # Log status for debugging + Write-Message -Message "Operation Status: $($operation.status)" -Level Debug + + + } while ($operation.status -notin @("Succeeded", "Completed", "Failed")) + + # Step 5: Return the operation result + return $operation + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "An error occurred while checking the operation: $errorDetails" -Level Error + throw + } +} \ No newline at end of file diff --git a/source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 b/source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 new file mode 100644 index 00000000..00000c6c --- /dev/null +++ b/source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS +Retrieves the result of a completed long-running operation from the Microsoft Fabric API. + +.DESCRIPTION +The Get-FabricLongRunningOperationResult function queries the Microsoft Fabric API to fetch the result +of a specific long-running operation. This is typically used after confirming the operation has completed successfully. + +.PARAMETER operationId +The unique identifier of the completed long-running operation whose result you want to retrieve. + +.EXAMPLE +Get-FabricLongRunningOperationResult -operationId "12345-abcd-67890-efgh" + +This command fetches the result of the operation with the specified operationId. + +.NOTES +- Ensure the Fabric API headers (e.g., authorization tokens) are defined in $FabricConfig.FabricHeaders. +- This function does not handle polling. Ensure the operation is in a terminal state before calling this function. + +.AUTHOR +Tiago Balabuch + +#> +function Get-FabricLongRunningOperationResult { + param ( + [Parameter(Mandatory = $true)] + [string]$operationId + ) + + # Step 1: Construct the API URL + $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}/result" -f $operationId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + try { + # Step 2: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + + # Step 3: Return the result + Write-Message -Message "Result response code: $statusCode" -Level Debug + Write-Message -Message "Result return: $response" -Level Debug + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Debug + Write-Message -Message "Error: $($response.message)" -Level Debug + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Debug + Write-Message "Error Code: $($response.errorCode)" -Level Debug + } + + return $response + } + catch { + # Step 3: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "An error occurred while returning the operation result: $errorDetails" -Level Error + throw + } +} \ No newline at end of file diff --git a/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 b/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 new file mode 100644 index 00000000..00000c6c --- /dev/null +++ b/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS +Retrieves the result of a completed long-running operation from the Microsoft Fabric API. + +.DESCRIPTION +The Get-FabricLongRunningOperationResult function queries the Microsoft Fabric API to fetch the result +of a specific long-running operation. This is typically used after confirming the operation has completed successfully. + +.PARAMETER operationId +The unique identifier of the completed long-running operation whose result you want to retrieve. + +.EXAMPLE +Get-FabricLongRunningOperationResult -operationId "12345-abcd-67890-efgh" + +This command fetches the result of the operation with the specified operationId. + +.NOTES +- Ensure the Fabric API headers (e.g., authorization tokens) are defined in $FabricConfig.FabricHeaders. +- This function does not handle polling. Ensure the operation is in a terminal state before calling this function. + +.AUTHOR +Tiago Balabuch + +#> +function Get-FabricLongRunningOperationResult { + param ( + [Parameter(Mandatory = $true)] + [string]$operationId + ) + + # Step 1: Construct the API URL + $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}/result" -f $operationId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + try { + # Step 2: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + + # Step 3: Return the result + Write-Message -Message "Result response code: $statusCode" -Level Debug + Write-Message -Message "Result return: $response" -Level Debug + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Debug + Write-Message -Message "Error: $($response.message)" -Level Debug + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Debug + Write-Message "Error Code: $($response.errorCode)" -Level Debug + } + + return $response + } + catch { + # Step 3: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "An error occurred while returning the operation result: $errorDetails" -Level Error + throw + } +} \ No newline at end of file diff --git a/source/Public/Utils/Invoke-FabricAPIRequest.ps1 b/source/Public/Utils/Invoke-FabricAPIRequest.ps1 new file mode 100644 index 00000000..c2d4f894 --- /dev/null +++ b/source/Public/Utils/Invoke-FabricAPIRequest.ps1 @@ -0,0 +1,133 @@ +function Invoke-FabricAPIRequest { + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [hashtable]$Headers, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$BaseURI, + + [Parameter(Mandatory = $true)] + [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] + [string] $Method, + + [Parameter(Mandatory = $false)] + [string] $Body, + + [Parameter(Mandatory = $false)] + [string] $ContentType = "application/json; charset=utf-8" + ) + + $continuationToken = $null + $results = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + do { + $apiEndpointURI = $BaseURI + if ($null -ne $continuationToken) { + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + + if ($BaseURI -like "*`?*") { + # URI already has parameters, append with & + $apiEndpointURI = "$BaseURI&continuationToken=$encodedToken" + } + else { + # No existing parameters, append with ? + $apiEndpointURI = "$BaseURI?continuationToken=$encodedToken" + } + } + Write-Message -Message "Calling API: $apiEndpointURI" -Level Debug + + $invokeParams = @{ + Headers = $Headers + Uri = $apiEndpointURI + Method = $Method + ErrorAction = 'Stop' + SkipHttpErrorCheck = $true + ResponseHeadersVariable = 'responseHeader' + StatusCodeVariable = 'statusCode' + # TimeoutSec = $timeoutSec + } + + if ($method -in @('Post', 'Put', 'Patch') -and $body) { + $invokeParams.Body = $body + $invokeParams.ContentType = $contentType + } + + $response = Invoke-RestMethod @invokeParams + switch ($statusCode) { + + 200 { + Write-Message -Message "API call succeeded." -Level Debug + # Step 5: Handle and log the response + if ($response) { + if ($response.PSObject.Properties.Name -contains 'value') { + $results += $response.value + } + elseif ($response.PSObject.Properties.Name -contains 'accessEntities') { + $results += $response.accessEntities + } + else { + $results += $response + } + $continuationToken = $response.PSObject.Properties.Match("continuationToken") ? $response.continuationToken : $null + } + else { + Write-Message -Message "No data in response" -Level Debug + $continuationToken = $null + } + } + 201 { + Write-Message -Message "Resource created successfully." -Level Info + return $response + } + 202 { + # Step 6: Handle long-running operations + Write-Message -Message "Request accepted. Provisioning in progress." -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + # Need to implement a retry mechanism for long running operations + # [string]$retryAfter = $responseHeader["Retry-After"] + Write-Message -Message "Operation ID: '$operationId', Location: '$location'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation succeeded. Fetching result." -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + 400 { $errorMsg = "Bad Request" } + 401 { $errorMsg = "Unauthorized" } + 403 { $errorMsg = "Forbidden" } + 404 { $errorMsg = "Not Found" } + 409 { $errorMsg = "Conflict" } + 429 { $errorMsg = "Too Many Requests" } + 500 { $errorMsg = "Internal Server Error" } + default { $errorMsg = "Unexpected response code: $statusCode" } + } + + if ($statusCode -notin 200, 201, 202) { + Write-Message -Message "$errorMsg : $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message -Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } while ($null -ne $continuationToken) + + return $results +} diff --git a/source/Public/Utils/Set-FabricApiHeaders copy.ps1 b/source/Public/Utils/Set-FabricApiHeaders copy.ps1 new file mode 100644 index 00000000..e1c7a43d --- /dev/null +++ b/source/Public/Utils/Set-FabricApiHeaders copy.ps1 @@ -0,0 +1,116 @@ +<# +.SYNOPSIS +Sets the Fabric API headers with a valid token for the specified Azure tenant. + +.DESCRIPTION +The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. +It also updates the token expiration time and global tenant ID. + +.PARAMETER TenantId +The Azure tenant ID for which the access token is requested. + +.PARAMETER AppId +The Azure app ID for which the service principal access token is requested. + +.PARAMETER AppSecret +The Azure App secret for which the service principal access token is requested. + +.EXAMPLE +Set-FabricApiHeaders -TenantId "your-tenant-id" + +Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. + +.EXAMPLE +$tenantId = "999999999-99999-99999-9999-999999999999" +$appId = "888888888-88888-88888-8888-888888888888" +$appSecret = "your-app-secret" +$secureAppSecret = $appSecret | ConvertTo-SecureString -AsPlainText -Force + +Set-FabricApiHeader -TenantId $tenantId -AppId $appId -AppSecret $secureAppSecret +Logs in to Azure with the specified tenant ID, retrieves an access token for the service principal, and configures the Fabric headers. + +.NOTES +- Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). +- Relies on a global `$FabricConfig` object for storing headers and token metadata. + +.AUTHOR +Tiago Balabuch +#> + +function Set-FabricApiHeaders { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$TenantId, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$AppId, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [System.Security.SecureString]$AppSecret + ) + + try { + # Step 1: Connect to the Azure account + Write-Message -Message "Logging in to Azure tenant: $TenantId" -Level Info + + # Step 2: Performing validation checks on the parameters passed to a function or script. + # Checks if 'AppId' is provided without 'AppSecret' and vice versa. + if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { + Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error + throw "AppSecret is required when using AppId." + } + if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { + Write-Message -Message "AppId is required when using AppSecret." -Level Error + throw "AppId is required when using AppId." + } + # Step 3: Connect to the Azure account + # Using AppId and AppSecret + if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { + + Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug + Write-Message -Message "Logging in using the AppId: $AppId" -Level Info + $psCredential = [pscredential]::new($AppId, $AppSecret) + Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId + + } + # Using the current user + else { + + Write-Message -Message "Logging in using the current user" -Level Debug + Write-Message -Message "Logging in using the current user" -Level Info + Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null + } + + ## Step 4: Retrieve the access token for the Fabric API + Write-Message -Message "Retrieve the access token for the Fabric API: $TenantId" -Level Debug + $fabricToken = Get-AzAccessToken -AsSecureString -ResourceUrl $FabricConfig.ResourceUrl -ErrorAction Stop -WarningAction SilentlyContinue + + ## Step 5: Extract the plain token from the secure string + Write-Message -Message "Extract the plain token from the secure string" -Level Debug + $plainToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($fabricToken.Token) + ) + + ## Step 6: Set the headers in the global configuration + Write-Message -Message "Set the headers in the global configuration" -Level Debug + $FabricConfig.FabricHeaders = @{ + 'Content-Type' = 'application/json' + 'Authorization' = "Bearer $plainToken" + } + + ## Step 7: Update token metadata in the global configuration + Write-Message -Message "Update token metadata in the global configuration" -Level Debug + $FabricConfig.TokenExpiresOn = $fabricToken.ExpiresOn + $FabricConfig.TenantIdGlobal = $TenantId + + Write-Message -Message "Fabric token successfully configured." -Level Info + } + catch { + # Step 8: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to set Fabric token: $errorDetails" -Level Error + throw "Unable to configure Fabric token. Ensure tenant and API configurations are correct." + } +} diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 new file mode 100644 index 00000000..e1c7a43d --- /dev/null +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -0,0 +1,116 @@ +<# +.SYNOPSIS +Sets the Fabric API headers with a valid token for the specified Azure tenant. + +.DESCRIPTION +The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. +It also updates the token expiration time and global tenant ID. + +.PARAMETER TenantId +The Azure tenant ID for which the access token is requested. + +.PARAMETER AppId +The Azure app ID for which the service principal access token is requested. + +.PARAMETER AppSecret +The Azure App secret for which the service principal access token is requested. + +.EXAMPLE +Set-FabricApiHeaders -TenantId "your-tenant-id" + +Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. + +.EXAMPLE +$tenantId = "999999999-99999-99999-9999-999999999999" +$appId = "888888888-88888-88888-8888-888888888888" +$appSecret = "your-app-secret" +$secureAppSecret = $appSecret | ConvertTo-SecureString -AsPlainText -Force + +Set-FabricApiHeader -TenantId $tenantId -AppId $appId -AppSecret $secureAppSecret +Logs in to Azure with the specified tenant ID, retrieves an access token for the service principal, and configures the Fabric headers. + +.NOTES +- Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). +- Relies on a global `$FabricConfig` object for storing headers and token metadata. + +.AUTHOR +Tiago Balabuch +#> + +function Set-FabricApiHeaders { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$TenantId, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$AppId, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [System.Security.SecureString]$AppSecret + ) + + try { + # Step 1: Connect to the Azure account + Write-Message -Message "Logging in to Azure tenant: $TenantId" -Level Info + + # Step 2: Performing validation checks on the parameters passed to a function or script. + # Checks if 'AppId' is provided without 'AppSecret' and vice versa. + if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { + Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error + throw "AppSecret is required when using AppId." + } + if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { + Write-Message -Message "AppId is required when using AppSecret." -Level Error + throw "AppId is required when using AppId." + } + # Step 3: Connect to the Azure account + # Using AppId and AppSecret + if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { + + Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug + Write-Message -Message "Logging in using the AppId: $AppId" -Level Info + $psCredential = [pscredential]::new($AppId, $AppSecret) + Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId + + } + # Using the current user + else { + + Write-Message -Message "Logging in using the current user" -Level Debug + Write-Message -Message "Logging in using the current user" -Level Info + Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null + } + + ## Step 4: Retrieve the access token for the Fabric API + Write-Message -Message "Retrieve the access token for the Fabric API: $TenantId" -Level Debug + $fabricToken = Get-AzAccessToken -AsSecureString -ResourceUrl $FabricConfig.ResourceUrl -ErrorAction Stop -WarningAction SilentlyContinue + + ## Step 5: Extract the plain token from the secure string + Write-Message -Message "Extract the plain token from the secure string" -Level Debug + $plainToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($fabricToken.Token) + ) + + ## Step 6: Set the headers in the global configuration + Write-Message -Message "Set the headers in the global configuration" -Level Debug + $FabricConfig.FabricHeaders = @{ + 'Content-Type' = 'application/json' + 'Authorization' = "Bearer $plainToken" + } + + ## Step 7: Update token metadata in the global configuration + Write-Message -Message "Update token metadata in the global configuration" -Level Debug + $FabricConfig.TokenExpiresOn = $fabricToken.ExpiresOn + $FabricConfig.TenantIdGlobal = $TenantId + + Write-Message -Message "Fabric token successfully configured." -Level Info + } + catch { + # Step 8: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to set Fabric token: $errorDetails" -Level Error + throw "Unable to configure Fabric token. Ensure tenant and API configurations are correct." + } +} diff --git a/source/Public/Utils/Test-FabricApiResponse.ps1 b/source/Public/Utils/Test-FabricApiResponse.ps1 new file mode 100644 index 00000000..f27f261b --- /dev/null +++ b/source/Public/Utils/Test-FabricApiResponse.ps1 @@ -0,0 +1,53 @@ +function Test-FabricApiResponse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + $statusCode, + [Parameter(Mandatory = $false)] + $response, + [Parameter(Mandatory = $false)] + $responseHeader, + [Parameter(Mandatory = $false)] + $Name, + [Parameter(Mandatory = $false)] + $typeName = 'Fabric Item' + ) + + switch ($statusCode) { + 201 { + Write-Message -Message "$typeName '$Name' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "$typeName '$Name' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + +} diff --git a/source/Public/Warehouse/Get-FabricWarehouse.ps1 b/source/Public/Warehouse/Get-FabricWarehouse.ps1 new file mode 100644 index 00000000..304ede6f --- /dev/null +++ b/source/Public/Warehouse/Get-FabricWarehouse.ps1 @@ -0,0 +1,101 @@ +<# +.SYNOPSIS + Retrieves warehouse details from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function retrieves warehouse details from a specified workspace using either the provided WarehouseId or WarehouseName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the warehouse exists. This parameter is mandatory. + +.PARAMETER WarehouseId + The unique identifier of the warehouse to retrieve. This parameter is optional. + +.PARAMETER WarehouseName + The name of the warehouse to retrieve. This parameter is optional. + +.EXAMPLE + Get-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseId "warehouse-67890" + This example retrieves the warehouse details for the warehouse with ID "warehouse-67890" in the workspace with ID "workspace-12345". + +.EXAMPLE + Get-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseName "My Warehouse" + This example retrieves the warehouse details for the warehouse named "My Warehouse" in the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Get-FabricWarehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WarehouseId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WarehouseName + ) + + try { + # Step 1: Handle ambiguous input + if ($WarehouseId -and $WarehouseName) { + Write-Message -Message "Both 'WarehouseId' and 'WarehouseName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + + + # Step 4: Loop to retrieve all capacities with continuation token + $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + + $Warehouses = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get ` + -Body $null + + # Step 8: Filter results based on provided parameters + $Warehouse = if ($WarehouseId) { + $Warehouses | Where-Object { $_.Id -eq $WarehouseId } + } + elseif ($WarehouseName) { + $Warehouses | Where-Object { $_.DisplayName -eq $WarehouseName } + } + else { + # Return all Warehouses if no filter is provided + Write-Message -Message "No filter provided. Returning all Warehouses." -Level Debug + $Warehouses + } + + # Step 9: Handle results + if ($Warehouse) { + Write-Message -Message "Warehouse found matching the specified criteria." -Level Debug + return $Warehouse + } + else { + Write-Message -Message "No Warehouse found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Warehouse. Error: $errorDetails" -Level Error + } + +} diff --git a/source/Public/Warehouse/New-FabricWarehouse.ps1 b/source/Public/Warehouse/New-FabricWarehouse.ps1 new file mode 100644 index 00000000..2b8a49af --- /dev/null +++ b/source/Public/Warehouse/New-FabricWarehouse.ps1 @@ -0,0 +1,83 @@ +<# +.SYNOPSIS + Creates a new warehouse in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new warehouse + in the specified workspace. It supports optional parameters for warehouse description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the warehouse will be created. This parameter is mandatory. + +.PARAMETER WarehouseName + The name of the warehouse to be created. This parameter is mandatory. + +.PARAMETER WarehouseDescription + An optional description for the warehouse. + +.EXAMPLE + New-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseName "New Warehouse" -WarehouseDescription "Description of the new warehouse" + This example creates a new warehouse named "New Warehouse" in the workspace with ID "workspace-12345" with the provided description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function New-FabricWarehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WarehouseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WarehouseDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $WarehouseName + } + + if ($WarehouseDescription) { + $body.description = $WarehouseDescription + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post ` + -Body $bodyJson + + Write-Message -Message "Data Warehouse created successfully!" -Level Info + return $response + + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Warehouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Warehouse/Remove-FabricWarehouse.ps1 b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 new file mode 100644 index 00000000..42b245f0 --- /dev/null +++ b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 @@ -0,0 +1,61 @@ +<# +.SYNOPSIS + Removes a warehouse from a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a DELETE request to the Microsoft Fabric API to remove a warehouse + from the specified workspace using the provided WorkspaceId and WarehouseId. + +.PARAMETER WorkspaceId + The unique identifier of the workspace from which the warehouse will be removed. + +.PARAMETER WarehouseId + The unique identifier of the warehouse to be removed. + +.EXAMPLE + Remove-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseId "warehouse-67890" + This example removes the warehouse with ID "warehouse-67890" from the workspace with ID "workspace-12345". + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch +#> +function Remove-FabricWarehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WarehouseId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointURI = "{0}/workspaces/{1}/warehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WarehouseId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -Method Delete ` + + Write-Message -Message "Warehouse '$WarehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + return $response + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Warehouse '$WarehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Warehouse/Update-FabricWarehouse.ps1 b/source/Public/Warehouse/Update-FabricWarehouse.ps1 new file mode 100644 index 00000000..fd6e2856 --- /dev/null +++ b/source/Public/Warehouse/Update-FabricWarehouse.ps1 @@ -0,0 +1,91 @@ +<# +.SYNOPSIS + Updates an existing warehouse in a specified Microsoft Fabric workspace. + +.DESCRIPTION + This function sends a PATCH request to the Microsoft Fabric API to update an existing warehouse + in the specified workspace. It supports optional parameters for warehouse description. + +.PARAMETER WorkspaceId + The unique identifier of the workspace where the warehouse exists. This parameter is optional. + +.PARAMETER WarehouseId + The unique identifier of the warehouse to be updated. This parameter is mandatory. + +.PARAMETER WarehouseName + The new name of the warehouse. This parameter is mandatory. + +.PARAMETER WarehouseDescription + An optional new description for the warehouse. + +.EXAMPLE + Update-FabricWarehouse -WorkspaceId "workspace-12345" -WarehouseId "warehouse-67890" -WarehouseName "Updated Warehouse" -WarehouseDescription "Updated description" + This example updates the warehouse with ID "warehouse-67890" in the workspace with ID "workspace-12345" with a new name and description. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + + Author: Tiago Balabuch + +#> +function Update-FabricWarehouse { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WarehouseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WarehouseName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WarehouseDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointURI = "{0}/workspaces/{1}/warehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WarehouseId + Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $WarehouseName + } + + if ($WarehouseDescription) { + $body.description = $WarehouseDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -Method Patch ` + -Body $bodyJson + + # Step 6: Handle results + Write-Message -Message "Warehouse '$WarehouseName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update Warehouse. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Add-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace copy/Add-FabricWorkspaceIdentity.ps1 new file mode 100644 index 00000000..cc042432 --- /dev/null +++ b/source/Public/Workspace copy/Add-FabricWorkspaceIdentity.ps1 @@ -0,0 +1,101 @@ +<# +.SYNOPSIS +Provisions an identity for a Fabric workspace. + +.DESCRIPTION +The `Add-FabricWorkspaceIdentity` function provisions an identity for a specified workspace by making an API call. + +.PARAMETER WorkspaceId +The unique identifier of the workspace for which the identity will be provisioned. + +.EXAMPLE +Add-FabricWorkspaceIdentity -WorkspaceId "workspace123" + +Provisions a Managed Identity for the workspace with ID "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Add-FabricWorkspaceIdentity { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/provisionIdentity" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Workspace identity was successfully provisioned for workspace '$WorkspaceId'." -Level Info + return $response + } + 202 { + Write-Message -Message "Workspace identity provisioning accepted for workspace '$WorkspaceId'. Provisioning in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 5: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to provision workspace identity. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..4d29053b --- /dev/null +++ b/source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 @@ -0,0 +1,112 @@ +<# +.SYNOPSIS +Assigns a role to a principal for a specified Fabric workspace. + +.DESCRIPTION +The `Add-FabricWorkspaceRoleAssignments` function assigns a role (e.g., Admin, Contributor, Member, Viewer) to a principal (e.g., User, Group, ServicePrincipal) in a Fabric workspace by making a POST request to the API. + +.PARAMETER WorkspaceId +The unique identifier of the workspace. + +.PARAMETER PrincipalId +The unique identifier of the principal (User, Group, etc.) to assign the role. + +.PARAMETER PrincipalType +The type of the principal. Allowed values: Group, ServicePrincipal, ServicePrincipalProfile, User. + +.PARAMETER WorkspaceRole +The role to assign to the principal. Allowed values: Admin, Contributor, Member, Viewer. + +.EXAMPLE +Add-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -PrincipalId "principal123" -PrincipalType "User" -WorkspaceRole "Admin" + +Assigns the Admin role to the user with ID "principal123" in the workspace "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Add-FabricWorkspaceRoleAssignment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$PrincipalId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Group', 'ServicePrincipal', 'ServicePrincipalProfile', 'User')] + [string]$PrincipalType, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Admin', 'Contributor', 'Member', 'Viewer')] + [string]$WorkspaceRole + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + principal = @{ + id = $PrincipalId + type = $PrincipalType + } + role = $WorkspaceRole + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 4 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 201) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle empty response + if (-not $response) { + Write-Message -Message "No data returned from the API." -Level Warning + return $null + } + + Write-Message -Message "Role '$WorkspaceRole' assigned to principal '$PrincipalId' successfully in workspace '$WorkspaceId'." -Level Info + return $response + + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to assign role. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Assign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace copy/Assign-FabricWorkspaceCapacity.ps1 new file mode 100644 index 00000000..0754a94b --- /dev/null +++ b/source/Public/Workspace copy/Assign-FabricWorkspaceCapacity.ps1 @@ -0,0 +1,83 @@ +<# +.SYNOPSIS +Assigns a Fabric workspace to a specified capacity. + +.DESCRIPTION +The `Assign-FabricWorkspaceCapacity` function sends a POST request to assign a workspace to a specific capacity. + +.PARAMETER WorkspaceId +The unique identifier of the workspace to be assigned. + +.PARAMETER CapacityId +The unique identifier of the capacity to which the workspace should be assigned. + +.EXAMPLE +Assign-FabricWorkspaceCapacity -WorkspaceId "workspace123" -CapacityId "capacity456" + +Assigns the workspace with ID "workspace123" to the capacity "capacity456". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Assign-FabricWorkspaceCapacity { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$CapacityId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/assignToCapacity" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + capacityId = $CapacityId + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 4 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 202) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Successfully assigned workspace with ID '$WorkspaceId' to capacity with ID '$CapacityId'." -Level Info + } + catch { + # Step 6: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to assign workspace with ID '$WorkspaceId' to capacity with ID '$CapacityId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Get-FabricWorkspace.ps1 b/source/Public/Workspace copy/Get-FabricWorkspace.ps1 new file mode 100644 index 00000000..77797006 --- /dev/null +++ b/source/Public/Workspace copy/Get-FabricWorkspace.ps1 @@ -0,0 +1,149 @@ +<# +.SYNOPSIS +Retrieves details of a Microsoft Fabric workspace by its ID or name. + +.DESCRIPTION +The `Get-FabricWorkspace` function fetches workspace details from the Fabric API. It supports filtering by WorkspaceId or WorkspaceName. + +.PARAMETER WorkspaceId +The unique identifier of the workspace to retrieve. + +.PARAMETER WorkspaceName +The display name of the workspace to retrieve. + +.EXAMPLE +Get-FabricWorkspace -WorkspaceId "workspace123" + +Fetches details of the workspace with ID "workspace123". + +.EXAMPLE +Get-FabricWorkspace -WorkspaceName "MyWorkspace" + +Fetches details of the workspace with the name "MyWorkspace". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Returns the matching workspace details or all workspaces if no filter is provided. + +Author: Tiago Balabuch +#> + +function Get-FabricWorkspace { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WorkspaceName + ) + + try { + # Step 1: Handle ambiguous input + if ($WorkspaceId -and $WorkspaceName) { + Write-Message -Message "Both 'WorkspaceId' and 'WorkspaceName' were provided. Please specify only one." -Level Error + return $null + } + + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $workspaces = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $workspaces += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $workspace = if ($WorkspaceId) { + $workspaces | Where-Object { $_.Id -eq $WorkspaceId } + } + elseif ($WorkspaceName) { + $workspaces | Where-Object { $_.DisplayName -eq $WorkspaceName } + } + else { + # Return all workspaces if no filter is provided + Write-Message -Message "No filter provided. Returning all workspaces." -Level Debug + $workspaces + } + + # Step 9: Handle results + if ($workspace) { + Write-Message -Message "Workspace found matching the specified criteria." -Level Debug + return $workspace + } + else { + Write-Message -Message "No workspace found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve workspace. Error: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..02b2f585 --- /dev/null +++ b/source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 @@ -0,0 +1,153 @@ +<# +.SYNOPSIS +Retrieves role assignments for a specified Fabric workspace. + +.DESCRIPTION +The `Get-FabricWorkspaceRoleAssignments` function fetches the role assignments associated with a Fabric workspace by making a GET request to the API. If `WorkspaceRoleAssignmentId` is provided, it retrieves the specific role assignment. + +.PARAMETER WorkspaceId +The unique identifier of the workspace to fetch role assignments for. + +.PARAMETER WorkspaceRoleAssignmentId +(Optional) The unique identifier of a specific role assignment to retrieve. + +.EXAMPLE +Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" + +Fetches all role assignments for the workspace with the ID "workspace123". + +.EXAMPLE +Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "role123" + +Fetches the role assignment with the ID "role123" for the workspace "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Get-FabricWorkspaceRoleAssignment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceRoleAssignmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $workspaceRoles = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $workspaceRoles += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + # Step 8: Filter results based on provided parameters + $roleAssignments = if ($WorkspaceRoleAssignmentId) { + $workspaceRoles | Where-Object { $_.Id -eq $WorkspaceRoleAssignmentId } + } + else { + $workspaceRoles + } + + # Step 9: Handle results + if ($roleAssignments) { + Write-Message -Message "Found $($roleAssignments.Count) role assignments for WorkspaceId '$WorkspaceId'." -Level Debug + # Transform data into custom objects + $results = foreach ($obj in $roleAssignments) { + [PSCustomObject]@{ + ID = $obj.id + PrincipalId = $obj.principal.id + DisplayName = $obj.principal.displayName + Type = $obj.principal.type + UserPrincipalName = $obj.principal.userDetails.userPrincipalName + aadAppId = $obj.principal.servicePrincipalDetails.aadAppId + Role = $obj.role + } + } + return $results + } + else { + if ($WorkspaceRoleAssignmentId) { + Write-Message -Message "No role assignment found with ID '$WorkspaceRoleAssignmentId' for WorkspaceId '$WorkspaceId'." -Level Warning + } + else { + Write-Message -Message "No role assignments found for WorkspaceId '$WorkspaceId'." -Level Warning + } + return @() + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/New-FabricWorkspace.ps1 b/source/Public/Workspace copy/New-FabricWorkspace.ps1 new file mode 100644 index 00000000..4da24893 --- /dev/null +++ b/source/Public/Workspace copy/New-FabricWorkspace.ps1 @@ -0,0 +1,122 @@ +<# +.SYNOPSIS +Creates a new Fabric workspace with the specified display name. + +.DESCRIPTION +The `Add-FabricWorkspace` function creates a new workspace in the Fabric platform by sending a POST request to the API. It validates the display name and handles both success and error responses. + +.PARAMETER WorkspaceName +The display name of the workspace to be created. Must only contain alphanumeric characters, spaces, and underscores. + +.EXAMPLE +Add-FabricWorkspace -WorkspaceName "NewWorkspace" + +Creates a workspace named "NewWorkspace". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function New-FabricWorkspace { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WorkspaceName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CapacityId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $WorkspaceName + } + + if ($WorkspaceDescription) { + $body.description = $WorkspaceDescription + } + + if ($CapacityId) { + $body.capacityId = $CapacityId + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Workspace '$WorkspaceName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Workspace '$WorkspaceName' creation accepted. Provisioning in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create workspace. Error: $errorDetails" -Level Error + + } +} diff --git a/source/Public/Workspace copy/Remove-FabricWorkspace.ps1 b/source/Public/Workspace copy/Remove-FabricWorkspace.ps1 new file mode 100644 index 00000000..d65a2be7 --- /dev/null +++ b/source/Public/Workspace copy/Remove-FabricWorkspace.ps1 @@ -0,0 +1,68 @@ +<# +.SYNOPSIS +Deletes an existing Fabric workspace by its workspace ID. + +.DESCRIPTION +The `Remove-FabricWorkspace` function deletes a workspace in the Fabric platform by sending a DELETE request to the API. It validates the workspace ID and handles both success and error responses. + +.PARAMETER WorkspaceId +The unique identifier of the workspace to be deleted. + +.EXAMPLE +Remove-FabricWorkspace -WorkspaceId "workspace123" + +Deletes the workspace with the ID "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> +function Remove-FabricWorkspace { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Workspace '$WorkspaceId' deleted successfully!" -Level Info + return $null + + } + catch { + # Step 5: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error + return $null + } +} diff --git a/source/Public/Workspace copy/Remove-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace copy/Remove-FabricWorkspaceIdentity.ps1 new file mode 100644 index 00000000..2a55d5d4 --- /dev/null +++ b/source/Public/Workspace copy/Remove-FabricWorkspaceIdentity.ps1 @@ -0,0 +1,94 @@ +<# +.SYNOPSIS +Deprovisions the Managed Identity for a specified Fabric workspace. + +.DESCRIPTION +The `Remove-FabricWorkspaceCapacity` function deprovisions the Managed Identity from the given workspace by calling the appropriate API endpoint. + +.PARAMETER WorkspaceId +The unique identifier of the workspace from which the identity will be removed. + +.EXAMPLE +Remove-FabricWorkspaceCapacity -WorkspaceId "workspace123" + +Deprovisions the Managed Identity for the workspace with ID "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Remove-FabricWorkspaceIdentity { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/deprovisionIdentity" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle and log the response + switch ($statusCode) { + 200 { + Write-Message -Message "Workspace identity was successfully deprovisioned for workspace '$WorkspaceId'." -Level Info + return $response.value + } + 202 { + Write-Message -Message "Workspace identity deprovisioning accepted for workspace '$WorkspaceId'. Deprovisioning in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 5: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to deprovision workspace identity. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Remove-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace copy/Remove-FabricWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..36be3a18 --- /dev/null +++ b/source/Public/Workspace copy/Remove-FabricWorkspaceRoleAssignment.ps1 @@ -0,0 +1,74 @@ +<# +.SYNOPSIS +Removes a role assignment from a Fabric workspace. + +.DESCRIPTION +The `Remove-FabricWorkspaceRoleAssignment` function deletes a specific role assignment from a Fabric workspace by making a DELETE request to the API. + +.PARAMETER WorkspaceId +The unique identifier of the workspace. + +.PARAMETER WorkspaceRoleAssignmentId +The unique identifier of the role assignment to be removed. + +.EXAMPLE +Remove-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "role123" + +Removes the role assignment with the ID "role123" from the workspace "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + +#> + +function Remove-FabricWorkspaceRoleAssignment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceRoleAssignmentId + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WorkspaceRoleAssignmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Role assignment '$WorkspaceRoleAssignmentId' successfully removed from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to remove role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Unassign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace copy/Unassign-FabricWorkspaceCapacity.ps1 new file mode 100644 index 00000000..8c8ce8bf --- /dev/null +++ b/source/Public/Workspace copy/Unassign-FabricWorkspaceCapacity.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS +Unassigns a Fabric workspace from its capacity. + +.DESCRIPTION +The `Unassign-FabricWorkspaceCapacity` function sends a POST request to unassign a workspace from its assigned capacity. + +.PARAMETER WorkspaceId +The unique identifier of the workspace to be unassigned from its capacity. + +.EXAMPLE +Unassign-FabricWorkspaceCapacity -WorkspaceId "workspace123" + +Unassigns the workspace with ID "workspace123" from its capacity. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Unassign-FabricWorkspaceCapacity { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/unassignFromCapacity" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Message + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + + # Step 4: Validate the response code + if ($statusCode -ne 202) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Workspace capacity has been successfully unassigned from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to unassign workspace from capacity. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Update-FabricWorkspace.ps1 b/source/Public/Workspace copy/Update-FabricWorkspace.ps1 new file mode 100644 index 00000000..77b1056a --- /dev/null +++ b/source/Public/Workspace copy/Update-FabricWorkspace.ps1 @@ -0,0 +1,103 @@ +<# +.SYNOPSIS +Updates the properties of a Fabric workspace. + +.DESCRIPTION +The `Update-FabricWorkspace` function updates the name and/or description of a specified Fabric workspace by making a PATCH request to the API. + +.PARAMETER WorkspaceId +The unique identifier of the workspace to be updated. + +.PARAMETER WorkspaceName +The new name for the workspace. + +.PARAMETER WorkspaceDescription +(Optional) The new description for the workspace. + +.EXAMPLE +Update-FabricWorkspace -WorkspaceId "workspace123" -WorkspaceName "NewWorkspaceName" + +Updates the name of the workspace with the ID "workspace123" to "NewWorkspaceName". + +.EXAMPLE +Update-FabricWorkspace -WorkspaceId "workspace123" -WorkspaceName "NewName" -WorkspaceDescription "Updated description" + +Updates both the name and description of the workspace "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Update-FabricWorkspace { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WorkspaceName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceDescription + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $WorkspaceName + } + + if ($WorkspaceDescription) { + $body.description = $WorkspaceDescription + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle results + Write-Message -Message "Workspace '$WorkspaceName' updated successfully!" -Level Info + return $response + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update workspace. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace copy/Update-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace copy/Update-FabricWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..f619e461 --- /dev/null +++ b/source/Public/Workspace copy/Update-FabricWorkspaceRoleAssignment.ps1 @@ -0,0 +1,104 @@ +<# +.SYNOPSIS +Updates the role assignment for a specific principal in a Fabric workspace. + +.DESCRIPTION +The `Update-FabricWorkspaceRoleAssignment` function updates the role assigned to a principal in a workspace by making a PATCH request to the API. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the role assignment exists. + +.PARAMETER WorkspaceRoleAssignmentId +The unique identifier of the role assignment to be updated. + +.PARAMETER WorkspaceRole +The new role to assign to the principal. Must be one of the following: +- Admin +- Contributor +- Member +- Viewer + +.EXAMPLE +Update-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "assignment456" -WorkspaceRole "Admin" + +Updates the role assignment to "Admin" for the specified workspace and role assignment. + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch +#> + +function Update-FabricWorkspaceRoleAssignment { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceRoleAssignmentId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Admin', 'Contributor', 'Member', 'Viewer')] + [string]$WorkspaceRole + ) + + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WorkspaceRoleAssignmentId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Message + + # Step 3: Construct the request body + $body = @{ + role = $WorkspaceRole + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 4 -Compress + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle empty response + if (-not $response) { + Write-Message -Message "No data returned from the API." -Level Warning + return $null + } + + Write-Message -Message "Role assignment $WorkspaceRoleAssignmentId updated successfully in workspace '$WorkspaceId'." -Level Info + return $response + + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to update role assignment. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..f1de0d1c --- /dev/null +++ b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 @@ -0,0 +1,94 @@ +function Add-FabricWorkspaceRoleAssignment { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Adds a role assignment to a user in a workspace. + +.DESCRIPTION + Adds a role assignment to a user in a workspace. The User is identified by the principalId and the role is + identified by the Role parameter. The Workspace is identified by the WorkspaceId. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the role assignment should be added. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. + +.PARAMETER PrincipalId + Id of the principal for which the role assignment should be added. The value for PrincipalId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. At the + moment only principal type 'User' is supported. + +.PARAMETER Role + The role to assign to the principal. The value for Role is a string. An example of a string is 'Admin'. + The values that can be used are 'Admin', 'Contributor', 'Member' and 'Viewer'. + + +.EXAMPLE + Add-RtiWorkspaceRoleAssignment ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -PrincipalId '12345678-1234-1234-1234-123456789012' ` + -Role 'Admin' + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/add-workspace-role-assignment?tabs=HTTP + + +.NOTES + TODO: Add functionallity to add role assignments to groups. + TODO: Add functionallity to add a user by SPN. +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Alias("Id")] + [Parameter(Mandatory=$true)] + [string]$WorkspaceId, + + [Parameter(Mandatory=$true)] + [string]$principalId, + + [ValidateSet("Admin", "Contributor", "Member" , "Viewer")] + [Parameter(Mandatory=$true)] + [string]$Role + ) + +begin { + + # Check if session is established - if not throw error + if ($null -eq $FabricSession.headerParams) { + throw "No session established to Fabric Real-Time Intelligence. Please run Connect-FabricAccount" + } + + # Create body of request + $body = @{ + 'principal' = @{ + 'id' = $principalId + 'type' = "User" + } + 'role' = $Role + } | ConvertTo-Json ` + -Depth 1 + + # Create Workspace API URL + $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces/$WorkspaceId/roleassignments" +} + +process { + + if($PSCmdlet.ShouldProcess($WorkspaceName)) { + # Call Workspace API + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $WorkspaceApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspace.ps1 b/source/Public/Workspace/Get-FabricWorkspace.ps1 new file mode 100644 index 00000000..753e808b --- /dev/null +++ b/source/Public/Workspace/Get-FabricWorkspace.ps1 @@ -0,0 +1,45 @@ +<# +.SYNOPSIS + Retrieves all Fabric workspaces. + +.DESCRIPTION + The Get-FabricWorkspace function retrieves all Fabric workspaces. It invokes the Fabric API to get the workspaces and outputs the result. + +.EXAMPLE + Get-FabricWorkspace + + This command retrieves all Fabric workspaces. + +.INPUTS + None. You cannot pipe inputs to this function. + +.OUTPUTS + Object. This function returns the Fabric workspaces. + +.NOTES + This function was originally written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip +#> + +Function Get-FabricWorkspace { + [Alias("Get-FabWorkspace")] + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$false)] + [string]$workspaceId + ) + + Confirm-FabricAuthToken | Out-Null + + # Invoke the Fabric API to get the workspaces + if ($workspaceId) { + $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)" -Method Get + } else { + $result = Invoke-FabricAPIRequest -Uri "workspaces" -Method Get + } + + + # Output the result + return $result.value +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspace2.ps1 b/source/Public/Workspace/Get-FabricWorkspace2.ps1 new file mode 100644 index 00000000..f6b1b484 --- /dev/null +++ b/source/Public/Workspace/Get-FabricWorkspace2.ps1 @@ -0,0 +1,190 @@ +function Get-FabricWorkspace2 { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric Workspaces + +.DESCRIPTION + Retrieves Fabric Workspaces. Without the WorkspaceName or WorkspaceID parameter, + all Workspaces are returned. If you want to retrieve a specific Workspace, you can + use the WorkspaceName, an CapacityID, a WorkspaceType, a WorkspaceState or the WorkspaceID + parameter. The WorkspaceId parameter has precedence over all other parameters because it + is most specific. + +.PARAMETER WorkspaceId + Id of the Fabric Workspace to retrieve. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER WorkspaceName + The name of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. + +.PARAMETER WorkspaceCapacityId + The Id of the Capacity to retrieve. This parameter cannot be used together with WorkspaceID. + The value for WorkspaceCapacityId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER WorkspaceType + The type of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. + The value for WorkspaceType is a string. An example of a string is 'Personal'. The values that + can be used are 'Personal', 'Workspace' and 'Adminworkspace'. + +.PARAMETER WorkspaceState + The state of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. + The value for WorkspaceState is a string. An example of a string is 'active'. The values that + can be used are 'active' and 'deleted'. + +.EXAMPLE + Get-FabricWorkspace + + This example will retrieve all Workspaces. + +.EXAMPLE + Get-FabricWorkspace ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' + + This example will retrieve the Workspace with the ID '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricWorkspace ` + -WorkspaceName 'MyWorkspace' + + This example will retrieve the Workspace with the name 'MyWorkspace'. + +.EXAMPLE + Get-FabricWorkspace ` + -WorkspaceCapacityId '12345678-1234-1234-1234-123456789012' + + This example will retrieve the Workspaces with the Capacity ID '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricWorkspace ` + -WorkspaceType 'Personal' + + This example will retrieve the Workspaces with the type 'Personal'. + +.EXAMPLE + Get-FabricWorkspace ` + -WorkspaceState 'active' + + This example will retrieve the Workspaces with the state 'active'. + +.NOTES + + Revsion History: + + - 2024-12-22 - FGE: Added Verbose Output + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/get-workspace?tabs=HTTP + + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces?tabs=HTTP +#> + +[CmdletBinding()] + param ( + + [Alias("Id")] + [string]$WorkspaceId, + + [Alias("Name")] + [string]$WorkspaceName, + + [Alias("CapacityId")] + [string]$WorkspaceCapacityId, + + [ValidateSet("Personal", "Workspace", "Adminworkspace")] + [Alias("Type")] + [string]$WorkspaceType, + + [ValidateSet("active", "deleted")] + [Alias("State")] + [string]$WorkspaceState + ) + +begin { + + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "WorkspaceID has to be used alone" + if ($PSBoundParameters.ContainsKey("WorkspaceName") -and + ($PSBoundParameters.ContainsKey("WorkspaceID") ` + -or $PSBoundParameters.ContainsKey("WorkspaceCapcityId") ` + -or $PSBoundParameters.ContainsKey("WorkspaceType") ` + -or $PSBoundParameters.ContainsKey("WorkspaceState"))) { + throw "Parameters WorkspaceName, WorkspaceCapacityId, WorkspaceType or WorkspaceState and WorkspaceID cannot be used together!" + } + + # Create Workspace API URL + $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces" + + # Create URL for WebAPI Call if WorkspaceID is provided + if ($PSBoundParameters.ContainsKey("WorkspaceID")) { + $workspaceApiUrlId = $workspaceApiUrl + '/' + $WorkspaceID + } + + Write-Verbose "If there are any parameters, we need to filter the API call, the URL will be constructed here." + $workspaceApiFilter = $workspaceApiUrl + + if ($PSBoundParameters.ContainsKey("WorkspaceName")) { + $workspaceApiFilter = $workspaceApiFilter + "?name=$WorkspaceName" + } + + if ($PSBoundParameters.ContainsKey("WorkspaceCapacityId")) { + $workspaceApiFilter = $workspaceApiFilter + "?capacityId=$WorkspaceCapacityId" + } + + if ($PSBoundParameters.ContainsKey("WorkspaceType")) { + $workspaceApiFilter = $workspaceApiFilter + "?type=$WorkspaceType" + } + + if ($PSBoundParameters.ContainsKey("WorkspaceState")) { + $workspaceApiFilter = $workspaceApiFilter + "?state=$WorkspaceState" + } + Write-Verbose "Workspace API URL: $workspaceApiFilter" + +} + +process { + + Write-Verbose "Providing a WorkspaceID is so specific that this will have precedence over any other parameter" + if ($PSBoundParameters.ContainsKey("WorkspaceID")) { + Write-Verbose "Calling Workspace API with WorkspaceId $WorkspaceId" + Write-Verbose "---------------------------------------------------" + Write-Verbose "Sending the following values to the Workspace API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $workspaceApiUrlId" + Write-Verbose "ContentType: application/json" + # Call Workspace API for WorkspaceID + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $workspaceApiUrlId ` + -ContentType "application/json" + + $response + } + else { + Write-Verbose "Calling Workspace API with Filter" + Write-Verbose "---------------------------------" + Write-Verbose "Sending the following values to the Workspace API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: GET" + Write-Verbose "URI: $workspaceApiFilter" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $workspaceApiFilter ` + -ContentType "application/json" + + $response.Workspaces + } + +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 b/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 new file mode 100644 index 00000000..81188a66 --- /dev/null +++ b/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 @@ -0,0 +1,53 @@ +<# +.SYNOPSIS + Retrieves the refresh history of all datasets in a specified PowerBI workspace. + +.DESCRIPTION + The Get-FabricWorkspaceDatasetRefreshes function uses the PowerBI cmdlets to retrieve the refresh history of all datasets in a specified workspace. + It uses the workspace ID to get the workspace and its datasets, and then retrieves the refresh history for each dataset. + +.PARAMETER WorkspaceID + The ID of the PowerBI workspace. This is a mandatory parameter. + +.EXAMPLE + Get-FabricWorkspaceDatasetRefreshes -WorkspaceID "12345678-90ab-cdef-1234-567890abcdef" + + This command retrieves the refresh history of all datasets in the workspace with the specified ID. + +.INPUTS + String. You can pipe a string that contains the workspace ID to Get-FabricWorkspaceDatasetRefreshes. + +.OUTPUTS + Array. Get-FabricWorkspaceDatasetRefreshes returns an array of refresh history objects. + +.NOTES + Alias: Get-PowerBIWorkspaceDatasetRefreshes, Get-FabWorkspaceDatasetRefreshes +#> + +# Define a function to get the refresh history of all datasets in a PowerBI workspace +function Get-FabricWorkspaceDatasetRefreshes { + # Set aliases for the function + [Alias("Get-FabWorkspaceDatasetRefreshes")] + param( + # Define a mandatory parameter for the workspace ID + [Parameter(Mandatory=$true)] + [string]$WorkspaceID + ) + + Confirm-FabricAuthToken | Out-Null + + # Get the workspace using the workspace ID + $wsp = Get-FabricWorkspace -workspaceid $WorkspaceID + # Initialize an array to store the refresh history + $refs = @() + # Get all datasets in the workspace + $datasets = Get-FabricDataset -workspaceid $wsp.Id + + # Loop over each dataset + foreach ($dataset in $datasets) { + # Get the refresh history of the dataset and add it to the array + $refs += Get-FabricDatasetRefreshes -datasetid $dataset.Id -workspaceId $wsp.Id + } + # Return the refresh history array + return $refs +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 new file mode 100644 index 00000000..119a219d --- /dev/null +++ b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 @@ -0,0 +1,56 @@ +function Get-FabricWorkspaceRoleAssignment { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Retrieves Fabric Workspace Role Assignments + +.DESCRIPTION + Retrieves Fabric Workspace Role Assignments. Without the WorkspaceName or WorkspaceID parameter, + +.PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Role Assignments should be retrieved. + The value for WorkspaceId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.EXAMPLE + Get-FabricWorkspaceRoleAssignment ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' + + This example will retrieve all Role Assignments for the Workspace with the ID '12345678-1234-1234-1234-123456789012'. + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/get-workspace-role-assignment?tabs=HTTP + +.LINK + https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/list-workspace-role-assignments?tabs=HTTP +#> + +[CmdletBinding()] + param ( + + [Alias("Id")] + [string]$WorkspaceId + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + # Create Workspace API URL + $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces/$WorkspaceId/roleassignments" +} + +process { + # Call Workspace API for WorkspaceID + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method GET ` + -Uri $workspaceApiUrl ` + -ContentType "application/json" + + $response.Workspaces + +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 b/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 new file mode 100644 index 00000000..f94d101a --- /dev/null +++ b/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 @@ -0,0 +1,60 @@ +<# +.SYNOPSIS +Retrieves workspace usage metrics data. + +.DESCRIPTION +The Get-FabricWorkspaceUsageMetricsData function retrieves workspace usage metrics. It supports multiple aliases for flexibility. + +.PARAMETER workspaceId +The ID of the workspace. This is a mandatory parameter. + +.PARAMETER username +The username. This is a mandatory parameter. + +.EXAMPLE +Get-FabricWorkspaceUsageMetricsData -workspaceId "your-workspace-id" -username "your-username" + +This example retrieves the workspace usage metrics for a specific workspace given the workspace ID and username. + +.NOTES +The function retrieves the PowerBI access token and creates a new usage metrics report. It then defines the names of the reports to retrieve, initializes an empty hashtable to store the reports, and for each report name, retrieves the report and adds it to the hashtable. It then returns the hashtable of reports. +#> + +# This function retrieves workspace usage metrics. +function Get-FabricWorkspaceUsageMetricsData { + # Define aliases for the function for flexibility. + [Alias("Get-FabWorkspaceUsageMetricsData")] + + # Define parameters for the workspace ID and username. + param( + [Parameter(Mandatory = $true)] + [string]$workspaceId, + [Parameter(Mandatory = $false)] + [string]$username = "" + ) + + # Create a new workspace usage metrics dataset. + $datasetId = New-FabricWorkspaceUsageMetricsReport -workspaceId $workspaceId + + # Define the names of the reports to retrieve. + $reportnames = @("'Workspace views'", "'Report pages'", "Users", "Reports", "'Report views'", "'Report page views'", "'Report load times'") + + # Initialize an empty hashtable to store the reports. + $reports = @{} + + # For each report name, retrieve the report and add it to the hashtable. + if ($username -eq "") { + foreach ($reportname in $reportnames) { + $report = Get-FabricUsagemetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname + $reports += @{ $reportname.replace("'", "") = $report } + } + } + else { + foreach ($reportname in $reportnames) { + $report = Get-FabricUsagemetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname -ImpersonatedUser $username + $reports += @{ $reportname.replace("'", "") = $report } + } + } + # Return the hashtable of reports. + return $reports +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 b/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 new file mode 100644 index 00000000..38426fcd --- /dev/null +++ b/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 @@ -0,0 +1,57 @@ +<# +.SYNOPSIS +Retrieves the users of a workspace. + +.DESCRIPTION +The Get-FabricWorkspaceUsers function retrieves the users of a workspace. It supports multiple aliases for flexibility. + +.PARAMETER WorkspaceId +The ID of the workspace. This is a mandatory parameter for the 'WorkspaceId' parameter set. + +.PARAMETER Workspace +The workspace object. This is a mandatory parameter for the 'WorkspaceObject' parameter set and can be piped into the function. + +.EXAMPLE +Get-FabricWorkspaceUsers -WorkspaceId "your-workspace-id" + +This example retrieves the users of a workspace given the workspace ID. + +.EXAMPLE +$workspace | Get-FabricWorkspaceUsers + +This example retrieves the users of a workspace given a workspace object. + +.NOTES +The function defines parameters for the workspace ID and workspace object. If the parameter set name is 'WorkspaceId', it retrieves the workspace object. It then makes a GET request to the PowerBI API to retrieve the users of the workspace and returns the 'value' property of the response, which contains the users. +#> + +# This function retrieves the users of a workspace. +function Get-FabricWorkspaceUsers { + # Define aliases for the function for flexibility. + [Alias("Get-FabWorkspaceUsers")] + + # Define parameters for the workspace ID and workspace object. + param( + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true )] + $Workspace + ) + + begin { + Confirm-FabricAuthToken | Out-Null + } + + process { + # If the parameter set name is 'WorkspaceId', retrieve the workspace object. + if ($PSCmdlet.ParameterSetName -eq 'WorkspaceId') { + $workspace = Get-PowerBIWorkspace -Id $WorkspaceId + } + + # Make a GET request to the PowerBI API to retrieve the users of the workspace. + # The function returns the 'value' property of the response, which contains the users. + return (Invoke-PowerBIRestMethod -Method get -Url ("groups/$($workspace.Id)/users") | ConvertFrom-Json).value + } + +} \ No newline at end of file diff --git a/source/Public/Workspace/New-FabricWorkspace.ps1 b/source/Public/Workspace/New-FabricWorkspace.ps1 new file mode 100644 index 00000000..931ca5f6 --- /dev/null +++ b/source/Public/Workspace/New-FabricWorkspace.ps1 @@ -0,0 +1,80 @@ +<# +.SYNOPSIS + Creates a new Fabric workspace. + +.DESCRIPTION + The New-FabricWorkspace function creates a new Fabric workspace. It uses the provided name to create the workspace. If the workspace already exists and the skipErrorIfExists switch is provided, it does not throw an error. + +.PARAMETER Name + The name of the new Fabric workspace. This is a mandatory parameter. + +.PARAMETER SkipErrorIfExists + A switch that indicates whether to skip the error if the workspace already exists. If provided, the function does not throw an error if the workspace already exists. + +.EXAMPLE + New-FabricWorkspace -Name "NewWorkspace" -SkipErrorIfExists + + This command creates a new Fabric workspace named "NewWorkspace". If the workspace already exists, it does not throw an error. + +.INPUTS + String, Switch. You can pipe a string that contains the name and a switch that indicates whether to skip the error if the workspace already exists to New-FabricWorkspace. + +.OUTPUTS + String. This function returns the ID of the new Fabric workspace. + +.NOTES + This function was originally written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip +#> + +Function New-FabricWorkspace { + <# + .SYNOPSIS + Creates a new Fabric workspace. + #> + [CmdletBinding(SupportsShouldProcess)] + param + ( + [string]$name, + [switch]$skipErrorIfExists + ) + + # Create a request body for the Fabric API + $itemRequest = @{ + displayName = $name + } | ConvertTo-Json + + try { + # Invoke the Fabric API to create the workspace + if ($PSCmdlet.ShouldProcess("workspaces", "create")) { + $createResult = Invoke-FabricAPIRequest -Uri "workspaces" -Method Post -Body $itemRequest + + # Display a message indicating the workspace was created + Write-Output "Workspace created" + + # Output the ID of the new workspace + Write-Output $createResult.id + } + } + catch { + $ex = $_.Exception + + if ($skipErrorIfExists) { + if ($ex.Message -ilike "*409*") { + Write-Output "Workspace already exists" + + $listWorkspaces = Invoke-FabricAPIRequest -Uri "workspaces" -Method Get + + $workspace = $listWorkspaces | Where-Object { $_.displayName -ieq $name } + + if (!$workspace) { + throw "Cannot find workspace '$name'" + } + Write-Output $workspace.id + } + else { + throw + } + } + } +} diff --git a/source/Public/Workspace/New-FabricWorkspace2.ps1 b/source/Public/Workspace/New-FabricWorkspace2.ps1 new file mode 100644 index 00000000..16369223 --- /dev/null +++ b/source/Public/Workspace/New-FabricWorkspace2.ps1 @@ -0,0 +1,89 @@ +function New-FabricWorkspace2 { +#Requires -Version 7.1 + +<# +.SYNOPSIS + Creates a new Fabric Workspace + +.DESCRIPTION + Creates a new Fabric Workspace + +.PARAMETER CapacityID + Id of the Fabric Capacity for which the Workspace should be created. The value for CapacityID is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + +.PARAMETER WorkspaceName + The name of the Workspace to create. This parameter is mandatory. + +.PARAMETER WorkspaceDescription + The description of the Workspace to create. + +.EXAMPLE + New-FabricWorkspace ` + -CapacityID '12345678-1234-1234-1234-123456789012' ` + -WorkspaceName 'TestWorkspace' ` + -WorkspaceDescription 'This is a Test Workspace' + + This example will create a new Workspace with the name 'TestWorkspace' and the description 'This is a test workspace'. + +.NOTES + Revsion History: + + - 2024-12-22 - FGE: Added Verbose Output +#> + +[CmdletBinding(SupportsShouldProcess)] + param ( + + [Parameter(Mandatory=$true)] + [string]$CapacityId, + + [Parameter(Mandatory=$true)] + [Alias("Name", "DisplayName")] + [string]$WorkspaceName, + + [ValidateLength(0, 4000)] + [Alias("Description")] + [string]$WorkspaceDescription + ) + +begin { + Confirm-FabricAuthToken | Out-Null + + Write-Verbose "Create body of request" + $body = @{ + 'displayName' = $WorkspaceName + 'description' = $WorkspaceDescription + 'capacityId' = $CapacityId + } | ConvertTo-Json ` + -Depth 1 + + Write-Verbose "Create Workspace API URL" + $WorkspaceApiUrl = "$($FabricSession.BaseApiUrl)/workspaces" + } + +process { + + if($PSCmdlet.ShouldProcess($WorkspaceName)) { + Write-Verbose "Calling Workspace API" + Write-Verbose "---------------------" + Write-Verbose "Sending the following values to the Workspace API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $WorkspaceApiUrl" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + $response = Invoke-RestMethod ` + -Headers $FabricSession.headerParams ` + -Method POST ` + -Uri $WorkspaceApiUrl ` + -Body ($body) ` + -ContentType "application/json" + + $response + } +} + +end {} + +} \ No newline at end of file diff --git a/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 b/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 new file mode 100644 index 00000000..4f2a0084 --- /dev/null +++ b/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 @@ -0,0 +1,48 @@ +<# +.SYNOPSIS +Retrieves the workspace usage metrics dataset ID. + +.DESCRIPTION +The New-FabricWorkspaceUsageMetricsReport function retrieves the workspace usage metrics dataset ID. It supports multiple aliases for flexibility. + +.PARAMETER workspaceId +The ID of the workspace. This is a mandatory parameter. + +.EXAMPLE +New-FabricWorkspaceUsageMetricsReport -workspaceId "your-workspace-id" + +This example retrieves the workspace usage metrics dataset ID for a specific workspace given the workspace ID. + +.NOTES +The function retrieves the PowerBI access token and the Fabric API cluster URI. It then makes a GET request to the Fabric API to retrieve the workspace usage metrics dataset ID, parses the response and replaces certain keys to match the expected format, and returns the 'dbName' property of the first model in the response, which is the dataset ID. +#> + +# This function retrieves the workspace usage metrics dataset ID. +function New-FabricWorkspaceUsageMetricsReport { + # Define aliases for the function for flexibility. + [Alias("New-FabWorkspaceUsageMetricsReport")] + [CmdletBinding(SupportsShouldProcess)] + # Define a parameter for the workspace ID. + param( + [Parameter(Mandatory = $true)] + [string]$workspaceId + ) + + Confirm-FabricAuthToken | Out-Null + + # Retrieve the Fabric API cluster URI. + $url = get-FabricAPIclusterURI + + # Make a GET request to the Fabric API to retrieve the workspace usage metrics dataset ID. + if ($PSCmdlet.ShouldProcess("Workspace Usage Metrics Report", "Retrieve")) { + $data = Invoke-WebRequest -Uri "$url/$workspaceId/usageMetricsReportV2?experience=power-bi" -Headers $FabricSession.HeaderParams -ErrorAction SilentlyContinue + # Parse the response and replace certain keys to match the expected format. + $response = $data.Content.ToString().Replace("nextRefreshTime", "NextRefreshTime").Replace("lastRefreshTime", "LastRefreshTime") | ConvertFrom-Json + + # Return the 'dbName' property of the first model in the response, which is the dataset ID. + return $response.models[0].dbName + } + else { + return $null + } +} \ No newline at end of file diff --git a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 new file mode 100644 index 00000000..e54d8fd4 --- /dev/null +++ b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 @@ -0,0 +1,71 @@ +<# +.SYNOPSIS +Sets a PowerBI workspace to a capacity. + +.DESCRIPTION +The Register-FabricWorkspaceToCapacity function Sets a PowerBI workspace to a capacity. It supports multiple aliases for flexibility. + +.PARAMETER WorkspaceId +The ID of the workspace to be Seted. This is a mandatory parameter. + +.PARAMETER Workspace +The workspace object to be Seted. This is a mandatory parameter and can be piped into the function. + +.PARAMETER CapacityId +The ID of the capacity to which the workspace will be Seted. This is a mandatory parameter. + +.EXAMPLE +Register-FabricWorkspaceToCapacity -WorkspaceId "Workspace-GUID" -CapacityId "Capacity-GUID" + +This example Sets the workspace with ID "Workspace-GUID" to the capacity with ID "Capacity-GUID". + +.EXAMPLE +$workspace | Register-FabricWorkspaceToCapacity -CapacityId "Capacity-GUID" + +This example Sets the workspace object stored in the $workspace variable to the capacity with ID "Capacity-GUID". The workspace object is piped into the function. + +.NOTES +The function makes a POST request to the PowerBI API to Set the workspace to the capacity. The PowerBI access token is retrieved using the Get-PowerBIAccessToken function. +#> + + +# This function Sets a PowerBI workspace to a capacity. +# It supports multiple aliases for flexibility. +function Register-FabricWorkspaceToCapacity { + [Alias("Register-FabWorkspaceToCapacity")] + [CmdletBinding(SupportsShouldProcess)] + param( + # WorkspaceId is a mandatory parameter. It represents the ID of the workspace to be Seted. + [Parameter(ParameterSetName = 'WorkspaceId')] + [string]$WorkspaceId, + + # Workspace is a mandatory parameter. It represents the workspace object to be Seted. + # This parameter can be piped into the function. + [Parameter(ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true)] + $Workspace, + + # CapacityId is a mandatory parameter. It represents the ID of the capacity to which the workspace will be Seted. + [Parameter(Mandatory = $true)] + [string]$CapacityId + ) + Process { + # If the parameter set name is 'WorkspaceObject', the workspace ID is extracted from the workspace object. + if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { + $workspaceid = $workspace.id + } + + # The body of the request is created. It contains the capacity ID. + $body = @{ + capacityId = $CapacityId + } + + Confirm-FabricAuthToken | Out-Null + + # The workspace is Seted to the capacity by making a POST request to the PowerBI API. + # The function returns the value property of the response. + if ($PSCmdlet.ShouldProcess("Set workspace $workspaceid to capacity $CapacityId")) { + #return (Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/assignToCapacity" -Method POST -Body $body).value + return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/assignToCapacity" -Body $body + } + } +} \ No newline at end of file diff --git a/source/Public/Workspace/Remove-FabricWorkspace.ps1 b/source/Public/Workspace/Remove-FabricWorkspace.ps1 new file mode 100644 index 00000000..aec748af --- /dev/null +++ b/source/Public/Workspace/Remove-FabricWorkspace.ps1 @@ -0,0 +1,37 @@ +<# +.SYNOPSIS +Removes a workspace. + +.DESCRIPTION +The Remove-FabricWorkspace function removes a workspace. It supports multiple aliases for flexibility. + +.PARAMETER groupID +The ID of the group (workspace). This is a mandatory parameter. + +.EXAMPLE +Remove-FabricWorkspace -groupID "your-group-id" + +This example removes a workspace given the group ID. + +.NOTES +The function retrieves the PowerBI access token and makes a DELETE request to the PowerBI API to remove the workspace. +#> + +# This function removes a workspace. +function Remove-FabricWorkspace { + [CmdletBinding(SupportsShouldProcess)] + # Define aliases for the function for flexibility. + + # Define a parameter for the group ID. + Param ( + [Parameter(Mandatory = $true)] + [string]$workspaceID + ) + + Confirm-FabricAuthToken | Out-Null + + # Make a DELETE request to the PowerBI API to remove the workspace. + if ($PSCmdlet.ShouldProcess("Remove workspace $workspaceID")) { + return Invoke-FabricAPIRequest -Uri "workspaces/$workspaceID" -Method Delete + } +} \ No newline at end of file diff --git a/source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 b/source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 new file mode 100644 index 00000000..9b4f0ae4 --- /dev/null +++ b/source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 @@ -0,0 +1,58 @@ +<# +.SYNOPSIS +Unregisters a workspace from a capacity. + +.DESCRIPTION +The Unregister-FabricWorkspaceToCapacity function unregisters a workspace from a capacity in PowerBI. It can be used to remove a workspace from a capacity, allowing it to be assigned to a different capacity or remain unassigned. + +.PARAMETER WorkspaceId +Specifies the ID of the workspace to be unregistered from the capacity. This parameter is mandatory when using the 'WorkspaceId' parameter set. + +.PARAMETER Workspace +Specifies the workspace object to be unregistered from the capacity. This parameter is mandatory when using the 'WorkspaceObject' parameter set. The workspace object can be piped into the function. + +.EXAMPLE +Unregister-FabricWorkspaceToCapacity -WorkspaceId "12345678" +Unregisters the workspace with ID "12345678" from the capacity. + +.EXAMPLE +Get-FabricWorkspace | Unregister-FabricWorkspaceToCapacity +Unregisters the workspace objects piped from Get-FabricWorkspace from the capacity. + +.INPUTS +System.Management.Automation.PSCustomObject + +.OUTPUTS +System.Object + +.NOTES +Author: Your Name +Date: Today's Date +#> + +function Unregister-FabricWorkspaceToCapacity { + [Alias("Unregister-FabWorkspaceToCapacity")] + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true)] + $Workspace + ) + + begin { + Confirm-FabricAuthToken | Out-Null + } + + Process { + if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { + $workspaceid = $workspace.id + } + + if ($PSCmdlet.ShouldProcess("Unassigns workspace $workspaceid from a capacity")) { + return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/unassignFromCapacity" + #return (Invoke-FabricAPIRequest -Uri "workspaces/$workspaceid/unassignFromCapacity" -Method POST).value + } + } +} \ No newline at end of file diff --git a/tests/Unit/Classes/class1.tests.ps1 b/tests/Unit/Classes/class1.tests.ps1 deleted file mode 100644 index 1237a875..00000000 --- a/tests/Unit/Classes/class1.tests.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path -$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { - ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and - $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } - ).BaseName - -Import-Module $ProjectName - -InModuleScope $ProjectName { - Describe class1 { - Context 'Type creation' { - It 'Has created a type named class1' { - 'class1' -as [Type] | Should -BeOfType [Type] - } - } - - Context 'Constructors' { - It 'Has a default constructor' { - $instance = [class1]::new() - $instance | Should -Not -BeNullOrEmpty - $instance.GetType().Name | Should -Be 'class1' - } - } - - Context 'Methods' { - BeforeEach { - $instance = [class1]::new() - } - - It 'Overrides the ToString method' { - # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. - $instance.ToString() | Should -Be 'This calss is class1' - } - } - - Context 'Properties' { - BeforeEach { - $instance = [class1]::new() - } - - It 'Has a Name property' { - $instance.Name | Should -Be 'Class1' - } - } - } -} diff --git a/tests/Unit/Classes/class11.tests.ps1 b/tests/Unit/Classes/class11.tests.ps1 deleted file mode 100644 index c51e3853..00000000 --- a/tests/Unit/Classes/class11.tests.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path -$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { - ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and - $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } - ).BaseName - -Import-Module $ProjectName - -InModuleScope $ProjectName { - Describe class11 { - Context 'Type creation' { - It 'Has created a type named class11' { - 'class11' -as [Type] | Should -BeOfType [Type] - } - } - - Context 'Constructors' { - It 'Has a default constructor' { - $instance = [class11]::new() - $instance | Should -Not -BeNullOrEmpty - $instance.GetType().Name | Should -Be 'class11' - } - } - - Context 'Methods' { - BeforeEach { - $instance = [class11]::new() - } - - It 'Overrides the ToString method' { - # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. - $instance.ToString() | Should -Be 'This calss is class11:class1' - } - } - - Context 'Properties' { - BeforeEach { - $instance = [class11]::new() - } - - It 'Has a Name property' { - $instance.Name | Should -Be 'Class11' - } - } - } -} diff --git a/tests/Unit/Classes/class12.tests.ps1 b/tests/Unit/Classes/class12.tests.ps1 deleted file mode 100644 index f6b24dcc..00000000 --- a/tests/Unit/Classes/class12.tests.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path -$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { - ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and - $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } - ).BaseName - -Import-Module $ProjectName - -InModuleScope $ProjectName { - Describe class12 { - Context 'Type creation' { - It 'Has created a type named class12' { - 'class12' -as [Type] | Should -BeOfType [Type] - } - } - - Context 'Constructors' { - It 'Has a default constructor' { - $instance = [class12]::new() - $instance | Should -Not -BeNullOrEmpty - $instance.GetType().Name | Should -Be 'class12' - } - } - - Context 'Methods' { - BeforeEach { - $instance = [class12]::new() - } - - It 'Overrides the ToString method' { - # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. - $instance.ToString() | Should -Be 'This calss is class12:class1' - } - } - - Context 'Properties' { - BeforeEach { - $instance = [class12]::new() - } - - It 'Has a Name property' { - $instance.Name | Should -Be 'Class12' - } - } - } -} diff --git a/tests/Unit/Classes/class2.tests.ps1 b/tests/Unit/Classes/class2.tests.ps1 deleted file mode 100644 index 16d327a8..00000000 --- a/tests/Unit/Classes/class2.tests.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path -$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { - ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and - $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop }catch{$false}) } - ).BaseName - -Import-Module $ProjectName - -InModuleScope $ProjectName { - Describe class2 { - Context 'Type creation' { - It 'Has created a type named class2' { - 'class2' -as [Type] | Should -BeOfType [Type] - } - } - - Context 'Constructors' { - It 'Has a default constructor' { - $instance = [class2]::new() - $instance | Should -Not -BeNullOrEmpty - $instance.GetType().Name | Should -Be 'class2' - } - } - - Context 'Methods' { - BeforeEach { - $instance = [class2]::new() - } - - It 'Overrides the ToString method' { - # Typo "calss" is inherited from definition. Preserved here as validation is demonstrative. - $instance.ToString() | Should -Be 'This calss is class2' - } - } - - Context 'Properties' { - BeforeEach { - $instance = [class2]::new() - } - - It 'Has a Name property' { - $instance.Name | Should -Be 'Class2' - } - } - } -} diff --git a/tests/Unit/Private/Get-PrivateFunction.tests.ps1 b/tests/Unit/Private/Get-PrivateFunction.tests.ps1 deleted file mode 100644 index d1a902bf..00000000 --- a/tests/Unit/Private/Get-PrivateFunction.tests.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -BeforeAll { - $script:dscModuleName = 'FabricTools' - - Import-Module -Name $script:dscModuleName -} - -AfterAll { - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscModuleName -All | Remove-Module -Force -} - -Describe Get-PrivateFunction { - Context 'When calling the function with string value' { - It 'Should return a single object' { - InModuleScope -ModuleName $dscModuleName { - $return = Get-PrivateFunction -PrivateData 'string' - - ($return | Measure-Object).Count | Should -Be 1 - } - } - - It 'Should return a string based on the parameter PrivateData' { - InModuleScope -ModuleName $dscModuleName { - $return = Get-PrivateFunction -PrivateData 'string' - - $return | Should -Be 'string' - } - } - } -} - diff --git a/tests/Unit/Public/Get-Something.tests.ps1 b/tests/Unit/Public/Get-Something.tests.ps1 deleted file mode 100644 index c9ed0bd2..00000000 --- a/tests/Unit/Public/Get-Something.tests.ps1 +++ /dev/null @@ -1,91 +0,0 @@ -BeforeAll { - $script:dscModuleName = 'FabricTools' - - Import-Module -Name $script:dscModuleName -} - -AfterAll { - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscModuleName -All | Remove-Module -Force -} - -Describe Get-Something { - BeforeAll { - Mock -CommandName Get-PrivateFunction -MockWith { - # This return the value passed to the Get-PrivateFunction parameter $PrivateData. - $PrivateData - } -ModuleName $dscModuleName - } - - Context 'When passing values using named parameters' { - It 'Should call the private function once' { - { Get-Something -Data 'value' } | Should -Not -Throw - - Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 1 -Scope It -ModuleName $dscModuleName - } - - It 'Should return a single object' { - $return = Get-Something -Data 'value' - - ($return | Measure-Object).Count | Should -Be 1 - } - - It 'Should return the correct string value' { - $return = Get-Something -Data 'value' - - $return | Should -Be 'value' - } - } - - Context 'When passing values over the pipeline' { - It 'Should call the private function two times' { - { 'value1', 'value2' | Get-Something } | Should -Not -Throw - - Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 2 -Scope It -ModuleName $dscModuleName - } - - It 'Should return an array with two items' { - $return = 'value1', 'value2' | Get-Something - - $return.Count | Should -Be 2 - } - - It 'Should return an array with the correct string values' { - $return = 'value1', 'value2' | Get-Something - - $return[0] | Should -Be 'value1' - $return[1] | Should -Be 'value2' - } - - It 'Should accept values from the pipeline by property name' { - $return = 'value1', 'value2' | ForEach-Object { - [PSCustomObject]@{ - Data = $_ - OtherProperty = 'other' - } - } | Get-Something - - $return[0] | Should -Be 'value1' - $return[1] | Should -Be 'value2' - } - } - - Context 'When passing WhatIf' { - It 'Should support the parameter WhatIf' { - (Get-Command -Name 'Get-Something').Parameters.ContainsKey('WhatIf') | Should -Be $true - } - - It 'Should not call the private function' { - { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw - - Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 0 -Scope It -ModuleName $dscModuleName - } - - It 'Should return $null' { - $return = Get-Something -Data 'value' -WhatIf - - $return | Should -BeNullOrEmpty - } - } -} - From bae001b39b71fadadbbff48b96dcbfc57b5632f8 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 18:10:47 +0100 Subject: [PATCH 08/76] Update CONTRIBUTING.md to clarify testing steps Revised the instructions for running Pester tests and building the module. This enhances clarity for contributors on how to execute tests and ensures they understand the importance of the build process before testing. --- CONTRIBUTING.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ee62948..5c8c3480 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,18 +59,17 @@ Add this to your VS Code settings to enable it: ``` This will build the module and create a new folder in the root of the repository called `output`. It will also load the new module into your current session. -4. You can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: +4. AFTER building, you can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: ```PowerShell - Invoke-Pester + Invoke-Pester ./tests/ ``` This will run all the tests in the `tests` folder and output the results to the console. 5. You can also simulate the deployment testing by running the following command: ```PowerShell - ./build.ps1 -Tasks test + ./build.ps1 -Tasks build,test ``` - 6. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. # Thanks! From d1b4f9742189b8a541d506bd3accd365b09b8e0b Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 18:13:44 +0100 Subject: [PATCH 09/76] Add Contributor Covenant Code of Conduct Introduce a comprehensive Code of Conduct to foster an open and welcoming environment for all contributors. This document outlines our pledge, standards, responsibilities, and enforcement policies to ensure a positive community experience. --- CODE_OF_CONDUCT.md | 47 ++++++++++++++++++++++++++++++++++++++++++++-- README.md | 6 +++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d7589ddb..7e77a3a2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,46 @@ -# Code of Conduct +# Contributor Covenant Code of Conduct -This project has adopted the [DSC Community Code of Conduct](https://dsccommunity.org/code_of_conduct). +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/README.md b/README.md index 5eb44acb..212ccef8 100644 --- a/README.md +++ b/README.md @@ -79,14 +79,14 @@ Set-FabricAuthToken -reset The entire history of changes to this module can be find here: [Release Notes](ReleaseNotes.md) +## Code of Conduct +This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the project team. ## Contributing Contributions to FabricTools are welcome. -> Note: In this early stage of development, we are working hard to build strong foundations for this module and further contribution guidance to ensure the quality of this code. **Therefore, please get in touch with us before you commit any code and create a PR.** - -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to develop, test and the process for submitting pull requests to us. ## Authors From d82f543bd3f52e3d0150a9a9d3e2a91ec87c83f8 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Thu, 8 May 2025 18:30:16 +0100 Subject: [PATCH 10/76] Add instructions for running specific test tags Updated the CONTRIBUTING.md to include information on running specific test tags such as FunctionalQuality, TestQuality, and HelpQuality. This enhancement aims to provide clearer guidance for contributors on how to execute targeted tests during the development process. --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c8c3480..15c8effa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,6 +65,8 @@ Add this to your VS Code settings to enable it: ``` This will run all the tests in the `tests` folder and output the results to the console. +You can also run specific tags such as FunctionalQuality,TestQuality, HelpQuality + 5. You can also simulate the deployment testing by running the following command: ```PowerShell ./build.ps1 -Tasks build,test From 8a01711d52951c7136d8a8f15ab1cf36539baacb Mon Sep 17 00:00:00 2001 From: jpomfret Date: Fri, 9 May 2025 19:43:55 +0100 Subject: [PATCH 11/76] Update CONTRIBUTING.md to clarify installation steps for Microsoft.PowerShell.PSResourceGet module --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15c8effa..b1726dd7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,16 @@ The workflow for using this and developing the code is shown below. git checkout -b newStuff # give it a proper name! ``` +1. Make sure you have the latest Microsoft.PowerShell.PSResourceGet module + +```PowerShell +-- Find the latest version on the gallery +Find-Module Microsoft.PowerShell.PSResourceGet + +-- Install side-by-side +Install-Module Microsoft.PowerShell.PSResourceGet -Force +``` + 1. Develop your updates in the source repository You should also resolve all dependencies before you start developing. This will ensure that you have all the required modules installed and loaded into your session. @@ -77,6 +87,7 @@ You can also run specific tags such as FunctionalQuality,TestQuality, HelpQualit # Thanks! We will review your PR and get back to you as soon as we can. We are all volunteers and do this in our spare time, so please be patient with us. We will try to get back to you within a week, but it may take longer if we are busy. If you have any questions or need help, please feel free to reach out to us on the [GitHub Discussions]( ) + ## How to submit changes: TODO: Pull Request protocol etc. You might also include what response they'll get back from the team on submission, or any caveats about the speed of response. From bcf147bf4cadf64d2c25480d9e032b573a1426c2 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Fri, 9 May 2025 20:17:41 +0100 Subject: [PATCH 12/76] Add PowerShell functions for managing Fabric recovery points and configurations --- source/Private/Get-FabricUri.ps1 | 75 ++++++++ source/Public/Config/Get-FabricConfig.ps1 | 32 ++++ source/Public/Config/Set-FabricConfig.ps1 | 61 ++++++ .../Get-FabricRecoveryPoint.ps1 | 130 +++++++++++++ .../New-FabricRecoveryPoint.ps1 | 101 ++++++++++ .../Remove-FabricRecoveryPoint.ps1 | 151 +++++++++++++++ .../Restore-FabricRecoveryPoint.ps1 | 179 ++++++++++++++++++ tests/Unit/Get-FabricConfig.tests.ps1 | 10 + tests/Unit/Get-FabricRecoveryPoint.tests.ps1 | 10 + tests/Unit/Get-FabricUri.tests.ps1 | 11 ++ tests/Unit/New-FabricRecoveryPoint.tests.ps1 | 10 + .../Unit/Remove-FabricRecoveryPoint.tests.ps1 | 10 + .../Restore-FabricRecoveryPoint.tests.ps1 | 10 + tests/Unit/Set-FabricConfig.tests.ps1 | 10 + 14 files changed, 800 insertions(+) create mode 100644 source/Private/Get-FabricUri.ps1 create mode 100644 source/Public/Config/Get-FabricConfig.ps1 create mode 100644 source/Public/Config/Set-FabricConfig.ps1 create mode 100644 source/Public/Restore Points/Get-FabricRecoveryPoint.ps1 create mode 100644 source/Public/Restore Points/New-FabricRecoveryPoint.ps1 create mode 100644 source/Public/Restore Points/Remove-FabricRecoveryPoint.ps1 create mode 100644 source/Public/Restore Points/Restore-FabricRecoveryPoint.ps1 create mode 100644 tests/Unit/Get-FabricConfig.tests.ps1 create mode 100644 tests/Unit/Get-FabricRecoveryPoint.tests.ps1 create mode 100644 tests/Unit/Get-FabricUri.tests.ps1 create mode 100644 tests/Unit/New-FabricRecoveryPoint.tests.ps1 create mode 100644 tests/Unit/Remove-FabricRecoveryPoint.tests.ps1 create mode 100644 tests/Unit/Restore-FabricRecoveryPoint.tests.ps1 create mode 100644 tests/Unit/Set-FabricConfig.tests.ps1 diff --git a/source/Private/Get-FabricUri.ps1 b/source/Private/Get-FabricUri.ps1 new file mode 100644 index 00000000..c6e72a21 --- /dev/null +++ b/source/Private/Get-FabricUri.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS +Internal function to connect to Fabric and setup the uri and headers for commands. + +.DESCRIPTION +Internal function to connect to Fabric and setup the uri and headers for commands. + +Requires the Workspace and DataWarehouse GUIDs to connect to. + +.PARAMETER BaseUrl +Defaults to api.powerbi.com + +.PARAMETER WorkspaceGUID +This is the workspace GUID in which the data warehouse resides. + +.PARAMETER DataWarehouseGUID +The GUID for the data warehouse which we want to retrieve restore points for. + +.PARAMETER BatchId +The BatchId to use for the request. If this is set then the batches endpoint will be used. + +.EXAMPLE +Get-FabricUri -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' + +Connects to the specified Fabric Data Warehouse and sets up the headers and uri for future commands. + +.EXAMPLE +Get-FabricUri -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' -BatchId 'GUID-GUID-GUID-GUID' + +Connects to the specified Fabric Data Warehouse and sets up the headers and uri for checking the progress of an operation with a specific batchId. + +#> +function Get-FabricUri { + param ( + $BaseUrl = 'api.powerbi.com', + [parameter(Mandatory)] + [String]$WorkspaceGUID, + [parameter(Mandatory)] + [String]$DataWarehouseGUID, + + [String]$BatchId + ) + + try { + $headers = Get-PowerBIAccessToken + } catch { + try { + Stop-PSFFunction -Message ('Not able to get a token - execute Connect-PowerBIServiceAccount manually first') -EnableException + # Write-PSFMessage -Level Warning -Message ('Not able to get a token - will execute Connect-PowerBIServiceAccount') + #TODO: This doesn't work the first time - is it waiting for response? + # $conn = Connect-PowerBIServiceAccount + # if($conn) { + # Write-PSFMessage -Level Info -Message ('Successfully connected to PowerBI') + # } + } catch { + throw 'Not able to get a token - manually try and run Connect-PowerBIServiceAccount' + } + } + + if($BatchId) { + $Uri = ('https://{0}/v1.0/myorg/groups/{1}/datawarehouses/{2}/batches/{3}' -f $baseurl, $workspaceGUID, $dataWarehouseGUID, $BatchId) + $method = 'Get' + } else { + $Uri = ('https://{0}/v1.0/myorg/groups/{1}/datawarehouses/{2}/' -f $baseurl, $workspaceGUID, $dataWarehouseGUID) + $method = 'Post' + } + + return @{ + Uri = $Uri + Headers = $headers + Method = $method + ContentType = 'application/json' + } + #TODO: Change this to be a saved config property? +} diff --git a/source/Public/Config/Get-FabricConfig.ps1 b/source/Public/Config/Get-FabricConfig.ps1 new file mode 100644 index 00000000..608e5748 --- /dev/null +++ b/source/Public/Config/Get-FabricConfig.ps1 @@ -0,0 +1,32 @@ +<# +.SYNOPSIS +Gets the configuration for use with all functions in the PSFabricTools module. + +.DESCRIPTION +Gets the configuration for use with all functions in the PSFabricTools module. + +.PARAMETER ConfigName +The name of the configuration to retrieve. + +.EXAMPLE +PS> Get-FabricConfig + +Gets all the configuration values for the PSFabricTools module and outputs them + +.EXAMPLE +PS> Get-FabricConfig -ConfigName BaseUrl + +Gets the BaseUrl configuration value for the PSFabricTools module. +#> + +function Get-FabricConfig { + param ( + [String]$ConfigName + ) + + if ($ConfigName) { + Get-PSFConfig -Module PSFabricTools -Name $ConfigName + } else { + Get-PSFConfig -Module PSFabricTools + } +} diff --git a/source/Public/Config/Set-FabricConfig.ps1 b/source/Public/Config/Set-FabricConfig.ps1 new file mode 100644 index 00000000..b94211f7 --- /dev/null +++ b/source/Public/Config/Set-FabricConfig.ps1 @@ -0,0 +1,61 @@ +<# +.SYNOPSIS +Register the configuration for use with all functions in the PSFabricTools module. + +.DESCRIPTION +Register the configuration for use with all functions in the PSFabricTools module. + +.PARAMETER WorkspaceGUID +This is the workspace GUID in which the Data Warehouse resides. + +.PARAMETER DataWarehouseGUID +The GUID for the Data Warehouse which we want to retrieve restore points for. + +.PARAMETER BaseUrl +Defaults to api.powerbi.com + +.PARAMETER SkipPersist +If set, the configuration will not be persisted to the registry. + +.EXAMPLE +PS> Set-FabricConfig -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' + +Registers the specified Fabric Data Warehouse configuration for use with all functions in the PSFabricTools module. + +.EXAMPLE +PS> Set-FabricConfig -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' -SkipPersist + +Registers the specified Fabric Data Warehouse configuration for use with all functions in the PSFabricTools module - but does not persist the values, only uses them for the current session. + +#> + +function Set-FabricConfig { + [CmdletBinding(SupportsShouldProcess)] + param ( + [String]$WorkspaceGUID, + + [String]$DataWarehouseGUID, + + $BaseUrl = 'api.powerbi.com', + + [switch]$SkipPersist + ) + + if ($PSCmdlet.ShouldProcess("Setting Fabric Configuration")) { + + if ($BaseUrl) { + Set-PSFConfig -Module PSFabricTools -Name BaseUrl -Value $BaseUrl + } + if ($WorkspaceGUID) { + Set-PSFConfig -Module PSFabricTools -Name WorkspaceGUID -Value $WorkspaceGUID + } + if ($DataWarehouseGUID) { + Set-PSFConfig -Module PSFabricTools -Name DataWarehouseGUID -Value $DataWarehouseGUID + } + + # Register the config values in the registry if skip persist is not set + if (-not $SkipPersist) { + Register-PSFConfig -Module PSFabricTools -Scope SystemMandatory + } + } +} diff --git a/source/Public/Restore Points/Get-FabricRecoveryPoint.ps1 b/source/Public/Restore Points/Get-FabricRecoveryPoint.ps1 new file mode 100644 index 00000000..1f17a76f --- /dev/null +++ b/source/Public/Restore Points/Get-FabricRecoveryPoint.ps1 @@ -0,0 +1,130 @@ +<# +.SYNOPSIS +Get a list of Fabric recovery points. + +.DESCRIPTION +Get a list of Fabric recovery points. Results can be filter by date or type. + +.PARAMETER BaseUrl +Defaults to api.powerbi.com + +.PARAMETER WorkspaceGUID +This is the workspace GUID in which the data warehouse resides. + +.PARAMETER DataWarehouseGUID +The GUID for the data warehouse which we want to retrieve restore points for. + +.PARAMETER Since +Filter the results to only include restore points created after this date. + +.PARAMETER Type +Filter the results to only include restore points of this type. + +.PARAMETER CreateTime +The specific unique time of the restore point to remove. Get this from Get-FabricRecoveryPoint. + +.EXAMPLE +PS> Get-FabricRecoveryPoint -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' + +Gets all the available recovery points for the specified data warehouse, in the specified workspace. + +.NOTES +Based on API calls from this blog post: https://blog.fabric.microsoft.com/en-US/blog/the-art-of-data-warehouse-recovery-within-microsoft-fabric/ + +#> + +function Get-FabricRecoveryPoint { + param ( + [String]$WorkspaceGUID, + + [String]$DataWarehouseGUID, + + [String]$BaseUrl = 'api.powerbi.com', + + [DateTime]$Since, + + [ValidateSet("automatic", "userDefined")] + [string]$Type, + #TODO: accept a list of times + [string]$CreateTime + + ) + + #region handle the config parameters + if(-not $WorkspaceGUID) { + $WorkspaceGUID = Get-PSFConfigValue -FullName PSFabricTools.WorkspaceGUID + } + + if(-not $DataWarehouseGUID) { + $DataWarehouseGUID = Get-PSFConfigValue -FullName PSFabricTools.DataWarehouseGUID + } + + if(-not $BaseUrl) { + $BaseUrl = Get-PSFConfigValue -FullName PSFabricTools.BaseUrl + } + + if (-not $WorkspaceGUID -or -not $DataWarehouseGUID -or -not $BaseUrl) { + Stop-PSFFunction -Message 'WorkspaceGUID, DataWarehouseGUID, and BaseUrl are required parameters. Either set them with Set-FabricConfig or pass them in as parameter values' -EnableException $true + } else { + Write-PSFMessage -Level Verbose -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl) + } + #endregion + + #region setting up the API call + try { + # Get token and setup the uri + $getUriParam = @{ + BaseUrl = $BaseUrl + WorkspaceGUID = $WorkspaceGUID + DataWarehouseGUID = $DataWarehouseGUID + } + $iwr = Get-FabricUri @getUriParam + } catch { + Stop-PSFFunction -Message 'Failed to get Fabric URI - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region call the API + if (-not $iwr) { + Stop-PSFFunction -Message 'No URI received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } else { + + # set the body to list restore points + $command = [PSCustomObject]@{ + Commands = @(@{ + '$type' = 'WarehouseListRestorePointsCommand' + }) + } + + try { + # add the body and invoke + $iwr.Add('Body', ($command | ConvertTo-Json -Compress)) + $content = Invoke-WebRequest @iwr + + if($content) { + # change output to be a PowerShell object and view restore points + $restorePoints = ($content.Content | ConvertFrom-Json).operationInformation.progressDetail.restorePoints + + if($CreateTime) { + $restorePoints = $restorePoints | Select-Object @{l='createTimeWhere';e={get-date($_.createTime) -Format 'yyyy-MM-ddTHH:mm:ssZ'}}, * | Where-Object createTimeWhere -eq $createTime + } + + if($Since) { + $restorePoints = $restorePoints | Where-Object { $_.createTime -gt $Since } + } + + if($Type) { + $restorePoints = $restorePoints | Where-Object { $_.createMode -eq $Type } + } + + $restorePoints | Select-Object @{l='createTime';e={get-date($_.createTime) -Format 'yyyy-MM-ddTHH:mm:ssZ'}}, @{l='friendlyCreateTime';e={$_.createTime}}, label, createMode, type, createdByUserObjectId | Sort-Object createTime + #TODO: default view rather than select\sort? + } else { + Stop-PSFFunction -Message 'No Content received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + } catch { + Stop-PSFFunction -Message 'Issue calling Invoke-WebRequest' -ErrorRecord $_ -EnableException $true + } + } + #endregion +} diff --git a/source/Public/Restore Points/New-FabricRecoveryPoint.ps1 b/source/Public/Restore Points/New-FabricRecoveryPoint.ps1 new file mode 100644 index 00000000..7c718a2a --- /dev/null +++ b/source/Public/Restore Points/New-FabricRecoveryPoint.ps1 @@ -0,0 +1,101 @@ +<# +.SYNOPSIS +Create a recovery point for a Fabric data warehouse + +.DESCRIPTION +Create a recovery point for a Fabric data warehouse + +.PARAMETER BaseUrl +Defaults to api.powerbi.com + +.PARAMETER WorkspaceGUID +This is the workspace GUID in which the data warehouse resides. + +.PARAMETER DataWarehouseGUID +The GUID for the data warehouse which we want to retrieve restore points for. + +.EXAMPLE +PS> New-FabricRecoveryPoint + +Create a new recovery point for the data warehouse specified in the configuration. + +.EXAMPLE +PS> New-FabricRecoveryPoint -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' + +Create a new recovery point for the specified data warehouse, in the specified workspace. + +#> +function New-FabricRecoveryPoint { + [CmdletBinding(SupportsShouldProcess)] + param ( + [String]$WorkspaceGUID, + + [String]$DataWarehouseGUID, + + [String]$BaseUrl = 'api.powerbi.com' + ) + + #region handle the config parameters + if(-not $WorkspaceGUID) { + $WorkspaceGUID = Get-PSFConfigValue -FullName PSFabricTools.WorkspaceGUID + } + + if(-not $DataWarehouseGUID) { + $DataWarehouseGUID = Get-PSFConfigValue -FullName PSFabricTools.DataWarehouseGUID + } + + if(-not $BaseUrl) { + $BaseUrl = Get-PSFConfigValue -FullName PSFabricTools.BaseUrl + } + + if (-not $WorkspaceGUID -or -not $DataWarehouseGUID -or -not $BaseUrl) { + Stop-PSFFunction -Message 'WorkspaceGUID, DataWarehouseGUID, and BaseUrl are required parameters. Either set them with Set-FabricConfig or pass them in as parameter values' -EnableException $true + } else { + Write-PSFMessage -Level Verbose -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl) + } + #endregion + + if ($PSCmdlet.ShouldProcess("Create a recovery point for a Fabric Data Warehouse")) { + #region setting up the API call + try { + # Get token and setup the uri + $getUriParam = @{ + BaseUrl = $BaseUrl + WorkspaceGUID = $WorkspaceGUID + DataWarehouseGUID = $DataWarehouseGUID + } + $iwr = Get-FabricUri @getUriParam + } catch { + Stop-PSFFunction -Message 'Failed to get Fabric URI - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region call the API + if (-not $iwr) { + Stop-PSFFunction -Message 'No URI received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } else { + $command = [PSCustomObject]@{ + Commands = @(@{ + '$type' = 'WarehouseCreateRestorePointCommand' + }) + } + + try { + # add the body and invoke + $iwr.Add('Body', ($command | ConvertTo-Json -Compress)) + $content = Invoke-WebRequest @iwr + + if($content) { + # change output to be a PowerShell object and view new restore point + #TODO: output - select default view but return more? + ($content.Content | ConvertFrom-Json) | Select-Object progressState,@{l='type';e={$_.operationInformation.progressDetail.restorePoint.type}},@{l='createTime';e={get-date($_.operationInformation.progressDetail.restorePoint.createTime) -format 'yyyy-MM-ddTHH:mm:ssZ'}},@{l='friendlyCreateTime';e={$_.operationInformation.progressDetail.restorePoint.createTime}}, @{l='label';e={$_.operationInformation.progressDetail.restorePoint.label}}, @{l='createMode';e={$_.operationInformation.progressDetail.restorePoint.createMode}}, @{l='description';e={$_.operationInformation.progressDetail.restorePoint.description}}, @{l='createdByUserObjectId';e={$_.operationInformation.progressDetail.restorePoint.createdByUserObjectId}}, @{l='lastModifiedByUserObjectId';e={$_.operationInformation.progressDetail.restorePoint.lastModifiedByUserObjectId}}, @{l='lastModifiedTime';e={$_.operationInformation.progressDetail.restorePoint.lastModifiedTime}} + } else { + Stop-PSFFunction -Message 'No Content received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + } catch { + Stop-PSFFunction -Message 'Issue calling Invoke-WebRequest' -ErrorRecord $_ -EnableException $true + } + } + #endregion + } +} diff --git a/source/Public/Restore Points/Remove-FabricRecoveryPoint.ps1 b/source/Public/Restore Points/Remove-FabricRecoveryPoint.ps1 new file mode 100644 index 00000000..3b5a5dcb --- /dev/null +++ b/source/Public/Restore Points/Remove-FabricRecoveryPoint.ps1 @@ -0,0 +1,151 @@ +<# +.SYNOPSIS +Remove a selected Fabric Recovery Point. + +.DESCRIPTION +Remove a selected Fabric Recovery Point. + +.PARAMETER CreateTime +The specific unique time of the restore point to remove. Get this from Get-FabricRecoveryPoint. + +.PARAMETER BaseUrl +Defaults to api.powerbi.com + +.PARAMETER WorkspaceGUID +This is the workspace GUID in which the data warehouse resides. + +.PARAMETER DataWarehouseGUID +The GUID for the data warehouse which we want to retrieve restore points for. + +.EXAMPLE +PS> Remove-FabricRecoveryPoint -CreateTime '2024-07-23T11:20:26Z' + +Remove a specific restore point from a Fabric Data Warehouse that has been set using Set-FabricConfig. + +.EXAMPLE +PS> Remove-FabricRecoveryPoint -CreateTime '2024-07-23T11:20:26Z' -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' + +Remove a specific restore point from a Fabric Data Warehouse, specifying the workspace and data warehouse GUIDs. + +.NOTES +General notes +#> +function Remove-FabricRecoveryPoint { + [CmdletBinding(SupportsShouldProcess)] + param ( + [string]$CreateTime, + + [String]$WorkspaceGUID, + + [String]$DataWarehouseGUID, + + [String]$BaseUrl = 'api.powerbi.com' + + #TODO - implement piping from get? or a way of interactively choosing points to remove + ) + + #region handle the config parameters + if(-not $WorkspaceGUID) { + $WorkspaceGUID = Get-PSFConfigValue -FullName PSFabricTools.WorkspaceGUID + } + + if(-not $DataWarehouseGUID) { + $DataWarehouseGUID = Get-PSFConfigValue -FullName PSFabricTools.DataWarehouseGUID + } + + if(-not $BaseUrl) { + $BaseUrl = Get-PSFConfigValue -FullName PSFabricTools.BaseUrl + } + + if (-not $WorkspaceGUID -or -not $DataWarehouseGUID -or -not $BaseUrl) { + Stop-PSFFunction -Message 'WorkspaceGUID, DataWarehouseGUID, and BaseUrl are required parameters. Either set them with Set-FabricConfig or pass them in as parameter values' -EnableException $true + } else { + Write-PSFMessage -Level Verbose -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl) + } + #endregion + + if ($PSCmdlet.ShouldProcess("Remove recovery point for a Fabric Data Warehouse")) { + #region setting up the API call + try { + # Get token and setup the uri + $getUriParam = @{ + BaseUrl = $BaseUrl + WorkspaceGUID = $WorkspaceGUID + DataWarehouseGUID = $DataWarehouseGUID + } + $iwr = Get-FabricUri @getUriParam + } catch { + Stop-PSFFunction -Message 'Failed to get Fabric URI - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region call the API + if (-not $iwr) { + Stop-PSFFunction -Message 'No URI received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } else { + + # for the API this needs to be an array, even if it's just one item + [string[]]$CreateTimeObj = $CreateTime + + #region check restore point exists + #Get the restore point to make sure it exists - the fabric API doesn't really confirm we deleted anything so we will manually check + $getSplat = @{ + WorkspaceGUID = $WorkspaceGUID + DataWarehouseGUID = $DataWarehouseGUID + BaseUrl = $BaseUrl + CreateTime = $CreateTimeObj + } + + try { + if(Get-FabricRecoveryPoint @getSplat) { + Write-PSFMessage -Level Verbose -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}; CreateTime: {3} - restore point exists' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl, $CreateTime) + } else { + Stop-PSFFunction -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}; CreateTime: {3} - restore point not found!' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl, $CreateTime) -ErrorRecord $_ -EnableException $true + } + } catch { + Stop-PSFFunction -Message 'Issue calling Get-FabricRecoveryPoint to check restore point exists before removal' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region remove the restore point + $command = [PSCustomObject]@{ + commands = @([ordered]@{ + '$type' = 'WarehouseDeleteRestorePointsCommand' + 'RestorePointsToDelete' = $CreateTimeObj + }) + } + + try { + # add the body and invoke + $iwr.Add('Body', ($command | ConvertTo-Json -Compress -Depth 3)) + $content = Invoke-WebRequest @iwr + + if($content) { + # change output to be a PowerShell object and view new restore point + #TODO: output - select default view but return more? + $results = ($content.Content | ConvertFrom-Json) + } else { + Stop-PSFFunction -Message 'No Content received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + } catch { + Stop-PSFFunction -Message 'Issue calling Invoke-WebRequest' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region check restore point exists + try { + #Get the restore point to make sure it exists - the fabric API doesn't really confirm we deleted anything so we will manually check + if(Get-FabricRecoveryPoint @getSplat) { + Stop-PSFFunction -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}; CreateTime: {3} - restore point not was not successfully removed!' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl, $CreateTime) -ErrorRecord $_ -EnableException $true + } else { + Write-PSFMessage -Level Output -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}; CreateTime: {3} - restore point successfully removed' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl, $CreateTime) + $results + } + } catch { + Stop-PSFFunction -Message 'Issue calling Get-FabricRecoveryPoint to check restore point exists before removal' -ErrorRecord $_ -EnableException $true + } + #endregion + } + #endregion + } +} diff --git a/source/Public/Restore Points/Restore-FabricRecoveryPoint.ps1 b/source/Public/Restore Points/Restore-FabricRecoveryPoint.ps1 new file mode 100644 index 00000000..b0388fff --- /dev/null +++ b/source/Public/Restore Points/Restore-FabricRecoveryPoint.ps1 @@ -0,0 +1,179 @@ +<# +.SYNOPSIS +Restore a Fabric data warehouse to a specified restore pont. + +.DESCRIPTION +Restore a Fabric data warehouse to a specified restore pont. + +.PARAMETER CreateTime +The specific unique time of the restore point to remove. Get this from Get-FabricRecoveryPoint. + +.PARAMETER BaseUrl +Defaults to api.powerbi.com + +.PARAMETER WorkspaceGUID +This is the workspace GUID in which the data warehouse resides. + +.PARAMETER DataWarehouseGUID +The GUID for the data warehouse which we want to retrieve restore points for. + +.PARAMETER Wait +Wait for the restore to complete before returning. + +.EXAMPLE +PS> Restore-FabricRecoveryPoint -CreateTime '2024-07-23T11:20:26Z' + +Restore a Fabric Data Warehouse to a specific restore point that has been set using Set-FabricConfig. + +.EXAMPLE +PS> Restore-FabricRecoveryPoint -CreateTime '2024-07-23T11:20:26Z' -WorkspaceGUID 'GUID-GUID-GUID-GUID' -DataWarehouseGUID 'GUID-GUID-GUID-GUID' + +Restore a Fabric Data Warehouse to a specific restore point, specifying the workspace and data warehouse GUIDs. + +#> +function Restore-FabricRecoveryPoint { + [CmdletBinding(SupportsShouldProcess)] + param ( + [string]$CreateTime, + + [String]$WorkspaceGUID, + + [String]$DataWarehouseGUID, + + [String]$BaseUrl = 'api.powerbi.com', + + [switch]$Wait + + ) + + #region handle the config parameters + if(-not $WorkspaceGUID) { + $WorkspaceGUID = Get-PSFConfigValue -FullName PSFabricTools.WorkspaceGUID + } + + if(-not $DataWarehouseGUID) { + $DataWarehouseGUID = Get-PSFConfigValue -FullName PSFabricTools.DataWarehouseGUID + } + + if(-not $BaseUrl) { + $BaseUrl = Get-PSFConfigValue -FullName PSFabricTools.BaseUrl + } + + if (-not $WorkspaceGUID -or -not $DataWarehouseGUID -or -not $BaseUrl) { + Stop-PSFFunction -Message 'WorkspaceGUID, DataWarehouseGUID, and BaseUrl are required parameters. Either set them with Set-FabricConfig or pass them in as parameter values' -EnableException $true + } else { + Write-PSFMessage -Level Verbose -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl) + } + #endregion + + if ($PSCmdlet.ShouldProcess("Recover a Fabric Data Warehouse to a restore point")) { + #region setting up the API call + try { + # Get token and setup the uri + $getUriParam = @{ + BaseUrl = $BaseUrl + WorkspaceGUID = $WorkspaceGUID + DataWarehouseGUID = $DataWarehouseGUID + } + $iwr = Get-FabricUri @getUriParam + } catch { + Stop-PSFFunction -Message 'Failed to get Fabric URI - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region call the API + if (-not $iwr) { + Stop-PSFFunction -Message 'No URI received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } else { + + #region check restore point exists + #Get the restore point to make sure it exists before we try and restore to it + $getSplat = @{ + WorkspaceGUID = $WorkspaceGUID + DataWarehouseGUID = $DataWarehouseGUID + BaseUrl = $BaseUrl + CreateTime = $CreateTime + } + + try { + if(Get-FabricRecoveryPoint @getSplat) { + Write-PSFMessage -Level Verbose -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}; CreateTime: {3} - restore point exists' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl, $CreateTime) + } else { + Stop-PSFFunction -Message ('WorkspaceGUID: {0}; DataWarehouseGUID: {1}; BaseUrl: {2}; CreateTime: {3} - restore point not found!' -f $WorkspaceGUID, $DataWarehouseGUID, $BaseUrl, $CreateTime) -ErrorRecord $_ -EnableException $true + } + } catch { + Stop-PSFFunction -Message 'Issue calling Get-FabricRecoveryPoint to check restore point exists before attempting recovery' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region recover to the restore point + # command is now WarehouseRestoreInPlaceCommand and the RestorePoint is the create time of the specific restore point to use + $command = [PSCustomObject]@{ + commands = @([ordered]@{ + '$type' = 'WarehouseRestoreInPlaceCommand' + 'RestorePoint' = $CreateTime + }) + } + + try { + # add the body and invoke + $iwr.Add('Body', ($command | ConvertTo-Json -Compress)) + $content = Invoke-WebRequest @iwr + + if($content) { + #TODO: output - select default view but return more? + $content = ($content.Content | ConvertFrom-Json) + } else { + Stop-PSFFunction -Message 'No Content received from API - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + } catch { + Stop-PSFFunction -Message 'Issue calling Invoke-WebRequest' -ErrorRecord $_ -EnableException $true + } + #endregion + + #region check the progress of the restore + if ($Wait) { + # we need to append batches to the uri + try { + + while($Wait) { + # Get token and setup the uri + $getUriParam = @{ + BaseUrl = $BaseUrl + WorkspaceGUID = $WorkspaceGUID + DataWarehouseGUID = $DataWarehouseGUID + BatchId = $content.batchId + } + $iwr = Get-FabricUri @getUriParam + + $restoreProgress = ((Invoke-WebRequest @iwr).Content | ConvertFrom-Json) + + if($restoreProgress.progressState -eq 'inProgress') { + Write-PSFMessage -Level Output -Message 'Restore in progress' + } elseif ($restoreProgress.progressState -eq 'success') { + Write-PSFMessage -Level Output -Message 'Restore completed successfully' + $restoreProgress | Select-Object progressState, @{l='startDateTimeUtc';e={$_.startTimeStamp }}, @{l='RestorePointCreateTime';e={$CreateTime }} + $wait = $false + break + } else { + Write-PSFMessage -Level Output -Message 'Restore failed' + $restoreProgress | Select-Object progressState, @{l='startDateTimeUtc';e={$_.startTimeStamp }} + $wait = $false + break + } + + # wait a few seconds + Start-Sleep -Seconds 3 + } + } catch { + Stop-PSFFunction -Message 'Failed to get Fabric URI for the batchId - check authentication and parameters.' -ErrorRecord $_ -EnableException $true + } + + } else { + Write-PSFMessage -Level Output -Message 'Restore in progress - use the -Wait parameter to wait for restore to complete' + $content + } + } + #endregion + } +} diff --git a/tests/Unit/Get-FabricConfig.tests.ps1 b/tests/Unit/Get-FabricConfig.tests.ps1 new file mode 100644 index 00000000..6b2025d9 --- /dev/null +++ b/tests/Unit/Get-FabricConfig.tests.ps1 @@ -0,0 +1,10 @@ +Describe "Get-FabricConfig Unit Tests" -Tag 'UnitTests' { + Context "Validate parameters" { + It "Should only contain our specific parameters" { + $CommandName = 'Get-FabricConfig' + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'ConfigName' + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Get-FabricRecoveryPoint.tests.ps1 b/tests/Unit/Get-FabricRecoveryPoint.tests.ps1 new file mode 100644 index 00000000..c2227e3b --- /dev/null +++ b/tests/Unit/Get-FabricRecoveryPoint.tests.ps1 @@ -0,0 +1,10 @@ +Describe "Get-FabricRecoveryPoint Unit Tests" -Tag 'UnitTests' { + Context "Validate parameters" { + It "Should only contain our specific parameters" { + $CommandName = 'Get-FabricRecoveryPoint' + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'WorkspaceGUID','DataWarehouseGUID','BaseUrl','Since','Type','CreateTime' + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Get-FabricUri.tests.ps1 b/tests/Unit/Get-FabricUri.tests.ps1 new file mode 100644 index 00000000..d4e8abca --- /dev/null +++ b/tests/Unit/Get-FabricUri.tests.ps1 @@ -0,0 +1,11 @@ +Describe "Get-FabricUri Unit Tests" -Tag 'UnitTests' -Skip { + Context "Validate parameters" { + It "Should only contain our specific parameters" { + $CommandName = 'Get-FabricUri' + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'SqlInstance', 'SqlCredential', 'Database', 'Name', 'InputObject', 'EnableException', 'Value' + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} +#TODO: Fix test for internal function diff --git a/tests/Unit/New-FabricRecoveryPoint.tests.ps1 b/tests/Unit/New-FabricRecoveryPoint.tests.ps1 new file mode 100644 index 00000000..d91a1ad6 --- /dev/null +++ b/tests/Unit/New-FabricRecoveryPoint.tests.ps1 @@ -0,0 +1,10 @@ +Describe "New-FabricRecoveryPoint Unit Tests" -Tag 'UnitTests' { + Context "Validate parameters" { + It "Should only contain our specific parameters" { + $CommandName = 'New-FabricRecoveryPoint' + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'WorkspaceGUID','DataWarehouseGUID','BaseUrl' + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricRecoveryPoint.tests.ps1 b/tests/Unit/Remove-FabricRecoveryPoint.tests.ps1 new file mode 100644 index 00000000..e4b868c9 --- /dev/null +++ b/tests/Unit/Remove-FabricRecoveryPoint.tests.ps1 @@ -0,0 +1,10 @@ +Describe "Remove-FabricRecoveryPoint Unit Tests" -Tag 'UnitTests' { + Context "Validate parameters" { + It "Should only contain our specific parameters" { + $CommandName = 'Remove-FabricRecoveryPoint' + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'CreateTime','WorkspaceGUID','DataWarehouseGUID','BaseUrl' + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Restore-FabricRecoveryPoint.tests.ps1 b/tests/Unit/Restore-FabricRecoveryPoint.tests.ps1 new file mode 100644 index 00000000..f515187f --- /dev/null +++ b/tests/Unit/Restore-FabricRecoveryPoint.tests.ps1 @@ -0,0 +1,10 @@ +Describe "Restore-FabricRecoveryPoint Unit Tests" -Tag 'UnitTests' { + Context "Validate parameters" { + It "Should only contain our specific parameters" { + $CommandName = 'Restore-FabricRecoveryPoint' + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'CreateTime','WorkspaceGUID','DataWarehouseGUID','BaseUrl','Wait' + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Set-FabricConfig.tests.ps1 b/tests/Unit/Set-FabricConfig.tests.ps1 new file mode 100644 index 00000000..67f5a346 --- /dev/null +++ b/tests/Unit/Set-FabricConfig.tests.ps1 @@ -0,0 +1,10 @@ +Describe "Set-FabricConfig Unit Tests" -Tag 'UnitTests' { + Context "Validate parameters" { + It "Should only contain our specific parameters" { + $CommandName = 'Set-FabricConfig' + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'WorkspaceGUID','DataWarehouseGUID','BaseUrl','SkipPersist' + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} From bc1c0f37a67a6f5580e76abbae85e51e68691b05 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 13:43:14 +0100 Subject: [PATCH 13/76] Update Assert version in RequiredModules.psd1 Set Assert module version to "0.9.6" for compatibility with the latest features and improvements. --- RequiredModules.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index db956b79..d6b1b1d3 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -11,7 +11,7 @@ # Repository = 'PSGallery' # } #} - + Assert = "0.9.6" InvokeBuild = 'latest' PSScriptAnalyzer = 'latest' Pester = 'latest' From 95464eece46392357f82d8318030e02137c40cb7 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 13:44:05 +0100 Subject: [PATCH 14/76] Refactor Register-FabricWorkspaceToCapacity function Move function definition to the top and improve comments for clarity. This enhances readability and maintains consistency in the code structure. Thank you! --- .../Register-FabricWorkspaceToCapacity.ps1 | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 index e54d8fd4..afd7f312 100644 --- a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 +++ b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 @@ -1,6 +1,7 @@ -<# +function Register-FabricWorkspaceToCapacity { + <# .SYNOPSIS -Sets a PowerBI workspace to a capacity. +This function Sets a PowerBI workspace to a capacity. .DESCRIPTION The Register-FabricWorkspaceToCapacity function Sets a PowerBI workspace to a capacity. It supports multiple aliases for flexibility. @@ -28,10 +29,6 @@ This example Sets the workspace object stored in the $workspace variable to the The function makes a POST request to the PowerBI API to Set the workspace to the capacity. The PowerBI access token is retrieved using the Get-PowerBIAccessToken function. #> - -# This function Sets a PowerBI workspace to a capacity. -# It supports multiple aliases for flexibility. -function Register-FabricWorkspaceToCapacity { [Alias("Register-FabWorkspaceToCapacity")] [CmdletBinding(SupportsShouldProcess)] param( @@ -57,8 +54,8 @@ function Register-FabricWorkspaceToCapacity { # The body of the request is created. It contains the capacity ID. $body = @{ capacityId = $CapacityId - } - + } + Confirm-FabricAuthToken | Out-Null # The workspace is Seted to the capacity by making a POST request to the PowerBI API. @@ -68,4 +65,4 @@ function Register-FabricWorkspaceToCapacity { return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/assignToCapacity" -Body $body } } -} \ No newline at end of file +} From 76bc259fe58a1da835ee6f682e5dfb2cd9f587b7 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 13:44:13 +0100 Subject: [PATCH 15/76] Add thank you note to commit message instructions Updated the commit message guidelines to include a friendly thank you note at the end. This change aims to foster a positive contribution culture. Thank you! --- .github/copilot-commit-message-instructions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/copilot-commit-message-instructions.md b/.github/copilot-commit-message-instructions.md index 04696d99..2ba5c59a 100644 --- a/.github/copilot-commit-message-instructions.md +++ b/.github/copilot-commit-message-instructions.md @@ -4,4 +4,5 @@ Do not end the subject line with a period Separate the subject from the body with a blank line Use the imperative mood in the subject line The subject line should be a single sentence with an action word and target with some reasoning -Use the body to explain what and why in a friendly kind manner \ No newline at end of file +Use the body to explain what and why in a friendly kind manner +Say thank you at the end of the message From 090d1624e8458554b7264565b75e87d06bb75830 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:18:15 +0100 Subject: [PATCH 16/76] Update Documentation for Spark Custom Pool Parameters for Pester Help Tests This commit enhances the documentation for the Update-FabricSparkSettings function by adding descriptions for the optional parameters 'automaticLogEnabled' and 'notebookInteractiveRunEnabled'. This improvement aims to provide clearer guidance for users configuring Spark custom pools. Thank you! --- .../Spark/Update-FabricSparkSettings.ps1 | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/source/Public/Spark/Update-FabricSparkSettings.ps1 b/source/Public/Spark/Update-FabricSparkSettings.ps1 index 9320af81..852b327c 100644 --- a/source/Public/Spark/Update-FabricSparkSettings.ps1 +++ b/source/Public/Spark/Update-FabricSparkSettings.ps1 @@ -3,7 +3,7 @@ Updates an existing Spark custom pool in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool + This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool in the specified workspace. It supports various parameters for Spark custom pool configuration. .PARAMETER WorkspaceId @@ -39,6 +39,12 @@ .PARAMETER DynamicExecutorAllocationMaxExecutors The maximum number of executors for dynamic executor allocation in the Spark custom pool. This parameter is mandatory. +.PARAMETER automaticLogEnabled + Specifies whether automatic logging is enabled for the Spark custom pool. This parameter is optional. + +.PARAMETER notebookInteractiveRunEnabled + Specifies whether notebook interactive run is enabled for the Spark custom pool. This parameter is optional. + .EXAMPLE Update-FabricSparkSettings -WorkspaceId "workspace-12345" -SparkSettingsId "pool-67890" -InstancePoolName "Updated Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 This example updates the Spark custom pool with ID "pool-67890" in the workspace with ID "workspace-12345" with a new name and configuration. @@ -48,15 +54,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricSparkSettings { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] @@ -65,7 +71,7 @@ function Update-FabricSparkSettings { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$notebookInteractiveRunEnabled, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$customizeComputeEnabled, @@ -110,7 +116,7 @@ function Update-FabricSparkSettings { # Construct the request body with optional properties $body = @{} - + if ($PSBoundParameters.ContainsKey('automaticLogEnabled')) { $body.automaticLog = @{ enabled = $automaticLogEnabled @@ -138,10 +144,10 @@ function Update-FabricSparkSettings { } } else { Write-Message -Message "Both 'defaultPoolName' and 'defaultPoolType' must be provided together." -Level Error - throw + throw } } - + if ($PSBoundParameters.ContainsKey('EnvironmentName') -or $PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) { $body.environment = @{ name = $EnvironmentName From cd9be6ba85c179cb71cef72ce3e5afff94fbb4e4 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:18:28 +0100 Subject: [PATCH 17/76] Update Documentation for WorkspaceId Parameter for Pester Help Tests This commit adds a description for the WorkspaceId parameter in the Update-FabricNotebook function. This enhancement improves clarity for users and aids in understanding the function's requirements. Thank you! --- source/Public/Notebook/Update-FabricNotebook.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/Public/Notebook/Update-FabricNotebook.ps1 b/source/Public/Notebook/Update-FabricNotebook.ps1 index c5cc8203..8f1195ff 100644 --- a/source/Public/Notebook/Update-FabricNotebook.ps1 +++ b/source/Public/Notebook/Update-FabricNotebook.ps1 @@ -5,6 +5,9 @@ Updates the properties of a Fabric Notebook. .DESCRIPTION The `Update-FabricNotebook` function updates the name and/or description of a specified Fabric Notebook by making a PATCH request to the API. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Notebook exists. + .PARAMETER NotebookId The unique identifier of the Notebook to be updated. @@ -28,7 +31,7 @@ Updates both the name and description of the Notebook "Notebook123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricNotebook { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$NotebookId, From 5cecf3e046af32fd973c09e0aa72400a88237513 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:18:35 +0100 Subject: [PATCH 18/76] Update Parameter Documentation for WorkspaceId for Pester Help Tests This commit adds detailed documentation for the WorkspaceId parameter in the Update-FabricMirroredDatabase function. It clarifies that WorkspaceId is a mandatory field and provides context on its purpose within the function. Thank you! --- .../Mirrored Database/Update-FabricMirroredDatabase.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 index 09691385..a5f86f49 100644 --- a/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 @@ -5,6 +5,9 @@ Updates the properties of a Fabric MirroredDatabase. .DESCRIPTION The `Update-FabricMirroredDatabase` function updates the name and/or description of a specified Fabric MirroredDatabase by making a PATCH request to the API. +.PARAMETER WorkspaceId +(Mandatory) The unique identifier of the workspace where the MirroredDatabase resides. + .PARAMETER MirroredDatabaseId The unique identifier of the MirroredDatabase to be updated. @@ -28,7 +31,7 @@ Updates both the name and description of the MirroredDatabase "MirroredDatabase1 - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricMirroredDatabase { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$MirroredDatabaseId, From 67d29443c516c330dfe567e4a4f515af3b355124 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:18:51 +0100 Subject: [PATCH 19/76] Update Documentation for WorkspaceId Parameter for KQL Database, KQL Database Definition, KQL Queryset, and Lakehouse for Pester Help Tests This commit enhances the documentation by adding detailed descriptions for the WorkspaceId parameter across multiple scripts. This improvement aims to provide clearer guidance for users regarding the unique identifier of the workspace, ensuring better understanding and usability. Thank you! --- .../KQL Database/Update-FabricKQLDatabase.ps1 | 9 ++++-- .../Update-FabricKQLDatabaseDefinition.ps1 | 31 ++++++++++--------- .../KQL Queryset/Update-FabricKQLQueryset.ps1 | 9 ++++-- .../Lakehouse/Update-FabricLakehouse.ps1 | 9 ++++-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 index 4c15f3a2..62731282 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 @@ -5,6 +5,9 @@ Updates the properties of a Fabric KQLDatabase. .DESCRIPTION The `Update-FabricKQLDatabase` function updates the name and/or description of a specified Fabric KQLDatabase by making a PATCH request to the API. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLDatabase resides. + .PARAMETER KQLDatabaseId The unique identifier of the KQLDatabase to be updated. @@ -28,7 +31,7 @@ Updates both the name and description of the KQLDatabase "KQLDatabase123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricKQLDatabase { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLDatabaseId, diff --git a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 index 35df15fe..63a505d5 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of a KQLDatabase in a Microsoft Fabric workspace. .DESCRIPTION -This function allows updating the content or metadata of a KQLDatabase in a Microsoft Fabric workspace. +This function allows updating the content or metadata of a KQLDatabase in a Microsoft Fabric workspace. The KQLDatabase content can be provided as file paths, and metadata updates can optionally be enabled. .PARAMETER WorkspaceId @@ -19,9 +19,12 @@ The KQLDatabase content can be provided as file paths, and metadata updates can (Optional) The file path to the KQLDatabase's platform-specific definition file. The content will be encoded as Base64 and sent in the request. .PARAMETER UpdateMetadata -(Optional)A boolean flag indicating whether to update the KQLDatabase's metadata. +(Optional)A boolean flag indicating whether to update the KQLDatabase's metadata. Default: `$false`. +.PARAMETER KQLDatabasePathSchemaDefinition +(Optional) The file path to the KQLDatabase's schema definition file. The content will be encoded as Base64 and sent in the request. + .EXAMPLE Update-FabricKQLDatabaseDefinition -WorkspaceId "12345" -KQLDatabaseId "67890" -KQLDatabasePathDefinition "C:\KQLDatabases\KQLDatabase.ipynb" @@ -38,7 +41,7 @@ Updates both the content and metadata of the KQLDatabase with ID `67890` in the - The KQLDatabase content is encoded as Base64 before being sent to the Fabric API. - This function handles asynchronous operations and retrieves operation results if required. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -56,7 +59,7 @@ function Update-FabricKQLDatabaseDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLDatabasePathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLDatabasePathPlatformDefinition, @@ -75,7 +78,7 @@ function Update-FabricKQLDatabaseDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId if($KQLDatabasePathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -83,12 +86,12 @@ function Update-FabricKQLDatabaseDefinition { $body = @{ definition = @{ parts = @() - } + } } - + if ($KQLDatabasePathDefinition) { $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition - + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -150,7 +153,7 @@ function Update-FabricKQLDatabaseDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -161,13 +164,13 @@ function Update-FabricKQLDatabaseDefinition { Write-Message -Message "Update definition for KQLDatabase '$KQLDatabaseId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result @@ -178,13 +181,13 @@ function Update-FabricKQLDatabaseDefinition { $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error diff --git a/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 index 4377448c..868bd231 100644 --- a/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 @@ -14,6 +14,9 @@ The new name for the KQLQueryset. .PARAMETER KQLQuerysetDescription (Optional) The new description for the KQLQueryset. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLQueryset exists. + .EXAMPLE Update-FabricKQLQueryset -KQLQuerysetId "KQLQueryset123" -KQLQuerysetName "NewKQLQuerysetName" @@ -28,7 +31,7 @@ Updates both the name and description of the KQLQueryset "KQLQueryset123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricKQLQueryset { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLQuerysetId, diff --git a/source/Public/Lakehouse/Update-FabricLakehouse.ps1 b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 index c4b9879b..ed251ca7 100644 --- a/source/Public/Lakehouse/Update-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 @@ -5,6 +5,9 @@ Updates the properties of a Fabric Lakehouse. .DESCRIPTION The `Update-FabricLakehouse` function updates the name and/or description of a specified Fabric Lakehouse by making a PATCH request to the API. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Lakehouse exists. + .PARAMETER LakehouseId The unique identifier of the Lakehouse to be updated. @@ -28,7 +31,7 @@ Updates both the name and description of the Lakehouse "Lakehouse123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricLakehouse { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$LakehouseId, From 32d2e5c3dca7803f58052b641eb59b63cce265f9 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:19:17 +0100 Subject: [PATCH 20/76] Update commit message instructions for clarity Clarify the subject line requirements by adding that it should include "for Pester Help Tests" for better context. Thank you! --- .github/copilot-commit-message-instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/copilot-commit-message-instructions.md b/.github/copilot-commit-message-instructions.md index 2ba5c59a..57e25aea 100644 --- a/.github/copilot-commit-message-instructions.md +++ b/.github/copilot-commit-message-instructions.md @@ -1,8 +1,8 @@ -Limit the subject line to 50 characters +# Limit the subject line to 50 characters Capitalize the subject/description line Do not end the subject line with a period Separate the subject from the body with a blank line Use the imperative mood in the subject line -The subject line should be a single sentence with an action word and target with some reasoning +The subject line should be a single sentence with an action word and target with some reasoning add "for Pester Help Tests" Use the body to explain what and why in a friendly kind manner Say thank you at the end of the message From a684b165f64250c0cf1bcc90a41800fbedb334e8 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:30:21 +0100 Subject: [PATCH 21/76] Add GitHub Copilot commit message instructions for Pester Help Tests This change introduces a new setting in the VSCode configuration to specify the file for GitHub Copilot's commit message generation instructions. This enhancement aims to improve the clarity and usability of commit messages for Pester Help Tests. Thank you! --- .vscode/settings.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8bf1c69c..7770c550 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,5 +37,10 @@ "[markdown]": { "files.trimTrailingWhitespace": false, "files.encoding": "utf8" - } + }, + "github.copilot.chat.commitMessageGeneration.instructions": [ + { + "file": ".github/copilot-commit-message-instructions.md" + } + ] } From 53388dc4fcc14dd4377406f996fa833b0d82c922 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:58:32 +0100 Subject: [PATCH 22/76] Enhance Test-FabricApiResponse Documentation for Pester Help Tests Updated the function documentation to provide clear descriptions of parameters, examples, and notes. This improves usability and understanding for users working with the Fabric API response testing. Thank you! --- .../Public/Utils/Test-FabricApiResponse.ps1 | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/source/Public/Utils/Test-FabricApiResponse.ps1 b/source/Public/Utils/Test-FabricApiResponse.ps1 index f27f261b..5b19c577 100644 --- a/source/Public/Utils/Test-FabricApiResponse.ps1 +++ b/source/Public/Utils/Test-FabricApiResponse.ps1 @@ -1,4 +1,36 @@ function Test-FabricApiResponse { +<# +.SYNOPSIS +Tests the response from a Fabric API call and handles long-running operations. +.DESCRIPTION +Tests the response from a Fabric API call and handles long-running operations. It checks the status code and processes the response accordingly. +.PARAMETER statusCode +The HTTP status code returned from the API call. +.PARAMETER response +The response body from the API call. +.PARAMETER responseHeader +The response headers from the API call. +.PARAMETER Name +The name of the resource being created or updated. +.PARAMETER typeName +The type of resource being created or updated (default: 'Fabric Item'). + +.EXAMPLE +Test-FabricApiResponse -statusCode 201 -response $response -responseHeader $header -Name "MyResource" -typeName "Fabric Item" + +Handles the response from a Fabric API call with a 201 status code, indicating successful creation of a resource. + +.EXAMPLE +Test-FabricApiResponse -statusCode 202 -response $response -responseHeader $header -Name "MyResource" -typeName "Fabric Item" + +Handles the response from a Fabric API call with a 202 status code, indicating that the request has been accepted for processing. + +.NOTES +- This function is designed to be used within the context of a Fabric API client. +- It requires the `Write-Message` function to log messages at different levels (Info, Debug, Error). +- The function handles long-running operations by checking the status of the operation and retrieving the result if it has succeeded. +#> + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -20,28 +52,28 @@ function Test-FabricApiResponse { } 202 { Write-Message -Message "$typeName '$Name' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error From c49c4aaca97d6d74277c25b8cdebdb374a740ecc Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 14:58:42 +0100 Subject: [PATCH 23/76] Update WorkspaceId Parameter Documentation for Pester Help Tests This commit adds detailed documentation for the WorkspaceId parameter across multiple scripts, ensuring clarity on its purpose and usage. This enhancement will aid users in understanding how to effectively utilize the WorkspaceId in their operations. Thank you! --- RequiredModules.psd1 | 2 +- .../Capacity/Suspend-FabricCapacity.ps1 | 7 +- .../Environment/Update-FabricEnvironment.ps1 | 9 ++- .../Eventstream/Update-FabricEventstream.ps1 | 9 ++- .../Update-FabricKQLDashboard.ps1 | 9 ++- .../KQL Queryset/Set-FabricKQLQueryset.ps1 | 6 +- .../Start-FabricLakehouseTableMaintenance.ps1 | 74 +++++++++++++++---- .../Start-FabricMirroredDatabaseMirroring.ps1 | 33 +++++++-- .../Stop-FabricMirroredDatabaseMirroring.ps1 | 37 ++++++++-- source/Public/Set-FabricAuthToken.ps1 | 14 ++-- .../Spark/Update-FabricSparkSettings.ps1 | 21 ++++++ 11 files changed, 168 insertions(+), 53 deletions(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index d6b1b1d3..74581735 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -13,7 +13,7 @@ #} Assert = "0.9.6" InvokeBuild = 'latest' - PSScriptAnalyzer = 'latest' + PSScriptAnalyzer = '1.19.1' Pester = 'latest' ModuleBuilder = 'latest' ChangelogManagement = 'latest' diff --git a/source/Public/Capacity/Suspend-FabricCapacity.ps1 b/source/Public/Capacity/Suspend-FabricCapacity.ps1 index d7c3fe94..c6b14ce9 100644 --- a/source/Public/Capacity/Suspend-FabricCapacity.ps1 +++ b/source/Public/Capacity/Suspend-FabricCapacity.ps1 @@ -1,3 +1,6 @@ + +# This function suspends a capacity. +function Suspend-FabricCapacity { <# .SYNOPSIS Suspends a capacity. @@ -23,8 +26,6 @@ This example suspends a capacity given the subscription ID, resource group, and The function defines parameters for the subscription ID, resource group, and capacity. If the 'azToken' environment variable is null, it connects to the Azure account and sets the 'azToken' environment variable. It then defines the headers for the request, defines the URI for the request, and makes a GET request to the URI. #> -# This function suspends a capacity. -function Suspend-FabricCapacity { # Define aliases for the function for flexibility. [Alias("Suspend-PowerBICapacity", "Suspend-FabCapacity")] [CmdletBinding(SupportsShouldProcess)] @@ -49,4 +50,4 @@ function Suspend-FabricCapacity { return Invoke-RestMethod -Method POST -Uri $suspendCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop } -} \ No newline at end of file +} diff --git a/source/Public/Environment/Update-FabricEnvironment.ps1 b/source/Public/Environment/Update-FabricEnvironment.ps1 index 2c9364b0..daa06a5b 100644 --- a/source/Public/Environment/Update-FabricEnvironment.ps1 +++ b/source/Public/Environment/Update-FabricEnvironment.ps1 @@ -14,6 +14,9 @@ The new name for the Environment. .PARAMETER EnvironmentDescription (Optional) The new description for the Environment. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Environment resides. + .EXAMPLE Update-FabricEnvironment -EnvironmentId "Environment123" -EnvironmentName "NewEnvironmentName" @@ -28,7 +31,7 @@ Updates both the name and description of the Environment "Environment123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricEnvironment { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EnvironmentId, diff --git a/source/Public/Eventstream/Update-FabricEventstream.ps1 b/source/Public/Eventstream/Update-FabricEventstream.ps1 index 28103bf3..2ca2e39f 100644 --- a/source/Public/Eventstream/Update-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Update-FabricEventstream.ps1 @@ -14,6 +14,9 @@ The new name for the Eventstream. .PARAMETER EventstreamDescription (Optional) The new description for the Eventstream. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Eventstream resides. + .EXAMPLE Update-FabricEventstream -EventstreamId "Eventstream123" -EventstreamName "NewEventstreamName" @@ -28,7 +31,7 @@ Updates both the name and description of the Eventstream "Eventstream123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricEventstream { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EventstreamId, diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 index 874a614b..b73d2777 100644 --- a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 @@ -14,6 +14,9 @@ The new name for the KQLDashboard. .PARAMETER KQLDashboardDescription (Optional) The new description for the KQLDashboard. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLDashboard resides. + .EXAMPLE Update-FabricKQLDashboard -KQLDashboardId "KQLDashboard123" -KQLDashboardName "NewKQLDashboardName" @@ -28,7 +31,7 @@ Updates both the name and description of the KQLDashboard "KQLDashboard123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -37,8 +40,8 @@ function Update-FabricKQLDashboard { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLDashboardId, diff --git a/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 index f571d379..0294cf61 100644 --- a/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 @@ -1,6 +1,4 @@ function Set-FabricKQLQueryset { -#Requires -Version 7.1 - <# .SYNOPSIS Updates Properties of an existing Fabric KQLQueryset @@ -17,7 +15,7 @@ function Set-FabricKQLQueryset { The Id of the KQLQueryset to update. The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. -.PARAMETER KQLQuerysetName +.PARAMETER KQLQuerysetNewName The new name of the KQLQueryset. This parameter is optional. .PARAMETER KQLQuerysetDescription @@ -108,4 +106,4 @@ process { end {} -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 index c680beba..f6db24d9 100644 --- a/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 +++ b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 @@ -1,4 +1,48 @@ function Start-FabricLakehouseTableMaintenance { +<# +.SYNOPSIS + Initiates a table maintenance job for a specified Lakehouse in a Fabric workspace. +.DESCRIPTION + This function sends a POST request to the Fabric API to start a table maintenance job for a specified Lakehouse. + It allows for optional parameters such as schema name, table name, and Z-ordering columns. + The function also handles asynchronous operations and can wait for completion if specified. +.PARAMETER WorkspaceId + The unique identifier of the workspace where the Lakehouse resides. This parameter is mandatory. +.PARAMETER LakehouseId + The unique identifier of the Lakehouse for which the table maintenance job is to be initiated. This parameter is mandatory. +.PARAMETER JobType + The type of job to be initiated. Default is "TableMaintenance". This parameter is optional. +.PARAMETER SchemaName + The name of the schema in the Lakehouse. This parameter is optional. +.PARAMETER TableName + The name of the table in the Lakehouse. This parameter is optional. +.PARAMETER IsVOrder + A boolean flag indicating whether to apply V-ordering. This parameter is optional. +.PARAMETER ColumnsZOrderBy + An array of columns to be used for Z-ordering. This parameter is optional. +.PARAMETER retentionPeriod + The retention period for the table maintenance job. This parameter is optional. +.PARAMETER waitForCompletion + A boolean flag indicating whether to wait for the job to complete. Default is false. This parameter is optional. +.EXAMPLE + Start-FabricLakehouseTableMaintenance -WorkspaceId "12345" -LakehouseId "67890" -JobType "TableMaintenance" -SchemaName "dbo" -TableName "MyTable" -IsVOrder $true -ColumnsZOrderBy @("Column1", "Column2") -retentionPeriod "7:00:00" -waitForCompletion $true + Initiates a table maintenance job for the specified Lakehouse and waits for its completion. +.EXAMPLE + Start-FabricLakehouseTableMaintenance -WorkspaceId "12345" -LakehouseId "67890" -JobType "TableMaintenance" -SchemaName "dbo" -TableName "MyTable" -IsVOrder $false -ColumnsZOrderBy @("Column1", "Column2") -retentionPeriod "7:00:00" + Initiates a table maintenance job for the specified Lakehouse without waiting for its completion. +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + - This function handles asynchronous operations and retrieves operation results if required. + - The function uses the `Write-Message` function for logging and debugging purposes. + - The function uses the `Get-FabricLakehouse` function to retrieve Lakehouse details. + - The function uses the `Get-FabricLongRunningOperation` function to check the status of long-running operations. + - The function uses the `Invoke-RestMethod` cmdlet to make API requests. + +.NOTES + +#> + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -17,11 +61,11 @@ function Start-FabricLakehouseTableMaintenance { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$SchemaName, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$TableName, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$IsVOrder, @@ -38,7 +82,7 @@ function Start-FabricLakehouseTableMaintenance { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$waitForCompletion = $false - + ) try { @@ -46,14 +90,14 @@ function Start-FabricLakehouseTableMaintenance { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - - - $lakehouse = Get-FabricLakehouse -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId + + + $lakehouse = Get-FabricLakehouse -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and -not $SchemaName) { Write-Error "The Lakehouse '$lakehouse.displayName' has schema enabled, but no schema name was provided. Please specify the 'SchemaName' parameter to proceed." return } - + # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/jobs/instances?jobType={3}" -f $FabricConfig.BaseUrl, $WorkspaceId , $LakehouseId, $JobType Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -81,7 +125,7 @@ function Start-FabricLakehouseTableMaintenance { # Add it to the optimizeSettings in the request body $body.executionData.optimizeSettings.zOrderBy = $ColumnsZOrderBy } - + if ($retentionPeriod) { @@ -92,9 +136,9 @@ function Start-FabricLakehouseTableMaintenance { } } $body.executionData.vacuumSettings.retentionPeriod = $retentionPeriod - + } - + $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug @@ -110,7 +154,7 @@ function Start-FabricLakehouseTableMaintenance { -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - Write-Message -Message "Response Code: $statusCode" -Level Debug + Write-Message -Message "Response Code: $statusCode" -Level Debug # Step 5: Handle and log the response switch ($statusCode) { 201 { @@ -121,14 +165,14 @@ function Start-FabricLakehouseTableMaintenance { Write-Message -Message "Table maintenance job accepted and is now running in the background. Job execution is in progress." -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - + if ($waitForCompletion -eq $true) { - Write-Message -Message "Getting Long Running Operation status" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug return $operationStatus @@ -136,7 +180,7 @@ function Start-FabricLakehouseTableMaintenance { else { Write-Message -Message "The operation is running asynchronously." -Level Info Write-Message -Message "Use the returned details to check the operation status." -Level Info - Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info + Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info $operationDetails = [PSCustomObject]@{ OperationId = $operationId Location = $location diff --git a/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 index 19f3bce3..844bb4a8 100644 --- a/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 +++ b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 @@ -1,4 +1,23 @@ function Start-FabricMirroredDatabaseMirroring{ +<# +.SYNOPSIS + Starts the mirroring of a specified mirrored database in a given workspace. +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to start the mirroring of a specified mirrored database. + It requires the workspace ID and the mirrored database ID as parameters. +.PARAMETER WorkspaceId + The unique identifier of the workspace where the mirrored database resides. This parameter is mandatory. +.PARAMETER MirroredDatabaseId + The unique identifier of the mirrored database to be started. This parameter is mandatory. +.EXAMPLE + Start-FabricMirroredDatabaseMirroring -WorkspaceId "12345" -MirroredDatabaseId "67890" + Starts the mirroring of the mirrored database with ID `67890` in the workspace `12345`. +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + - This function handles asynchronous operations and retrieves operation results if required. + +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -15,10 +34,10 @@ function Start-FabricMirroredDatabaseMirroring{ Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/startMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -28,7 +47,7 @@ function Start-FabricMirroredDatabaseMirroring{ -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -37,15 +56,15 @@ function Start-FabricMirroredDatabaseMirroring{ Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 9: Handle results - Write-Message -Message "Database mirroring started successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info + Write-Message -Message "Database mirroring started successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info return } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to start MirroredDatabase. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 index 77d7404e..a88e8613 100644 --- a/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 +++ b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 @@ -1,6 +1,27 @@ +function Stop-FabricMirroredDatabaseMirroring{ +<# +.SYNOPSIS + Stops the mirroring of a specified mirrored database in a given workspace. +.DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to stop the mirroring of a specified mirrored database. + It requires the workspace ID and the mirrored database ID as parameters. +.PARAMETER WorkspaceId + The unique identifier of the workspace where the mirrored database resides. This parameter is mandatory. -function Stop-FabricMirroredDatabaseMirroring{ +.PARAMETER MirroredDatabaseId + The unique identifier of the mirrored database to be stopped. This parameter is mandatory. + +.EXAMPLE + Stop-FabricMirroredDatabaseMirroring -WorkspaceId "12345" -MirroredDatabaseId "67890" + Stops the mirroring of the mirrored database with ID `67890` in the workspace `12345`. + +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. + - This function handles asynchronous operations and retrieves operation results if required. + +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -17,10 +38,10 @@ function Stop-FabricMirroredDatabaseMirroring{ Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/stopMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -30,7 +51,7 @@ function Stop-FabricMirroredDatabaseMirroring{ -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -39,15 +60,15 @@ function Stop-FabricMirroredDatabaseMirroring{ Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 9: Handle results - Write-Message -Message "Database mirroring stopped successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info + Write-Message -Message "Database mirroring stopped successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info return } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to stop MirroredDatabase. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Set-FabricAuthToken.ps1 b/source/Public/Set-FabricAuthToken.ps1 index 591f8ec9..8d5910c8 100644 --- a/source/Public/Set-FabricAuthToken.ps1 +++ b/source/Public/Set-FabricAuthToken.ps1 @@ -1,3 +1,4 @@ +function Set-FabricAuthToken { <# .SYNOPSIS Sets the Fabric authentication token. @@ -20,6 +21,12 @@ .PARAMETER credential The credential. If provided, the function uses this credential to connect to the Azure account. +.PARAMETER reset + A switch parameter. If provided, the function resets the Fabric authentication token. + +.PARAMETER apiUrl + The API URL. If provided, the function sets the Fabric API URL to this value. + .EXAMPLE Set-FabricAuthToken -servicePrincipalId "12345678-90ab-cdef-1234-567890abcdef" -servicePrincipalSecret "secret" -tenantId "12345678-90ab-cdef-1234-567890abcdef" @@ -36,11 +43,6 @@ https://github.com/RuiRomano/fabricps-pbip #> -function Set-FabricAuthToken { - <# - .SYNOPSIS - Set authentication token for the Fabric service - #> [CmdletBinding(SupportsShouldProcess)] param ( @@ -101,4 +103,4 @@ function Set-FabricAuthToken { return $($FabricSession.FabricToken) } -} \ No newline at end of file +} diff --git a/source/Public/Spark/Update-FabricSparkSettings.ps1 b/source/Public/Spark/Update-FabricSparkSettings.ps1 index 852b327c..261cd0e6 100644 --- a/source/Public/Spark/Update-FabricSparkSettings.ps1 +++ b/source/Public/Spark/Update-FabricSparkSettings.ps1 @@ -45,6 +45,27 @@ .PARAMETER notebookInteractiveRunEnabled Specifies whether notebook interactive run is enabled for the Spark custom pool. This parameter is optional. +.PARAMETER customizeComputeEnabled + Specifies whether compute customization is enabled for the Spark custom pool. This parameter is optional. + +.PARAMETER defaultPoolName + The name of the default pool for the Spark custom pool. This parameter is optional. + +.PARAMETER defaultPoolType + The type of the default pool for the Spark custom pool. This parameter is optional and must be either 'Workspace' or 'Capacity'. + +.PARAMETER starterPoolMaxNode + The maximum number of nodes for the starter pool in the Spark custom pool. This parameter is optional. + +.PARAMETER starterPoolMaxExecutors + The maximum number of executors for the starter pool in the Spark custom pool. This parameter is optional. + +.PARAMETER EnvironmentName + The name of the environment for the Spark custom pool. This parameter is optional. + +.PARAMETER EnvironmentRuntimeVersion + The runtime version of the environment for the Spark custom pool. This parameter is optional. + .EXAMPLE Update-FabricSparkSettings -WorkspaceId "workspace-12345" -SparkSettingsId "pool-67890" -InstancePoolName "Updated Spark Pool" -NodeFamily "MemoryOptimized" -NodeSize "Large" -AutoScaleEnabled $true -AutoScaleMinNodeCount 1 -AutoScaleMaxNodeCount 10 -DynamicExecutorAllocationEnabled $true -DynamicExecutorAllocationMinExecutors 1 -DynamicExecutorAllocationMaxExecutors 10 This example updates the Spark custom pool with ID "pool-67890" in the workspace with ID "workspace-12345" with a new name and configuration. From 5fbc37ab69c95f94dd734df75bb348149c657c4a Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:01:10 +0100 Subject: [PATCH 24/76] Refactor Set-FabricApiHeaders Function for Pester Help Tests Clean up the Set-FabricApiHeaders function by removing unnecessary blank lines and ensuring consistent formatting. This improves readability and maintainability of the code for Pester Help Tests. Thank you! --- source/Public/Utils/Set-FabricApiHeaders copy.ps1 | 13 ++++++------- source/Public/Utils/Set-FabricApiHeaders.ps1 | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/source/Public/Utils/Set-FabricApiHeaders copy.ps1 b/source/Public/Utils/Set-FabricApiHeaders copy.ps1 index e1c7a43d..c6cf62ec 100644 --- a/source/Public/Utils/Set-FabricApiHeaders copy.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders copy.ps1 @@ -1,9 +1,10 @@ +function Set-FabricApiHeaders { <# .SYNOPSIS Sets the Fabric API headers with a valid token for the specified Azure tenant. .DESCRIPTION -The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. +The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. It also updates the token expiration time and global tenant ID. .PARAMETER TenantId @@ -36,8 +37,6 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the .AUTHOR Tiago Balabuch #> - -function Set-FabricApiHeaders { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -60,7 +59,7 @@ function Set-FabricApiHeaders { if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error throw "AppSecret is required when using AppId." - } + } if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { Write-Message -Message "AppId is required when using AppSecret." -Level Error throw "AppId is required when using AppId." @@ -68,16 +67,16 @@ function Set-FabricApiHeaders { # Step 3: Connect to the Azure account # Using AppId and AppSecret if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { - + Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug Write-Message -Message "Logging in using the AppId: $AppId" -Level Info $psCredential = [pscredential]::new($AppId, $AppSecret) Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId - } + } # Using the current user else { - + Write-Message -Message "Logging in using the current user" -Level Debug Write-Message -Message "Logging in using the current user" -Level Info Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 index e1c7a43d..c6cf62ec 100644 --- a/source/Public/Utils/Set-FabricApiHeaders.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -1,9 +1,10 @@ +function Set-FabricApiHeaders { <# .SYNOPSIS Sets the Fabric API headers with a valid token for the specified Azure tenant. .DESCRIPTION -The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. +The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. It also updates the token expiration time and global tenant ID. .PARAMETER TenantId @@ -36,8 +37,6 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the .AUTHOR Tiago Balabuch #> - -function Set-FabricApiHeaders { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -60,7 +59,7 @@ function Set-FabricApiHeaders { if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error throw "AppSecret is required when using AppId." - } + } if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { Write-Message -Message "AppId is required when using AppSecret." -Level Error throw "AppId is required when using AppId." @@ -68,16 +67,16 @@ function Set-FabricApiHeaders { # Step 3: Connect to the Azure account # Using AppId and AppSecret if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { - + Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug Write-Message -Message "Logging in using the AppId: $AppId" -Level Info $psCredential = [pscredential]::new($AppId, $AppSecret) Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId - } + } # Using the current user else { - + Write-Message -Message "Logging in using the current user" -Level Debug Write-Message -Message "Logging in using the current user" -Level Info Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null From f36065f32402a4a269303938fef6f82b4dd24c6c Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:02:18 +0100 Subject: [PATCH 25/76] Refactor Revoke-FabricExternalDataShares Function for Pester Help Tests Updated the Revoke-FabricExternalDataShares function to improve parameter documentation and code formatting. This enhances clarity and maintainability for future development and testing efforts. Thank you! --- .../Revoke-FabricExternalDataShares.ps1 | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 index 0d7f4cb2..bc9f71bb 100644 --- a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 +++ b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 @@ -1,3 +1,5 @@ + +function Revoke-FabricExternalDataShares { <# .SYNOPSIS Retrieves External Data Shares details from a specified Microsoft Fabric. @@ -6,8 +8,16 @@ This function retrieves External Data Shares details. It handles token validation, constructs the API URL, makes the API request, and processes the response. +.PARAMETER WorkspaceId + The unique identifier of the workspace where the External Data Shares resides. + +.PARAMETER ItemId + The unique identifier of the item associated with the External Data Shares. + +.PARAMETER ExternalDataShareId + The unique identifier of the External Data Share to be retrieved. .EXAMPLE - Get-FabricExternalDataShares + Get-FabricExternalDataShares This example retrieves the External Data Shares details .NOTES @@ -16,7 +26,6 @@ Author: Tiago Balabuch #> -function Revoke-FabricExternalDataShares { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -26,14 +35,14 @@ function Revoke-FabricExternalDataShares { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ItemId, - + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ExternalDataShareId ) try { - + # Step 2: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -42,11 +51,11 @@ function Revoke-FabricExternalDataShares { # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Constructing API endpoint URI..." -Level Debug $apiEndpointURI = "{0}/admin/workspaces/{1}/items/{2}/externalDataShares/{3}/revoke" -f $FabricConfig.BaseUrl, $WorkspaceId, $ItemId, $ExternalDataShareId - + $externalDataShares = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Post + -Method Post # Step 4: Return retrieved data Write-Message -Message "Successfully revoked external data shares." -Level Info @@ -56,6 +65,6 @@ function Revoke-FabricExternalDataShares { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error - } - + } + } From 2299bfe1791b65793ffb4ff8f32f5510150bf4be Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:03:27 +0100 Subject: [PATCH 26/76] Add missing function definition for Resume-FabricCapacity for Pester Help Tests This change ensures that the Resume-FabricCapacity function is properly defined in the script. It enhances the clarity and functionality of the code, making it easier to test and maintain. Thank you! --- source/Public/Capacity/Resume-FabricCapacity.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/Public/Capacity/Resume-FabricCapacity.ps1 b/source/Public/Capacity/Resume-FabricCapacity.ps1 index edf0d2f0..04ef64fc 100644 --- a/source/Public/Capacity/Resume-FabricCapacity.ps1 +++ b/source/Public/Capacity/Resume-FabricCapacity.ps1 @@ -1,3 +1,5 @@ + +function Resume-FabricCapacity { <# .SYNOPSIS Resumes a capacity. @@ -24,7 +26,7 @@ The function defines parameters for the subscription ID, resource group, and cap #> # This function resumes a capacity. -function Resume-FabricCapacity { + # Define aliases for the function for flexibility. [Alias("Resume-FabCapacity")] [CmdletBinding(SupportsShouldProcess)] @@ -48,4 +50,4 @@ function Resume-FabricCapacity { if ($PSCmdlet.ShouldProcess("Resume capacity $capacity")) { return Invoke-RestMethod -Method POST -Uri $resumeCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop } -} \ No newline at end of file +} From 256a4a82c187c77ce77bb3423a5ca7adc8f91caf Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:04:28 +0100 Subject: [PATCH 27/76] Remove unnecessary comments and add KQLDatabaseId parameter for Pester Help Tests This update cleans up the Remove-FabricKQLDatabase function by removing the version requirement comment and adding the KQLDatabaseId parameter documentation. This enhances clarity and ensures consistency in the function's usage for Pester Help Tests. Thank you! --- .../KQL Database/Remove-FabricKQLDatabase copy.ps1 | 7 +++---- source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 | 9 ++++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 index 2b5d69f2..5b746f8e 100644 --- a/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 @@ -1,3 +1,4 @@ +function Remove-FabricKQLDatabase { <# .SYNOPSIS Deletes an KQLDatabase from a specified workspace in Microsoft Fabric. @@ -20,11 +21,9 @@ Deletes the KQLDatabase with ID "67890" from workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Remove-FabricKQLDatabase { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -64,7 +63,7 @@ function Remove-FabricKQLDatabase { return $null } Write-Message -Message "KQLDatabase '$KQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - + } catch { # Step 5: Log and handle errors diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 index 411cec73..997f3ce4 100644 --- a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 @@ -1,6 +1,4 @@ function Remove-FabricKQLDatabase { -#Requires -Version 7.1 - <# .SYNOPSIS Removes an existing Fabric Eventhouse @@ -16,6 +14,11 @@ function Remove-FabricKQLDatabase { The Id of the Eventhouse to remove. The value for EventhouseId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER KQLDatabaseId + The Id of the Eventhouse to remove. The value for EventhouseId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. + This parameter is an alias for EventhouseId. + .EXAMPLE Remove-FabricEventhouse ` -WorkspaceId '12345678-1234-1234-1234-123456789012' ` @@ -74,4 +77,4 @@ process { end {} -} \ No newline at end of file +} From 79aeedda2133adbe21d0dfbcc655ceed02c89ae0 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:06:14 +0100 Subject: [PATCH 28/76] Add ItemID parameter to Remove-FabricItem function for Pester Help Tests This change introduces the ItemID parameter to the Remove-FabricItem function, allowing users to specify a particular item to remove regardless of any filters applied. This enhancement improves the function's flexibility and usability in Pester Help Tests. Thank you! --- source/Public/Item/Remove-FabricItem.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/Public/Item/Remove-FabricItem.ps1 b/source/Public/Item/Remove-FabricItem.ps1 index 8659760b..8dbd6f73 100644 --- a/source/Public/Item/Remove-FabricItem.ps1 +++ b/source/Public/Item/Remove-FabricItem.ps1 @@ -11,6 +11,9 @@ .PARAMETER Filter An optional filter to select items to remove. If provided, only items whose DisplayName matches the filter are removed. +.PARAMETER ItemID + The ID of a specific item to remove. If provided, this item is removed regardless of the filter. + .EXAMPLE Remove-FabricItems -WorkspaceID "12345678-90ab-cdef-1234-567890abcdef" -Filter "*test*" @@ -64,4 +67,4 @@ Function Remove-FabricItem { } } } -} \ No newline at end of file +} From 82a61d23f612a3462418af72e792ee48e8a5cb1b Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:06:26 +0100 Subject: [PATCH 29/76] Add blank line for improved readability in Remove-FabricKQLDatabase function for Pester Help Tests This change adds a blank line in the documentation section of the Remove-FabricKQLDatabase function. This improves readability and maintains consistency in the code formatting. Thank you! --- source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 index 5b746f8e..2d8bf8b6 100644 --- a/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 @@ -12,6 +12,7 @@ The `Remove-FabricKQLDatabase` function sends a DELETE request to the Fabric API .PARAMETER KQLDatabaseId (Mandatory) The ID of the KQLDatabase to be deleted. + .EXAMPLE Remove-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseId "67890" From ad109e69912410df180e6c078c51aec979c572c4 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:15:59 +0100 Subject: [PATCH 30/76] Refactor Set-FabricApiHeaders function for Pester Help Tests Updated the synopsis and description for clarity, ensuring that the function's purpose and parameters are clearly defined. This enhances the documentation for better understanding and usability in Pester Help Tests. Thank you! --- .../Utils/Set-FabricApiHeaders copy.ps1 | 38 +++++-------------- source/Public/Utils/Set-FabricApiHeaders.ps1 | 38 +++++-------------- 2 files changed, 20 insertions(+), 56 deletions(-) diff --git a/source/Public/Utils/Set-FabricApiHeaders copy.ps1 b/source/Public/Utils/Set-FabricApiHeaders copy.ps1 index c6cf62ec..a25a3f69 100644 --- a/source/Public/Utils/Set-FabricApiHeaders copy.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders copy.ps1 @@ -1,41 +1,23 @@ function Set-FabricApiHeaders { <# .SYNOPSIS -Sets the Fabric API headers with a valid token for the specified Azure tenant. - + Configures the Fabric API headers for authentication using Azure credentials. .DESCRIPTION -The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. -It also updates the token expiration time and global tenant ID. + This function sets the Fabric API headers by logging into Azure with the specified tenant ID and retrieving an access token. + It updates the global configuration with the token and tenant ID for subsequent API requests. .PARAMETER TenantId -The Azure tenant ID for which the access token is requested. - + The Azure tenant ID for which the access token is requested. .PARAMETER AppId -The Azure app ID for which the service principal access token is requested. - + The Azure app ID for which the service principal access token is requested. .PARAMETER AppSecret -The Azure App secret for which the service principal access token is requested. - + The Azure App secret for which the service principal access token is requested. .EXAMPLE -Set-FabricApiHeaders -TenantId "your-tenant-id" - -Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. - + Set-FabricApiHeaders -TenantId "your-tenant-id" + Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. .EXAMPLE -$tenantId = "999999999-99999-99999-9999-999999999999" -$appId = "888888888-88888-88888-8888-888888888888" -$appSecret = "your-app-secret" -$secureAppSecret = $appSecret | ConvertTo-SecureString -AsPlainText -Force - -Set-FabricApiHeader -TenantId $tenantId -AppId $appId -AppSecret $secureAppSecret -Logs in to Azure with the specified tenant ID, retrieves an access token for the service principal, and configures the Fabric headers. - -.NOTES -- Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). -- Relies on a global `$FabricConfig` object for storing headers and token metadata. - -.AUTHOR -Tiago Balabuch + $tenantId = "999999999-99999-99999-9999-999999999999" + $appId = "888888888-88888-88888-8888-888888888888" #> [CmdletBinding()] param ( diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 index c6cf62ec..a25a3f69 100644 --- a/source/Public/Utils/Set-FabricApiHeaders.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -1,41 +1,23 @@ function Set-FabricApiHeaders { <# .SYNOPSIS -Sets the Fabric API headers with a valid token for the specified Azure tenant. - + Configures the Fabric API headers for authentication using Azure credentials. .DESCRIPTION -The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. -It also updates the token expiration time and global tenant ID. + This function sets the Fabric API headers by logging into Azure with the specified tenant ID and retrieving an access token. + It updates the global configuration with the token and tenant ID for subsequent API requests. .PARAMETER TenantId -The Azure tenant ID for which the access token is requested. - + The Azure tenant ID for which the access token is requested. .PARAMETER AppId -The Azure app ID for which the service principal access token is requested. - + The Azure app ID for which the service principal access token is requested. .PARAMETER AppSecret -The Azure App secret for which the service principal access token is requested. - + The Azure App secret for which the service principal access token is requested. .EXAMPLE -Set-FabricApiHeaders -TenantId "your-tenant-id" - -Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. - + Set-FabricApiHeaders -TenantId "your-tenant-id" + Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. .EXAMPLE -$tenantId = "999999999-99999-99999-9999-999999999999" -$appId = "888888888-88888-88888-8888-888888888888" -$appSecret = "your-app-secret" -$secureAppSecret = $appSecret | ConvertTo-SecureString -AsPlainText -Force - -Set-FabricApiHeader -TenantId $tenantId -AppId $appId -AppSecret $secureAppSecret -Logs in to Azure with the specified tenant ID, retrieves an access token for the service principal, and configures the Fabric headers. - -.NOTES -- Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). -- Relies on a global `$FabricConfig` object for storing headers and token metadata. - -.AUTHOR -Tiago Balabuch + $tenantId = "999999999-99999-99999-9999-999999999999" + $appId = "888888888-88888-88888-8888-888888888888" #> [CmdletBinding()] param ( From 2352014f94cc59a9605d837b5c3d0f6844b9c02e Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Sun, 11 May 2025 15:18:48 +0100 Subject: [PATCH 31/76] Refactor Remove-FabricEnvironmentStagingLibrary function for Pester Help Tests This update improves the formatting and readability of the Remove-FabricEnvironmentStagingLibrary function. It ensures consistent spacing and enhances the overall structure of the code, making it easier to maintain and understand. Additionally, the Remove-FabricEventstream function has been updated to include a new parameter for the Eventstream name, providing more flexibility in specifying which Eventstream to delete. Thank you! --- .../Remove-FabricEnvironmentStagingLibrary.ps1 | 15 ++++++--------- .../Eventstream/Remove-FabricEventstream copy.ps1 | 7 +++---- .../Eventstream/Remove-FabricEventstream.ps1 | 9 ++++++--- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 index ebfd16f5..c5f3d5ad 100644 --- a/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 +++ b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 @@ -1,10 +1,10 @@ - +function Remove-FabricEnvironmentStagingLibrary { <# .SYNOPSIS Deletes a specified library from the staging environment in a Microsoft Fabric workspace. .DESCRIPTION -This function allows for the deletion of a library from the staging environment, one file at a time. +This function allows for the deletion of a library from the staging environment, one file at a time. It ensures token validity, constructs the appropriate API request, and handles both success and failure responses. .PARAMETER WorkspaceId @@ -25,12 +25,9 @@ Deletes the specified library from the staging environment in the specified work - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. - This function currently supports deleting one library at a time. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - - -function Remove-FabricEnvironmentStagingLibrary { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -40,7 +37,7 @@ function Remove-FabricEnvironmentStagingLibrary { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EnvironmentId, - + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$LibraryName @@ -51,7 +48,7 @@ function Remove-FabricEnvironmentStagingLibrary { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + Write-Message -Message "Token validation completed." -Level Debug # Step 2: Construct the API URL @@ -67,7 +64,7 @@ function Remove-FabricEnvironmentStagingLibrary { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 4: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error diff --git a/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 b/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 index ef59265f..598c66ac 100644 --- a/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 +++ b/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 @@ -1,3 +1,4 @@ +function Remove-FabricEventstream { <# .SYNOPSIS Deletes an Eventstream from a specified workspace in Microsoft Fabric. @@ -20,11 +21,9 @@ Deletes the Eventstream with ID "67890" from workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Remove-FabricEventstream { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -63,7 +62,7 @@ function Remove-FabricEventstream { return $null } Write-Message -Message "Eventstream '$EventstreamId' deleted successfully from workspace '$WorkspaceId'." -Level Info - + } catch { # Step 5: Log and handle errors diff --git a/source/Public/Eventstream/Remove-FabricEventstream.ps1 b/source/Public/Eventstream/Remove-FabricEventstream.ps1 index 25d85024..d6e6bd5b 100644 --- a/source/Public/Eventstream/Remove-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Remove-FabricEventstream.ps1 @@ -1,6 +1,4 @@ function Remove-FabricEventstream { -#Requires -Version 7.1 - <# .SYNOPSIS Removes an existing Fabric Eventstream @@ -16,6 +14,11 @@ function Remove-FabricEventstream { The Id of the Eventstream to delete. The value for Eventstream is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER EventstreamName + The name of the Eventstream to delete. The value for Eventstream is a string. + An example of a string is 'MyEventstream'. + The name of the Eventstream to delete. The value for Eventstream is a string. + .EXAMPLE Remove-FabricEventstream ` -WorkspaceId '12345678-1234-1234-1234-123456789012' ` @@ -102,4 +105,4 @@ process { end {} -} \ No newline at end of file +} From b58d48a3c9c358845afcb7f26d04b15a51383e95 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 08:45:28 +0100 Subject: [PATCH 32/76] Added from #17 --- source/Private/Get-FileDefinitionParts.ps1 | 57 + source/Private/Set-WellKnown.ps1 | 1093 ++++++++++++++++++++ source/Private/Test-TokenExpired.ps1 | 8 +- source/Private/Write-Message.ps1 | 84 ++ 4 files changed, 1238 insertions(+), 4 deletions(-) create mode 100644 source/Private/Get-FileDefinitionParts.ps1 create mode 100644 source/Private/Set-WellKnown.ps1 create mode 100644 source/Private/Write-Message.ps1 diff --git a/source/Private/Get-FileDefinitionParts.ps1 b/source/Private/Get-FileDefinitionParts.ps1 new file mode 100644 index 00000000..1131e74c --- /dev/null +++ b/source/Private/Get-FileDefinitionParts.ps1 @@ -0,0 +1,57 @@ +function Get-FileDefinitionParts { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$sourceDirectory + ) + try { + if (-Not (Test-Path $sourceDirectory)) { + Write-Message -Message "The specified source directory does not exist: $sourceDirectory" -Level Error + throw + } + + # Get all files from the directory recursively + Write-Message -Message "Get all files from the directory recursively" -Level Debug + $fileList = Get-ChildItem -Path $sourceDirectory -File -Recurse + + # Initialize the output JSON object + $jsonObject = @{ parts = @() } + + # Loop through the files to create parts dynamically + Write-Message -Message "Loop through the files to create parts dynamically" -Level Debug + foreach ($file in $fileList) { + + $relativePath = $file.FullName.Substring($sourceDirectory.Length + 1) -replace "\\", "/" + Write-Message -Message "File found: $relativePath" -Level Debug + Write-Message -Message "Starting encode to base64" -Level Debug + + $base64Content = Convert-ToBase64 -filePath $file.FullName + Write-Message -Message "Adding part to json object" -Level Debug + + $jsonObject.parts += @{ + path = $relativePath + payload = $base64Content + payloadType = "InlineBase64" + } + } + Write-Message -Message "Loop through the files finished" -Level Debug + + return $jsonObject + Write-Message -Message "Parts returned" -Level Debug + } + + catch { + # Step 4: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "An error occurred while getting file definition parts: $errorDetails" -Level Error + throw "An error occurred while encoding to Base64: $_" + } +} + + +# Example usage +#$sourceDirectory = "C:\temp\API\Notebook" +#Get-FileParts -sourceDirectory $sourceDirectory +#$fileParts = Get-FileParts -sourceDirectory $sourceDirectory +#$fileParts | ConvertTo-Json -Depth 10 diff --git a/source/Private/Set-WellKnown.ps1 b/source/Private/Set-WellKnown.ps1 new file mode 100644 index 00000000..7b64ef28 --- /dev/null +++ b/source/Private/Set-WellKnown.ps1 @@ -0,0 +1,1093 @@ +function Write-Log { + param ( + [Parameter(Mandatory = $true)] + [string]$Message, + + [Parameter(Mandatory = $false)] + [string]$Level = 'INFO', + + [Parameter(Mandatory = $false)] + [bool]$Stop = $true + ) + + $color = switch ($Level) { + 'INFO' { 'Green' } + 'WARN' { 'Yellow' } + 'ERROR' { 'Red' } + 'DEBUG' { 'DarkMagenta' } + default { 'Green' } + } + + $prefix = switch ($Level) { + 'INFO' { '*' } + 'WARN' { '!' } + 'ERROR' { 'X' } + 'DEBUG' { 'D' } + default { '*' } + } + + Write-Host -ForegroundColor $color "[$prefix] $Message" + + if ($Stop -and $Level -eq 'ERROR') { + exit 1 + } + } + + function Install-ModuleIfNotInstalled { + param ( + [Parameter(Mandatory = $true)] + [string]$ModuleName + ) + + if (-not (Get-Module -Name $ModuleName -ListAvailable)) { + try { + Write-Log -Message "Installing module: $ModuleName" -Level 'DEBUG' + Install-Module -Name $ModuleName -AllowClobber -Force -Scope CurrentUser -Repository PSGallery -Confirm:$false -SkipPublisherCheck -AcceptLicense + } + catch { + Write-Error $_.Exception.Message + Write-Log -Message "Unable to install module: $ModuleName" -Level 'ERROR' + } + } + } + + function Import-ModuleIfNotImported { + param ( + [Parameter(Mandatory = $true)] + [string]$ModuleName + ) + + if (-not (Get-Module -Name $ModuleName)) { + try { + Write-Log -Message "Importing module: $ModuleName" -Level 'DEBUG' + Import-Module -Name $ModuleName + } + catch { + Write-Error $_.Exception.Message + Write-Log -Message "Unable to import module: $ModuleName" -Level 'ERROR' + } + } + } + + function Invoke-FabricRest { + param ( + [Parameter(Mandatory = $false)] + [string]$Method = 'GET', + + [Parameter(Mandatory = $true)] + [string]$Endpoint, + + [Parameter(Mandatory = $false)] + [object]$Payload, + + [Parameter(Mandatory = $false)] + [int]$RetryCount = 3, + + [Parameter(Mandatory = $false)] + [int]$RetryDelaySeconds = 30 + ) + + try { + # Retrieve the Fabric access token + try { + $secureAccessToken = (Get-AzAccessToken -WarningAction SilentlyContinue -AsSecureString -ResourceUrl 'https://api.fabric.microsoft.com').Token + } + catch { + Write-Log -Message "Failed to retrieve access token." -Level 'ERROR' + } + + $uri = "https://api.fabric.microsoft.com/v1/$Endpoint" + $attempt = 0 + $response = $null + $responseHeaders = $null + $statusCode = $null + + while ($attempt -lt $RetryCount) { + try { + if ($Payload) { + $body = $Payload | ConvertTo-Json -Depth 10 -Compress + $response = Invoke-RestMethod -Authentication Bearer -Token $secureAccessToken -Uri $uri -Method $Method -ContentType 'application/json' -Body $body -ResponseHeadersVariable responseHeaders -StatusCodeVariable statusCode + } + else { + $response = Invoke-RestMethod -Authentication Bearer -Token $secureAccessToken -Uri $uri -Method $Method -ResponseHeadersVariable responseHeaders -StatusCodeVariable statusCode + } + + break + } + catch { + $statusCode = $_.Exception.Response.StatusCode.value__ + + if ($statusCode -eq 429) { + $retryAfter = $_.Exception.Response.Headers.RetryAfter.Delta.TotalSeconds + + $retryDelaySeconds = $RetryDelaySeconds + if ($retryAfter) { + $retryDelaySeconds = $retryAfter + } + + Write-Log -Message "Throttled. Waiting for $retryDelaySeconds seconds before retrying..." -Level 'DEBUG' + Start-Sleep -Seconds $retryDelaySeconds + + $attempt++ + } + else { + throw $_ + } + } + } + + if ($attempt -ge $RetryCount) { + Write-Log -Message "Maximum retry attempts reached. Request failed." -Level 'ERROR' + } + + if ($statusCode -eq 200 -or $statusCode -eq 201) { + return [PSCustomObject]@{ + Response = $response + Headers = $responseHeaders + } + } + + if ($statusCode -eq 202 -and $responseHeaders.Location -and $responseHeaders['x-ms-operation-id']) { + $operationId = [string]$responseHeaders['x-ms-operation-id'] + Write-Log -Message "Long Running Operation initiated. Operation ID: $operationId" -Level 'DEBUG' + $result = Get-LroResult -OperationId $operationId + + return [PSCustomObject]@{ + Response = $result.Response + Headers = $result.Headers + } + } + } + catch { + Write-Log -Message $_.Exception.Message -Level 'ERROR' + } + } + + function Get-LroResult { + param ( + [Parameter(Mandatory = $true)] + [string]$OperationId + ) + + $operationStatus = $null + while ($operationStatus -ne 'Succeeded') { + $result = Invoke-FabricRest -Method 'GET' -Endpoint "operations/$OperationId" + + $operationStatus = $result.Response.status + + if ($operationStatus -eq 'Failed') { + Write-Log -Message "Operation failed. Status: $operationStatus" -Level 'ERROR' + } + + if ($operationStatus -ne "Succeeded") { + $retryAfter = [int]$result.Headers['Retry-After'][0] + Start-Sleep -Seconds $retryAfter + } + } + + return Invoke-FabricRest -Method 'GET' -Endpoint "operations/$OperationId/result" + } + + function Set-FabricItem { + param ( + [Parameter(Mandatory = $true)] + [string]$DisplayName, + + [Parameter(Mandatory = $true)] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [string]$Type, + + [Parameter(Mandatory = $false)] + [object]$CreationPayload, + + [Parameter(Mandatory = $false)] + [object]$Definition + ) + + switch ($Type) { + 'DataPipeline' { + $itemEndpoint = 'dataPipelines' + } + 'Environment' { + $itemEndpoint = 'environments' + } + 'Eventhouse' { + $itemEndpoint = 'eventhouses' + } + 'Eventstream' { + $itemEndpoint = 'eventstreams' + } + 'GraphQLApi' { + $itemEndpoint = 'GraphQLApis' + } + 'KQLDashboard' { + $itemEndpoint = 'kqlDashboards' + } + 'KQLDatabase' { + $itemEndpoint = 'kqlDatabases' + } + 'KQLQueryset' { + $itemEndpoint = 'kqlQuerysets' + } + 'Lakehouse' { + $itemEndpoint = 'lakehouses' + } + 'MirroredDatabase' { + $itemEndpoint = 'mirroredDatabases' + } + 'MLExperiment' { + $itemEndpoint = 'mlExperiments' + } + 'MLModel' { + $itemEndpoint = 'mlModels' + } + 'Notebook' { + $itemEndpoint = 'notebooks' + } + 'Reflex' { + $itemEndpoint = 'reflexes' + } + 'Report' { + $itemEndpoint = 'reports' + } + 'SemanticModel' { + $itemEndpoint = 'semanticModels' + } + 'SparkJobDefinition' { + $itemEndpoint = 'sparkJobDefinitions' + } + 'SQLDatabase' { + $itemEndpoint = 'sqlDatabases' + } + 'Warehouse' { + $itemEndpoint = 'warehouses' + } + default { + $itemEndpoint = 'items' + } + } + + If ($CreationPayload -and $Definition) { + Write-Log -Message 'Only one of CreationPayload or Definition is allowed at time.' -Level 'ERROR' + } + + $definitionRequired = @('Report', 'SemanticModel', 'MirroredDatabase') + if ($Type -in $definitionRequired -and !$Definition) { + Write-Log -Message "Definition is required for Type: $Type" -Level 'ERROR' + } + + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId/$itemEndpoint" + $result = $results.Response.value | Where-Object { $_.displayName -eq $DisplayName } + if (!$result) { + Write-Log -Message "Creating ${Type}: $DisplayName" -Level 'WARN' + $payload = @{ + displayName = $DisplayName + description = $DisplayName + } + + if ($itemEndpoint -eq 'items') { + $payload['type'] = $Type + } + + if ($CreationPayload) { + $payload['creationPayload'] = $CreationPayload + } + + if ($Definition) { + $payload['definition'] = $Definition + } + + $result = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/$itemEndpoint" -Payload $payload).Response + } + + Write-Log -Message "${Type} - Name: $($result.displayName) / ID: $($result.id)" + + return $result + } + + function Get-DefinitionPartBase64 { + param ( + [Parameter(Mandatory = $true)] + [string]$Path, + + [Parameter(Mandatory = $false)] + [object]$Values + ) + + if (-not (Test-Path -Path $Path)) { + Write-Log -Message "File not found: $Path" -Level 'ERROR' + } + + $content = (Get-Content -Path $Path -Raw).Trim().ToString() + + if ($Values) { + foreach ($value in $Values) { + $content = $content.Replace($value.key, $value.value) + } + } + + return [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($content)) + } + + function Set-FabricDomain { + param ( + [Parameter(Mandatory = $true)] + [string]$DisplayName, + + [Parameter(Mandatory = $false)] + [string]$ParentDomainId + ) + + $results = Invoke-FabricRest -Method 'GET' -Endpoint "admin/domains" + $result = $results.Response.domains | Where-Object { $_.displayName -eq $DisplayName } + if (!$result) { + Write-Log -Message "Creating Domain: $DisplayName" -Level 'WARN' + $payload = @{ + displayName = $DisplayName + description = $DisplayName + } + + if ($ParentDomainId) { + $payload['parentDomainId'] = $ParentDomainId + } + + $result = (Invoke-FabricRest -Method 'POST' -Endpoint "admin/domains" -Payload $payload).Response + } + + if ($ParentDomainId) { + Write-Log -Message "Child Domain - Name: $($result.displayName) / ID: $($result.id)" + } + else { + Write-Log -Message "Parent Domain - Name: $($result.displayName) / ID: $($result.id)" + } + + return $result + } + + function Get-BaseName { + param ( + [Parameter(Mandatory = $false)] + [int]$Length = 10 + ) + + $base = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_BASE + + if (!$base) { + $base = -join ((65..90) + (97..122) | Get-Random -Count $Length | ForEach-Object { [char]$_ }) + } + + return $base + } + + function Get-DisplayName { + param ( + [Parameter(Mandatory = $true)] + [string]$Base, + + [Parameter(Mandatory = $false)] + [string]$Prefix = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX, + + [Parameter(Mandatory = $false)] + [string]$Suffix = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_SUFFIX, + + [Parameter(Mandatory = $false)] + [string]$Separator = '_' + ) + + $result = $Base + + # add prefix and suffix + if ($Prefix) { + $result = "${Prefix}${Separator}${result}" + } + + if ($Suffix) { + $result = "${result}${Separator}${Suffix}" + } + + return $result + } + + function Set-FabricWorkspace { + param ( + [Parameter(Mandatory = $true)] + [string]$DisplayName, + + [Parameter(Mandatory = $true)] + [string]$CapacityId + ) + + $workspaces = Invoke-FabricRest -Method 'GET' -Endpoint 'workspaces' + $workspace = $workspaces.Response.value | Where-Object { $_.displayName -eq $DisplayName } + if (!$workspace) { + Write-Log -Message "Creating Workspace: $DisplayName" -Level 'WARN' + $payload = @{ + displayName = $DisplayName + description = $DisplayName + capacityId = $CapacityId + } + $workspace = (Invoke-FabricRest -Method 'POST' -Endpoint 'workspaces' -Payload $payload).Response + } + + return $workspace + } + + function Set-FabricWorkspaceCapacity { + param ( + [Parameter(Mandatory = $true)] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [string]$CapacityId + ) + + $workspace = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId" + if ($workspace.Response.capacityId -ne $CapacityId) { + Write-Log -Message "Assigning Workspace to Capacity ID: $CapacityId" -Level 'WARN' + $payload = @{ + capacityId = $CapacityId + } + _ = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/assignToCapacity" -Payload $payload).Response + $workspace.Response.capacityId = $CapacityId + } + + return $workspace.Response + } + + function Set-FabricWorkspaceRoleAssignment { + param ( + [Parameter(Mandatory = $true)] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [object]$SPN + ) + + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId/roleAssignments" + $result = $results.Response.value | Where-Object { $_.id -eq $SPN.Id } + if (!$result) { + Write-Log -Message "Assigning SPN to Workspace: $($SPN.DisplayName)" -Level 'WARN' + $payload = @{ + principal = @{ + id = $SPN.Id + type = 'ServicePrincipal' + } + role = 'Admin' + } + $result = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/roleAssignments" -Payload $payload).Response + } + } + + function Set-FabricGatewayVirtualNetwork { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$DisplayName, + + [Parameter(Mandatory = $true)] + [string]$CapacityId, + + # Inactivity time (in minutes) before the gateway goes to auto-sleep. + # Allowed values: 30, 60, 90, 120, 150, 240, 360, 480, 720, 1440. + [Parameter(Mandatory = $true)] + [ValidateSet(30, 60, 90, 120, 150, 240, 360, 480, 720, 1440)] + [int]$InactivityMinutesBeforeSleep, + + # Number of member gateways (between 1 and 7). + [Parameter(Mandatory = $true)] + [ValidateRange(1, 7)] + [int]$NumberOfMemberGateways, + + # Azure virtual network details: + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string]$VirtualNetworkName, + + [Parameter(Mandatory = $true)] + [string]$SubnetName + ) + + # Attempt to check for an existing gateway with the same display name. + $existingGateways = Invoke-FabricRest -Method 'GET' -Endpoint "gateways" + $result = $existingGateways.Response.value | Where-Object { $_.displayName -eq $DisplayName } + if (!$result) { + # Construct the payload for creating a Virtual Network gateway. + # Refer to the API documentation for details on the request format :contentReference[oaicite:1]{index=1} and the Virtual Network Azure Resource :contentReference[oaicite:2]{index=2}. + $payload = @{ + type = "VirtualNetwork" + displayName = $DisplayName + capacityId = $CapacityId + inactivityMinutesBeforeSleep = $InactivityMinutesBeforeSleep + numberOfMemberGateways = $NumberOfMemberGateways + virtualNetworkAzureResource = @{ + subscriptionId = $SubscriptionId + resourceGroupName = $ResourceGroupName + virtualNetworkName = $VirtualNetworkName + subnetName = $SubnetName + } + } + + Write-Log -Message "Creating Virtual Network Gateway: $DisplayName" -Level 'WARN' + $newGateway = Invoke-FabricRest -Method 'POST' -Endpoint "gateways" -Payload $payload + $result = $newGateway.Response + } + + Write-Log -Message "Gateway Virtual Network - Name: $($result.displayName) / ID: $($result.id)" + return $result + } + + + function Set-AzureVirtualNetwork { + param( + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string]$VNetName, + + [Parameter(Mandatory = $true)] + [string]$Location, + + [Parameter(Mandatory = $true)] + [string[]]$AddressPrefixes, + + [Parameter(Mandatory = $true)] + [string]$SubnetName, + + [Parameter(Mandatory = $true)] + [string[]]$SubnetAddressPrefixes + ) + + # Attempt to get the existing Virtual Network + try { + $vnet = Get-AzVirtualNetwork -Name $VNetName -ResourceGroupName $ResourceGroupName -ErrorAction Stop + } + catch { + # VNet does not exist, so create it + Write-Log -Message "Creating VNet: $VNetName in Resource Group: $ResourceGroupName" -Level 'WARN' + $subnetConfig = New-AzVirtualNetworkSubnetConfig ` + -Name $SubnetName ` + -AddressPrefix $SubnetAddressPrefixes ` + + $subnetConfig = Add-AzDelegation ` + -Name 'PowerPlatformVnetAccess' ` + -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` + -Subnet $subnetConfig + + $vnet = New-AzVirtualNetwork ` + -Name $VNetName ` + -ResourceGroupName $ResourceGroupName ` + -Location $Location ` + -AddressPrefix $AddressPrefixes ` + -Subnet $subnetConfig + + # Commit creation + $vnet = $vnet | Set-AzVirtualNetwork + Write-Log -Message "Created VNet: $VNetName" -Level 'INFO' + } + + # If the VNet already exists, check for the subnet + $subnet = $vnet.Subnets | Where-Object { $_.Name -eq $SubnetName } + if (-not $subnet) { + # Subnet does not exist; add one with the delegation + Write-Log -Message "Adding subnet '$SubnetName' with delegation 'Microsoft.PowerPlatform/vnetaccesslinks' to VNet '$VNetName'." -Level 'WARN' + $subnetConfig = New-AzVirtualNetworkSubnetConfig ` + -Name $SubnetName ` + -AddressPrefix $SubnetAddressPrefixes ` + + $subnetConfig = Add-AzDelegation ` + -Name 'PowerPlatformVnetAccess' ` + -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` + -Subnet $subnetConfig + + $vnet = $vnet | Set-AzVirtualNetwork + } + else { + # Subnet exists; ensure it has the correct delegation + $existingDelegation = $subnet.Delegations | Where-Object { $_.ServiceName -eq 'Microsoft.PowerPlatform/vnetaccesslinks' } + if (-not $existingDelegation) { + Write-Log -Message "Subnet '$SubnetName' found but missing delegation to 'Microsoft.PowerPlatform/vnetaccesslinks'. Adding Microsoft.PowerPlatform/vnetaccesslinks delegation..." -Level 'WARN' + + $subnetConfig = Add-AzDelegation ` + -Name 'PowerPlatformVnetAccess' ` + -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` + -Subnet $subnet + + $vnet = $vnet | Set-AzVirtualNetwork + Write-Log -Message "Added missing delegation to subnet '$SubnetName'." -Level 'INFO' + } + } + Write-Log -Message "Az Virtual Network - Name: $($vnet.Name)" + + $userPrincipalName = $azContext.Account.Id + $principal = Get-AzADUser -UserPrincipalName $userPrincipalName + + $existingAssignment = Get-AzRoleAssignment -Scope $vnet.Id -ObjectId $principal.Id -ErrorAction SilentlyContinue | Where-Object { + $_.RoleDefinitionName -eq "Network Contributor" + } + + Write-Log "Assigning Network Contributor role to the principal on the virtual network $($VNetName)" + if (!$existingAssignment) { + New-AzRoleAssignment -ObjectId $principal.Id -RoleDefinitionName "Network Contributor" -Scope $vnet.Id + } + + return $vnet + } + + # Define an array of modules to install + $modules = @('Az.Accounts', 'Az.Resources', 'Az.Fabric', 'pwsh-dotenv', 'ADOPS', 'Az.Network') + + # Loop through each module and install if not installed + foreach ($module in $modules) { + Install-ModuleIfNotInstalled -ModuleName $module + Import-ModuleIfNotImported -ModuleName $module + } + + # Import the .env file into the environment variables + if (Test-Path -Path './wellknown.env') { + Import-Dotenv -Path ./wellknown.env -AllowClobber + } + + if (!$Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION) { + Write-Log -Message 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID, FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID, FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME, FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME and FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX and FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME and FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION are required environment variables.' -Level 'ERROR' + } + + # Check if already logged in to Azure, if not then login + $azContext = Get-AzContext + if (!$azContext -or $azContext.Tenant.Id -ne $Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or $azContext.Subscription.Id -ne $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID) { + Write-Log -Message 'Logging in to Azure.' -Level 'DEBUG' + Connect-AzAccount -Tenant $Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -SubscriptionId $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -UseDeviceAuthentication + $azContext = Get-AzContext + # Disconnect-AzAccount + } + # $currentUser = Get-AzADUser -SignedIn + + # Logged in to Azure DevOps + Write-Log -Message 'Logging in to Azure DevOps.' -Level 'DEBUG' + $secureAccessToken = (Get-AzAccessToken -WarningAction SilentlyContinue -AsSecureString -ResourceUrl '499b84ac-1321-427f-aa17-267ca6975798').Token + $unsecureAccessToken = $secureAccessToken | ConvertFrom-SecureString -AsPlainText + $azdoContext = Connect-ADOPS -TenantId $azContext.Tenant.Id -Organization $Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -OAuthToken $unsecureAccessToken + + $SPN = $null + if ($Env:FABRIC_TESTACC_WELLKNOWN_SPN_NAME) { + $SPN = Get-AzADServicePrincipal -DisplayName $Env:FABRIC_TESTACC_WELLKNOWN_SPN_NAME + } + + $wellKnown = @{} + + # Get Fabric Capacity ID + $capacities = Invoke-FabricRest -Method 'GET' -Endpoint 'capacities' + $capacity = $capacities.Response.value | Where-Object { $_.displayName -eq $Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME } + if (!$capacity) { + Write-Log -Message "Fabric Capacity: $($Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME)" + } + Write-Log -Message "Fabric Capacity - Name: $($Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME) / ID: $($capacity.id)" + $wellKnown['Capacity'] = @{ + id = $capacity.id + displayName = $capacity.displayName + sku = $capacity.sku + } + + $itemNaming = @{ + 'Dashboard' = 'dash' + 'Datamart' = 'dm' + 'DataPipeline' = 'dp' + 'Environment' = 'env' + 'Eventhouse' = 'eh' + 'Eventstream' = 'es' + 'GraphQLApi' = 'gql' + 'KQLDashboard' = 'kqldash' + 'KQLDatabase' = 'kqldb' + 'KQLQueryset' = 'kqlqs' + 'Lakehouse' = 'lh' + 'MirroredDatabase' = 'mdb' + 'MirroredWarehouse' = 'mwh' + 'MLExperiment' = 'mle' + 'MLModel' = 'mlm' + 'Notebook' = 'nb' + 'PaginatedReport' = 'prpt' + 'Reflex' = 'rx' + 'Report' = 'rpt' + 'SemanticModel' = 'sm' + 'SparkJobDefinition' = 'sjd' + 'SQLDatabase' = 'sqldb' + 'SQLEndpoint' = 'sqle' + 'Warehouse' = 'wh' + 'WorkspaceDS' = 'wsds' + 'WorkspaceRS' = 'wsrs' + 'DomainParent' = 'parent' + 'DomainChild' = 'child' + 'EntraServicePrincipal' = 'sp' + 'EntraGroup' = 'grp' + 'AzDOProject' = 'proj' + 'VirtualNetwork01' = 'vnet01' + 'VirtualNetwork02' = 'vnet02' + 'VirtualNetworkSubnet' = 'subnet' + 'GatewayVirtualNetwork' = 'gvnet' + } + + $baseName = Get-BaseName + $Env:FABRIC_TESTACC_WELLKNOWN_NAME_BASE = $baseName + + # Save env vars wellknown.env file + $envVarNames = @( + 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID', + 'FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID', + 'FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME' + 'FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION', + 'FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME', + 'FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME', + 'FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX', + 'FABRIC_TESTACC_WELLKNOWN_NAME_SUFFIX', + 'FABRIC_TESTACC_WELLKNOWN_NAME_BASE', + 'FABRIC_TESTACC_WELLKNOWN_SPN_NAME' + ) + + $envVars = $envVarNames | ForEach-Object { + $envVarName = $_ + if (Test-Path "Env:${envVarName}") { + $value = (Get-ChildItem "Env:${envVarName}").Value + "$envVarName=`"$value`"" + } + } + + $envVars -join "`n" | Set-Content -Path './wellknown.env' -Force -NoNewline -Encoding utf8 + + $displayName = Get-DisplayName -Base $baseName + + # Create WorkspaceRS if not exists + $displayNameTemp = "${displayName}_$($itemNaming['WorkspaceRS'])" + $workspace = Set-FabricWorkspace -DisplayName $displayNameTemp -CapacityId $capacity.id + + # Assign WorkspaceDS to Capacity if not already assigned or assigned to a different capacity + $workspace = Set-FabricWorkspaceCapacity -WorkspaceId $workspace.id -CapacityId $capacity.id + + Write-Log -Message "WorkspaceRS - Name: $($workspace.displayName) / ID: $($workspace.id)" + $wellKnown['WorkspaceRS'] = @{ + id = $workspace.id + displayName = $workspace.displayName + description = $workspace.description + } + + # Assign SPN to WorkspaceRS if not already assigned + if ($SPN) { + Set-FabricWorkspaceRoleAssignment -WorkspaceId $workspace.id -SPN $SPN + } + + # Create WorkspaceDS if not exists + $displayNameTemp = "${displayName}_$($itemNaming['WorkspaceDS'])" + $workspace = Set-FabricWorkspace -DisplayName $displayNameTemp -CapacityId $capacity.id + + # Assign WorkspaceDS to Capacity if not already assigned or assigned to a different capacity + $workspace = Set-FabricWorkspaceCapacity -WorkspaceId $workspace.id -CapacityId $capacity.id + + Write-Log -Message "WorkspaceDS - Name: $($workspace.displayName) / ID: $($workspace.id)" + $wellKnown['WorkspaceDS'] = @{ + id = $workspace.id + displayName = $workspace.displayName + description = $workspace.description + } + + # Assign SPN to WorkspaceRS if not already assigned + if ($SPN) { + Set-FabricWorkspaceRoleAssignment -WorkspaceId $workspace.id -SPN $SPN + } + + # Define an array of item types to create + $itemTypes = @('DataPipeline', 'Environment', 'Eventhouse', 'Eventstream', 'GraphQLApi', 'KQLDashboard', 'KQLQueryset', 'Lakehouse', 'MLExperiment', 'MLModel', 'Notebook', 'Reflex', 'SparkJobDefinition', 'SQLDatabase', 'Warehouse') + + # Loop through each item type and create if not exists + foreach ($itemType in $itemTypes) { + + $displayNameTemp = "${displayName}_$($itemNaming[$itemType])" + $item = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type $itemType + $wellKnown[$itemType] = @{ + id = $item.id + displayName = $item.displayName + description = $item.description + } + } + + # Create KQLDatabase if not exists + $displayNameTemp = "${displayName}_$($itemNaming['KQLDatabase'])" + $creationPayload = @{ + databaseType = 'ReadWrite' + parentEventhouseItemId = $wellKnown['Eventhouse'].id + } + $kqlDatabase = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'KQLDatabase' -CreationPayload $creationPayload + $wellKnown['KQLDatabase'] = @{ + id = $kqlDatabase.id + displayName = $kqlDatabase.displayName + description = $kqlDatabase.description + } + + # Create MirroredDatabase if not exists + $displayNameTemp = "${displayName}_$($itemNaming['MirroredDatabase'])" + $definition = @{ + parts = @( + @{ + path = "mirroring.json" + payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/mirrored_database/mirroring.json.tmpl' -Values @(@{ key = '{{ .DEFAULT_SCHEMA }}'; value = 'dbo' }) + payloadType = 'InlineBase64' + } + ) + } + $mirroredDatabase = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'MirroredDatabase' -Definition $definition + $wellKnown['MirroredDatabase'] = @{ + id = $mirroredDatabase.id + displayName = $mirroredDatabase.displayName + description = $mirroredDatabase.description + } + + # Create SemanticModel if not exists + $displayNameTemp = "${displayName}_$($itemNaming['SemanticModel'])" + $definition = @{ + parts = @( + @{ + path = 'definition.pbism' + payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/semantic_model_tmsl/definition.pbism' + payloadType = 'InlineBase64' + } + @{ + path = 'model.bim' + payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/semantic_model_tmsl/model.bim.tmpl' -Values @(@{ key = '{{ .ColumnName }}'; value = 'ColumnTest1' }) + payloadType = 'InlineBase64' + } + ) + } + $semanticModel = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'SemanticModel' -Definition $definition + $wellKnown['SemanticModel'] = @{ + id = $semanticModel.id + displayName = $semanticModel.displayName + description = $semanticModel.description + } + + # Create Report if not exists + $displayNameTemp = "${displayName}_$($itemNaming['Report'])" + $definition = @{ + parts = @( + @{ + path = 'definition.pbir' + payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/definition.pbir.tmpl' -Values @(@{ key = '{{ .SemanticModelID }}'; value = $semanticModel.id }) + payloadType = 'InlineBase64' + }, + @{ + path = 'report.json' + payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/report.json' + payloadType = 'InlineBase64' + }, + @{ + path = 'StaticResources/SharedResources/BaseThemes/CY24SU10.json' + payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/StaticResources/SharedResources/BaseThemes/CY24SU10.json' + payloadType = 'InlineBase64' + } + @{ + path = 'StaticResources/RegisteredResources/fabric_48_color10148978481469717.png' + payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/StaticResources/RegisteredResources/fabric_48_color10148978481469717.png' + payloadType = 'InlineBase64' + } + ) + } + $report = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'Report' -Definition $definition + $wellKnown['Report'] = @{ + id = $report.id + displayName = $report.displayName + description = $report.description + } + + # Create Parent Domain if not exists + $displayNameTemp = "${displayName}_$($itemNaming['DomainParent'])" + $parentDomain = Set-FabricDomain -DisplayName $displayNameTemp + $wellKnown['DomainParent'] = @{ + id = $parentDomain.id + displayName = $parentDomain.displayName + description = $parentDomain.description + } + + # Create Child Domain if not exists + $displayNameTemp = "${displayName}_$($itemNaming['DomainChild'])" + $childDomain = Set-FabricDomain -DisplayName $displayNameTemp -ParentDomainId $parentDomain.id + $wellKnown['DomainChild'] = @{ + id = $childDomain.id + displayName = $childDomain.displayName + description = $childDomain.description + } + + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/lakehouses/$($wellKnown['Lakehouse']['id'])/tables" + $result = $results.Response.data | Where-Object { $_.name -eq 'publicholidays' } + if (!$result) { + Write-Log -Message "!!! Please go to the Lakehouse and manually run 'Start with sample data' to populate the data !!!" -Level 'ERROR' -Stop $false + Write-Log -Message "Lakehouse: https://app.fabric.microsoft.com/groups/$($workspace.id)/lakehouses/$($wellKnown['Lakehouse']['id'])" -Level 'WARN' + } + $wellKnown['Lakehouse']['tableName'] = 'publicholidays' + + $displayNameTemp = "${displayName}_$($itemNaming['Dashboard'])" + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/dashboards" + $result = $results.Response.value | Where-Object { $_.displayName -eq $displayNameTemp } + if (!$result) { + Write-Log -Message "!!! Please create a Dashboard manually (with Display Name: ${displayNameTemp}), and update details in the well-known file !!!" -Level 'ERROR' -Stop $false + Write-Log -Message "Workspace: https://app.fabric.microsoft.com/groups/$($workspace.id)" -Level 'WARN' + } + $wellKnown['Dashboard'] = @{ + id = if ($result) { $result.id } else { '00000000-0000-0000-0000-000000000000' } + displayName = if ($result) { $result.displayName } else { $displayNameTemp } + description = if ($result) { $result.description } else { '' } + } + + $displayNameTemp = "${displayName}_$($itemNaming['Datamart'])" + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/datamarts" + $result = $results.Response.value | Where-Object { $_.displayName -eq $displayNameTemp } + if (!$result) { + Write-Log -Message "!!! Please create a Datamart manually (with Display Name: ${displayNameTemp}), and update details in the well-known file !!!" -Level 'ERROR' -Stop $false + Write-Log -Message "Workspace: https://app.fabric.microsoft.com/groups/$($workspace.id)" -Level 'WARN' + } + $wellKnown['Datamart'] = @{ + id = if ($result) { $result.id } else { '00000000-0000-0000-0000-000000000000' } + displayName = if ($result) { $result.displayName } else { $displayNameTemp } + description = if ($result) { $result.description } else { '' } + } + + # Create SP if not exists + $displayNameTemp = "${displayName}_$($itemNaming['EntraServicePrincipal'])" + $entraSp = Get-AzADServicePrincipal -DisplayName $displayNameTemp + if (!$entraSp) { + Write-Log -Message "Creating Service Principal: $displayNameTemp" -Level 'WARN' + $entraApp = New-AzADApplication -DisplayName $displayNameTemp + $entraSp = New-AzADServicePrincipal -ApplicationId $entraApp.AppId + } + Write-Log -Message "Service Principal - Name: $($entraSp.DisplayName) / ID: $($entraSp.id)" + $wellKnown['Principal'] = @{ + id = $entraSp.Id + type = 'ServicePrincipal' + name = $entraSp.DisplayName + appId = $entraSp.AppId + } + + # Create Group if not exists + $displayNameTemp = "${displayName}_$($itemNaming['EntraGroup'])" + $entraGroup = Get-AzADGroup -DisplayName $displayNameTemp + if (!$entraGroup) { + Write-Log -Message "Creating Group: $displayNameTemp" -Level 'WARN' + $entraGroup = New-AzADGroup -DisplayName $displayNameTemp -MailNickname $displayNameTemp -SecurityEnabled + # New-AzADGroupOwner -GroupId $entraGroup.Id -OwnerId $currentUser.Id + } + Write-Log -Message "Group - Name: $($entraGroup.DisplayName) / ID: $($entraGroup.Id)" + $wellKnown['Group'] = @{ + type = 'Group' + id = $entraGroup.Id + name = $entraGroup.DisplayName + } + + # Create AzDO Project if not exists + $displayNameTemp = "${displayName}_$($itemNaming['AzDOProject'])" + $azdoProject = Get-ADOPSProject -Name $displayNameTemp + if (!$azdoProject) { + Write-Log -Message "Creating AzDO Project: $displayNameTemp" -Level 'WARN' + $azdoProject = New-ADOPSProject -Name $displayNameTemp -Visibility Private -Wait + } + Write-Log -Message "AzDO Project - Name: $($azdoProject.name) / ID: $($azdoProject.id)" + + # Create AzDO Repository if not exists + $azdoRepo = Get-ADOPSRepository -Project $azdoProject.name -Repository 'test' + if (!$azdoRepo) { + Write-Log -Message "Creating AzDO Repository: test" -Level 'WARN' + $azdoRepo = New-ADOPSRepository -Project $azdoProject.name -Name 'test' + Initialize-ADOPSRepository -RepositoryId $azdoRepo.id | Out-Null + } + Write-Log -Message "AzDO Repository - Name: $($azdoRepo.name) / ID: $($azdoRepo.id)" + $wellKnown['AzDO'] = @{ + organizationName = $azdoContext.Organization + projectId = $azdoProject.id + projectName = $azdoProject.name + repositoryId = $azdoRepo.id + repositoryName = $azdoRepo.name + } + + if ($SPN) { + $body = @{ + originId = $SPN.Id + } + $bodyJson = $body | ConvertTo-Json + $azdoSPN = Invoke-ADOPSRestMethod -Uri "https://vssps.dev.azure.com/$($azdoContext.Organization)/_apis/graph/serviceprincipals?api-version=7.2-preview.1" -Method Post -Body $bodyJson + $result = Set-ADOPSGitPermission -ProjectId $azdoProject.id -RepositoryId $azdoRepo.id -Descriptor $azdoSPN.descriptor -Allow 'GenericContribute', 'PullRequestContribute', 'CreateBranch', 'CreateTag', 'GenericRead' + } + + # Register the Microsoft.PowerPlatform resource provider + Write-Log -Message "Registering Microsoft.PowerPlatform resource provider" -Level 'WARN' + Register-AzResourceProvider -ProviderNamespace "Microsoft.PowerPlatform" + + # Create Azure Virtual Network 1 if not exists + $vnetName = "${displayName}_$($itemNaming['VirtualNetwork01'])" + $addrRange = "10.10.0.0/16" + $subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" + $subRange = "10.10.1.0/24" + + $vnet = Set-AzureVirtualNetwork ` + -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` + -VNetName $vnetName ` + -Location $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION ` + -AddressPrefixes $addrRange ` + -SubnetName $subName ` + -SubnetAddressPrefixes $subRange + + $wellKnown['VirtualNetwork01'] = @{ + name = $vnet.Name + resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME + subnetName = $subName + subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID + } + + + # Create Azure Virtual Network 2 if not exists + $vnetName = "${displayName}_$($itemNaming['VirtualNetwork02'])" + $addrRange = "10.10.0.0/16" + $subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" + $subRange = "10.10.1.0/24" + + $vnet = Set-AzureVirtualNetwork ` + -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` + -VNetName $vnetName ` + -Location $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION ` + -AddressPrefixes $addrRange ` + -SubnetName $subName ` + -SubnetAddressPrefixes $subRange + + $wellKnown['VirtualNetwork02'] = @{ + name = $vnet.Name + resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME + subnetName = $subName + subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID + } + + # Create Fabric Gateway Virtual Network if not exists + $displayNameTemp = "${displayName}_$($itemNaming['GatewayVirtualNetwork'])" + $inactivityMinutesBeforeSleep = 30 + $numberOfMemberGateways = 1 + + $gateway = Set-FabricGatewayVirtualNetwork ` + -DisplayName $displayNameTemp ` + -CapacityId $capacity.id ` + -InactivityMinutesBeforeSleep $inactivityMinutesBeforeSleep ` + -NumberOfMemberGateways $numberOfMemberGateways ` + -SubscriptionId $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID ` + -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` + -VirtualNetworkName $wellKnown['VirtualNetwork01'].name ` + -SubnetName $wellKnown['VirtualNetwork01'].subnetName + + $wellKnown['GatewayVirtualNetwork'] = @{ + id = $gateway.id + displayName = $gateway.displayName + type = $gateway.type + } + + # Save wellknown.json file + $wellKnownJson = $wellKnown | ConvertTo-Json + $wellKnownJson + $wellKnownJson | Set-Content -Path './internal/testhelp/fixtures/.wellknown.json' -Force -NoNewline -Encoding utf8 \ No newline at end of file diff --git a/source/Private/Test-TokenExpired.ps1 b/source/Private/Test-TokenExpired.ps1 index 1f3da68f..f1cfa57d 100644 --- a/source/Private/Test-TokenExpired.ps1 +++ b/source/Private/Test-TokenExpired.ps1 @@ -3,8 +3,8 @@ Checks if the Fabric token is expired and logs appropriate messages. .DESCRIPTION -The `Test-TokenExpired` function checks the expiration status of the Fabric token stored in the `$FabricConfig.TokenExpiresOn` variable. -If the token is expired, it logs an error message and provides guidance for refreshing the token. +The `Test-TokenExpired` function checks the expiration status of the Fabric token stored in the `$FabricConfig.TokenExpiresOn` variable. +If the token is expired, it logs an error message and provides guidance for refreshing the token. Otherwise, it logs that the token is still valid. .PARAMETER FabricConfig @@ -32,7 +32,7 @@ function Test-TokenExpired { try { # Ensure required properties have valid values - if ([string]::IsNullOrWhiteSpace($FabricConfig.TenantIdGlobal) -or + if ([string]::IsNullOrWhiteSpace($FabricConfig.TenantIdGlobal) -or [string]::IsNullOrWhiteSpace($FabricConfig.TokenExpiresOn)) { Write-Message -Message "Token details are missing. Please run 'Set-FabricApiHeaders' to configure them." -Level Error throw "MissingTokenDetailsException: Token details are missing." @@ -44,7 +44,7 @@ function Test-TokenExpired { } else { $tokenExpiryDate = [datetimeoffset]::Parse($FabricConfig.TokenExpiresOn) } - + # Check if the token is expired if ($tokenExpiryDate -le [datetimeoffset]::Now) { Write-Message -Message "Your authentication token has expired. Please sign in again to refresh your session." -Level Warning diff --git a/source/Private/Write-Message.ps1 b/source/Private/Write-Message.ps1 new file mode 100644 index 00000000..80da8e1e --- /dev/null +++ b/source/Private/Write-Message.ps1 @@ -0,0 +1,84 @@ +<# +.SYNOPSIS +Logs messages with different severity levels to the console and optionally to a file. + +.DESCRIPTION +The `Write-Message` function provides a unified way to log messages with levels such as Info, Error, Alert, Verbose, and Debug. +It supports logging to the console with color-coded messages and optionally writing logs to a file with timestamps. + +.PARAMETER Message +The message to log. Supports pipeline input. + +.PARAMETER Level +Specifies the log level. Supported values are Info, Error, Alert, Verbose, and Debug. + +.PARAMETER LogFile +(Optional) Specifies a file path to write the log messages to. If not provided, messages are only written to the console. + +.EXAMPLE +Write-Message -Message "This is an info message." -Level Info + +Logs an informational message to the console. + +.EXAMPLE +Write-Message -Message "Logging to file." -Level Info -LogFile "C:\Logs\MyLog.txt" + +Logs an informational message to the console and writes it to a file. + +.EXAMPLE +"Pipeline message" | Write-Message -Level Alert + +Logs a message from the pipeline with an Alert level. + +.NOTES +Author: Tiago Balabuch +#> + +function Write-Message { + [CmdletBinding()] + param ( + [Parameter(Mandatory, ValueFromPipeline)] + [string]$Message, + + [Parameter()] + [ValidateSet("Message","Info", "Error", "Warning","Critical", "Verbose", "Debug", IgnoreCase = $true)] + [string]$Level = "Info", + + [Parameter()] + [string]$LogFile + ) + + process { + try { + # Format timestamp + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + + # Construct log message + $logMessage = "[$timestamp] [$Level] $Message" + + # Write log message to console with colors + switch ($Level) { + "Message" { Write-Host $logMessage -ForegroundColor White } + "Info" { Write-Host $logMessage -ForegroundColor Green } + "Error" { Write-Host $logMessage -ForegroundColor Red } + "Warning" { Write-Host $logMessage -ForegroundColor Yellow } + "Critical" { Write-Host $logMessage -ForegroundColor Red } + "Verbose" { Write-Verbose $logMessage } + "Debug" { Write-Debug $logMessage } + + } + + # Optionally write log message to a file + if ($LogFile) { + try { + Add-Content -Path $LogFile -Value $logMessage -Encoding UTF8 + } catch { + # Catch and log any errors when writing to file + Write-Host "[ERROR] Failed to write to log file '$LogFile': $_" -ForegroundColor Red + } + } + } catch { + Write-Host "[ERROR] An unexpected error occurred: $_" -ForegroundColor Red + } + } +} From 93315158d7f3e109c293614c02064f6469decdac Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 08:51:52 +0100 Subject: [PATCH 33/76] Merge Capacity functions from Tiagos code #17 --- .../Capacity/Get-FabricCapacity copy.ps1 | 92 ---------------- source/Public/Capacity/Get-FabricCapacity.ps1 | 101 +++++++++++++----- .../Public/Capacity/Resume-FabricCapacity.ps1 | 1 - .../Capacity/Suspend-FabricCapacity.ps1 | 1 - 4 files changed, 77 insertions(+), 118 deletions(-) delete mode 100644 source/Public/Capacity/Get-FabricCapacity copy.ps1 diff --git a/source/Public/Capacity/Get-FabricCapacity copy.ps1 b/source/Public/Capacity/Get-FabricCapacity copy.ps1 deleted file mode 100644 index a36c88ca..00000000 --- a/source/Public/Capacity/Get-FabricCapacity copy.ps1 +++ /dev/null @@ -1,92 +0,0 @@ - -<# -.SYNOPSIS - Retrieves capacity details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves capacity details from a specified workspace using either the provided capacityId or capacityName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER capacityId - The unique identifier of the capacity to retrieve. This parameter is optional. - -.PARAMETER capacityName - The name of the capacity to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricCapacity -capacityId "capacity-12345" - This example retrieves the capacity details for the capacity with ID "capacity-12345". - -.EXAMPLE - Get-FabricCapacity -capacityName "MyCapacity" - This example retrieves the capacity details for the capacity named "MyCapacity". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch -#> -function Get-FabricCapacity { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$capacityId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$capacityName - ) - try { - # Handle ambiguous input - if ($capacityId -and $capacityName) { - Write-Message -Message "Both 'capacityId' and 'capacityName' were provided. Please specify only one." -Level Error - return $null - } - - # Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Construct the API endpoint URL - $apiEndpointURI = "{0}/capacities" -f $FabricConfig.BaseUrl - - # Invoke the Fabric API to retrieve capacity details - $capacities = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - - - # Filter results based on provided parameters - $response = if ($capacityId) { - $capacities | Where-Object { $_.Id -eq $capacityId } - } - elseif ($capacityName) { - $capacities | Where-Object { $_.DisplayName -eq $capacityName } - } - else { - # No filter, return all capacities - Write-Message -Message "No filter specified. Returning all capacities." -Level Debug - return $capacities - } - - # Handle results - if ($response) { - Write-Message -Message "Capacity found matching the specified criteria." -Level Debug - return $response - } - else { - Write-Message -Message "No capacity found matching the specified criteria." -Level Warning - return $null - } - } - catch { - # Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error - return $null - } -} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacity.ps1 b/source/Public/Capacity/Get-FabricCapacity.ps1 index 78fa93d9..a36c88ca 100644 --- a/source/Public/Capacity/Get-FabricCapacity.ps1 +++ b/source/Public/Capacity/Get-FabricCapacity.ps1 @@ -1,39 +1,92 @@ + <# .SYNOPSIS -Retrieves the fabric capacity information. + Retrieves capacity details from a specified Microsoft Fabric workspace. .DESCRIPTION -This function makes a GET request to the Fabric API to retrieve the tenant settings. + This function retrieves capacity details from a specified workspace using either the provided capacityId or capacityName. + It handles token validation, constructs the API URL, makes the API request, and processes the response. + +.PARAMETER capacityId + The unique identifier of the capacity to retrieve. This parameter is optional. -.PARAMETER capacity -Specifies the capacity to retrieve information for. If not provided, all capacities will be retrieved. +.PARAMETER capacityName + The name of the capacity to retrieve. This parameter is optional. .EXAMPLE -Get-FabricCapacity -capacity "exampleCapacity" -Retrieves the fabric capacity information for the specified capacity. + Get-FabricCapacity -capacityId "capacity-12345" + This example retrieves the capacity details for the capacity with ID "capacity-12345". -Get-FabricCapacity -Retrieves the fabric capacity information for all capacities. +.EXAMPLE + Get-FabricCapacity -capacityName "MyCapacity" + This example retrieves the capacity details for the capacity named "MyCapacity". -#> +.NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. -function Get-FabricCapacity { - # Define aliases for the function for flexibility. - [Alias("Get-FabCapacity")] + Author: Tiago Balabuch +#> +function Get-FabricCapacity { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$capacityId, - Param( - [Parameter(Mandatory=$false)] - [string]$capacity + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$capacityName ) + try { + # Handle ambiguous input + if ($capacityId -and $capacityName) { + Write-Message -Message "Both 'capacityId' and 'capacityName' were provided. Please specify only one." -Level Error + return $null + } - Confirm-FabricAuthToken | Out-Null + # Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Construct the API endpoint URL + $apiEndpointURI = "{0}/capacities" -f $FabricConfig.BaseUrl + + # Invoke the Fabric API to retrieve capacity details + $capacities = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get - if ($capacity) { - $result = Invoke-FabricAPIRequest -uri "capacities/$capacity" -Method GET - } else { - $result = Invoke-FabricAPIRequest -uri "capacities" -Method GET + + # Filter results based on provided parameters + $response = if ($capacityId) { + $capacities | Where-Object { $_.Id -eq $capacityId } + } + elseif ($capacityName) { + $capacities | Where-Object { $_.DisplayName -eq $capacityName } + } + else { + # No filter, return all capacities + Write-Message -Message "No filter specified. Returning all capacities." -Level Debug + return $capacities + } + + # Handle results + if ($response) { + Write-Message -Message "Capacity found matching the specified criteria." -Level Debug + return $response + } + else { + Write-Message -Message "No capacity found matching the specified criteria." -Level Warning + return $null + } } - - return $result.value - -} \ No newline at end of file + catch { + # Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error + return $null + } +} \ No newline at end of file diff --git a/source/Public/Capacity/Resume-FabricCapacity.ps1 b/source/Public/Capacity/Resume-FabricCapacity.ps1 index 04ef64fc..d78a8f07 100644 --- a/source/Public/Capacity/Resume-FabricCapacity.ps1 +++ b/source/Public/Capacity/Resume-FabricCapacity.ps1 @@ -1,4 +1,3 @@ - function Resume-FabricCapacity { <# .SYNOPSIS diff --git a/source/Public/Capacity/Suspend-FabricCapacity.ps1 b/source/Public/Capacity/Suspend-FabricCapacity.ps1 index c6b14ce9..9e665c9f 100644 --- a/source/Public/Capacity/Suspend-FabricCapacity.ps1 +++ b/source/Public/Capacity/Suspend-FabricCapacity.ps1 @@ -1,4 +1,3 @@ - # This function suspends a capacity. function Suspend-FabricCapacity { <# From 8fd4cdd79e82547bf06f95217a65dbd626d2baf3 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 09:31:14 +0100 Subject: [PATCH 34/76] Update Private Function file to include a function so that there is no code that will run so that errors are not created and no envrionment is altered during import see #17 --- source/Private/Set-WellKnown.ps1 | 345 ++++++++++++++++--------------- 1 file changed, 174 insertions(+), 171 deletions(-) diff --git a/source/Private/Set-WellKnown.ps1 b/source/Private/Set-WellKnown.ps1 index 7b64ef28..57897389 100644 --- a/source/Private/Set-WellKnown.ps1 +++ b/source/Private/Set-WellKnown.ps1 @@ -2,14 +2,14 @@ function Write-Log { param ( [Parameter(Mandatory = $true)] [string]$Message, - + [Parameter(Mandatory = $false)] [string]$Level = 'INFO', - + [Parameter(Mandatory = $false)] [bool]$Stop = $true ) - + $color = switch ($Level) { 'INFO' { 'Green' } 'WARN' { 'Yellow' } @@ -17,7 +17,7 @@ function Write-Log { 'DEBUG' { 'DarkMagenta' } default { 'Green' } } - + $prefix = switch ($Level) { 'INFO' { '*' } 'WARN' { '!' } @@ -25,20 +25,20 @@ function Write-Log { 'DEBUG' { 'D' } default { '*' } } - + Write-Host -ForegroundColor $color "[$prefix] $Message" - + if ($Stop -and $Level -eq 'ERROR') { exit 1 } } - + function Install-ModuleIfNotInstalled { param ( [Parameter(Mandatory = $true)] [string]$ModuleName ) - + if (-not (Get-Module -Name $ModuleName -ListAvailable)) { try { Write-Log -Message "Installing module: $ModuleName" -Level 'DEBUG' @@ -50,13 +50,13 @@ function Write-Log { } } } - + function Import-ModuleIfNotImported { param ( [Parameter(Mandatory = $true)] [string]$ModuleName ) - + if (-not (Get-Module -Name $ModuleName)) { try { Write-Log -Message "Importing module: $ModuleName" -Level 'DEBUG' @@ -68,25 +68,25 @@ function Write-Log { } } } - + function Invoke-FabricRest { param ( [Parameter(Mandatory = $false)] [string]$Method = 'GET', - + [Parameter(Mandatory = $true)] [string]$Endpoint, - + [Parameter(Mandatory = $false)] [object]$Payload, - + [Parameter(Mandatory = $false)] [int]$RetryCount = 3, - + [Parameter(Mandatory = $false)] [int]$RetryDelaySeconds = 30 ) - + try { # Retrieve the Fabric access token try { @@ -95,13 +95,13 @@ function Write-Log { catch { Write-Log -Message "Failed to retrieve access token." -Level 'ERROR' } - + $uri = "https://api.fabric.microsoft.com/v1/$Endpoint" $attempt = 0 $response = $null $responseHeaders = $null $statusCode = $null - + while ($attempt -lt $RetryCount) { try { if ($Payload) { @@ -111,23 +111,23 @@ function Write-Log { else { $response = Invoke-RestMethod -Authentication Bearer -Token $secureAccessToken -Uri $uri -Method $Method -ResponseHeadersVariable responseHeaders -StatusCodeVariable statusCode } - + break } catch { $statusCode = $_.Exception.Response.StatusCode.value__ - + if ($statusCode -eq 429) { $retryAfter = $_.Exception.Response.Headers.RetryAfter.Delta.TotalSeconds - + $retryDelaySeconds = $RetryDelaySeconds if ($retryAfter) { $retryDelaySeconds = $retryAfter } - + Write-Log -Message "Throttled. Waiting for $retryDelaySeconds seconds before retrying..." -Level 'DEBUG' Start-Sleep -Seconds $retryDelaySeconds - + $attempt++ } else { @@ -135,23 +135,23 @@ function Write-Log { } } } - + if ($attempt -ge $RetryCount) { Write-Log -Message "Maximum retry attempts reached. Request failed." -Level 'ERROR' } - + if ($statusCode -eq 200 -or $statusCode -eq 201) { return [PSCustomObject]@{ Response = $response Headers = $responseHeaders } } - + if ($statusCode -eq 202 -and $responseHeaders.Location -and $responseHeaders['x-ms-operation-id']) { $operationId = [string]$responseHeaders['x-ms-operation-id'] Write-Log -Message "Long Running Operation initiated. Operation ID: $operationId" -Level 'DEBUG' $result = Get-LroResult -OperationId $operationId - + return [PSCustomObject]@{ Response = $result.Response Headers = $result.Headers @@ -162,50 +162,50 @@ function Write-Log { Write-Log -Message $_.Exception.Message -Level 'ERROR' } } - + function Get-LroResult { param ( [Parameter(Mandatory = $true)] [string]$OperationId ) - + $operationStatus = $null while ($operationStatus -ne 'Succeeded') { $result = Invoke-FabricRest -Method 'GET' -Endpoint "operations/$OperationId" - + $operationStatus = $result.Response.status - + if ($operationStatus -eq 'Failed') { Write-Log -Message "Operation failed. Status: $operationStatus" -Level 'ERROR' } - + if ($operationStatus -ne "Succeeded") { $retryAfter = [int]$result.Headers['Retry-After'][0] Start-Sleep -Seconds $retryAfter } } - + return Invoke-FabricRest -Method 'GET' -Endpoint "operations/$OperationId/result" } - + function Set-FabricItem { param ( [Parameter(Mandatory = $true)] [string]$DisplayName, - + [Parameter(Mandatory = $true)] [string]$WorkspaceId, - + [Parameter(Mandatory = $true)] [string]$Type, - + [Parameter(Mandatory = $false)] [object]$CreationPayload, - + [Parameter(Mandatory = $false)] [object]$Definition ) - + switch ($Type) { 'DataPipeline' { $itemEndpoint = 'dataPipelines' @@ -268,16 +268,16 @@ function Write-Log { $itemEndpoint = 'items' } } - + If ($CreationPayload -and $Definition) { Write-Log -Message 'Only one of CreationPayload or Definition is allowed at time.' -Level 'ERROR' } - + $definitionRequired = @('Report', 'SemanticModel', 'MirroredDatabase') if ($Type -in $definitionRequired -and !$Definition) { Write-Log -Message "Definition is required for Type: $Type" -Level 'ERROR' } - + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId/$itemEndpoint" $result = $results.Response.value | Where-Object { $_.displayName -eq $DisplayName } if (!$result) { @@ -286,60 +286,60 @@ function Write-Log { displayName = $DisplayName description = $DisplayName } - + if ($itemEndpoint -eq 'items') { $payload['type'] = $Type } - + if ($CreationPayload) { $payload['creationPayload'] = $CreationPayload } - + if ($Definition) { $payload['definition'] = $Definition } - + $result = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/$itemEndpoint" -Payload $payload).Response } - + Write-Log -Message "${Type} - Name: $($result.displayName) / ID: $($result.id)" - + return $result } - + function Get-DefinitionPartBase64 { param ( [Parameter(Mandatory = $true)] [string]$Path, - + [Parameter(Mandatory = $false)] [object]$Values ) - + if (-not (Test-Path -Path $Path)) { Write-Log -Message "File not found: $Path" -Level 'ERROR' } - + $content = (Get-Content -Path $Path -Raw).Trim().ToString() - + if ($Values) { foreach ($value in $Values) { $content = $content.Replace($value.key, $value.value) } } - + return [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($content)) } - + function Set-FabricDomain { param ( [Parameter(Mandatory = $true)] [string]$DisplayName, - + [Parameter(Mandatory = $false)] [string]$ParentDomainId ) - + $results = Invoke-FabricRest -Method 'GET' -Endpoint "admin/domains" $result = $results.Response.domains | Where-Object { $_.displayName -eq $DisplayName } if (!$result) { @@ -348,77 +348,77 @@ function Write-Log { displayName = $DisplayName description = $DisplayName } - + if ($ParentDomainId) { $payload['parentDomainId'] = $ParentDomainId } - + $result = (Invoke-FabricRest -Method 'POST' -Endpoint "admin/domains" -Payload $payload).Response } - + if ($ParentDomainId) { Write-Log -Message "Child Domain - Name: $($result.displayName) / ID: $($result.id)" } else { Write-Log -Message "Parent Domain - Name: $($result.displayName) / ID: $($result.id)" } - + return $result } - + function Get-BaseName { param ( [Parameter(Mandatory = $false)] [int]$Length = 10 ) - + $base = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_BASE - + if (!$base) { $base = -join ((65..90) + (97..122) | Get-Random -Count $Length | ForEach-Object { [char]$_ }) } - + return $base } - + function Get-DisplayName { param ( [Parameter(Mandatory = $true)] [string]$Base, - + [Parameter(Mandatory = $false)] [string]$Prefix = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX, - + [Parameter(Mandatory = $false)] [string]$Suffix = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_SUFFIX, - + [Parameter(Mandatory = $false)] [string]$Separator = '_' ) - + $result = $Base - + # add prefix and suffix if ($Prefix) { $result = "${Prefix}${Separator}${result}" } - + if ($Suffix) { $result = "${result}${Separator}${Suffix}" } - + return $result } - + function Set-FabricWorkspace { param ( [Parameter(Mandatory = $true)] [string]$DisplayName, - + [Parameter(Mandatory = $true)] [string]$CapacityId ) - + $workspaces = Invoke-FabricRest -Method 'GET' -Endpoint 'workspaces' $workspace = $workspaces.Response.value | Where-Object { $_.displayName -eq $DisplayName } if (!$workspace) { @@ -430,19 +430,19 @@ function Write-Log { } $workspace = (Invoke-FabricRest -Method 'POST' -Endpoint 'workspaces' -Payload $payload).Response } - + return $workspace } - + function Set-FabricWorkspaceCapacity { param ( [Parameter(Mandatory = $true)] [string]$WorkspaceId, - + [Parameter(Mandatory = $true)] [string]$CapacityId ) - + $workspace = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId" if ($workspace.Response.capacityId -ne $CapacityId) { Write-Log -Message "Assigning Workspace to Capacity ID: $CapacityId" -Level 'WARN' @@ -452,19 +452,19 @@ function Write-Log { _ = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/assignToCapacity" -Payload $payload).Response $workspace.Response.capacityId = $CapacityId } - + return $workspace.Response } - + function Set-FabricWorkspaceRoleAssignment { param ( [Parameter(Mandatory = $true)] [string]$WorkspaceId, - + [Parameter(Mandatory = $true)] [object]$SPN ) - + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId/roleAssignments" $result = $results.Response.value | Where-Object { $_.id -eq $SPN.Id } if (!$result) { @@ -479,41 +479,41 @@ function Write-Log { $result = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/roleAssignments" -Payload $payload).Response } } - + function Set-FabricGatewayVirtualNetwork { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$DisplayName, - + [Parameter(Mandatory = $true)] [string]$CapacityId, - + # Inactivity time (in minutes) before the gateway goes to auto-sleep. # Allowed values: 30, 60, 90, 120, 150, 240, 360, 480, 720, 1440. [Parameter(Mandatory = $true)] [ValidateSet(30, 60, 90, 120, 150, 240, 360, 480, 720, 1440)] [int]$InactivityMinutesBeforeSleep, - + # Number of member gateways (between 1 and 7). [Parameter(Mandatory = $true)] [ValidateRange(1, 7)] [int]$NumberOfMemberGateways, - + # Azure virtual network details: [Parameter(Mandatory = $true)] [string]$SubscriptionId, - + [Parameter(Mandatory = $true)] [string]$ResourceGroupName, - + [Parameter(Mandatory = $true)] [string]$VirtualNetworkName, - + [Parameter(Mandatory = $true)] [string]$SubnetName ) - + # Attempt to check for an existing gateway with the same display name. $existingGateways = Invoke-FabricRest -Method 'GET' -Endpoint "gateways" $result = $existingGateways.Response.value | Where-Object { $_.displayName -eq $DisplayName } @@ -533,38 +533,38 @@ function Write-Log { subnetName = $SubnetName } } - + Write-Log -Message "Creating Virtual Network Gateway: $DisplayName" -Level 'WARN' $newGateway = Invoke-FabricRest -Method 'POST' -Endpoint "gateways" -Payload $payload $result = $newGateway.Response } - + Write-Log -Message "Gateway Virtual Network - Name: $($result.displayName) / ID: $($result.id)" return $result } - - + + function Set-AzureVirtualNetwork { param( [Parameter(Mandatory = $true)] [string]$ResourceGroupName, - + [Parameter(Mandatory = $true)] [string]$VNetName, - + [Parameter(Mandatory = $true)] [string]$Location, - + [Parameter(Mandatory = $true)] [string[]]$AddressPrefixes, - + [Parameter(Mandatory = $true)] [string]$SubnetName, - + [Parameter(Mandatory = $true)] [string[]]$SubnetAddressPrefixes ) - + # Attempt to get the existing Virtual Network try { $vnet = Get-AzVirtualNetwork -Name $VNetName -ResourceGroupName $ResourceGroupName -ErrorAction Stop @@ -575,24 +575,24 @@ function Write-Log { $subnetConfig = New-AzVirtualNetworkSubnetConfig ` -Name $SubnetName ` -AddressPrefix $SubnetAddressPrefixes ` - + $subnetConfig = Add-AzDelegation ` -Name 'PowerPlatformVnetAccess' ` -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` -Subnet $subnetConfig - + $vnet = New-AzVirtualNetwork ` -Name $VNetName ` -ResourceGroupName $ResourceGroupName ` -Location $Location ` -AddressPrefix $AddressPrefixes ` -Subnet $subnetConfig - + # Commit creation $vnet = $vnet | Set-AzVirtualNetwork Write-Log -Message "Created VNet: $VNetName" -Level 'INFO' } - + # If the VNet already exists, check for the subnet $subnet = $vnet.Subnets | Where-Object { $_.Name -eq $SubnetName } if (-not $subnet) { @@ -601,12 +601,12 @@ function Write-Log { $subnetConfig = New-AzVirtualNetworkSubnetConfig ` -Name $SubnetName ` -AddressPrefix $SubnetAddressPrefixes ` - + $subnetConfig = Add-AzDelegation ` -Name 'PowerPlatformVnetAccess' ` -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` -Subnet $subnetConfig - + $vnet = $vnet | Set-AzVirtualNetwork } else { @@ -614,51 +614,53 @@ function Write-Log { $existingDelegation = $subnet.Delegations | Where-Object { $_.ServiceName -eq 'Microsoft.PowerPlatform/vnetaccesslinks' } if (-not $existingDelegation) { Write-Log -Message "Subnet '$SubnetName' found but missing delegation to 'Microsoft.PowerPlatform/vnetaccesslinks'. Adding Microsoft.PowerPlatform/vnetaccesslinks delegation..." -Level 'WARN' - + $subnetConfig = Add-AzDelegation ` -Name 'PowerPlatformVnetAccess' ` -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` -Subnet $subnet - + $vnet = $vnet | Set-AzVirtualNetwork Write-Log -Message "Added missing delegation to subnet '$SubnetName'." -Level 'INFO' } } Write-Log -Message "Az Virtual Network - Name: $($vnet.Name)" - + $userPrincipalName = $azContext.Account.Id $principal = Get-AzADUser -UserPrincipalName $userPrincipalName - + $existingAssignment = Get-AzRoleAssignment -Scope $vnet.Id -ObjectId $principal.Id -ErrorAction SilentlyContinue | Where-Object { $_.RoleDefinitionName -eq "Network Contributor" } - + Write-Log "Assigning Network Contributor role to the principal on the virtual network $($VNetName)" if (!$existingAssignment) { New-AzRoleAssignment -ObjectId $principal.Id -RoleDefinitionName "Network Contributor" -Scope $vnet.Id } - + return $vnet } - + + function Invoke-DontRunThisCode { + # Define an array of modules to install $modules = @('Az.Accounts', 'Az.Resources', 'Az.Fabric', 'pwsh-dotenv', 'ADOPS', 'Az.Network') - + # Loop through each module and install if not installed foreach ($module in $modules) { Install-ModuleIfNotInstalled -ModuleName $module Import-ModuleIfNotImported -ModuleName $module } - + # Import the .env file into the environment variables if (Test-Path -Path './wellknown.env') { Import-Dotenv -Path ./wellknown.env -AllowClobber } - - if (!$Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION) { - Write-Log -Message 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID, FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID, FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME, FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME and FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX and FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME and FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION are required environment variables.' -Level 'ERROR' - } - + + # if (!$Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -or # !$Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -or # !$Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION) { + # Write-Log -Message 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID, # FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID, FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME, # FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME and FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX and # FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME and FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION # are required environment variables.' -Level 'ERROR' + # } + # Check if already logged in to Azure, if not then login $azContext = Get-AzContext if (!$azContext -or $azContext.Tenant.Id -ne $Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or $azContext.Subscription.Id -ne $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID) { @@ -668,20 +670,20 @@ function Write-Log { # Disconnect-AzAccount } # $currentUser = Get-AzADUser -SignedIn - + # Logged in to Azure DevOps Write-Log -Message 'Logging in to Azure DevOps.' -Level 'DEBUG' $secureAccessToken = (Get-AzAccessToken -WarningAction SilentlyContinue -AsSecureString -ResourceUrl '499b84ac-1321-427f-aa17-267ca6975798').Token $unsecureAccessToken = $secureAccessToken | ConvertFrom-SecureString -AsPlainText $azdoContext = Connect-ADOPS -TenantId $azContext.Tenant.Id -Organization $Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -OAuthToken $unsecureAccessToken - + $SPN = $null if ($Env:FABRIC_TESTACC_WELLKNOWN_SPN_NAME) { $SPN = Get-AzADServicePrincipal -DisplayName $Env:FABRIC_TESTACC_WELLKNOWN_SPN_NAME } - + $wellKnown = @{} - + # Get Fabric Capacity ID $capacities = Invoke-FabricRest -Method 'GET' -Endpoint 'capacities' $capacity = $capacities.Response.value | Where-Object { $_.displayName -eq $Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME } @@ -694,7 +696,7 @@ function Write-Log { displayName = $capacity.displayName sku = $capacity.sku } - + $itemNaming = @{ 'Dashboard' = 'dash' 'Datamart' = 'dm' @@ -732,10 +734,10 @@ function Write-Log { 'VirtualNetworkSubnet' = 'subnet' 'GatewayVirtualNetwork' = 'gvnet' } - + $baseName = Get-BaseName $Env:FABRIC_TESTACC_WELLKNOWN_NAME_BASE = $baseName - + # Save env vars wellknown.env file $envVarNames = @( 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID', @@ -749,7 +751,7 @@ function Write-Log { 'FABRIC_TESTACC_WELLKNOWN_NAME_BASE', 'FABRIC_TESTACC_WELLKNOWN_SPN_NAME' ) - + $envVars = $envVarNames | ForEach-Object { $envVarName = $_ if (Test-Path "Env:${envVarName}") { @@ -757,55 +759,55 @@ function Write-Log { "$envVarName=`"$value`"" } } - + $envVars -join "`n" | Set-Content -Path './wellknown.env' -Force -NoNewline -Encoding utf8 - + $displayName = Get-DisplayName -Base $baseName - + # Create WorkspaceRS if not exists $displayNameTemp = "${displayName}_$($itemNaming['WorkspaceRS'])" $workspace = Set-FabricWorkspace -DisplayName $displayNameTemp -CapacityId $capacity.id - + # Assign WorkspaceDS to Capacity if not already assigned or assigned to a different capacity $workspace = Set-FabricWorkspaceCapacity -WorkspaceId $workspace.id -CapacityId $capacity.id - + Write-Log -Message "WorkspaceRS - Name: $($workspace.displayName) / ID: $($workspace.id)" $wellKnown['WorkspaceRS'] = @{ id = $workspace.id displayName = $workspace.displayName description = $workspace.description } - + # Assign SPN to WorkspaceRS if not already assigned if ($SPN) { Set-FabricWorkspaceRoleAssignment -WorkspaceId $workspace.id -SPN $SPN } - + # Create WorkspaceDS if not exists $displayNameTemp = "${displayName}_$($itemNaming['WorkspaceDS'])" $workspace = Set-FabricWorkspace -DisplayName $displayNameTemp -CapacityId $capacity.id - + # Assign WorkspaceDS to Capacity if not already assigned or assigned to a different capacity $workspace = Set-FabricWorkspaceCapacity -WorkspaceId $workspace.id -CapacityId $capacity.id - + Write-Log -Message "WorkspaceDS - Name: $($workspace.displayName) / ID: $($workspace.id)" $wellKnown['WorkspaceDS'] = @{ id = $workspace.id displayName = $workspace.displayName description = $workspace.description } - + # Assign SPN to WorkspaceRS if not already assigned if ($SPN) { Set-FabricWorkspaceRoleAssignment -WorkspaceId $workspace.id -SPN $SPN } - + # Define an array of item types to create $itemTypes = @('DataPipeline', 'Environment', 'Eventhouse', 'Eventstream', 'GraphQLApi', 'KQLDashboard', 'KQLQueryset', 'Lakehouse', 'MLExperiment', 'MLModel', 'Notebook', 'Reflex', 'SparkJobDefinition', 'SQLDatabase', 'Warehouse') - + # Loop through each item type and create if not exists foreach ($itemType in $itemTypes) { - + $displayNameTemp = "${displayName}_$($itemNaming[$itemType])" $item = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type $itemType $wellKnown[$itemType] = @{ @@ -814,7 +816,7 @@ function Write-Log { description = $item.description } } - + # Create KQLDatabase if not exists $displayNameTemp = "${displayName}_$($itemNaming['KQLDatabase'])" $creationPayload = @{ @@ -827,7 +829,7 @@ function Write-Log { displayName = $kqlDatabase.displayName description = $kqlDatabase.description } - + # Create MirroredDatabase if not exists $displayNameTemp = "${displayName}_$($itemNaming['MirroredDatabase'])" $definition = @{ @@ -845,7 +847,7 @@ function Write-Log { displayName = $mirroredDatabase.displayName description = $mirroredDatabase.description } - + # Create SemanticModel if not exists $displayNameTemp = "${displayName}_$($itemNaming['SemanticModel'])" $definition = @{ @@ -868,7 +870,7 @@ function Write-Log { displayName = $semanticModel.displayName description = $semanticModel.description } - + # Create Report if not exists $displayNameTemp = "${displayName}_$($itemNaming['Report'])" $definition = @{ @@ -901,7 +903,7 @@ function Write-Log { displayName = $report.displayName description = $report.description } - + # Create Parent Domain if not exists $displayNameTemp = "${displayName}_$($itemNaming['DomainParent'])" $parentDomain = Set-FabricDomain -DisplayName $displayNameTemp @@ -910,7 +912,7 @@ function Write-Log { displayName = $parentDomain.displayName description = $parentDomain.description } - + # Create Child Domain if not exists $displayNameTemp = "${displayName}_$($itemNaming['DomainChild'])" $childDomain = Set-FabricDomain -DisplayName $displayNameTemp -ParentDomainId $parentDomain.id @@ -919,7 +921,7 @@ function Write-Log { displayName = $childDomain.displayName description = $childDomain.description } - + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/lakehouses/$($wellKnown['Lakehouse']['id'])/tables" $result = $results.Response.data | Where-Object { $_.name -eq 'publicholidays' } if (!$result) { @@ -927,7 +929,7 @@ function Write-Log { Write-Log -Message "Lakehouse: https://app.fabric.microsoft.com/groups/$($workspace.id)/lakehouses/$($wellKnown['Lakehouse']['id'])" -Level 'WARN' } $wellKnown['Lakehouse']['tableName'] = 'publicholidays' - + $displayNameTemp = "${displayName}_$($itemNaming['Dashboard'])" $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/dashboards" $result = $results.Response.value | Where-Object { $_.displayName -eq $displayNameTemp } @@ -940,7 +942,7 @@ function Write-Log { displayName = if ($result) { $result.displayName } else { $displayNameTemp } description = if ($result) { $result.description } else { '' } } - + $displayNameTemp = "${displayName}_$($itemNaming['Datamart'])" $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/datamarts" $result = $results.Response.value | Where-Object { $_.displayName -eq $displayNameTemp } @@ -953,7 +955,7 @@ function Write-Log { displayName = if ($result) { $result.displayName } else { $displayNameTemp } description = if ($result) { $result.description } else { '' } } - + # Create SP if not exists $displayNameTemp = "${displayName}_$($itemNaming['EntraServicePrincipal'])" $entraSp = Get-AzADServicePrincipal -DisplayName $displayNameTemp @@ -969,7 +971,7 @@ function Write-Log { name = $entraSp.DisplayName appId = $entraSp.AppId } - + # Create Group if not exists $displayNameTemp = "${displayName}_$($itemNaming['EntraGroup'])" $entraGroup = Get-AzADGroup -DisplayName $displayNameTemp @@ -984,7 +986,7 @@ function Write-Log { id = $entraGroup.Id name = $entraGroup.DisplayName } - + # Create AzDO Project if not exists $displayNameTemp = "${displayName}_$($itemNaming['AzDOProject'])" $azdoProject = Get-ADOPSProject -Name $displayNameTemp @@ -993,7 +995,7 @@ function Write-Log { $azdoProject = New-ADOPSProject -Name $displayNameTemp -Visibility Private -Wait } Write-Log -Message "AzDO Project - Name: $($azdoProject.name) / ID: $($azdoProject.id)" - + # Create AzDO Repository if not exists $azdoRepo = Get-ADOPSRepository -Project $azdoProject.name -Repository 'test' if (!$azdoRepo) { @@ -1009,7 +1011,7 @@ function Write-Log { repositoryId = $azdoRepo.id repositoryName = $azdoRepo.name } - + if ($SPN) { $body = @{ originId = $SPN.Id @@ -1018,17 +1020,17 @@ function Write-Log { $azdoSPN = Invoke-ADOPSRestMethod -Uri "https://vssps.dev.azure.com/$($azdoContext.Organization)/_apis/graph/serviceprincipals?api-version=7.2-preview.1" -Method Post -Body $bodyJson $result = Set-ADOPSGitPermission -ProjectId $azdoProject.id -RepositoryId $azdoRepo.id -Descriptor $azdoSPN.descriptor -Allow 'GenericContribute', 'PullRequestContribute', 'CreateBranch', 'CreateTag', 'GenericRead' } - + # Register the Microsoft.PowerPlatform resource provider Write-Log -Message "Registering Microsoft.PowerPlatform resource provider" -Level 'WARN' Register-AzResourceProvider -ProviderNamespace "Microsoft.PowerPlatform" - + # Create Azure Virtual Network 1 if not exists $vnetName = "${displayName}_$($itemNaming['VirtualNetwork01'])" $addrRange = "10.10.0.0/16" $subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" $subRange = "10.10.1.0/24" - + $vnet = Set-AzureVirtualNetwork ` -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` -VNetName $vnetName ` @@ -1036,21 +1038,21 @@ function Write-Log { -AddressPrefixes $addrRange ` -SubnetName $subName ` -SubnetAddressPrefixes $subRange - + $wellKnown['VirtualNetwork01'] = @{ name = $vnet.Name resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME subnetName = $subName subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID } - - + + # Create Azure Virtual Network 2 if not exists $vnetName = "${displayName}_$($itemNaming['VirtualNetwork02'])" $addrRange = "10.10.0.0/16" $subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" $subRange = "10.10.1.0/24" - + $vnet = Set-AzureVirtualNetwork ` -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` -VNetName $vnetName ` @@ -1058,19 +1060,19 @@ function Write-Log { -AddressPrefixes $addrRange ` -SubnetName $subName ` -SubnetAddressPrefixes $subRange - + $wellKnown['VirtualNetwork02'] = @{ name = $vnet.Name resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME subnetName = $subName subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID } - + # Create Fabric Gateway Virtual Network if not exists $displayNameTemp = "${displayName}_$($itemNaming['GatewayVirtualNetwork'])" $inactivityMinutesBeforeSleep = 30 $numberOfMemberGateways = 1 - + $gateway = Set-FabricGatewayVirtualNetwork ` -DisplayName $displayNameTemp ` -CapacityId $capacity.id ` @@ -1080,14 +1082,15 @@ function Write-Log { -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` -VirtualNetworkName $wellKnown['VirtualNetwork01'].name ` -SubnetName $wellKnown['VirtualNetwork01'].subnetName - + $wellKnown['GatewayVirtualNetwork'] = @{ id = $gateway.id displayName = $gateway.displayName type = $gateway.type } - + # Save wellknown.json file $wellKnownJson = $wellKnown | ConvertTo-Json $wellKnownJson - $wellKnownJson | Set-Content -Path './internal/testhelp/fixtures/.wellknown.json' -Force -NoNewline -Encoding utf8 \ No newline at end of file + $wellKnownJson | Set-Content -Path './internal/testhelp/fixtures/.wellknown.json' -Force -NoNewline -Encoding utf8 +} From 2cbba1165b7a4a871eb363dfc75cd6b768301c90 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 09:38:32 +0100 Subject: [PATCH 35/76] Enhance KQL and Lakehouse Functions - Updated `Update-FabricKQLDatabase` and `Update-FabricKQLDatabaseDefinition` functions to support ShouldProcess for better confirmation handling. - Removed duplicate and obsolete `Get-FabricKQLQueryset copy.ps1` and `Remove-FabricKQLQueryset copy.ps1` scripts. - Refactored `Get-FabricKQLQueryset` to improve clarity and maintainability, ensuring proper error handling and response validation. - Updated `Remove-FabricKQLQueryset` to include ShouldProcess support and improved error logging. - Deleted `Set-FabricKQLQueryset.ps1` as it was no longer needed. - Removed obsolete `New-FabricLakehouse copy.ps1` and refactored `New-FabricLakehouse` to enhance functionality and error handling. - Improved documentation across all modified scripts for clarity and consistency. --- .../Eventhouse/Get-FabricEventhouse copy.ps1 | 156 -------- .../Eventhouse/Get-FabricEventhouse.ps1 | 325 ++++++++--------- .../Eventhouse/New-FabricEventhouse copy.ps1 | 193 ---------- .../Eventhouse/New-FabricEventhouse.ps1 | 252 ++++++++----- .../Remove-FabricEventhouse copy.ps1 | 73 ---- .../Eventhouse/Remove-FabricEventhouse.ps1 | 122 +++---- .../Eventhouse/Set-FabricEventhouse.ps1 | 130 ------- .../Get-FabricEventstream copy.ps1 | 159 -------- .../Eventstream/Get-FabricEventstream.ps1 | 208 ++++++----- .../New-FabricEventstream copy.ps1 | 188 ---------- .../Eventstream/New-FabricEventstream.ps1 | 224 ++++++++---- .../Remove-FabricEventstream copy.ps1 | 72 ---- .../Eventstream/Remove-FabricEventstream.ps1 | 120 +++---- .../Eventstream/Set-FabricEventstream.ps1 | 113 ------ .../Revoke-FabricExternalDataShares.ps1 | 5 +- source/Public/Item/Remove-FabricItem.ps1 | 2 +- .../Get-FabricKQLDashboard copy.ps1 | 155 -------- .../KQL Dashboard/Get-FabricKQLDashboard.ps1 | 201 +++++++---- .../Get-FabricKQLDashboardDefinition copy.ps1 | 120 ------- .../Get-FabricKQLDashboardDefinition.ps1 | 173 ++++----- .../New-FabricKQLDashboard copy.ps1 | 188 ---------- .../KQL Dashboard/New-FabricKQLDashboard.ps1 | 230 ++++++++---- .../Update-FabricKQLDashboard.ps1 | 9 +- .../Get-FabricKQLDatabase copy.ps1 | 153 -------- .../KQL Database/Get-FabricKQLDatabase.ps1 | 315 +++++++--------- .../New-FabricKQLDatabase copy.ps1 | 288 --------------- .../KQL Database/New-FabricKQLDatabase.ps1 | 338 ++++++++++++++---- .../Remove-FabricKQLDatabase copy.ps1 | 74 ---- .../KQL Database/Remove-FabricKQLDatabase.ps1 | 98 +++-- .../KQL Database/Set-FabricKQLDatabase.ps1 | 114 ------ .../KQL Database/Update-FabricKQLDatabase.ps1 | 2 +- .../Update-FabricKQLDatabaseDefinition.ps1 | 2 +- .../Get-FabricKQLQueryset copy.ps1 | 154 -------- .../KQL Queryset/Get-FabricKQLQueryset.ps1 | 220 +++++++----- .../Remove-FabricKQLQueryset copy.ps1 | 73 ---- .../KQL Queryset/Remove-FabricKQLQueryset.ps1 | 94 +++-- .../KQL Queryset/Set-FabricKQLQueryset.ps1 | 109 ------ .../Lakehouse/New-FabricLakehouse copy.ps1 | 136 ------- .../Public/Lakehouse/New-FabricLakehouse.ps1 | 195 +++++----- 39 files changed, 1785 insertions(+), 3998 deletions(-) delete mode 100644 source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 delete mode 100644 source/Public/Eventhouse/New-FabricEventhouse copy.ps1 delete mode 100644 source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 delete mode 100644 source/Public/Eventhouse/Set-FabricEventhouse.ps1 delete mode 100644 source/Public/Eventstream/Get-FabricEventstream copy.ps1 delete mode 100644 source/Public/Eventstream/New-FabricEventstream copy.ps1 delete mode 100644 source/Public/Eventstream/Remove-FabricEventstream copy.ps1 delete mode 100644 source/Public/Eventstream/Set-FabricEventstream.ps1 delete mode 100644 source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 delete mode 100644 source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 delete mode 100644 source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 delete mode 100644 source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 delete mode 100644 source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 delete mode 100644 source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 delete mode 100644 source/Public/KQL Database/Set-FabricKQLDatabase.ps1 delete mode 100644 source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 delete mode 100644 source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 delete mode 100644 source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 delete mode 100644 source/Public/Lakehouse/New-FabricLakehouse copy.ps1 diff --git a/source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 b/source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 deleted file mode 100644 index dabafa46..00000000 --- a/source/Public/Eventhouse/Get-FabricEventhouse copy.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -<# -.SYNOPSIS - Retrieves Eventhouse details from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function retrieves Eventhouse details from a specified workspace using either the provided EventhouseId or EventhouseName. - It handles token validation, constructs the API URL, makes the API request, and processes the response. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Eventhouse exists. This parameter is mandatory. - -.PARAMETER EventhouseId - The unique identifier of the Eventhouse to retrieve. This parameter is optional. - -.PARAMETER EventhouseName - The name of the Eventhouse to retrieve. This parameter is optional. - -.EXAMPLE - Get-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" - This example retrieves the Eventhouse details for the Eventhouse with ID "eventhouse-67890" in the workspace with ID "workspace-12345". - -.EXAMPLE - Get-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseName "My Eventhouse" - This example retrieves the Eventhouse details for the Eventhouse named "My Eventhouse" in the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Get-FabricEventhouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventhouseName - ) - try { - - # Step 1: Handle ambiguous input - if ($EventhouseId -and $EventhouseName) { - Write-Message -Message "Both 'EventhouseId' and 'EventhouseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $eventhouses = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId - # Step 3: Loop to retrieve data with continuation token - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $eventhouses += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $eventhouse = if ($EventhouseId) { - $eventhouses | Where-Object { $_.Id -eq $EventhouseId } - } - elseif ($EventhouseName) { - $eventhouses | Where-Object { $_.DisplayName -eq $EventhouseName } - } - else { - # Return all eventhouses if no filter is provided - Write-Message -Message "No filter provided. Returning all Eventhouses." -Level Debug - $eventhouses - } - - # Step 9: Handle results - if ($eventhouse) { - Write-Message -Message "Eventhouse found in the Workspace '$WorkspaceId'." -Level Debug - return $eventhouse - } - else { - Write-Message -Message "No Eventhouse found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error - } - -} diff --git a/source/Public/Eventhouse/Get-FabricEventhouse.ps1 b/source/Public/Eventhouse/Get-FabricEventhouse.ps1 index 4d3bc871..467c3d30 100644 --- a/source/Public/Eventhouse/Get-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Get-FabricEventhouse.ps1 @@ -1,213 +1,186 @@ function Get-FabricEventhouse { -#Requires -Version 7.1 - <# -.SYNOPSIS - Retrieves Fabric Eventhouses - -.DESCRIPTION - Retrieves Fabric Eventhouses. Without the EventhouseName or EventhouseID parameter, all Eventhouses are returned. - If you want to retrieve a specific Eventhouse, you can use the EventhouseName or EventhouseID parameter. These - parameters cannot be used together. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventhouses should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. + .SYNOPSIS + Retrieves Fabric Eventhouses -.PARAMETER EventhouseName - The name of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseID. + .DESCRIPTION + Retrieves Fabric Eventhouses. Without the EventhouseName or EventhouseID parameter, all Eventhouses are returned. + If you want to retrieve a specific Eventhouse, you can use the EventhouseName or EventhouseID parameter. These + parameters cannot be used together. -.PARAMETER EventhouseId - The Id of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseName. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. + .PARAMETER WorkspaceId + Id of the Fabric Workspace for which the Eventhouses should be retrieved. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. -.EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' + .PARAMETER EventhouseName + The name of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseID. - This example will give you all Eventhouses in the Workspace. + .PARAMETER EventhouseId + The Id of the Eventhouse to retrieve. This parameter cannot be used together with EventhouseName. The value for WorkspaceId is a GUID. + An example of a GUID is '12345678-1234-1234-1234-123456789012'. -.EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' + .EXAMPLE + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' - This example will give you all Information about the Eventhouse with the name 'MyEventhouse'. + This example will give you all Eventhouses in the Workspace. -.EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' + .EXAMPLE + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseName 'MyEventhouse' - This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. + This example will give you all Information about the Eventhouse with the name 'MyEventhouse'. .EXAMPLE - Get-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -Verbose + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' - This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. - It will also give you verbose output which is useful for debugging. + This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/list-eventhouses?tabs=HTTP + .EXAMPLE + Get-FabricEventhouse ` + -WorkspaceId '12345678-1234-1234-1234-123456789012' ` + -EventhouseId '12345678-1234-1234-1234-123456789012' ` + -Verbose -.NOTES - TODO: Add functionality to list all Eventhouses in the subscription. To do so fetch all workspaces - and then all eventhouses in each workspace. + This example will give you all Information about the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. + It will also give you verbose output which is useful for debugging. - Revsion History: + .LINK + https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/list-eventhouses?tabs=HTTP - - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName - - 2024-11-16 - FGE: Added Verbose Output - - 2024-11-27 - FGE: Added more Verbose Output -#> + .NOTES + TODO: Add functionality to list all Eventhouses in the subscription. To do so fetch all workspaces + and then all eventhouses in each workspace. -# + Revsion History: -[CmdletBinding()] + - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName + - 2024-11-16 - FGE: Added Verbose Output + - 2024-11-27 - FGE: Added more Verbose Output +#> + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Alias("Name","DisplayName")] - [string]$EventhouseName, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseId, - [Alias("Id")] - [string]$EventhouseId + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventhouseName ) + try { -begin { - - Confirm-FabricAuthToken | Out-Null - - # You can either use Name or WorkspaceID - Write-Verbose "Checking if EventhouseName and EventhouseID are used together. This is not allowed" - if ($PSBoundParameters.ContainsKey("EventhouseName") -and $PSBoundParameters.ContainsKey("EventhouseID")) { - throw "Parameters EventhouseName and EventhouseID cannot be used together" - } + # Step 1: Handle ambiguous input + if ($EventhouseId -and $EventhouseName) { + Write-Message -Message "Both 'EventhouseId' and 'EventhouseName' were provided. Please specify only one." -Level Error + return $null + } - # Create Eventhouse API - $eventhouseAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses" - Write-Verbose "Creating the URL for the Eventhouse API: $eventhouseAPI" + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug - $eventhouseAPIEventhouseId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" - Write-Verbose "Creating the URL for the Eventhouse API when the Id is used: $eventhouseAPIEventhouseId" + # Step 3: Initialize variables + $continuationToken = $null + $eventhouses = @() -} + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } -process { - - if ($PSBoundParameters.ContainsKey("EventhouseId")) { - Write-Verbose "Calling Eventhouse API with EventhouseId" - Write-Verbose "----------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $eventhouseAPIEventhouseId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventhouseAPIEventhouseId ` - -ContentType "application/json" - - Write-Verbose "Adding the member queryServiceUri: $($response.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $response.properties.queryServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding the member ingestionServiceUri: $($response.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $response.properties.ingestionServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding the member databasesItemIds: $($response.properties.databasesItemIds)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databasesItemIds' ` - -Value $response.properties.databasesItemIds ` - -InputObject $response ` - -Force - - Write-Verbose "Adding the member minimumConsumptionUnits: $($response.properties.minimumConsumptionUnits)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'minimumConsumptionUnits' ` - -Value $response.properties.minimumConsumptionUnits ` - -InputObject $response ` - -Force - - $response - } - else { - Write-Verbose "Calling Eventhouse API without EventhouseId" - Write-Verbose "-------------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $eventhouseAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventhouseAPI ` - -ContentType "application/json" - - foreach ($eventhouse in $response.value) { - Write-Verbose "Adding the member queryServiceUri: $($eventhouse.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $eventhouse.properties.queryServiceUri ` - -InputObject $eventhouse ` - -Force - - Write-Verbose "Adding the member ingestionServiceUri: $($eventhouse.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $eventhouse.properties.ingestionServiceUri ` - -InputObject $eventhouse ` - -Force - - Write-Verbose "Adding the member databasesItemIds: $($eventhouse.properties.databasesItemIds)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databasesItemIds' ` - -Value $eventhouse.properties.databasesItemIds ` - -InputObject $eventhouse ` - -Force - - Write-Verbose "Adding the member minimumConsumptionUnits: $($eventhouse.properties.minimumConsumptionUnits)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'minimumConsumptionUnits' ` - -Value $eventhouse.properties.minimumConsumptionUnits ` - -InputObject $eventhouse ` - -Force + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId + # Step 3: Loop to retrieve data with continuation token + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $eventhouses += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $eventhouse = if ($EventhouseId) { + $eventhouses | Where-Object { $_.Id -eq $EventhouseId } + } + elseif ($EventhouseName) { + $eventhouses | Where-Object { $_.DisplayName -eq $EventhouseName } + } + else { + # Return all eventhouses if no filter is provided + Write-Message -Message "No filter provided. Returning all Eventhouses." -Level Debug + $eventhouses } - if ($PSBoundParameters.ContainsKey("EventhouseName")) { - Write-Verbose "Filtering the Eventhouse by EventhouseName: $EventhouseName" - $response.value | ` - Where-Object { $_.displayName -eq $EventhouseName } + # Step 9: Handle results + if ($eventhouse) { + Write-Message -Message "Eventhouse found in the Workspace '$WorkspaceId'." -Level Debug + return $eventhouse } else { - Write-Verbose "Returning all Eventhouses" - $response.value + Write-Message -Message "No Eventhouse found matching the provided criteria." -Level Warning + return $null } } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error + } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/Eventhouse/New-FabricEventhouse copy.ps1 b/source/Public/Eventhouse/New-FabricEventhouse copy.ps1 deleted file mode 100644 index de5979b9..00000000 --- a/source/Public/Eventhouse/New-FabricEventhouse copy.ps1 +++ /dev/null @@ -1,193 +0,0 @@ -<# -.SYNOPSIS - Creates a new Eventhouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Eventhouse - in the specified workspace. It supports optional parameters for Eventhouse description and path definitions. - -.PARAMETER WorkspaceId - The unique identifier of the workspace where the Eventhouse will be created. This parameter is mandatory. - -.PARAMETER EventhouseName - The name of the Eventhouse to be created. This parameter is mandatory. - -.PARAMETER EventhouseDescription - An optional description for the Eventhouse. - -.PARAMETER EventhousePathDefinition - An optional path to the Eventhouse definition file to upload. - -.PARAMETER EventhousePathPlatformDefinition - An optional path to the platform-specific definition file to upload. - -.EXAMPLE - New-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseName "New Eventhouse" -EventhouseDescription "Description of the new Eventhouse" - This example creates a new Eventhouse named "New Eventhouse" in the workspace with ID "workspace-12345" with the provided description. - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function New-FabricEventhouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventhouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhousePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventhousePathPlatformDefinition - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EventhouseName - } - - if ($EventhouseDescription) { - $body.description = $EventhouseDescription - } - if ($EventhousePathDefinition) { - $eventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition - - if (-not [string]::IsNullOrEmpty($eventhouseEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "EventhouseProperties.json" - payload = $eventhouseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error - return $null - } - } - - if ($EventhousePathPlatformDefinition) { - $eventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($eventhouseEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $eventhouseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Eventhouse '$EventhouseName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Eventhouse '$EventhouseName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Eventhouse. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/Eventhouse/New-FabricEventhouse.ps1 b/source/Public/Eventhouse/New-FabricEventhouse.ps1 index 103be974..c88a0a12 100644 --- a/source/Public/Eventhouse/New-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/New-FabricEventhouse.ps1 @@ -1,104 +1,194 @@ function New-FabricEventhouse { -#Requires -Version 7.1 - <# -.SYNOPSIS - Creates a new Fabric Eventhouse - -.DESCRIPTION - Creates a new Fabric Eventhouse + .SYNOPSIS + Creates a new Eventhouse in a specified Microsoft Fabric workspace. -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the Eventhouse should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. + .DESCRIPTION + This function sends a POST request to the Microsoft Fabric API to create a new Eventhouse + in the specified workspace. It supports optional parameters for Eventhouse description and path definitions. -.PARAMETER EventhouseName - The name of the Eventhouse to create. + .PARAMETER WorkspaceId + The unique identifier of the workspace where the Eventhouse will be created. This parameter is mandatory. -.PARAMETER EventhouseDescription - The description of the Eventhouse to create. + .PARAMETER EventhouseName + The name of the Eventhouse to be created. This parameter is mandatory. -.EXAMPLE - New-FabricEventhouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' ` - -EventhouseDescription 'This is my Eventhouse' + .PARAMETER EventhouseDescription + An optional description for the Eventhouse. - This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. + .PARAMETER EventhousePathDefinition + An optional path to the Eventhouse definition file to upload. -.EXAMPLE - New-FabricEventhouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' ` - -EventhouseDescription 'This is my Eventhouse' ` - -Verbose + .PARAMETER EventhousePathPlatformDefinition + An optional path to the platform-specific definition file to upload. - This example will create a new Eventhouse with the name 'MyEventhouse' and the description 'This is my Eventhouse'. - It will also give you verbose output which is useful for debugging. + .EXAMPLE + New-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseName "New Eventhouse" -EventhouseDescription "Description of the new Eventhouse" + This example creates a new Eventhouse named "New Eventhouse" in the workspace with ID "workspace-12345" with the provided description. -.NOTES - Revsion History: + .NOTES + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName - - 2024-11-27 - FGE: Added Verbose Output + Author: Tiago Balabuch - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP #> -[CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] [string]$EventhouseName, - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$EventhouseDescription - ) - -begin { - Confirm-FabricAuthToken | Out-Null + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseDescription, - # Create body of request - $body = @{ - 'displayName' = $EventhouseName - 'description' = $EventhouseDescription - } | ConvertTo-Json ` - -Depth 1 + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhousePathDefinition, - # Create Eventhouse API URL - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses" - } - -process { - - Write-Verbose "Calling Eventhouse API" - Write-Verbose "----------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($EventhouseName)) { + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventhousePathPlatformDefinition + ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EventhouseName + } + + if ($EventhouseDescription) { + $body.description = $EventhouseDescription + } + if ($EventhousePathDefinition) { + $eventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition + + if (-not [string]::IsNullOrEmpty($eventhouseEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "EventhouseProperties.json" + payload = $eventhouseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error + return $null + } + } + + if ($EventhousePathPlatformDefinition) { + $eventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($eventhouseEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $eventhouseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $eventhouseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Eventhouse '$EventhouseName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Eventhouse '$EventhouseName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Eventhouse. Error: $errorDetails" -Level Error } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 b/source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 deleted file mode 100644 index 0bdfd275..00000000 --- a/source/Public/Eventhouse/Remove-FabricEventhouse copy.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS - Removes an Eventhouse from a specified Microsoft Fabric workspace. - -.DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an Eventhouse - from the specified workspace using the provided WorkspaceId and EventhouseId. - -.PARAMETER WorkspaceId - The unique identifier of the workspace from which the Eventhouse will be removed. - -.PARAMETER EventhouseId - The unique identifier of the Eventhouse to be removed. - -.EXAMPLE - Remove-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" - This example removes the Eventhouse with ID "eventhouse-67890" from the workspace with ID "workspace-12345". - -.NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. - - Author: Tiago Balabuch - -#> -function Remove-FabricEventhouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventhouseId - ) - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Handle response - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Eventhouse '$EventhouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Eventhouse '$EventhouseId'. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 index cc4274e5..bcbf6487 100644 --- a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 @@ -1,39 +1,25 @@ function Remove-FabricEventhouse { -#Requires -Version 7.1 - <# .SYNOPSIS - Removes an existing Fabric Eventhouse + Removes an Eventhouse from a specified Microsoft Fabric workspace. .DESCRIPTION - Removes an existing Fabric Eventhouse + This function sends a DELETE request to the Microsoft Fabric API to remove an Eventhouse + from the specified workspace using the provided WorkspaceId and EventhouseId. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventhouse should be deleted. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. + The unique identifier of the workspace from which the Eventhouse will be removed. .PARAMETER EventhouseId - The Id of the Eventhouse to delete. The value for EventhouseId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. EventhouseId and EventhouseName cannot be used together. + The unique identifier of the Eventhouse to be removed. .PARAMETER EventhouseName The name of the Eventhouse to delete. EventhouseId and EventhouseName cannot be used together. .EXAMPLE - Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' - - This example will delete the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' from - the Workspace with the Id '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseName 'MyEventhouse' + Remove-FabricEventhouse -WorkspaceId "workspace-12345" -EventhouseId "eventhouse-67890" + This example removes the Eventhouse with ID "eventhouse-67890" from the workspace with ID "workspace-12345". - This example will delete the Eventhouse with the name 'MyEventhouse' from the Workspace with the - Id '12345678-1234-1234-1234-123456789012'. .NOTES Revsion History: @@ -43,64 +29,58 @@ function Remove-FabricEventhouse { .LINK https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/delete-eventhouse?tabs=HTTP -#> -[CmdletBinding(SupportsShouldProcess)] - param ( + - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + - Calls `Test-TokenExpired` to ensure token validity before making the API request. - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, + Author: Tiago Balabuch - [Alias("Id")] - [string]$EventhouseId, +#> - [Alias("Name", "DisplayName")] - [string]$EventhouseName + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventhouseId ) + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Check if EventhouseName and EventhouseID are used together. This is not allowed" - if ($PSBoundParameters.ContainsKey("EventhouseName") -and $PSBoundParameters.ContainsKey("EventhouseID")) { - throw "Parameters EventhouseName and EventhouseID cannot be used together" - } - - if ($PSBoundParameters.ContainsKey("EventhouseName")) { - Write-Verbose "Eventhouse Name $EventhouseName is used. Get Eventhouse ID from Eventhouse Name" - $eh = Get-FabricEventhouse ` - -WorkspaceId $WorkspaceId ` - -EventhouseName $EventhouseName - - $EventhouseId = $eh.id - Write-Verbose "Eventhouse ID is $EventhouseId" - } - - # Create Eventhouse API URL - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" -} + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug -process { - - Write-Verbose "Calling Eventhouse API with EventhouseId" - Write-Verbose "----------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($EventhouseName)) { + # Step 3: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $eventhouseApiUrl ` - -ContentType "application/json" - - $response + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Handle response + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Eventhouse '$EventhouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Eventhouse '$EventhouseId'. Error: $errorDetails" -Level Error } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/Eventhouse/Set-FabricEventhouse.ps1 b/source/Public/Eventhouse/Set-FabricEventhouse.ps1 deleted file mode 100644 index 701b19f4..00000000 --- a/source/Public/Eventhouse/Set-FabricEventhouse.ps1 +++ /dev/null @@ -1,130 +0,0 @@ -function Set-FabricEventhouse { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Updates Properties of an existing Fabric Eventhouse - -.DESCRIPTION - Updates Properties of an existing Fabric Eventhouse - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventhouse should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseId - The Id of the Eventhouse to update. The value for EventhouseId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseNewName - The new name of the Eventhouse. - -.PARAMETER EventhouseDescription - The new description of the Eventhouse. - -.EXAMPLE - Set-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -EventhouseNewName 'MyNewEventhouse' ` - -EventhouseDescription 'This is my new Eventhouse' - - This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' - in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to - have the name 'MyNewEventhouse' and the description - 'This is my new Eventhouse'. - -.EXAMPLE - Set-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' ` - -EventhouseNewName 'MyNewEventhouse' ` - -EventhouseDescription 'This is my new Eventhouse' ` - -Verbose - - This example will update the Eventhouse with the Id '12345678-1234-1234-1234-123456789012' - in the Workspace with the Id '12345678-1234-1234-1234-123456789012' to - have the name 'MyNewEventhouse' and the description 'This is my new Eventhouse'. - It will also give you verbose output which is useful for debugging. - -.NOTES - TODO: Add functionality to update Eventhouse properties using EventhouseName instead of EventhouseId - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added NewDisplaName as Alias for EventhouseName - - 2024-11-27 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventhouse/items/create-eventhouse?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$EventhouseId, - - [Alias("NewName", "NewDisplayName")] - [string]$EventhouseNewName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$EventhouseDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - # Create body of request - $body = @{} - - if ($PSBoundParameters.ContainsKey("EventhouseNewName")) { - Write-Verbose "New name found for Eventhouse. New name is: $EventhouseNewName" - $body["displayName"] = $EventhouseNewName - } - - if ($PSBoundParameters.ContainsKey("EventhouseDescription")) { - Write-Verbose "Description found for Eventhouse. Description is: $EventhouseDescription" - $body["description"] = $EventhouseDescription - } - - $body = $body ` - | ConvertTo-Json ` - -Depth 1 - - # Create Eventhouse API URL - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventhouses/$EventhouseId" -} - -process { - - Write-Verbose "Calling Eventhouse API with EventhouseId" - Write-Verbose "----------------------------------------" - Write-Verbose "Sending the following values to the Eventhouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($EventhouseId)) { - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $eventhouseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/source/Public/Eventstream/Get-FabricEventstream copy.ps1 b/source/Public/Eventstream/Get-FabricEventstream copy.ps1 deleted file mode 100644 index 28f6448a..00000000 --- a/source/Public/Eventstream/Get-FabricEventstream copy.ps1 +++ /dev/null @@ -1,159 +0,0 @@ -<# -.SYNOPSIS -Retrieves an Eventstream or a list of Eventstreams from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricEventstream` function sends a GET request to the Fabric API to retrieve Eventstream details for a given workspace. It can filter the results by `EventstreamName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query Eventstreams. - -.PARAMETER EventstreamName -(Optional) The name of the specific Eventstream to retrieve. - -.EXAMPLE -Get-FabricEventstream -WorkspaceId "12345" -EventstreamName "Development" - -Retrieves the "Development" Eventstream from workspace "12345". - -.EXAMPLE -Get-FabricEventstream -WorkspaceId "12345" - -Retrieves all Eventstreams in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricEventstream { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventstreamName - ) - - try { - # Step 1: Handle ambiguous input - if ($EventstreamId -and $EventstreamName) { - Write-Message -Message "Both 'EventstreamId' and 'EventstreamName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - $continuationToken = $null - $eventstreams = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId - - # Step 3: Loop to retrieve data with continuation token - - - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $eventstreams += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - - # Step 8: Filter results based on provided parameters - $eventstream = if ($EventstreamId) { - $eventstreams | Where-Object { $_.Id -eq $EventstreamId } - } - elseif ($EventstreamName) { - $eventstreams | Where-Object { $_.DisplayName -eq $EventstreamName } - } - else { - # Return all eventstreams if no filter is provided - Write-Message -Message "No filter provided. Returning all Eventstreams." -Level Debug - $eventstreams - } - - # Step 9: Handle results - if ($eventstream) { - Write-Message -Message "Eventstream found matching the specified criteria." -Level Debug - return $eventstream - } - else { - Write-Message -Message "No Eventstream found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error - } - -} diff --git a/source/Public/Eventstream/Get-FabricEventstream.ps1 b/source/Public/Eventstream/Get-FabricEventstream.ps1 index 3189c52a..98c25e87 100644 --- a/source/Public/Eventstream/Get-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Get-FabricEventstream.ps1 @@ -1,9 +1,7 @@ function Get-FabricEventstream { -#Requires -Version 7.1 - <# .SYNOPSIS - Retrieves Fabric Eventstreams +Retrieves an Eventstream or a list of Eventstreams from a specified workspace in Microsoft Fabric. .DESCRIPTION Retrieves Fabric Eventstreams. Without the EventstreamName or EventstreamID parameter, all Eventstreams are returned. @@ -11,123 +9,157 @@ function Get-FabricEventstream { parameters cannot be used together. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventstreams should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +(Mandatory) The ID of the workspace to query Eventstreams. .PARAMETER EventstreamName - The name of the Eventstream to retrieve. This parameter cannot be used together with EventstreamID. - +(Optional) The name of the specific Eventstream to retrieve. .PARAMETER EventstreamId The Id of the Eventstream to retrieve. This parameter cannot be used together with EventstreamName. The value for EventstreamId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. .EXAMPLE - Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' +Get-FabricEventstream -WorkspaceId "12345" -EventstreamName "Development" - This example will give you all Eventstreams in the Workspace. +Retrieves the "Development" Eventstream from workspace "12345". .EXAMPLE - Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamName 'MyEventstream' - - This example will give you all Information about the Eventstream with the name 'MyEventstream'. - -.EXAMPLE - Get-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' - - This example will give you all Information about the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. +Get-FabricEventstream -WorkspaceId "12345" -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/get-eventstream?tabs=HTTP +Retrieves all Eventstreams in workspace "12345". .NOTES - TODO: Add functionality to list all Eventhouses. To do so fetch all workspaces and - then all eventhouses in each workspace. +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName - - 2024-11-27 - FGE: Added Verbose Output #> -[CmdletBinding()] + + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Alias("Name", "DisplayName")] - [string]$EventstreamName, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamId, - [Alias("Id")] - [string]$EventstreamId + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$EventstreamName ) -begin { + try { + # Step 1: Handle ambiguous input + if ($EventstreamId -and $EventstreamName) { + Write-Message -Message "Both 'EventstreamId' and 'EventstreamName' were provided. Please specify only one." -Level Error + return $null + } - Confirm-FabricAuthToken | Out-Null + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug - Write-Verbose "You can either use Name or WorkspaceID not both. If both are used throw error" - if ($PSBoundParameters.ContainsKey("EventstreamName") -and $PSBoundParameters.ContainsKey("EventstreamID")) { - throw "Parameters EventstreamName and EventstreamID cannot be used together" - } + $continuationToken = $null + $eventstreams = @() - # Create Eventhouse API - $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams" + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } - $eventstreamAPIEventstreamIdUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams/$EventstreamId" + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId -} + # Step 3: Loop to retrieve data with continuation token -process { - - if ($PSBoundParameters.ContainsKey("EventstreamId")) { - Write-Verbose "Calling Eventstream API with EventstreamId" - Write-Verbose "------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $eventstreamAPIEventstreamIdUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventstreamAPIEventstreamIdUrl ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling Eventstream API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $eventstreamApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" + + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $eventstreamApiUrl ` - -ContentType "application/json" - - if ($PSBoundParameters.ContainsKey("EventstreamName")) { - Write-Verbose "Filtering Eventstream with name $EventstreamName" - $response.value | ` - Where-Object { $_.displayName -eq $EventstreamName } + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $eventstreams += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + + # Step 8: Filter results based on provided parameters + $eventstream = if ($EventstreamId) { + $eventstreams | Where-Object { $_.Id -eq $EventstreamId } + } + elseif ($EventstreamName) { + $eventstreams | Where-Object { $_.DisplayName -eq $EventstreamName } } else { - Write-Verbose "Returning all Eventstreams" - $response.value + # Return all eventstreams if no filter is provided + Write-Message -Message "No filter provided. Returning all Eventstreams." -Level Debug + $eventstreams + } + + # Step 9: Handle results + if ($eventstream) { + Write-Message -Message "Eventstream found matching the specified criteria." -Level Debug + return $eventstream + } + else { + Write-Message -Message "No Eventstream found matching the provided criteria." -Level Warning + return $null } } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error + } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/Eventstream/New-FabricEventstream copy.ps1 b/source/Public/Eventstream/New-FabricEventstream copy.ps1 deleted file mode 100644 index f5def5b5..00000000 --- a/source/Public/Eventstream/New-FabricEventstream copy.ps1 +++ /dev/null @@ -1,188 +0,0 @@ -<# -.SYNOPSIS -Creates a new Eventstream in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new Eventstream -in the specified workspace. It supports optional parameters for Eventstream description -and path definitions for the Eventstream content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the Eventstream will be created. - -.PARAMETER EventstreamName -The name of the Eventstream to be created. - -.PARAMETER EventstreamDescription -An optional description for the Eventstream. - -.PARAMETER EventstreamPathDefinition -An optional path to the Eventstream definition file (e.g., .ipynb file) to upload. - -.PARAMETER EventstreamPathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricEventstream -WorkspaceId "workspace-12345" -EventstreamName "New Eventstream" -EventstreamPathDefinition "C:\Eventstreams\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricEventstream { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$EventstreamName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $EventstreamName - } - - if ($EventstreamDescription) { - $body.description = $EventstreamDescription - } - - if ($EventstreamPathDefinition) { - $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition - - if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "eventstream" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "eventstream.json" - payload = $EventstreamEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error - return $null - } - } - - if ($EventstreamPathPlatformDefinition) { - $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "eventstream" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $EventstreamEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Eventstream '$EventstreamName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Eventstream '$EventstreamName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Eventstream. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/Eventstream/New-FabricEventstream.ps1 b/source/Public/Eventstream/New-FabricEventstream.ps1 index 35f5f662..117b6233 100644 --- a/source/Public/Eventstream/New-FabricEventstream.ps1 +++ b/source/Public/Eventstream/New-FabricEventstream.ps1 @@ -1,87 +1,189 @@ function New-FabricEventstream { -#Requires -Version 7.1 - <# .SYNOPSIS - Creates a new Fabric Eventstream +Creates a new Eventstream in a specified Microsoft Fabric workspace. .DESCRIPTION - Creates a new Fabric Eventstream +This function sends a POST request to the Microsoft Fabric API to create a new Eventstream +in the specified workspace. It supports optional parameters for Eventstream description +and path definitions for the Eventstream content. -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the Eventstream should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Eventstream will be created. .PARAMETER EventstreamName - The name of the Eventstream to create. +The name of the Eventstream to be created. .PARAMETER EventstreamDescription - The description of the Eventstream to create. +An optional description for the Eventstream. -.EXAMPLE - New-FabricEventstream - -WorkspaceID '12345678-1234-1234-1234-123456789012' - -EventstreamName 'MyEventstream' - -EventstreamDescription 'This is my Eventstream' +.PARAMETER EventstreamPathDefinition +An optional path to the Eventstream definition file (e.g., .ipynb file) to upload. - This example will create a new Eventstream with the name 'MyEventstream' and the description 'This is my Eventstream'. +.PARAMETER EventstreamPathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. -.NOTES - Revsion History: +.EXAMPLE +#TODO Fix example name + Add-FabricEventstream -WorkspaceId "workspace-12345" -EventstreamName "New Eventstream" -EventstreamPathDefinition "C:\Eventstreams\example.ipynb" - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName - - 2024-11-30 - FGE: Added Verbose Output + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/eventstream/items/create-eventstream?tabs=HTTP -#> +Author: Tiago Balabuch -[CmdletBinding(SupportsShouldProcess)] +#> +#TODO SupportsShouldProcess + [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name","DisplayName")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] [string]$EventstreamName, - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$EventstreamDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{ - 'displayName' = $EventstreamName - 'description' = $EventstreamDescription - } | ConvertTo-Json ` - -Depth 1 + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamDescription, - # Create Eventhouse API URL - $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams" - } + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamPathDefinition, -process { + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamPathPlatformDefinition + ) - # Call Eventstream API - if($PSCmdlet.ShouldProcess($EventstreamName)) { + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $EventstreamName + } + + if ($EventstreamDescription) { + $body.description = $EventstreamDescription + } + + if ($EventstreamPathDefinition) { + $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition + + if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "eventstream" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "eventstream.json" + payload = $EventstreamEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error + return $null + } + } + + if ($EventstreamPathPlatformDefinition) { + $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "eventstream" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $EventstreamEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $eventstreamApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Eventstream '$EventstreamName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Eventstream '$EventstreamName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Eventstream. Error: $errorDetails" -Level Error } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 b/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 deleted file mode 100644 index 598c66ac..00000000 --- a/source/Public/Eventstream/Remove-FabricEventstream copy.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -function Remove-FabricEventstream { -<# -.SYNOPSIS -Deletes an Eventstream from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricEventstream` function sends a DELETE request to the Fabric API to remove a specified Eventstream from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the Eventstream to delete. - -.PARAMETER EventstreamId -(Mandatory) The ID of the Eventstream to be deleted. - -.EXAMPLE -Remove-FabricEventstream -WorkspaceId "12345" -EventstreamId "67890" - -Deletes the Eventstream with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$EventstreamId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "Eventstream '$EventstreamId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete Eventstream '$EventstreamId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/Eventstream/Remove-FabricEventstream.ps1 b/source/Public/Eventstream/Remove-FabricEventstream.ps1 index d6e6bd5b..30f68ae3 100644 --- a/source/Public/Eventstream/Remove-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Remove-FabricEventstream.ps1 @@ -1,18 +1,16 @@ function Remove-FabricEventstream { <# .SYNOPSIS - Removes an existing Fabric Eventstream +Deletes an Eventstream from a specified workspace in Microsoft Fabric. .DESCRIPTION - Removes an existing Fabric Eventstream +The `Remove-FabricEventstream` function sends a DELETE request to the Fabric API to remove a specified Eventstream from a given workspace. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventstream should be deleted. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +(Mandatory) The ID of the workspace containing the Eventstream to delete. .PARAMETER EventstreamId - The Id of the Eventstream to delete. The value for Eventstream is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +(Mandatory) The ID of the Eventstream to be deleted. .PARAMETER EventstreamName The name of the Eventstream to delete. The value for Eventstream is a string. @@ -20,89 +18,63 @@ function Remove-FabricEventstream { The name of the Eventstream to delete. The value for Eventstream is a string. .EXAMPLE - Remove-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' +Remove-FabricEventstream -WorkspaceId "12345" -EventstreamId "67890" - This example will delete the Eventstream with the Id '12345678-1234-1234-1234-123456789012' from - the Workspace. - -.EXAMPLE - Remove-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamName 'MyEventstream' - - This example will delete the Eventstream with the name 'MyEventstream' from the Workspace. +Deletes the Eventstream with ID "67890" from workspace "12345". .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. - Revsion History: +Author: Tiago Balabuch - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamName - - 2024-12-08 - FGE: Added Verbose Output #> - - -[CmdletBinding(SupportsShouldProcess)] +#TODO SupportsShouldProcess + [CmdletBinding()] param ( - - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Alias("Id")] - [string]$EventstreamId, - - [Alias("Name","DisplayName")] - [string]$EventstreamName - + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$EventstreamId +#TODO Add EventstreamName parameter to validate the name of the Eventstream to delete. ) -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use Name or WorkspaceID not both. If both are used throw error" - if ($PSBoundParameters.ContainsKey("EventstreamId") -and $PSBoundParameters.ContainsKey("EventstreamName")) { - throw "Parameters EventstreamId and EventstreamName cannot be used together" - } - - if ($PSBoundParameters.ContainsKey("EventstreamName")) { - Write-Verbose "The name $EventstreamName was provided. Fetching EventstreamId." - - $eh = Get-FabricEventstream ` - -WorkspaceId $WorkspaceId ` - -EventstreamName $EventstreamName - - $EventstreamId = $eh.id - Write-Verbose "EventstreamId: $EventstreamId" - } - - $eventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/eventstreams/$EventstreamId" + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug -} + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug -process { - - # Call Eventstream API - if($PSCmdlet.ShouldProcess($EventstreamName)) { - Write-Verbose "Calling Eventstream API with EventstreamId" - Write-Verbose "------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $eventstreamApiUrl" - Write-Verbose "ContentType: application/json" + # Step 3: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $eventstreamApiUrl ` - -ContentType "application/json" + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "Eventstream '$EventstreamId' deleted successfully from workspace '$WorkspaceId'." -Level Info - $response } -} - -end {} - + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete Eventstream '$EventstreamId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error + } } diff --git a/source/Public/Eventstream/Set-FabricEventstream.ps1 b/source/Public/Eventstream/Set-FabricEventstream.ps1 deleted file mode 100644 index c15a8805..00000000 --- a/source/Public/Eventstream/Set-FabricEventstream.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -function Set-FabricEventstream { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Updates Properties of an existing Fabric Eventstream - -.DESCRIPTION - Updates Properties of an existing Fabric Eventstream - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Eventstream should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventstreamId - The Id of the Eventstream to update. The value for EventstreamId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventstreamNewName - The new name of the Eventstream. - -.PARAMETER EventstreamDescription - The new description of the Eventstream. - -.EXAMPLE - Set-FabricEventstream ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventstreamId '12345678-1234-1234-1234-123456789012' ` - -EventstreamNewName 'MyNewEventstream' ` - -EventstreamDescription 'This is my new Eventstream' - - This example will update the Eventstream with the Id '12345678-1234-1234-1234-123456789012'. - -.NOTES - TODO: Add functionality to update Eventstream properties using EventstreamName instead of EventstreamId - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for EventStreamNewName - - 2024-12-08 - FGE: Added Verbose Output - Added Aliases for EventstreamNewName and EventstreamDescription - Corrected typo in EventstreamNewName Variable -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$EventstreamId, - - [Alias("NewName","NewDisplayName")] - [string]$EventstreamNewName, - - [ValidateLength(0, 256)] - [Alias("Description","NewDescription", "EventstreamNewDescription")] - [string]$EventstreamDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{} - - if ($PSBoundParameters.ContainsKey("EventstreamNewName")) { - Write-Verbose "Setting EventstreamNewName: $EventstreamNewName" - $body["displayName"] = $EventstreamNewName - } - - if ($PSBoundParameters.ContainsKey("EventstreamDescription")) { - Write-Verbose "Setting EventstreamDescription: $EventstreamDescription" - $body["description"] = $EventstreamDescription - } - - $body = $body ` - | ConvertTo-Json ` - -Depth 1 - - # Create Eventstream API URL - $EventstreamApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/Eventstreams/$EventstreamId" - } - -process { - - if($PSCmdlet.ShouldProcess($EventhouseName)) { - Write-Verbose "Calling Eventstream API with EventstreamId" - Write-Verbose "------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $EventstreamApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $EventstreamApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 index bc9f71bb..f165eea8 100644 --- a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 +++ b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 @@ -1,4 +1,3 @@ - function Revoke-FabricExternalDataShares { <# .SYNOPSIS @@ -16,7 +15,8 @@ function Revoke-FabricExternalDataShares { .PARAMETER ExternalDataShareId The unique identifier of the External Data Share to be retrieved. -.EXAMPLE + + .EXAMPLE Get-FabricExternalDataShares This example retrieves the External Data Shares details @@ -26,6 +26,7 @@ function Revoke-FabricExternalDataShares { Author: Tiago Balabuch #> + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] diff --git a/source/Public/Item/Remove-FabricItem.ps1 b/source/Public/Item/Remove-FabricItem.ps1 index 8dbd6f73..9dbc4685 100644 --- a/source/Public/Item/Remove-FabricItem.ps1 +++ b/source/Public/Item/Remove-FabricItem.ps1 @@ -12,7 +12,7 @@ An optional filter to select items to remove. If provided, only items whose DisplayName matches the filter are removed. .PARAMETER ItemID - The ID of a specific item to remove. If provided, this item is removed regardless of the filter. + The ID of a specific item to remove. If provided, this item is removed regardless of the filter .EXAMPLE Remove-FabricItems -WorkspaceID "12345678-90ab-cdef-1234-567890abcdef" -Filter "*test*" diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 deleted file mode 100644 index 66087294..00000000 --- a/source/Public/KQL Dashboard/Get-FabricKQLDashboard copy.ps1 +++ /dev/null @@ -1,155 +0,0 @@ -<# -.SYNOPSIS -Retrieves an KQLDashboard or a list of KQLDashboards from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricKQLDashboard` function sends a GET request to the Fabric API to retrieve KQLDashboard details for a given workspace. It can filter the results by `KQLDashboardName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query KQLDashboards. - -.PARAMETER KQLDashboardName -(Optional) The name of the specific KQLDashboard to retrieve. - -.EXAMPLE -Get-FabricKQLDashboard -WorkspaceId "12345" -KQLDashboardName "Development" - -Retrieves the "Development" KQLDashboard from workspace "12345". - -.EXAMPLE -Get-FabricKQLDashboard -WorkspaceId "12345" - -Retrieves all KQLDashboards in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricKQLDashboard { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDashboardName - ) - - try { - # Step 1: Handle ambiguous input - if ($KQLDashboardId -and $KQLDashboardName) { - Write-Message -Message "Both 'KQLDashboardId' and 'KQLDashboardName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $KQLDashboards = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $KQLDashboards += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $KQLDashboard = if ($KQLDashboardId) { - $KQLDashboards | Where-Object { $_.Id -eq $KQLDashboardId } - } - elseif ($KQLDashboardName) { - $KQLDashboards | Where-Object { $_.DisplayName -eq $KQLDashboardName } - } - else { - # Return all KQLDashboards if no filter is provided - Write-Message -Message "No filter provided. Returning all KQLDashboards." -Level Debug - $KQLDashboards - } - - # Step 9: Handle results - if ($KQLDashboard) { - Write-Message -Message "KQLDashboard found matching the specified criteria." -Level Debug - return $KQLDashboard - } - else { - Write-Message -Message "No KQLDashboard found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error - } - -} diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 index 74a10285..442a8a99 100644 --- a/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 @@ -1,112 +1,159 @@ -function Get-FabricKQLDashboard { -#Requires -Version 7.1 - <# .SYNOPSIS - Retrieves Fabric KQLDashboards +Retrieves an KQLDashboard or a list of KQLDashboards from a specified workspace in Microsoft Fabric. .DESCRIPTION - Retrieves Fabric KQLDashboards. Without the KQLDashboardName or KQLDashboardID parameter, all KQLDashboards are returned. - If you want to retrieve a specific KQLDashboard, you can use the KQLDashboardName or KQLDashboardID parameter. These - parameters cannot be used together. +The `Get-FabricKQLDashboard` function sends a GET request to the Fabric API to retrieve KQLDashboard details for a given workspace. It can filter the results by `KQLDashboardName`. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLDashboards should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +(Mandatory) The ID of the workspace to query KQLDashboards. .PARAMETER KQLDashboardName - The name of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardID. +(Optional) The name of the specific KQLDashboard to retrieve. + +.EXAMPLE +Get-FabricKQLDashboard -WorkspaceId "12345" -KQLDashboardName "Development" + +Retrieves the "Development" KQLDashboard from workspace "12345". .PARAMETER KQLDashboardID The Id of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardName. The value for KQLDashboardID is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. .EXAMPLE - Get-FabricKQLDashboard +Get-FabricKQLDashboard -WorkspaceId "12345" + +Retrieves all KQLDashboards in workspace "12345". .NOTES - TODO: Add functionality to list all KQLDashboards. To do so fetch all workspaces and - then all KQLDashboards in each workspace. +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName - - 2024-12-08 - FGE: Added Verbose Output -#> +Author: Tiago Balabuch +#> -[CmdletBinding()] +function Get-FabricKQLDashboard { + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Alias("Name","DisplayName")] - [string]$KQLDashboardName, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardId, - [Alias("Id")] - [string]$KQLDashboardId + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDashboardName ) -begin { - - Confirm-FabricAuthToken | Out-Null + try { + # Step 1: Handle ambiguous input + if ($KQLDashboardId -and $KQLDashboardName) { + Write-Message -Message "Both 'KQLDashboardId' and 'KQLDashboardName' were provided. Please specify only one." -Level Error + return $null + } - Write-Verbode "You can either use Name or WorkspaceID" - if ($PSBoundParameters.ContainsKey("KQLDashboardName") -and $PSBoundParameters.ContainsKey("KQLDashboardId")) { - throw "Parameters KQLDashboardName and KQLDashboardId cannot be used together" - } + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug - # Create KQLDashboard API - $KQLDashboardAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards" + # Step 3: Initialize variables + $continuationToken = $null + $KQLDashboards = @() - $KQLDashboardAPIKQLDashboardId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards/$KQLDashboardId" + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } -} + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $KQLDashboards += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $KQLDashboard = if ($KQLDashboardId) { + $KQLDashboards | Where-Object { $_.Id -eq $KQLDashboardId } + } + elseif ($KQLDashboardName) { + $KQLDashboards | Where-Object { $_.DisplayName -eq $KQLDashboardName } + } + else { + # Return all KQLDashboards if no filter is provided + Write-Message -Message "No filter provided. Returning all KQLDashboards." -Level Debug + $KQLDashboards + } -process { - - if ($PSBoundParameters.ContainsKey("KQLDashboardId")) { - Write-Verbose "Get KQLDashboard with ID $KQLDashboardId" - Write-Verbose "Calling KQLDashboard API with KQLDashboardId" - Write-Verbose "--------------------------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDashboardAPIKQLDashboardId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDashboardAPIKQLDashboardId ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling KQLDashboard API" - Write-Verbose "------------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDashboardAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDashboardAPI ` - -ContentType "application/json" - - if ($PSBoundParameters.ContainsKey("KQLDashboardName")) { - Write-Verbose "Filtering KQLDashboards by name. Name: $KQLDashboardName" - $response.value | ` - Where-Object { $_.displayName -eq $KQLDashboardName } + # Step 9: Handle results + if ($KQLDashboard) { + Write-Message -Message "KQLDashboard found matching the specified criteria." -Level Debug + return $KQLDashboard } else { - Write-Verbose "Returning all KQLDashboards" - $response.value + Write-Message -Message "No KQLDashboard found matching the provided criteria." -Level Warning + return $null } } -} - -end {} + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error + } -} \ No newline at end of file +} diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 deleted file mode 100644 index 4f0cf5d1..00000000 --- a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition copy.ps1 +++ /dev/null @@ -1,120 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the definition of a KQLDashboard from a specific workspace in Microsoft Fabric. - -.DESCRIPTION -This function fetches the KQLDashboard's content or metadata from a workspace. -Handles both synchronous and asynchronous operations, with detailed logging and error handling. - -.PARAMETER WorkspaceId -(Mandatory) The unique identifier of the workspace from which the KQLDashboard definition is to be retrieved. - -.PARAMETER KQLDashboardId -(Optional)The unique identifier of the KQLDashboard whose definition needs to be retrieved. - -.PARAMETER KQLDashboardFormat -Specifies the format of the KQLDashboard definition. - -.EXAMPLE -Get-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" - -Retrieves the definition of the KQLDashboard with ID `67890` from the workspace with ID `12345` in the `ipynb` format. - -.EXAMPLE -Get-FabricKQLDashboardDefinition -WorkspaceId "12345" - -Retrieves the definitions of all KQLDashboards in the workspace with ID `12345` in the `ipynb` format. - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Handles long-running operations asynchronously. - -#> -function Get-FabricKQLDashboardDefinition { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardFormat - ) - - try { - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - - if ($KQLDashboardFormat) { - $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLDashboardFormat - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code and handle the response - switch ($statusCode) { - 200 { - Write-Message -Message "KQLDashboard '$KQLDashboardId' definition retrieved successfully!" -Level Debug - return $response.definition.parts - } - 202 { - - Write-Message -Message "Getting KQLDashboard '$KQLDashboardId' definition request accepted. Retrieving in progress!" -Level Debug - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult.definition.parts - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - - } - } - catch { - # Step 9: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error - } - -} diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 index c74147fe..4f0cf5d1 100644 --- a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 @@ -1,119 +1,120 @@ -function Get-FabricKQLDashboardDefinition { -#Requires -Version 7.1 <# .SYNOPSIS - Retrieves Fabric KQLDashboard Definitions for a given KQLDashboard. +Retrieves the definition of a KQLDashboard from a specific workspace in Microsoft Fabric. .DESCRIPTION - Retrieves the Definition of the Fabric KQLDashboard that is specified by the KQLDashboardName or KQLDashboardID. - The KQLDashboard Definition contains the parts of the KQLDashboard, which are the visualizations and their configuration. - This is provided as a JSON object. +This function fetches the KQLDashboard's content or metadata from a workspace. +Handles both synchronous and asynchronous operations, with detailed logging and error handling. .PARAMETER WorkspaceId - Id of the Fabric Workspace in which the KQLDashboard exists. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +(Mandatory) The unique identifier of the workspace from which the KQLDashboard definition is to be retrieved. -.PARAMETER KQLDashboardName - The name of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardID. +.PARAMETER KQLDashboardId +(Optional)The unique identifier of the KQLDashboard whose definition needs to be retrieved. -.PARAMETER KQLDashboardID - The Id of the KQLDashboard to retrieve. This parameter cannot be used together with KQLDashboardName. The value for KQLDashboardID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER KQLDashboardFormat +Specifies the format of the KQLDashboard definition. .EXAMPLE - Get-FabricKQLDashboardDefinition ` - -WorkspaceId "12345678-1234-1234-1234-123456789012" ` - -KQLDashboardName "MyKQLDashboard" +Get-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" - This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the - Workspace with the ID "12345678-1234-1234-1234-123456789012". +Retrieves the definition of the KQLDashboard with ID `67890` from the workspace with ID `12345` in the `ipynb` format. .EXAMPLE - $db = Get-FabricKQLDashboardDefinition ` - -WorkspaceId "12345678-1234-1234-1234-123456789012" ` - -KQLDashboardName "MyKQLDashboard" - - $db[0].payload | ` - Set-Content ` - -Path "C:\temp\mydashboard.json" - - This example retrieves the KQLDashboard Definition for the KQLDashboard named "MyKQLDashboard" in the - Workspace with the ID "12345678-1234-1234-1234-123456789012". - The definition is saved to a file named "mydashboard.json". +Get-FabricKQLDashboardDefinition -WorkspaceId "12345" +Retrieves the definitions of all KQLDashboards in the workspace with ID `12345` in the `ipynb` format. .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Handles long-running operations asynchronously. - Revision History: - - 2024-11-16 - FGE: First version - - 2024-12-08 - FGE: Added Verbose Output #> - - -[CmdletBinding()] +function Get-FabricKQLDashboardDefinition { + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Alias("Name","DisplayName")] - [string]$KQLDashboardName, - - [Alias("Id")] + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] [string]$KQLDashboardId, - [string]$Format + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardFormat ) -begin { - - Confirm-FabricAuthToken | Out-Null + try { + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug - Write-Verbose "You can either use Name or WorkspaceID" - if ($PSBoundParameters.ContainsKey("KQLDashboardName") -and $PSBoundParameters.ContainsKey("KQLDashboardId")) { - throw "Parameters KQLDashboardName and KQLDashboardId cannot be used together" - } - - # Create KQLDashboard API + # Step 3: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - $KQLDashboardAPIKQLDashboardId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards/$KQLDashboardId/getDefinition" - -} + if ($KQLDashboardFormat) { + $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $KQLDashboardFormat + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug -process { - - if ($PSBoundParameters.ContainsKey("KQLDashboardId")) { - Write-Verbose "Get KQLDashboardDefinition with ID $KQLDashboardId" - Write-Verbose "Calling KQLDashboard API with KQLDashboardId" - Write-Verbose "--------------------------------------------" - Write-Verbose "Sending the following values to the KQLDashboard API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $KQLDashboardAPIKQLDashboardId" - Write-Verbose "Body of request: $$null" - Write-Verbose "ContentType: application/json" + # Step 4: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $KQLDashboardAPIKQLDashboardId ` - -Body $null ` - -ContentType "application/json" - - $parts = $response.definition.parts - Write-Verbose "Decoding the payload of the parts: $parts" - - foreach ($part in $parts) { - $bytes = [System.Convert]::FromBase64String($part.payload) - Write-Verbose "Returned bytes for part $part.name: $bytes" - $decodedText = [System.Text.Encoding]::UTF8.GetString($bytes) - Write-Verbose "decodedText for part $part.name: $decodedText" - $part.payload = $decodedText + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code and handle the response + switch ($statusCode) { + 200 { + Write-Message -Message "KQLDashboard '$KQLDashboardId' definition retrieved successfully!" -Level Debug + return $response.definition.parts + } + 202 { + + Write-Message -Message "Getting KQLDashboard '$KQLDashboardId' definition request accepted. Retrieving in progress!" -Level Debug + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult.definition.parts + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } - - $parts } + catch { + # Step 9: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error + } + } - -end {} - -} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 b/source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 deleted file mode 100644 index 50694d70..00000000 --- a/source/Public/KQL Dashboard/New-FabricKQLDashboard copy.ps1 +++ /dev/null @@ -1,188 +0,0 @@ -<# -.SYNOPSIS -Creates a new KQLDashboard in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLDashboard -in the specified workspace. It supports optional parameters for KQLDashboard description -and path definitions for the KQLDashboard content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the KQLDashboard will be created. - -.PARAMETER KQLDashboardName -The name of the KQLDashboard to be created. - -.PARAMETER KQLDashboardDescription -An optional description for the KQLDashboard. - -.PARAMETER KQLDashboardPathDefinition -An optional path to the KQLDashboard definition file (e.g., .ipynb file) to upload. - -.PARAMETER KQLDashboardPathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricKQLDashboard -WorkspaceId "workspace-12345" -KQLDashboardName "New KQLDashboard" -KQLDashboardPathDefinition "C:\KQLDashboards\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricKQLDashboard { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDashboardName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardPathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDashboardPathPlatformDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $KQLDashboardName - } - - if ($KQLDashboardDescription) { - $body.description = $KQLDashboardDescription - } - - if ($KQLDashboardPathDefinition) { - $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition - - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = "KQLDashboard" - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = "RealTimeDashboard.json" - payload = $KQLDashboardEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error - return $null - } - } - - if ($KQLDashboardPathPlatformDefinition) { - $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { - # Initialize definition if it doesn't exist - if (-not $body.definition) { - $body.definition = @{ - format = $null - parts = @() - } - } - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLDashboardEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "KQLDashboard '$KQLDashboardName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "KQLDashboard '$KQLDashboardName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create KQLDashboard. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 index e3e05f2e..50694d70 100644 --- a/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 @@ -1,92 +1,188 @@ -function New-FabricKQLDashboard { -#Requires -Version 7.1 - <# .SYNOPSIS - Creates a new Fabric KQLDashboard +Creates a new KQLDashboard in a specified Microsoft Fabric workspace. .DESCRIPTION - Creates a new Fabric KQLDashboard +This function sends a POST request to the Microsoft Fabric API to create a new KQLDashboard +in the specified workspace. It supports optional parameters for KQLDashboard description +and path definitions for the KQLDashboard content. -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the KQLDashboard should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLDashboard will be created. .PARAMETER KQLDashboardName - The name of the KQLDashboard to create. +The name of the KQLDashboard to be created. .PARAMETER KQLDashboardDescription - The description of the KQLDashboard to create. +An optional description for the KQLDashboard. -.EXAMPLE - New-FabricDashboard ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -KQLDashboardName 'MyKQLDashboard' ` - -KQLDashboardDescription 'This is my KQLDashboard' +.PARAMETER KQLDashboardPathDefinition +An optional path to the KQLDashboard definition file (e.g., .ipynb file) to upload. + +.PARAMETER KQLDashboardPathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. - This example will create a new KQLDashboard with the name 'MyKQLDashboard' and the description 'This is my KQLDashboard'. +.EXAMPLE + Add-FabricKQLDashboard -WorkspaceId "workspace-12345" -KQLDashboardName "New KQLDashboard" -KQLDashboardPathDefinition "C:\KQLDashboards\example.ipynb" -.NOTES + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. - Revsion History: +Author: Tiago Balabuch - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDashboardName - - 2024-12-08 - FGE: Added Verbose Output #> -[CmdletBinding(SupportsShouldProcess)] +function New-FabricKQLDashboard { + [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] [string]$KQLDashboardName, - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$KQLDashboardDescription - + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardPathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDashboardPathPlatformDefinition ) -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{ - 'displayName' = $KQLDashboardName - 'description' = $KQLDashboardDescription - } | ConvertTo-Json ` - -Depth 1 - - # Create KQLDashboard API URL - $KQLDashboardApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDashboards" - } - -process { - - if($PSCmdlet.ShouldProcess($KQLDashboardName)) { - Write-Verbose "Calling KQLDashboard API" - Write-Verbose "------------------------" - Write-Verbose "Sending the following values to the KQLDashboard API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $KQLDashboardApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $KQLDashboardName + } + + if ($KQLDashboardDescription) { + $body.description = $KQLDashboardDescription + } + + if ($KQLDashboardPathDefinition) { + $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition + + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = "KQLDashboard" + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = "RealTimeDashboard.json" + payload = $KQLDashboardEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error + return $null + } + } + + if ($KQLDashboardPathPlatformDefinition) { + $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { + # Initialize definition if it doesn't exist + if (-not $body.definition) { + $body.definition = @{ + format = $null + parts = @() + } + } + + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLDashboardEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $KQLDashboardApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "KQLDashboard '$KQLDashboardName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "KQLDashboard '$KQLDashboardName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create KQLDashboard. Error: $errorDetails" -Level Error } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 index b73d2777..874a614b 100644 --- a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 @@ -14,9 +14,6 @@ The new name for the KQLDashboard. .PARAMETER KQLDashboardDescription (Optional) The new description for the KQLDashboard. -.PARAMETER WorkspaceId -The unique identifier of the workspace where the KQLDashboard resides. - .EXAMPLE Update-FabricKQLDashboard -KQLDashboardId "KQLDashboard123" -KQLDashboardName "NewKQLDashboardName" @@ -31,7 +28,7 @@ Updates both the name and description of the KQLDashboard "KQLDashboard123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -40,8 +37,8 @@ function Update-FabricKQLDashboard { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLDashboardId, diff --git a/source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 deleted file mode 100644 index 94677fe6..00000000 --- a/source/Public/KQL Database/Get-FabricKQLDatabase copy.ps1 +++ /dev/null @@ -1,153 +0,0 @@ -<# -.SYNOPSIS -Retrieves an KQLDatabase or a list of KQLDatabases from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricKQLDatabase` function sends a GET request to the Fabric API to retrieve KQLDatabase details for a given workspace. It can filter the results by `KQLDatabaseName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query KQLDatabases. - -.PARAMETER KQLDatabaseName -(Optional) The name of the specific KQLDatabase to retrieve. - -.EXAMPLE -Get-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseName "Development" - -Retrieves the "Development" KQLDatabase from workspace "12345". - -.EXAMPLE -Get-FabricKQLDatabase -WorkspaceId "12345" - -Retrieves all KQLDatabases in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> -function Get-FabricKQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDatabaseName - ) - - try { - # Step 1: Handle ambiguous input - if ($KQLDatabaseId -and $KQLDatabaseName) { - Write-Message -Message "Both 'KQLDatabaseId' and 'KQLDatabaseName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Initialize variables - $continuationToken = $null - $KQLDatabases = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $KQLDatabases += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $KQLDatabase = if ($KQLDatabaseId) { - $KQLDatabases | Where-Object { $_.Id -eq $KQLDatabaseId } - } - elseif ($KQLDatabaseName) { - $KQLDatabases | Where-Object { $_.DisplayName -eq $KQLDatabaseName } - } - else { - # Return all KQLDatabases if no filter is provided - Write-Message -Message "No filter provided. Returning all KQLDatabases." -Level Debug - $KQLDatabases - } - - # Step 9: Handle results - if ($KQLDatabase) { - Write-Message -Message "KQLDatabase found matching the specified criteria." -Level Debug - return $KQLDatabase - } - else { - Write-Message -Message "No KQLDatabase found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error - } - -} diff --git a/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 index 701cd233..94677fe6 100644 --- a/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 @@ -1,228 +1,153 @@ -function Get-FabricKQLDatabase { -#Requires -Version 7.1 - <# .SYNOPSIS - Retrieves Fabric KQLDatabases +Retrieves an KQLDatabase or a list of KQLDatabases from a specified workspace in Microsoft Fabric. .DESCRIPTION - Retrieves Fabric KQLDatabases. Without the KQLDatabaseName or KQLDatabaseID parameter, - all KQLDatabases are returned. If you want to retrieve a specific KQLDatabase, you can - use the KQLDatabaseName or KQLDatabaseID parameter. These parameters cannot be used together. +The `Get-FabricKQLDatabase` function sends a GET request to the Fabric API to retrieve KQLDatabase details for a given workspace. It can filter the results by `KQLDatabaseName`. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLDatabases should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +(Mandatory) The ID of the workspace to query KQLDatabases. .PARAMETER KQLDatabaseName - The name of the KQLDatabase to retrieve. This parameter cannot be used together with KQLDatabaseID. - -.PARAMETER KQLDatabaseID - The Id of the KQLDatabase to retrieve. This parameter cannot be used together with KQLDatabaseName. - The value for KQLDatabaseID is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' - - This example will retrieve the KQLDatabase with the name 'MyKQLDatabase'. +(Optional) The name of the specific KQLDatabase to retrieve. .EXAMPLE - Get-FabricKQLDatabase +Get-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseName "Development" - This example will retrieve all KQLDatabases in the workspace that is specified - by the WorkspaceId. +Retrieves the "Development" KQLDatabase from workspace "12345". .EXAMPLE - Get-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseId '12345678-1234-1234-1234-123456789012' +Get-FabricKQLDatabase -WorkspaceId "12345" - This example will retrieve the KQLDatabase with the ID '12345678-1234-1234-1234-123456789012'. +Retrieves all KQLDatabases in workspace "12345". .NOTES - TODO: Add functionality to list all KQLDatabases. To do so fetch all workspaces and - then all KQLDatabases in each workspace. +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName - - 2024-12-08 - FGE: Added Verbose Output +Author: Tiago Balabuch #> - - -[CmdletBinding()] +function Get-FabricKQLDatabase { + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Alias("Name","DisplayName")] - [string]$KQLDatabaseName, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseId, - [Alias("Id")] - [string]$KQLDatabaseId + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDatabaseName ) -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use KQLDatabaseName or KQLDatabaseID not both. If both are used throw error" - if ($PSBoundParameters.ContainsKey("KQLDatabaseName") -and $PSBoundParameters.ContainsKey("KQLDatabaseId")) { - throw "Parameters KQLDatabaseName and KQLDatabaseId cannot be used together" - } - - # Create KQLDatabase API - $KQLDatabaseAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/kqldatabases" - - $KQLDatabaseAPIKQLDatabaseId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/kqldatabases/$KQLDatabaseId" - -} + try { + # Step 1: Handle ambiguous input + if ($KQLDatabaseId -and $KQLDatabaseName) { + Write-Message -Message "Both 'KQLDatabaseId' and 'KQLDatabaseName' were provided. Please specify only one." -Level Error + return $null + } -process { - - if ($PSBoundParameters.ContainsKey("KQLDatabaseId")) { - Write-Verbose "Calling KQLDatabase API with KQLDatabaseId : $KQLDatabaseId" - Write-Verbose "-------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLDatabase API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDatabaseAPIKQLDatabaseId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDatabaseAPIKQLDatabaseId ` - -ContentType "application/json" - - Write-Verbose "Adding Members to the Output object for convenience" - Write-Verbose "Adding Member parentEventhouseItemId with value $($response.properties.parentEventhouseItemId)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'parentEventhouseItemId' ` - -Value $response.properties.parentEventhouseItemId ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member queryServiceUri with value $($response.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $response.properties.queryServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member ingestionServiceUri with value $($response.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $response.properties.ingestionServiceUri ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member databaseType with value $($response.properties.databaseType)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databaseType' ` - -Value $response.properties.databaseType ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member oneLakeStandardStoragePeriod with value $($response.properties.oneLakeStandardStoragePeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeStandardStoragePeriod' ` - -Value $response.properties.oneLakeStandardStoragePeriod ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member oneLakeCachingPeriod with value $($response.properties.oneLakeCachingPeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeCachingPeriod' ` - -Value $response.properties.oneLakeCachingPeriod ` - -InputObject $response ` - -Force - - $response - } - else { - Write-Verbose "Calling KQLDatabase API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the KQLDatabase API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLDatabaseAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLDatabaseAPI ` - -ContentType "application/json" - - Write-Verbose "Adding Members to the Output object for convenience" - foreach ($kqlDatabase in $response.value) { - Write-Verbose "Adding Member parentEventhouseItemId with value $($response.properties.parentEventhouseItemId)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'parentEventhouseItemId' ` - -Value $response.properties.parentEventhouseItemId ` - -InputObject $response ` - -Force - - Write-Verbose "Adding Member queryServiceUri with value $($kqlDatabase.properties.queryServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'queryServiceUri' ` - -Value $kqlDatabase.properties.queryServiceUri ` - -InputObject $kqlDatabase ` - -Force - - Write-Verbose "Adding Member ingestionServiceUri with value $($kqlDatabase.properties.ingestionServiceUri)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'ingestionServiceUri' ` - -Value $kqlDatabase.properties.ingestionServiceUri ` - -InputObject $kqlDatabase ` - -Force - Write-Verbose "Adding Member databaseType with value $($kqlDatabase.properties.databaseType)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'databaseType' ` - -Value $kqlDatabase.properties.databaseType ` - -InputObject $kqlDatabase ` - -Force - - Write-Verbose "Adding Member oneLakeStandardStoragePeriod with value $($kqlDatabase.properties.oneLakeStandardStoragePeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeStandardStoragePeriod' ` - -Value $kqlDatabase.properties.oneLakeStandardStoragePeriod ` - -InputObject $kqlDatabase ` - -Force - - Write-Verbose "Adding Member oneLakeCachingPeriod with value $($kqlDatabase.properties.oneLakeCachingPeriod)" - Add-Member ` - -MemberType NoteProperty ` - -Name 'oneLakeCachingPeriod' ` - -Value $kqlDatabase.properties.oneLakeCachingPeriod ` - -InputObject $kqlDatabase ` - -Force + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + # Step 3: Initialize variables + $continuationToken = $null + $KQLDatabases = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $KQLDatabases += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $KQLDatabase = if ($KQLDatabaseId) { + $KQLDatabases | Where-Object { $_.Id -eq $KQLDatabaseId } + } + elseif ($KQLDatabaseName) { + $KQLDatabases | Where-Object { $_.DisplayName -eq $KQLDatabaseName } + } + else { + # Return all KQLDatabases if no filter is provided + Write-Message -Message "No filter provided. Returning all KQLDatabases." -Level Debug + $KQLDatabases } - if ($PSBoundParameters.ContainsKey("KQLDatabaseName")) { - Write-Verbose "Filtering KQLDatabases by name. Name: $KQLDatabaseName" - $response.value | ` - Where-Object { $_.displayName -eq $KQLDatabaseName } + # Step 9: Handle results + if ($KQLDatabase) { + Write-Message -Message "KQLDatabase found matching the specified criteria." -Level Debug + return $KQLDatabase } else { - Write-Verbose "Returning all KQLDatabases" - $response.value + Write-Message -Message "No KQLDatabase found matching the provided criteria." -Level Warning + return $null } } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error + } + } - -end {} - -} \ No newline at end of file diff --git a/source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 deleted file mode 100644 index 21098872..00000000 --- a/source/Public/KQL Database/New-FabricKQLDatabase copy.ps1 +++ /dev/null @@ -1,288 +0,0 @@ -<# -.SYNOPSIS -Creates a new KQLDatabase in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLDatabase -in the specified workspace. It supports optional parameters for KQLDatabase description -and path definitions for the KQLDatabase content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the KQLDatabase will be created. - -.PARAMETER KQLDatabaseName -The name of the KQLDatabase to be created. - -.PARAMETER KQLDatabaseDescription -An optional description for the KQLDatabase. - -.PARAMETER KQLDatabasePathDefinition -An optional path to the KQLDatabase definition file (e.g., .ipynb file) to upload. - -.PARAMETER KQLDatabasePathPlatformDefinition -An optional path to the platform-specific definition (e.g., .platform file) to upload. - -.EXAMPLE - Add-FabricKQLDatabase -WorkspaceId "workspace-12345" -KQLDatabaseName "New KQLDatabase" -KQLDatabasePathDefinition "C:\KQLDatabases\example.ipynb" - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Precedent Request Body - - Definition file high priority. - - CreationPayload is evaluate only if Definition file is not provided. - - invitationToken has priority over all other payload fields. - -Author: Tiago Balabuch - -#> - -function New-FabricKQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLDatabaseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$parentEventhouseId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet("ReadWrite", "Shortcut")] - [string]$KQLDatabaseType, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLInvitationToken, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLSourceClusterUri, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLSourceDatabaseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathPlatformDefinition, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabasePathSchemaDefinition - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body ### This is working - $body = @{ - displayName = $KQLDatabaseName - } - - if ($KQLDatabaseDescription) { - $body.description = $KQLDatabaseDescription - } - - if ($KQLDatabasePathDefinition) { - $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition - - $body.definition = @{ - parts = @() - } - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { - - - # Add new part to the parts array - $body.definition.parts += @{ - path = "DatabaseProperties.json" - payload = $KQLDatabaseEncodedContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error - return $null - } - - if ($KQLDatabasePathPlatformDefinition) { - $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { - - # Add new part to the parts array - $body.definition.parts += @{ - path = ".platform" - payload = $KQLDatabaseEncodedPlatformContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in platform definition." -Level Error - return $null - } - - } - if ($KQLDatabasePathSchemaDefinition) { - $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition - - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { - - # Add new part to the parts array - $body.definition.parts += @{ - path = "DatabaseSchema.kql" - payload = $KQLDatabaseEncodedSchemaContent - payloadType = "InlineBase64" - } - } - else { - Write-Message -Message "Invalid or empty content in schema definition." -Level Error - return $null - } - } - - } - else { - if ($KQLDatabaseType -eq "Shortcut") { - if (-not $parentEventhouseId) { - Write-Message -Message "Error: 'parentEventhouseId' is required for Shortcut type." -Level Error - return $null - } - if (-not ($KQLInvitationToken -or $KQLSourceClusterUri -or $KQLSourceDatabaseName)) { - Write-Message -Message "Error: Provide either 'KQLInvitationToken', 'KQLSourceClusterUri', or 'KQLSourceDatabaseName'." -Level Error - return $null - } - if ($KQLInvitationToken) { - Write-Message -Message "Info: 'KQLInvitationToken' is provided." -Level Warning - - if ($KQLSourceClusterUri) { - Write-Message -Message "Warning: 'KQLSourceClusterUri' is ignored when 'KQLInvitationToken' is provided." -Level Warning - #$KQLSourceClusterUri = $null - } - if ($KQLSourceDatabaseName) { - Write-Message -Message "Warning: 'KQLSourceDatabaseName' is ignored when 'KQLInvitationToken' is provided." -Level Warning - #$KQLSourceDatabaseName = $null - } - } - if ($KQLSourceClusterUri -and -not $KQLSourceDatabaseName) { - Write-Message -Message "Error: 'KQLSourceDatabaseName' is required when 'KQLSourceClusterUri' is provided." -Level Error - return $null - } - } - - # Validate ReadWrite type database - if ($KQLDatabaseType -eq "ReadWrite" -and -not $parentEventhouseId) { - Write-Message -Message "Error: 'parentEventhouseId' is required for ReadWrite type." -Level Error - return $null - } - - $body.creationPayload = @{ - databaseType = $KQLDatabaseType - parentEventhouseItemId = $parentEventhouseId - } - - if ($KQLDatabaseType -eq "Shortcut") { - if ($KQLInvitationToken) { - - $body.creationPayload.invitationToken = $KQLInvitationToken - } - if ($KQLSourceClusterUri -and -not $KQLInvitationToken) { - $body.creationPayload.sourceClusterUri = $KQLSourceClusterUri - } - if ($KQLSourceDatabaseName -and -not $KQLInvitationToken) { - $body.creationPayload.sourceDatabaseName = $KQLSourceDatabaseName - } - } - - - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "KQLDatabase '$KQLDatabaseName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "KQLDatabase '$KQLDatabaseName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create KQLDatabase. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 index e019acd7..21098872 100644 --- a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 @@ -1,106 +1,288 @@ -function New-FabricKQLDatabase { -#Requires -Version 7.1 - <# .SYNOPSIS - Creates a new Fabric KQLDatabase +Creates a new KQLDatabase in a specified Microsoft Fabric workspace. .DESCRIPTION - Creates a new Fabric KQLDatabase. The KQLDatabase is created in the specified Workspace and Eventhouse. - It will be created with the specified name and description. +This function sends a POST request to the Microsoft Fabric API to create a new KQLDatabase +in the specified workspace. It supports optional parameters for KQLDatabase description +and path definitions for the KQLDatabase content. + +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLDatabase will be created. -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the KQLDatabase should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER KQLDatabaseName +The name of the KQLDatabase to be created. -.PARAMETER EventhouseID - Id of the Fabric Eventhouse for which the KQLDatabase should be created. The value for EventhouseID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER KQLDatabaseDescription +An optional description for the KQLDatabase. -.PARAMETER KQLDatabaseName - The name of the KQLDatabase to create. The name must be unique within the eventhouse and is a - mandatory parameter. +.PARAMETER KQLDatabasePathDefinition +An optional path to the KQLDatabase definition file (e.g., .ipynb file) to upload. -.PARAMETER KQLDatabaseDescription - The description of the KQLDatabase to create. +.PARAMETER KQLDatabasePathPlatformDefinition +An optional path to the platform-specific definition (e.g., .platform file) to upload. .EXAMPLE - New-FabricKQLDatabase ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -EventhouseID '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' ` - -KQLDatabaseDescription 'This is my KQLDatabase' + Add-FabricKQLDatabase -WorkspaceId "workspace-12345" -KQLDatabaseName "New KQLDatabase" -KQLDatabasePathDefinition "C:\KQLDatabases\example.ipynb" - This example will create a new KQLDatabase with the name 'MyKQLDatabase' and the description 'This is my KQLDatabase'. + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Precedent Request Body + - Definition file high priority. + - CreationPayload is evaluate only if Definition file is not provided. + - invitationToken has priority over all other payload fields. -.NOTES - Revsion History: +Author: Tiago Balabuch - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName - - 2024-12-08 - FGE: Added Verbose Output #> -[CmdletBinding(SupportsShouldProcess)] +function New-FabricKQLDatabase { + [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLDatabaseName, - [Parameter(Mandatory=$true)] - [string]$EventhouseID, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabaseDescription, - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] - [string]$KQLDatabaseName, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$parentEventhouseId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet("ReadWrite", "Shortcut")] + [string]$KQLDatabaseType, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLInvitationToken, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLSourceClusterUri, - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$KQLDatabaseDescription + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLSourceDatabaseName, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathPlatformDefinition, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLDatabasePathSchemaDefinition ) -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body for the request" - $body = @{ - 'displayName' = $KQLDatabaseName - 'description' = $KQLDatabaseDescription - 'creationPayload'= @{ - 'databaseType' = "ReadWrite"; - 'parentEventhouseItemId' = $EventhouseId} - } | ConvertTo-Json ` - -Depth 1 - - # Create KQLDatabase API URL - $KQLDatabaseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases" - } + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug -process { - - if($PSCmdlet.ShouldProcess($EventhouseName)) { - Write-Verbose "Calling KQLDatabase API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $KQLDatabaseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $KQLDatabaseApiUrl ` - -Body ($body) ` - -ContentType "application/json" + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - $response - } -} + # Step 3: Construct the request body ### This is working + $body = @{ + displayName = $KQLDatabaseName + } -end { + if ($KQLDatabaseDescription) { + $body.description = $KQLDatabaseDescription + } -} + if ($KQLDatabasePathDefinition) { + $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition + + $body.definition = @{ + parts = @() + } + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { + + + # Add new part to the parts array + $body.definition.parts += @{ + path = "DatabaseProperties.json" + payload = $KQLDatabaseEncodedContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error + return $null + } + + if ($KQLDatabasePathPlatformDefinition) { + $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { -} \ No newline at end of file + # Add new part to the parts array + $body.definition.parts += @{ + path = ".platform" + payload = $KQLDatabaseEncodedPlatformContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in platform definition." -Level Error + return $null + } + + } + if ($KQLDatabasePathSchemaDefinition) { + $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition + + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { + + # Add new part to the parts array + $body.definition.parts += @{ + path = "DatabaseSchema.kql" + payload = $KQLDatabaseEncodedSchemaContent + payloadType = "InlineBase64" + } + } + else { + Write-Message -Message "Invalid or empty content in schema definition." -Level Error + return $null + } + } + + } + else { + if ($KQLDatabaseType -eq "Shortcut") { + if (-not $parentEventhouseId) { + Write-Message -Message "Error: 'parentEventhouseId' is required for Shortcut type." -Level Error + return $null + } + if (-not ($KQLInvitationToken -or $KQLSourceClusterUri -or $KQLSourceDatabaseName)) { + Write-Message -Message "Error: Provide either 'KQLInvitationToken', 'KQLSourceClusterUri', or 'KQLSourceDatabaseName'." -Level Error + return $null + } + if ($KQLInvitationToken) { + Write-Message -Message "Info: 'KQLInvitationToken' is provided." -Level Warning + + if ($KQLSourceClusterUri) { + Write-Message -Message "Warning: 'KQLSourceClusterUri' is ignored when 'KQLInvitationToken' is provided." -Level Warning + #$KQLSourceClusterUri = $null + } + if ($KQLSourceDatabaseName) { + Write-Message -Message "Warning: 'KQLSourceDatabaseName' is ignored when 'KQLInvitationToken' is provided." -Level Warning + #$KQLSourceDatabaseName = $null + } + } + if ($KQLSourceClusterUri -and -not $KQLSourceDatabaseName) { + Write-Message -Message "Error: 'KQLSourceDatabaseName' is required when 'KQLSourceClusterUri' is provided." -Level Error + return $null + } + } + + # Validate ReadWrite type database + if ($KQLDatabaseType -eq "ReadWrite" -and -not $parentEventhouseId) { + Write-Message -Message "Error: 'parentEventhouseId' is required for ReadWrite type." -Level Error + return $null + } + + $body.creationPayload = @{ + databaseType = $KQLDatabaseType + parentEventhouseItemId = $parentEventhouseId + } + + if ($KQLDatabaseType -eq "Shortcut") { + if ($KQLInvitationToken) { + + $body.creationPayload.invitationToken = $KQLInvitationToken + } + if ($KQLSourceClusterUri -and -not $KQLInvitationToken) { + $body.creationPayload.sourceClusterUri = $KQLSourceClusterUri + } + if ($KQLSourceDatabaseName -and -not $KQLInvitationToken) { + $body.creationPayload.sourceDatabaseName = $KQLSourceDatabaseName + } + } + + + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "KQLDatabase '$KQLDatabaseName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "KQLDatabase '$KQLDatabaseName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create KQLDatabase. Error: $errorDetails" -Level Error + } +} diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 deleted file mode 100644 index 2d8bf8b6..00000000 --- a/source/Public/KQL Database/Remove-FabricKQLDatabase copy.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -function Remove-FabricKQLDatabase { -<# -.SYNOPSIS -Deletes an KQLDatabase from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricKQLDatabase` function sends a DELETE request to the Fabric API to remove a specified KQLDatabase from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the KQLDatabase to delete. - -.PARAMETER KQLDatabaseId -(Mandatory) The ID of the KQLDatabase to be deleted. - - -.EXAMPLE -Remove-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseId "67890" - -Deletes the KQLDatabase with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLDatabaseId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "KQLDatabase '$KQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete KQLDatabase '$KQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 index 997f3ce4..2b5d69f2 100644 --- a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 @@ -1,80 +1,74 @@ -function Remove-FabricKQLDatabase { <# .SYNOPSIS - Removes an existing Fabric Eventhouse +Deletes an KQLDatabase from a specified workspace in Microsoft Fabric. .DESCRIPTION - Removes an existing Fabric Eventhouse. The Eventhouse is removed from the specified Workspace. +The `Remove-FabricKQLDatabase` function sends a DELETE request to the Fabric API to remove a specified KQLDatabase from a given workspace. .PARAMETER WorkspaceId - Id of the Fabric Workspace from which the Eventhouse should be removed. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER EventhouseId - The Id of the Eventhouse to remove. The value for EventhouseId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +(Mandatory) The ID of the workspace containing the KQLDatabase to delete. .PARAMETER KQLDatabaseId - The Id of the Eventhouse to remove. The value for EventhouseId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - This parameter is an alias for EventhouseId. +(Mandatory) The ID of the KQLDatabase to be deleted. .EXAMPLE - Remove-FabricEventhouse ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -EventhouseId '12345678-1234-1234-1234-123456789012' +Remove-FabricKQLDatabase -WorkspaceId "12345" -KQLDatabaseId "67890" - This example will remove the Eventhouse with the Id '12345678-1234-1234-1234-123456789012'. +Deletes the KQLDatabase with ID "67890" from workspace "12345". .NOTES - TODO: Add functionality to remove Eventhouse by name. +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. - Revsion History: - - - 2024-12-08 - FGE: Added Verbose Output +Author: Tiago Balabuch #> - -[CmdletBinding(SupportsShouldProcess)] +function Remove-FabricKQLDatabase { + [CmdletBinding()] param ( - - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [Alias("Id")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$KQLDatabaseId ) -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create Eventhouse API URL" - $eventhouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases/$KQLDatabaseId" + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug -} - -process { + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - if($PSCmdlet.ShouldProcess($KQLDatabaseId)) { - Write-Verbose "Calling KQLDatabase API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the Eventstream API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $eventhouseApiUrl" - Write-Verbose "ContentType: application/json" + # Step 3: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $eventhouseApiUrl ` - -ContentType "application/json" - - $response + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "KQLDatabase '$KQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info + + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete KQLDatabase '$KQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} - -end {} - } diff --git a/source/Public/KQL Database/Set-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Set-FabricKQLDatabase.ps1 deleted file mode 100644 index ddeade86..00000000 --- a/source/Public/KQL Database/Set-FabricKQLDatabase.ps1 +++ /dev/null @@ -1,114 +0,0 @@ -function Set-FabricKQLDatabase { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Updates Properties of an existing Fabric KQLDatabase - -.DESCRIPTION - Updates Properties of an existing Fabric KQLDatabase. The KQLDatabase is updated - in the specified Workspace. The KQLDatabaseId is used to identify the KQLDatabase - that should be updated. The KQLDatabaseNewName and KQLDatabaseDescription are the - properties that can be updated. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLDatabase should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER KQLDatabaseId - The Id of the KQLDatabase to update. The value for KQLDatabaseId is a GUID. - An example of a GUID is '12345678-1234-123-1234-123456789012'. - -.PARAMETER NewKQLDatabaseName - The new name of the KQLDatabase. - -.PARAMETER KQLDatabaseDescription - The new description of the KQLDatabase. The description can be up to 256 characters long. - -.EXAMPLE - Set-FabricKQLDatabase ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseId '12345678-1234-1234-1234-123456789012' ` - -NewKQLDatabaseNewName 'MyNewKQLDatabase' ` - -KQLDatabaseDescription 'This is my new KQLDatabase' - - This example will update the KQLDatabase with the Id '12345678-1234-1234-1234-123456789012'. - It will update the name to 'MyNewKQLDatabase' and the description to 'This is my new KQLDatabase'. - -.NOTES - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLDatabaseName - - 2024-12-08 - FGE: Added Verbose Output - Renamed Parameter KQLDatabaseName to NewKQLDatabaseNewName - -.LINK - -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$KQLDatabaseId, - - [Alias("NewName", "NewDisplayName")] - [string]$NewKQLDatabaseName, - - [Alias("Description")] - [ValidateLength(0, 256)] - [string]$KQLDatabaseDescription - - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{} - - if ($PSBoundParameters.ContainsKey("NewKQLDatabaseName")) { - $body["displayName"] = $NewKQLDatabaseName - } - - if ($PSBoundParameters.ContainsKey("KQLDatabaseDescription")) { - $body["description"] = $KQLDatabaseDescription - } - - $body = $body | ConvertTo-Json -Depth 1 - - # Create KQLDatabase API URL - $KQLDatabaseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLDatabases/$KQLDatabaseId" - } - -process { - - if($PSCmdlet.ShouldProcess($KQLDatabaseId)) { - Write-Verbose "Calling KQLDatabase API with KQLDatabaseId $KQLDatabaseId" - Write-Verbose "------------------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLDatabase API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $KQLDatabaseApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $KQLDatabaseApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 index 62731282..35b2544e 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 @@ -36,7 +36,7 @@ Author: Tiago Balabuch #> function Update-FabricKQLDatabase { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 index 63a505d5..4ff0155b 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 @@ -46,7 +46,7 @@ Author: Tiago Balabuch #> function Update-FabricKQLDatabaseDefinition { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 deleted file mode 100644 index b1c06d8b..00000000 --- a/source/Public/KQL Queryset/Get-FabricKQLQueryset copy.ps1 +++ /dev/null @@ -1,154 +0,0 @@ -<# -.SYNOPSIS -Retrieves an KQLQueryset or a list of KQLQuerysets from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Get-FabricKQLQueryset` function sends a GET request to the Fabric API to retrieve KQLQueryset details for a given workspace. It can filter the results by `KQLQuerysetName`. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace to query KQLQuerysets. - -.PARAMETER KQLQuerysetName -(Optional) The name of the specific KQLQueryset to retrieve. - -.EXAMPLE -Get-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetName "Development" - -Retrieves the "Development" KQLQueryset from workspace "12345". - -.EXAMPLE -Get-FabricKQLQueryset -WorkspaceId "12345" - -Retrieves all KQLQuerysets in workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function Get-FabricKQLQueryset { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$KQLQuerysetName - ) - - try { - # Step 1: Handle ambiguous input - if ($KQLQuerysetId -and $KQLQuerysetName) { - Write-Message -Message "Both 'KQLQuerysetId' and 'KQLQuerysetName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $KQLQuerysets = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets" -f $FabricConfig.BaseUrl, $WorkspaceId - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $KQLQuerysets += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $KQLQueryset = if ($KQLQuerysetId) { - $KQLQuerysets | Where-Object { $_.Id -eq $KQLQuerysetId } - } - elseif ($KQLQuerysetName) { - $KQLQuerysets | Where-Object { $_.DisplayName -eq $KQLQuerysetName } - } - else { - # Return all KQLQuerysets if no filter is provided - Write-Message -Message "No filter provided. Returning all KQLQuerysets." -Level Debug - $KQLQuerysets - } - - # Step 9: Handle results - if ($KQLQueryset) { - Write-Message -Message "KQLQueryset found matching the specified criteria." -Level Debug - return $KQLQueryset - } - else { - Write-Message -Message "No KQLQueryset found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error - } - -} diff --git a/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 index ce7629a4..b1c06d8b 100644 --- a/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 @@ -1,130 +1,154 @@ -function Get-FabricKQLQueryset { -#Requires -Version 7.1 - <# .SYNOPSIS - Retrieves Fabric KQLQuerysets +Retrieves an KQLQueryset or a list of KQLQuerysets from a specified workspace in Microsoft Fabric. .DESCRIPTION - Retrieves Fabric KQLQuerysets. Without the KQLQuerysetName or KQLQuerysetId parameter, - all KQLQuerysets are returned in the given Workspace. If you want to retrieve a specific - KQLQueryset, you can use the KQLQuerysetName or KQLQuerysetId parameter. These parameters - cannot be used together. +The `Get-FabricKQLQueryset` function sends a GET request to the Fabric API to retrieve KQLQueryset details for a given workspace. It can filter the results by `KQLQuerysetName`. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLQuerysets should be retrieved. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. +(Mandatory) The ID of the workspace to query KQLQuerysets. .PARAMETER KQLQuerysetName - The name of the KQLQueryset to retrieve. This parameter cannot be used together with KQLQuerysetId. - -.PARAMETER KQLQuerysetId - The Id of the KQLQueryset to retrieve. This parameter cannot be used together with KQLQuerysetName. - The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetName 'MyKQLQueryset' - - This example will retrieve the KQLQueryset with the name 'MyKQLQueryset'. +(Optional) The name of the specific KQLQueryset to retrieve. .EXAMPLE - Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' +Get-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetName "Development" - This example will retrieve all KQLQuerysets in the workspace that is specified - by the WorkspaceId. +Retrieves the "Development" KQLQueryset from workspace "12345". .EXAMPLE - Get-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' +Get-FabricKQLQueryset -WorkspaceId "12345" - This example will retrieve the KQLQueryset with the ID '12345678-1234-1234-1234-123456789012'. +Retrieves all KQLQuerysets in workspace "12345". .NOTES - TODO: Add functionality to list all KQLQuerysets. To do so fetch all workspaces and - then all KQLQuerysets in each workspace. +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. - Revision History: - - 2024-11-09 - FGE: Added DisplaName as Alias for KQLQuerysetName - - 2024-12-22 - FGE: Added Verbose Output -#> +Author: Tiago Balabuch +#> -[CmdletBinding()] +function Get-FabricKQLQueryset { + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Alias("Name","DisplayName")] - [string]$KQLQuerysetName, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KQLQuerysetId, - [Alias("Id")] - [string]$KQLQuerysetId + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$KQLQuerysetName ) -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "You can either use Name or WorkspaceID" - if ($PSBoundParameters.ContainsKey("KQLQuerysetName") -and $PSBoundParameters.ContainsKey("KQLQuerysetId")) { - throw "Parameters KQLQuerysetName and KQLQuerysetId cannot be used together" - } - - # Create KQLQueryset API - $KQLQuerysetAPI = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets" - - $KQLQuerysetAPIKQLQuerysetId = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" + try { + # Step 1: Handle ambiguous input + if ($KQLQuerysetId -and $KQLQuerysetName) { + Write-Message -Message "Both 'KQLQuerysetId' and 'KQLQuerysetName' were provided. Please specify only one." -Level Error + return $null + } -} + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $KQLQuerysets = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets" -f $FabricConfig.BaseUrl, $WorkspaceId + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $KQLQuerysets += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $KQLQueryset = if ($KQLQuerysetId) { + $KQLQuerysets | Where-Object { $_.Id -eq $KQLQuerysetId } + } + elseif ($KQLQuerysetName) { + $KQLQuerysets | Where-Object { $_.DisplayName -eq $KQLQuerysetName } + } + else { + # Return all KQLQuerysets if no filter is provided + Write-Message -Message "No filter provided. Returning all KQLQuerysets." -Level Debug + $KQLQuerysets + } -process { - - if ($PSBoundParameters.ContainsKey("KQLQuerysetId")) { - Write-Verbose "Calling KQLQueryset API with KQLQuerysetId $KQLQuerysetId" - Write-Verbose "------------------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLQuerysetAPIKQLQuerysetId" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLQuerysetAPIKQLQuerysetId ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling KQLQueryset API" - Write-Verbose "------------------------------------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $KQLQuerysetAPI" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $KQLQuerysetAPI ` - -ContentType "application/json" - - if ($PSBoundParameters.ContainsKey("KQLQuerysetName")) { - Write-Verbose "Filtering KQLQuerysets by name. Name: $KQLQuerysetName" - $response.value | ` - Where-Object { $_.displayName -eq $KQLQuerysetName } + # Step 9: Handle results + if ($KQLQueryset) { + Write-Message -Message "KQLQueryset found matching the specified criteria." -Level Debug + return $KQLQueryset } else { - Write-Verbose "Returning all KQLQuerysets" - $response.value + Write-Message -Message "No KQLQueryset found matching the provided criteria." -Level Warning + return $null } } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error + } + } - -end {} - -} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 b/source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 deleted file mode 100644 index d9a48c5d..00000000 --- a/source/Public/KQL Queryset/Remove-FabricKQLQueryset copy.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -<# -.SYNOPSIS -Deletes an KQLQueryset from a specified workspace in Microsoft Fabric. - -.DESCRIPTION -The `Remove-FabricKQLQueryset` function sends a DELETE request to the Fabric API to remove a specified KQLQueryset from a given workspace. - -.PARAMETER WorkspaceId -(Mandatory) The ID of the workspace containing the KQLQueryset to delete. - -.PARAMETER KQLQuerysetId -(Mandatory) The ID of the KQLQueryset to be deleted. - -.EXAMPLE -Remove-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetId "67890" - -Deletes the KQLQueryset with ID "67890" from workspace "12345". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Validates token expiration before making the API request. - -Author: Tiago Balabuch - -#> - -function Remove-FabricKQLQueryset { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$KQLQuerysetId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - Write-Message -Message "KQLQueryset '$KQLQuerysetId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { - # Step 5: Log and handle errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to delete KQLQueryset '$KQLQuerysetId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 index 68d2b3df..f33273fb 100644 --- a/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 @@ -1,77 +1,73 @@ -function Remove-FabricKQLQueryset { -#Requires -Version 7.1 - <# .SYNOPSIS - Removes an existing Fabric KQLQueryset. +Deletes an KQLQueryset from a specified workspace in Microsoft Fabric. .DESCRIPTION - Removes an existing Fabric KQLQueryset. The Eventhouse is identified by the WorkspaceId and KQLQuerysetId. +The `Remove-FabricKQLQueryset` function sends a DELETE request to the Fabric API to remove a specified KQLQueryset from a given workspace. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLQueryset should be removed. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. +(Mandatory) The ID of the workspace containing the KQLQueryset to delete. .PARAMETER KQLQuerysetId - The Id of the KQLQueryset to remove. The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - This parameter is mandatory. +(Mandatory) The ID of the KQLQueryset to be deleted. .EXAMPLE - Remove-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' +Remove-FabricKQLQueryset -WorkspaceId "12345" -KQLQuerysetId "67890" -.NOTES - TODO: Add functionality to remove KQLQueryset by name. +Deletes the KQLQueryset with ID "67890" from workspace "12345". - Revsion History: +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Validates token expiration before making the API request. - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-12-22 - FGE: Added Verbose Output +Author: Tiago Balabuch #> - -[CmdletBinding(SupportsShouldProcess)] +function Remove-FabricKQLQueryset { + [CmdletBinding(SupportsShouldProcess)] param ( - - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [Alias("Id")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$KQLQuerysetId - ) -begin { - Confirm-FabricAuthToken | Out-Null - - # Create KQLQueryset API URL - $querysetApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" -} + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug -process { + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Call KQL Queryset API - if($PSCmdlet.ShouldProcess($KQLQuerysetId)) { - Write-Verbose "Calling KQLQueryset API" - Write-Verbose "-----------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: DELETE" - Write-Verbose "URI: $querysetApiUrl" - Write-Verbose "ContentType: application/json" + # Step 3: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method DELETE ` - -Uri $querysetApiUrl ` - -ContentType "application/json" + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + Write-Message -Message "KQLQueryset '$KQLQuerysetId' deleted successfully from workspace '$WorkspaceId'." -Level Info - $response + } + catch { + # Step 5: Log and handle errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to delete KQLQueryset '$KQLQuerysetId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 deleted file mode 100644 index 0294cf61..00000000 --- a/source/Public/KQL Queryset/Set-FabricKQLQueryset.ps1 +++ /dev/null @@ -1,109 +0,0 @@ -function Set-FabricKQLQueryset { -<# -.SYNOPSIS - Updates Properties of an existing Fabric KQLQueryset - -.DESCRIPTION - Updates Properties of an existing Fabric KQLQueryset. The KQLQueryset is identified by - the WorkspaceId and KQLQuerysetId. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace for which the KQLQueryset should be updated. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. - -.PARAMETER KQLQuerysetId - The Id of the KQLQueryset to update. The value for KQLQuerysetId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - This parameter is mandatory. - -.PARAMETER KQLQuerysetNewName - The new name of the KQLQueryset. This parameter is optional. - -.PARAMETER KQLQuerysetDescription - The new description of the KQLQueryset. This parameter is optional. - -.EXAMPLE - Set-FabricKQLQueryset ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetId '12345678-1234-1234-1234-123456789012' ` - -KQLQuerysetNewName 'MyKQLQueryset' ` - -KQLQuerysetDescription 'This is my KQLQueryset' - - This example will update the KQLQueryset. The KQLQueryset will have the name 'MyKQLQueryset' - and the description 'This is my KQLQueryset'. - -.NOTES - - Revsion History: - - - 2024-11-07 - FGE: Implemented SupportShouldProcess - - 2024-11-09 - FGE: Added NewDisplaName as Alias for KQLQuerysetNewName - - 2024-12-22 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/KQLQueryset/items/create-KQLQueryset?tabs=HTTP -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$WorkspaceId, - - [Parameter(Mandatory=$true)] - [Alias("Id")] - [string]$KQLQuerysetId, - - [Alias("NewName", "NewDisplayName")] - [string]$KQLQuerysetNewName, - - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$KQLQuerysetDescription - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{} - - if ($PSBoundParameters.ContainsKey("KQLQuerysetName")) { - $body["displayName"] = $KQLQuerysetNName - } - - if ($PSBoundParameters.ContainsKey("KQLQuerysetDescription")) { - $body["description"] = $KQLQuerysetDescription - } - - $body = $body | ConvertTo-Json -Depth 1 - - # Create KQLQueryset API URL - $KQLQuerysetApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/KQLQuerysets/$KQLQuerysetId" - } - -process { - - # Call KQLQueryset API - if($PSCmdlet.ShouldProcess($KQLQuerysetId)) { - Write-Verbose "Calling KQLQueryset API with KQLQuerysetId $KQLQuerysetId" - Write-Verbose "---------------------------------------------------------" - Write-Verbose "Sending the following values to the KQLQueryset API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: PATCH" - Write-Verbose "URI: $KQLQuerysetApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method PATCH ` - -Uri $KQLQuerysetApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} diff --git a/source/Public/Lakehouse/New-FabricLakehouse copy.ps1 b/source/Public/Lakehouse/New-FabricLakehouse copy.ps1 deleted file mode 100644 index f24c8feb..00000000 --- a/source/Public/Lakehouse/New-FabricLakehouse copy.ps1 +++ /dev/null @@ -1,136 +0,0 @@ -<# -.SYNOPSIS -Creates a new Lakehouse in a specified Microsoft Fabric workspace. - -.DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new Lakehouse -in the specified workspace. It supports optional parameters for Lakehouse description -and path definitions for the Lakehouse content. - -.PARAMETER WorkspaceId -The unique identifier of the workspace where the Lakehouse will be created. - -.PARAMETER LakehouseName -The name of the Lakehouse to be created. - -.PARAMETER LakehouseDescription -An optional description for the Lakehouse. - -.PARAMETER LakehouseEnableSchemas -An optional path to enable schemas in the Lakehouse - -.EXAMPLE - Add-FabricLakehouse -WorkspaceId "workspace-12345" -LakehouseName "New Lakehouse" -LakehouseEnableSchemas $true - - .NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch - -#> - -function New-FabricLakehouse { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$LakehouseName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$LakehouseDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [bool]$LakehouseEnableSchemas = $false - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $LakehouseName - } - - if ($LakehouseDescription) { - $body.description = $LakehouseDescription - } - - if ($true -eq $LakehouseEnableSchemas) { - $body.creationPayload = @{ - enableSchemas = $LakehouseEnableSchemas - } - } - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Lakehouse '$LakehouseName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Lakehouse '$LakehouseName' creation accepted. Provisioning in progress!" -Level Info - - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create Lakehouse. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/Lakehouse/New-FabricLakehouse.ps1 b/source/Public/Lakehouse/New-FabricLakehouse.ps1 index 37e622fa..7f594cb4 100644 --- a/source/Public/Lakehouse/New-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/New-FabricLakehouse.ps1 @@ -1,113 +1,136 @@ -function New-FabricLakehouse { -#Requires -Version 7.1 - <# .SYNOPSIS - Creates a new Fabric Lakehouse +Creates a new Lakehouse in a specified Microsoft Fabric workspace. .DESCRIPTION - Creates a new Fabric Lakehouse +This function sends a POST request to the Microsoft Fabric API to create a new Lakehouse +in the specified workspace. It supports optional parameters for Lakehouse description +and path definitions for the Lakehouse content. -.PARAMETER WorkspaceID - Id of the Fabric Workspace for which the Lakehouse should be created. The value for WorkspaceID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the Lakehouse will be created. .PARAMETER LakehouseName - The name of the Lakehouse to create. +The name of the Lakehouse to be created. .PARAMETER LakehouseDescription - The description of the Lakehouse to create. +An optional description for the Lakehouse. + +.PARAMETER LakehouseEnableSchemas +An optional path to enable schemas in the Lakehouse .EXAMPLE - New-FabricLakehouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -LakehouseName 'MyLakehouse' ` - -LakehouseSchemaEnabled $true ` - -LakehouseDescription 'This is my Lakehouse' - - This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my Lakehouse'. - - .EXAMPLE - New-FabricLakehouse ` - -WorkspaceID '12345678-1234-1234-1234-123456789012' ` - -LakehouseName 'MyLakehouse' ` - -LakehouseSchemaEnabled $true ` - -LakehouseDescription 'This is my Lakehouse' - -Verbose - - This example will create a new Lakehouse with the name 'MyLakehouse' and the description 'This is my Lakehouse'. - It will also give you verbose output which is useful for debugging. - -.NOTES - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/lakehouse/items/create-lakehouse?tabs=HTTP + Add-FabricLakehouse -WorkspaceId "workspace-12345" -LakehouseName "New Lakehouse" -LakehouseEnableSchemas $true + + .NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch + #> -[CmdletBinding(SupportsShouldProcess)] +function New-FabricLakehouse { + [CmdletBinding(SupportsShouldProcess)] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [string]$WorkspaceID, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] [string]$LakehouseName, - [Parameter(Mandatory=$true)] - [Alias("LakehouseSchemaEnabled")] - [bool]$SchemaEnabled, + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$LakehouseDescription, - [ValidateLength(0, 256)] - [Alias("Description")] - [string]$LakehouseDescription + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [bool]$LakehouseEnableSchemas = $false ) -begin { - Confirm-FabricAuthToken | Out-Null - - #create payload - $body = [ordered]@{ - 'displayName' = $LakehouseName - 'description' = $LakehouseDescription - } + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug - #add enableSchema element if $LakehouseSchemaEnabled is true - if ($LakehouseSchemaEnabled) { - $body['creationPayload'] = @{ - 'enableSchemas' = $LakehouseSchemaEnabled - } - } + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - #format payload - $body = $body | ConvertTo-Json -Depth 1 + # Step 3: Construct the request body + $body = @{ + displayName = $LakehouseName + } - # Create Eventhouse API URL - $lakehouseApiUrl = "$($FabricSession.BaseApiUrl)/workspaces/$WorkspaceId/lakehouses" -} + if ($LakehouseDescription) { + $body.description = $LakehouseDescription + } -process { - - Write-Verbose "Calling Lakehouse API" - Write-Verbose "----------------------" - Write-Verbose "Sending the following values to the Lakehouse API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $lakehouseApiUrl" - Write-Verbose "Body of request: $bodyJson" - Write-Verbose "ContentType: application/json" - if($PSCmdlet.ShouldProcess($LakehouseName)) { - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $lakehouseApiUrl ` - -Body ($body) ` - -ContentType "application/json" + if ($true -eq $LakehouseEnableSchemas) { + $body.creationPayload = @{ + enableSchemas = $LakehouseEnableSchemas + } + } + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug - $response + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Lakehouse '$LakehouseName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Lakehouse '$LakehouseName' creation accepted. Provisioning in progress!" -Level Info + + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult + } + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } + } + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." + } + } + } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create Lakehouse. Error: $errorDetails" -Level Error } -} - -end {} - } From af5ea78f883714922dbc6ce1950f903009f27ce6 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 09:47:57 +0100 Subject: [PATCH 36/76] Add PowerShell functions for managing Fabric tenant settings and workspaces - Implement Get-FabricTenantSetting to retrieve tenant settings from the Fabric environment. - Add Get-FabricWorkspaceTenantSettingOverrides to fetch tenant setting overrides for all workspaces. - Create Revoke-FabricCapacityTenantSettingOverrides to remove tenant setting overrides from specific capacities. - Develop Update-FabricCapacityTenantSettingOverrides for updating tenant setting overrides for specified capacities. - Introduce Update-FabricTenantSetting for updating tenant settings with various parameters. - Add Add-FabricWorkspaceIdentity to provision identities for Fabric workspaces. - Implement Assign-FabricWorkspaceCapacity to assign workspaces to specific capacities. - Create Remove-FabricWorkspaceIdentity to deprovision identities from workspaces. - Develop Remove-FabricWorkspaceRoleAssignment to delete role assignments from workspaces. - Introduce Unassign-FabricWorkspaceCapacity to unassign workspaces from their capacities. - Implement Update-FabricWorkspace for updating workspace properties. - Create Update-FabricWorkspaceRoleAssignment to update role assignments for principals in workspaces. --- ...t-FabricCapacityTenantSettingOverrides.ps1 | 0 ...Get-FabricDomainTenantSettingOverrides.ps1 | 0 .../Get-FabricTenantSetting.ps1 | 0 ...-FabricWorkspaceTenantSettingOverrides.ps1 | 0 ...e-FabricCapacityTenantSettingOverrides.ps1 | 0 ...e-FabricCapacityTenantSettingOverrides.ps1 | 0 .../Update-FabricTenantSetting.ps1 | 0 .../Get-FabricLongRunningOperation copy.ps1 | 88 -------- ...-FabricLongRunningOperationResult copy.ps1 | 67 ------ .../Utils/Set-FabricApiHeaders copy.ps1 | 97 --------- source/Public/Utils/Set-FabricApiHeaders.ps1 | 49 +++-- .../Add-FabricWorkspaceRoleAssignment.ps1 | 112 ----------- .../Workspace copy/Get-FabricWorkspace.ps1 | 149 -------------- .../Get-FabricWorkspaceRoleAssignment.ps1 | 153 -------------- .../Workspace copy/New-FabricWorkspace.ps1 | 122 ----------- .../Workspace copy/Remove-FabricWorkspace.ps1 | 68 ------- .../Add-FabricWorkspaceIdentity.ps1 | 0 .../Add-FabricWorkspaceRoleAssignment.ps1 | 146 ++++++++------ .../Assign-FabricWorkspaceCapacity.ps1 | 0 .../Public/Workspace/Get-FabricWorkspace.ps1 | 158 ++++++++++++--- .../Public/Workspace/Get-FabricWorkspace2.ps1 | 190 ------------------ .../Get-FabricWorkspaceRoleAssignment.ps1 | 173 ++++++++++++---- .../Public/Workspace/New-FabricWorkspace.ps1 | 152 +++++++++----- .../Public/Workspace/New-FabricWorkspace2.ps1 | 89 -------- .../Register-FabricWorkspaceToCapacity.ps1 | 15 +- .../Workspace/Remove-FabricWorkspace.ps1 | 69 +++++-- .../Remove-FabricWorkspaceIdentity.ps1 | 12 +- .../Remove-FabricWorkspaceRoleAssignment.ps1 | 6 +- .../Unassign-FabricWorkspaceCapacity.ps1 | 4 +- .../Update-FabricWorkspace.ps1 | 2 +- .../Update-FabricWorkspaceRoleAssignment.ps1 | 8 +- 31 files changed, 554 insertions(+), 1375 deletions(-) rename source/Public/{Tenant copy => Tenant}/Get-FabricCapacityTenantSettingOverrides.ps1 (100%) rename source/Public/{Tenant copy => Tenant}/Get-FabricDomainTenantSettingOverrides.ps1 (100%) rename source/Public/{Tenant copy => Tenant}/Get-FabricTenantSetting.ps1 (100%) rename source/Public/{Tenant copy => Tenant}/Get-FabricWorkspaceTenantSettingOverrides.ps1 (100%) rename source/Public/{Tenant copy => Tenant}/Revoke-FabricCapacityTenantSettingOverrides.ps1 (100%) rename source/Public/{Tenant copy => Tenant}/Update-FabricCapacityTenantSettingOverrides.ps1 (100%) rename source/Public/{Tenant copy => Tenant}/Update-FabricTenantSetting.ps1 (100%) delete mode 100644 source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 delete mode 100644 source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 delete mode 100644 source/Public/Utils/Set-FabricApiHeaders copy.ps1 delete mode 100644 source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 source/Public/Workspace copy/Get-FabricWorkspace.ps1 delete mode 100644 source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 delete mode 100644 source/Public/Workspace copy/New-FabricWorkspace.ps1 delete mode 100644 source/Public/Workspace copy/Remove-FabricWorkspace.ps1 rename source/Public/{Workspace copy => Workspace}/Add-FabricWorkspaceIdentity.ps1 (100%) rename source/Public/{Workspace copy => Workspace}/Assign-FabricWorkspaceCapacity.ps1 (100%) delete mode 100644 source/Public/Workspace/Get-FabricWorkspace2.ps1 delete mode 100644 source/Public/Workspace/New-FabricWorkspace2.ps1 rename source/Public/{Workspace copy => Workspace}/Remove-FabricWorkspaceIdentity.ps1 (97%) rename source/Public/{Workspace copy => Workspace}/Remove-FabricWorkspaceRoleAssignment.ps1 (97%) rename source/Public/{Workspace copy => Workspace}/Unassign-FabricWorkspaceCapacity.ps1 (95%) rename source/Public/{Workspace copy => Workspace}/Update-FabricWorkspace.ps1 (98%) rename source/Public/{Workspace copy => Workspace}/Update-FabricWorkspaceRoleAssignment.ps1 (98%) diff --git a/source/Public/Tenant copy/Get-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 similarity index 100% rename from source/Public/Tenant copy/Get-FabricCapacityTenantSettingOverrides.ps1 rename to source/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 diff --git a/source/Public/Tenant copy/Get-FabricDomainTenantSettingOverrides.ps1 b/source/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 similarity index 100% rename from source/Public/Tenant copy/Get-FabricDomainTenantSettingOverrides.ps1 rename to source/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 diff --git a/source/Public/Tenant copy/Get-FabricTenantSetting.ps1 b/source/Public/Tenant/Get-FabricTenantSetting.ps1 similarity index 100% rename from source/Public/Tenant copy/Get-FabricTenantSetting.ps1 rename to source/Public/Tenant/Get-FabricTenantSetting.ps1 diff --git a/source/Public/Tenant copy/Get-FabricWorkspaceTenantSettingOverrides.ps1 b/source/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 similarity index 100% rename from source/Public/Tenant copy/Get-FabricWorkspaceTenantSettingOverrides.ps1 rename to source/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 diff --git a/source/Public/Tenant copy/Revoke-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 similarity index 100% rename from source/Public/Tenant copy/Revoke-FabricCapacityTenantSettingOverrides.ps1 rename to source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 diff --git a/source/Public/Tenant copy/Update-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 similarity index 100% rename from source/Public/Tenant copy/Update-FabricCapacityTenantSettingOverrides.ps1 rename to source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 diff --git a/source/Public/Tenant copy/Update-FabricTenantSetting.ps1 b/source/Public/Tenant/Update-FabricTenantSetting.ps1 similarity index 100% rename from source/Public/Tenant copy/Update-FabricTenantSetting.ps1 rename to source/Public/Tenant/Update-FabricTenantSetting.ps1 diff --git a/source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 b/source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 deleted file mode 100644 index 4fa2cc07..00000000 --- a/source/Public/Utils/Get-FabricLongRunningOperation copy.ps1 +++ /dev/null @@ -1,88 +0,0 @@ -<# -.SYNOPSIS -Monitors the status of a long-running operation in Microsoft Fabric. - -.DESCRIPTION -The Get-FabricLongRunningOperation function queries the Microsoft Fabric API to check the status of a -long-running operation. It periodically polls the operation until it reaches a terminal state (Succeeded or Failed). - -.PARAMETER operationId -The unique identifier of the long-running operation to be monitored. - -.PARAMETER retryAfter -The interval (in seconds) to wait between polling the operation status. The default is 5 seconds. - -.EXAMPLE -Get-FabricLongRunningOperation -operationId "12345-abcd-67890-efgh" -retryAfter 10 - -This command polls the status of the operation with the given operationId every 10 seconds until it completes. - -.NOTES -- Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - -.AUTHOR -Tiago Balabuch - -#> -function Get-FabricLongRunningOperation { - param ( - [Parameter(Mandatory = $false)] - [string]$operationId, - - [Parameter(Mandatory = $false)] - [string]$location, - - [Parameter(Mandatory = $false)] - [int]$retryAfter = 5 - ) - - # Step 1: Construct the API URL - if ($location) { - # Use the Location header to define the operationUrl - $apiEndpointUrl = $location - } - else { - $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}" -f $operationId - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - try { - do { - - # Step 2: Wait before the next request - if ($retryAfter) { - Start-Sleep -Seconds $retryAfter - } - else { - Start-Sleep -Seconds 5 # Default retry interval if no Retry-After header - } - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -ResponseHeadersVariable responseHeader ` - -StatusCodeVariable statusCode - - # Step 3: Parse the response - $jsonOperation = $response | ConvertTo-Json - $operation = $jsonOperation | ConvertFrom-Json - - # Log status for debugging - Write-Message -Message "Operation Status: $($operation.status)" -Level Debug - - - } while ($operation.status -notin @("Succeeded", "Completed", "Failed")) - - # Step 5: Return the operation result - return $operation - } - catch { - # Step 6: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while checking the operation: $errorDetails" -Level Error - throw - } -} \ No newline at end of file diff --git a/source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 b/source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 deleted file mode 100644 index 00000c6c..00000000 --- a/source/Public/Utils/Get-FabricLongRunningOperationResult copy.ps1 +++ /dev/null @@ -1,67 +0,0 @@ -<# -.SYNOPSIS -Retrieves the result of a completed long-running operation from the Microsoft Fabric API. - -.DESCRIPTION -The Get-FabricLongRunningOperationResult function queries the Microsoft Fabric API to fetch the result -of a specific long-running operation. This is typically used after confirming the operation has completed successfully. - -.PARAMETER operationId -The unique identifier of the completed long-running operation whose result you want to retrieve. - -.EXAMPLE -Get-FabricLongRunningOperationResult -operationId "12345-abcd-67890-efgh" - -This command fetches the result of the operation with the specified operationId. - -.NOTES -- Ensure the Fabric API headers (e.g., authorization tokens) are defined in $FabricConfig.FabricHeaders. -- This function does not handle polling. Ensure the operation is in a terminal state before calling this function. - -.AUTHOR -Tiago Balabuch - -#> -function Get-FabricLongRunningOperationResult { - param ( - [Parameter(Mandatory = $true)] - [string]$operationId - ) - - # Step 1: Construct the API URL - $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}/result" -f $operationId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - try { - # Step 2: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - - # Step 3: Return the result - Write-Message -Message "Result response code: $statusCode" -Level Debug - Write-Message -Message "Result return: $response" -Level Debug - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Debug - Write-Message -Message "Error: $($response.message)" -Level Debug - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Debug - Write-Message "Error Code: $($response.errorCode)" -Level Debug - } - - return $response - } - catch { - # Step 3: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "An error occurred while returning the operation result: $errorDetails" -Level Error - throw - } -} \ No newline at end of file diff --git a/source/Public/Utils/Set-FabricApiHeaders copy.ps1 b/source/Public/Utils/Set-FabricApiHeaders copy.ps1 deleted file mode 100644 index a25a3f69..00000000 --- a/source/Public/Utils/Set-FabricApiHeaders copy.ps1 +++ /dev/null @@ -1,97 +0,0 @@ -function Set-FabricApiHeaders { -<# -.SYNOPSIS - Configures the Fabric API headers for authentication using Azure credentials. -.DESCRIPTION - This function sets the Fabric API headers by logging into Azure with the specified tenant ID and retrieving an access token. - It updates the global configuration with the token and tenant ID for subsequent API requests. - -.PARAMETER TenantId - The Azure tenant ID for which the access token is requested. -.PARAMETER AppId - The Azure app ID for which the service principal access token is requested. -.PARAMETER AppSecret - The Azure App secret for which the service principal access token is requested. -.EXAMPLE - Set-FabricApiHeaders -TenantId "your-tenant-id" - Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. -.EXAMPLE - $tenantId = "999999999-99999-99999-9999-999999999999" - $appId = "888888888-88888-88888-8888-888888888888" -#> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$TenantId, - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$AppId, - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [System.Security.SecureString]$AppSecret - ) - - try { - # Step 1: Connect to the Azure account - Write-Message -Message "Logging in to Azure tenant: $TenantId" -Level Info - - # Step 2: Performing validation checks on the parameters passed to a function or script. - # Checks if 'AppId' is provided without 'AppSecret' and vice versa. - if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { - Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error - throw "AppSecret is required when using AppId." - } - if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { - Write-Message -Message "AppId is required when using AppSecret." -Level Error - throw "AppId is required when using AppId." - } - # Step 3: Connect to the Azure account - # Using AppId and AppSecret - if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { - - Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug - Write-Message -Message "Logging in using the AppId: $AppId" -Level Info - $psCredential = [pscredential]::new($AppId, $AppSecret) - Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId - - } - # Using the current user - else { - - Write-Message -Message "Logging in using the current user" -Level Debug - Write-Message -Message "Logging in using the current user" -Level Info - Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null - } - - ## Step 4: Retrieve the access token for the Fabric API - Write-Message -Message "Retrieve the access token for the Fabric API: $TenantId" -Level Debug - $fabricToken = Get-AzAccessToken -AsSecureString -ResourceUrl $FabricConfig.ResourceUrl -ErrorAction Stop -WarningAction SilentlyContinue - - ## Step 5: Extract the plain token from the secure string - Write-Message -Message "Extract the plain token from the secure string" -Level Debug - $plainToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( - [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($fabricToken.Token) - ) - - ## Step 6: Set the headers in the global configuration - Write-Message -Message "Set the headers in the global configuration" -Level Debug - $FabricConfig.FabricHeaders = @{ - 'Content-Type' = 'application/json' - 'Authorization' = "Bearer $plainToken" - } - - ## Step 7: Update token metadata in the global configuration - Write-Message -Message "Update token metadata in the global configuration" -Level Debug - $FabricConfig.TokenExpiresOn = $fabricToken.ExpiresOn - $FabricConfig.TenantIdGlobal = $TenantId - - Write-Message -Message "Fabric token successfully configured." -Level Info - } - catch { - # Step 8: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to set Fabric token: $errorDetails" -Level Error - throw "Unable to configure Fabric token. Ensure tenant and API configurations are correct." - } -} diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 index a25a3f69..e1c7a43d 100644 --- a/source/Public/Utils/Set-FabricApiHeaders.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -1,24 +1,43 @@ -function Set-FabricApiHeaders { <# .SYNOPSIS - Configures the Fabric API headers for authentication using Azure credentials. +Sets the Fabric API headers with a valid token for the specified Azure tenant. + .DESCRIPTION - This function sets the Fabric API headers by logging into Azure with the specified tenant ID and retrieving an access token. - It updates the global configuration with the token and tenant ID for subsequent API requests. +The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. +It also updates the token expiration time and global tenant ID. .PARAMETER TenantId - The Azure tenant ID for which the access token is requested. +The Azure tenant ID for which the access token is requested. + .PARAMETER AppId - The Azure app ID for which the service principal access token is requested. +The Azure app ID for which the service principal access token is requested. + .PARAMETER AppSecret - The Azure App secret for which the service principal access token is requested. +The Azure App secret for which the service principal access token is requested. + .EXAMPLE - Set-FabricApiHeaders -TenantId "your-tenant-id" - Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. +Set-FabricApiHeaders -TenantId "your-tenant-id" + +Logs in to Azure with the specified tenant ID, retrieves an access token for the current user, and configures the Fabric headers. + .EXAMPLE - $tenantId = "999999999-99999-99999-9999-999999999999" - $appId = "888888888-88888-88888-8888-888888888888" +$tenantId = "999999999-99999-99999-9999-999999999999" +$appId = "888888888-88888-88888-8888-888888888888" +$appSecret = "your-app-secret" +$secureAppSecret = $appSecret | ConvertTo-SecureString -AsPlainText -Force + +Set-FabricApiHeader -TenantId $tenantId -AppId $appId -AppSecret $secureAppSecret +Logs in to Azure with the specified tenant ID, retrieves an access token for the service principal, and configures the Fabric headers. + +.NOTES +- Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). +- Relies on a global `$FabricConfig` object for storing headers and token metadata. + +.AUTHOR +Tiago Balabuch #> + +function Set-FabricApiHeaders { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -41,7 +60,7 @@ function Set-FabricApiHeaders { if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error throw "AppSecret is required when using AppId." - } + } if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { Write-Message -Message "AppId is required when using AppSecret." -Level Error throw "AppId is required when using AppId." @@ -49,16 +68,16 @@ function Set-FabricApiHeaders { # Step 3: Connect to the Azure account # Using AppId and AppSecret if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { - + Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug Write-Message -Message "Logging in using the AppId: $AppId" -Level Info $psCredential = [pscredential]::new($AppId, $AppSecret) Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId - } + } # Using the current user else { - + Write-Message -Message "Logging in using the current user" -Level Debug Write-Message -Message "Logging in using the current user" -Level Info Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null diff --git a/source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index 4d29053b..00000000 --- a/source/Public/Workspace copy/Add-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -<# -.SYNOPSIS -Assigns a role to a principal for a specified Fabric workspace. - -.DESCRIPTION -The `Add-FabricWorkspaceRoleAssignments` function assigns a role (e.g., Admin, Contributor, Member, Viewer) to a principal (e.g., User, Group, ServicePrincipal) in a Fabric workspace by making a POST request to the API. - -.PARAMETER WorkspaceId -The unique identifier of the workspace. - -.PARAMETER PrincipalId -The unique identifier of the principal (User, Group, etc.) to assign the role. - -.PARAMETER PrincipalType -The type of the principal. Allowed values: Group, ServicePrincipal, ServicePrincipalProfile, User. - -.PARAMETER WorkspaceRole -The role to assign to the principal. Allowed values: Admin, Contributor, Member, Viewer. - -.EXAMPLE -Add-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -PrincipalId "principal123" -PrincipalType "User" -WorkspaceRole "Admin" - -Assigns the Admin role to the user with ID "principal123" in the workspace "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Add-FabricWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$PrincipalId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Group', 'ServicePrincipal', 'ServicePrincipalProfile', 'User')] - [string]$PrincipalType, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Admin', 'Contributor', 'Member', 'Viewer')] - [string]$WorkspaceRole - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - principal = @{ - id = $PrincipalId - type = $PrincipalType - } - role = $WorkspaceRole - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 4 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code - if ($statusCode -ne 201) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 6: Handle empty response - if (-not $response) { - Write-Message -Message "No data returned from the API." -Level Warning - return $null - } - - Write-Message -Message "Role '$WorkspaceRole' assigned to principal '$PrincipalId' successfully in workspace '$WorkspaceId'." -Level Info - return $response - - } - catch { - # Step 7: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to assign role. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/Workspace copy/Get-FabricWorkspace.ps1 b/source/Public/Workspace copy/Get-FabricWorkspace.ps1 deleted file mode 100644 index 77797006..00000000 --- a/source/Public/Workspace copy/Get-FabricWorkspace.ps1 +++ /dev/null @@ -1,149 +0,0 @@ -<# -.SYNOPSIS -Retrieves details of a Microsoft Fabric workspace by its ID or name. - -.DESCRIPTION -The `Get-FabricWorkspace` function fetches workspace details from the Fabric API. It supports filtering by WorkspaceId or WorkspaceName. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to retrieve. - -.PARAMETER WorkspaceName -The display name of the workspace to retrieve. - -.EXAMPLE -Get-FabricWorkspace -WorkspaceId "workspace123" - -Fetches details of the workspace with ID "workspace123". - -.EXAMPLE -Get-FabricWorkspace -WorkspaceName "MyWorkspace" - -Fetches details of the workspace with the name "MyWorkspace". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. -- Returns the matching workspace details or all workspaces if no filter is provided. - -Author: Tiago Balabuch -#> - -function Get-FabricWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WorkspaceName - ) - - try { - # Step 1: Handle ambiguous input - if ($WorkspaceId -and $WorkspaceName) { - Write-Message -Message "Both 'WorkspaceId' and 'WorkspaceName' were provided. Please specify only one." -Level Error - return $null - } - - # Step 2: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $workspaces = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $workspaces += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - - # Step 8: Filter results based on provided parameters - $workspace = if ($WorkspaceId) { - $workspaces | Where-Object { $_.Id -eq $WorkspaceId } - } - elseif ($WorkspaceName) { - $workspaces | Where-Object { $_.DisplayName -eq $WorkspaceName } - } - else { - # Return all workspaces if no filter is provided - Write-Message -Message "No filter provided. Returning all workspaces." -Level Debug - $workspaces - } - - # Step 9: Handle results - if ($workspace) { - Write-Message -Message "Workspace found matching the specified criteria." -Level Debug - return $workspace - } - else { - Write-Message -Message "No workspace found matching the provided criteria." -Level Warning - return $null - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve workspace. Error: $errorDetails" -Level Error - } -} \ No newline at end of file diff --git a/source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 deleted file mode 100644 index 02b2f585..00000000 --- a/source/Public/Workspace copy/Get-FabricWorkspaceRoleAssignment.ps1 +++ /dev/null @@ -1,153 +0,0 @@ -<# -.SYNOPSIS -Retrieves role assignments for a specified Fabric workspace. - -.DESCRIPTION -The `Get-FabricWorkspaceRoleAssignments` function fetches the role assignments associated with a Fabric workspace by making a GET request to the API. If `WorkspaceRoleAssignmentId` is provided, it retrieves the specific role assignment. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to fetch role assignments for. - -.PARAMETER WorkspaceRoleAssignmentId -(Optional) The unique identifier of a specific role assignment to retrieve. - -.EXAMPLE -Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" - -Fetches all role assignments for the workspace with the ID "workspace123". - -.EXAMPLE -Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "role123" - -Fetches the role assignment with the ID "role123" for the workspace "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function Get-FabricWorkspaceRoleAssignment { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceRoleAssignmentId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 3: Initialize variables - $continuationToken = $null - $workspaceRoles = @() - - if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { - Add-Type -AssemblyName System.Web - } - - # Step 4: Loop to retrieve all capacities with continuation token - Write-Message -Message "Loop started to get continuation token" -Level Debug - $baseApiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId - - do { - # Step 5: Construct the API URL - $apiEndpointUrl = $baseApiEndpointUrl - - if ($null -ne $continuationToken) { - # URL-encode the continuation token - $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) - $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken - } - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 7: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - # Step 8: Add data to the list - if ($null -ne $response) { - Write-Message -Message "Adding data to the list" -Level Debug - $workspaceRoles += $response.value - - # Update the continuation token if present - if ($response.PSObject.Properties.Match("continuationToken")) { - Write-Message -Message "Updating the continuation token" -Level Debug - $continuationToken = $response.continuationToken - Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { - Write-Message -Message "Updating the continuation token to null" -Level Debug - $continuationToken = $null - } - } - else { - Write-Message -Message "No data received from the API." -Level Warning - break - } - } while ($null -ne $continuationToken) - Write-Message -Message "Loop finished and all data added to the list" -Level Debug - # Step 8: Filter results based on provided parameters - $roleAssignments = if ($WorkspaceRoleAssignmentId) { - $workspaceRoles | Where-Object { $_.Id -eq $WorkspaceRoleAssignmentId } - } - else { - $workspaceRoles - } - - # Step 9: Handle results - if ($roleAssignments) { - Write-Message -Message "Found $($roleAssignments.Count) role assignments for WorkspaceId '$WorkspaceId'." -Level Debug - # Transform data into custom objects - $results = foreach ($obj in $roleAssignments) { - [PSCustomObject]@{ - ID = $obj.id - PrincipalId = $obj.principal.id - DisplayName = $obj.principal.displayName - Type = $obj.principal.type - UserPrincipalName = $obj.principal.userDetails.userPrincipalName - aadAppId = $obj.principal.servicePrincipalDetails.aadAppId - Role = $obj.role - } - } - return $results - } - else { - if ($WorkspaceRoleAssignmentId) { - Write-Message -Message "No role assignment found with ID '$WorkspaceRoleAssignmentId' for WorkspaceId '$WorkspaceId'." -Level Warning - } - else { - Write-Message -Message "No role assignments found for WorkspaceId '$WorkspaceId'." -Level Warning - } - return @() - } - } - catch { - # Step 10: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error - } -} diff --git a/source/Public/Workspace copy/New-FabricWorkspace.ps1 b/source/Public/Workspace copy/New-FabricWorkspace.ps1 deleted file mode 100644 index 4da24893..00000000 --- a/source/Public/Workspace copy/New-FabricWorkspace.ps1 +++ /dev/null @@ -1,122 +0,0 @@ -<# -.SYNOPSIS -Creates a new Fabric workspace with the specified display name. - -.DESCRIPTION -The `Add-FabricWorkspace` function creates a new workspace in the Fabric platform by sending a POST request to the API. It validates the display name and handles both success and error responses. - -.PARAMETER WorkspaceName -The display name of the workspace to be created. Must only contain alphanumeric characters, spaces, and underscores. - -.EXAMPLE -Add-FabricWorkspace -WorkspaceName "NewWorkspace" - -Creates a workspace named "NewWorkspace". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> - -function New-FabricWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] - [string]$WorkspaceName, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceDescription, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$CapacityId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $WorkspaceName - } - - if ($WorkspaceDescription) { - $body.description = $WorkspaceDescription - } - - if ($CapacityId) { - $body.capacityId = $CapacityId - } - - # Convert the body to JSON - $bodyJson = $body | ConvertTo-Json -Depth 2 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Workspace '$WorkspaceName' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "Workspace '$WorkspaceName' creation accepted. Provisioning in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - - return $operationResult - } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus - } - } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create workspace. Error: $errorDetails" -Level Error - - } -} diff --git a/source/Public/Workspace copy/Remove-FabricWorkspace.ps1 b/source/Public/Workspace copy/Remove-FabricWorkspace.ps1 deleted file mode 100644 index d65a2be7..00000000 --- a/source/Public/Workspace copy/Remove-FabricWorkspace.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -<# -.SYNOPSIS -Deletes an existing Fabric workspace by its workspace ID. - -.DESCRIPTION -The `Remove-FabricWorkspace` function deletes a workspace in the Fabric platform by sending a DELETE request to the API. It validates the workspace ID and handles both success and error responses. - -.PARAMETER WorkspaceId -The unique identifier of the workspace to be deleted. - -.EXAMPLE -Remove-FabricWorkspace -WorkspaceId "workspace123" - -Deletes the workspace with the ID "workspace123". - -.NOTES -- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. -- Calls `Test-TokenExpired` to ensure token validity before making the API request. - -Author: Tiago Balabuch -#> -function Remove-FabricWorkspace { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId - ) - - try { - # Step 1: Ensure token validity - Write-Message -Message "Validating token..." -Level Debug - Test-TokenExpired - Write-Message -Message "Token validation completed." -Level Debug - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 4: Validate the response code - if ($statusCode -ne 200) { - Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error - Write-Message -Message "Error: $($response.message)" -Level Error - Write-Message "Error Code: $($response.errorCode)" -Level Error - return $null - } - - Write-Message -Message "Workspace '$WorkspaceId' deleted successfully!" -Level Info - return $null - - } - catch { - # Step 5: Capture and log error details - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error - return $null - } -} diff --git a/source/Public/Workspace copy/Add-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 similarity index 100% rename from source/Public/Workspace copy/Add-FabricWorkspaceIdentity.ps1 rename to source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 diff --git a/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 index f1de0d1c..4d29053b 100644 --- a/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 @@ -1,94 +1,112 @@ -function Add-FabricWorkspaceRoleAssignment { -#Requires -Version 7.1 - <# .SYNOPSIS - Adds a role assignment to a user in a workspace. +Assigns a role to a principal for a specified Fabric workspace. .DESCRIPTION - Adds a role assignment to a user in a workspace. The User is identified by the principalId and the role is - identified by the Role parameter. The Workspace is identified by the WorkspaceId. +The `Add-FabricWorkspaceRoleAssignments` function assigns a role (e.g., Admin, Contributor, Member, Viewer) to a principal (e.g., User, Group, ServicePrincipal) in a Fabric workspace by making a POST request to the API. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the role assignment should be added. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. +The unique identifier of the workspace. .PARAMETER PrincipalId - Id of the principal for which the role assignment should be added. The value for PrincipalId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. This parameter is mandatory. At the - moment only principal type 'User' is supported. +The unique identifier of the principal (User, Group, etc.) to assign the role. -.PARAMETER Role - The role to assign to the principal. The value for Role is a string. An example of a string is 'Admin'. - The values that can be used are 'Admin', 'Contributor', 'Member' and 'Viewer'. +.PARAMETER PrincipalType +The type of the principal. Allowed values: Group, ServicePrincipal, ServicePrincipalProfile, User. +.PARAMETER WorkspaceRole +The role to assign to the principal. Allowed values: Admin, Contributor, Member, Viewer. .EXAMPLE - Add-RtiWorkspaceRoleAssignment ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -PrincipalId '12345678-1234-1234-1234-123456789012' ` - -Role 'Admin' - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/add-workspace-role-assignment?tabs=HTTP +Add-FabricWorkspaceRoleAssignment -WorkspaceId "workspace123" -PrincipalId "principal123" -PrincipalType "User" -WorkspaceRole "Admin" +Assigns the Admin role to the user with ID "principal123" in the workspace "workspace123". .NOTES - TODO: Add functionallity to add role assignments to groups. - TODO: Add functionallity to add a user by SPN. +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch #> -[CmdletBinding(SupportsShouldProcess)] +function Add-FabricWorkspaceRoleAssignment { + [CmdletBinding()] param ( - - [Alias("Id")] - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - [Parameter(Mandatory=$true)] - [string]$principalId, - - [ValidateSet("Admin", "Contributor", "Member" , "Viewer")] - [Parameter(Mandatory=$true)] - [string]$Role - ) + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$PrincipalId, -begin { + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Group', 'ServicePrincipal', 'ServicePrincipalProfile', 'User')] + [string]$PrincipalType, - # Check if session is established - if not throw error - if ($null -eq $FabricSession.headerParams) { - throw "No session established to Fabric Real-Time Intelligence. Please run Connect-FabricAccount" - } + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Admin', 'Contributor', 'Member', 'Viewer')] + [string]$WorkspaceRole + ) - # Create body of request - $body = @{ - 'principal' = @{ - 'id' = $principalId - 'type' = "User" + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + principal = @{ + id = $PrincipalId + type = $PrincipalType + } + role = $WorkspaceRole } - 'role' = $Role - } | ConvertTo-Json ` - -Depth 1 - - # Create Workspace API URL - $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces/$WorkspaceId/roleassignments" -} -process { + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 4 + Write-Message -Message "Request Body: $bodyJson" -Level Debug - if($PSCmdlet.ShouldProcess($WorkspaceName)) { - # Call Workspace API + # Step 4: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $WorkspaceApiUrl ` - -Body ($body) ` - -ContentType "application/json" + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Validate the response code + if ($statusCode -ne 201) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 6: Handle empty response + if (-not $response) { + Write-Message -Message "No data returned from the API." -Level Warning + return $null + } - $response + Write-Message -Message "Role '$WorkspaceRole' assigned to principal '$PrincipalId' successfully in workspace '$WorkspaceId'." -Level Info + return $response + + } + catch { + # Step 7: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to assign role. Error: $errorDetails" -Level Error } } - -end {} - -} \ No newline at end of file diff --git a/source/Public/Workspace copy/Assign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 similarity index 100% rename from source/Public/Workspace copy/Assign-FabricWorkspaceCapacity.ps1 rename to source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 diff --git a/source/Public/Workspace/Get-FabricWorkspace.ps1 b/source/Public/Workspace/Get-FabricWorkspace.ps1 index 753e808b..77797006 100644 --- a/source/Public/Workspace/Get-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspace.ps1 @@ -1,45 +1,149 @@ <# .SYNOPSIS - Retrieves all Fabric workspaces. +Retrieves details of a Microsoft Fabric workspace by its ID or name. .DESCRIPTION - The Get-FabricWorkspace function retrieves all Fabric workspaces. It invokes the Fabric API to get the workspaces and outputs the result. +The `Get-FabricWorkspace` function fetches workspace details from the Fabric API. It supports filtering by WorkspaceId or WorkspaceName. + +.PARAMETER WorkspaceId +The unique identifier of the workspace to retrieve. + +.PARAMETER WorkspaceName +The display name of the workspace to retrieve. .EXAMPLE - Get-FabricWorkspace +Get-FabricWorkspace -WorkspaceId "workspace123" - This command retrieves all Fabric workspaces. +Fetches details of the workspace with ID "workspace123". -.INPUTS - None. You cannot pipe inputs to this function. +.EXAMPLE +Get-FabricWorkspace -WorkspaceName "MyWorkspace" -.OUTPUTS - Object. This function returns the Fabric workspaces. +Fetches details of the workspace with the name "MyWorkspace". .NOTES - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. +- Returns the matching workspace details or all workspaces if no filter is provided. + +Author: Tiago Balabuch #> -Function Get-FabricWorkspace { - [Alias("Get-FabWorkspace")] +function Get-FabricWorkspace { [CmdletBinding()] - param - ( - [Parameter(Mandatory=$false)] - [string]$workspaceId + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WorkspaceName ) - - Confirm-FabricAuthToken | Out-Null - - # Invoke the Fabric API to get the workspaces - if ($workspaceId) { - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)" -Method Get - } else { - $result = Invoke-FabricAPIRequest -Uri "workspaces" -Method Get - } + try { + # Step 1: Handle ambiguous input + if ($WorkspaceId -and $WorkspaceName) { + Write-Message -Message "Both 'WorkspaceId' and 'WorkspaceName' were provided. Please specify only one." -Level Error + return $null + } - # Output the result - return $result.value + # Step 2: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $workspaces = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $workspaces += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + + # Step 8: Filter results based on provided parameters + $workspace = if ($WorkspaceId) { + $workspaces | Where-Object { $_.Id -eq $WorkspaceId } + } + elseif ($WorkspaceName) { + $workspaces | Where-Object { $_.DisplayName -eq $WorkspaceName } + } + else { + # Return all workspaces if no filter is provided + Write-Message -Message "No filter provided. Returning all workspaces." -Level Debug + $workspaces + } + + # Step 9: Handle results + if ($workspace) { + Write-Message -Message "Workspace found matching the specified criteria." -Level Debug + return $workspace + } + else { + Write-Message -Message "No workspace found matching the provided criteria." -Level Warning + return $null + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve workspace. Error: $errorDetails" -Level Error + } } \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspace2.ps1 b/source/Public/Workspace/Get-FabricWorkspace2.ps1 deleted file mode 100644 index f6b1b484..00000000 --- a/source/Public/Workspace/Get-FabricWorkspace2.ps1 +++ /dev/null @@ -1,190 +0,0 @@ -function Get-FabricWorkspace2 { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Retrieves Fabric Workspaces - -.DESCRIPTION - Retrieves Fabric Workspaces. Without the WorkspaceName or WorkspaceID parameter, - all Workspaces are returned. If you want to retrieve a specific Workspace, you can - use the WorkspaceName, an CapacityID, a WorkspaceType, a WorkspaceState or the WorkspaceID - parameter. The WorkspaceId parameter has precedence over all other parameters because it - is most specific. - -.PARAMETER WorkspaceId - Id of the Fabric Workspace to retrieve. The value for WorkspaceId is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER WorkspaceName - The name of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. - -.PARAMETER WorkspaceCapacityId - The Id of the Capacity to retrieve. This parameter cannot be used together with WorkspaceID. - The value for WorkspaceCapacityId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER WorkspaceType - The type of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. - The value for WorkspaceType is a string. An example of a string is 'Personal'. The values that - can be used are 'Personal', 'Workspace' and 'Adminworkspace'. - -.PARAMETER WorkspaceState - The state of the Workspace to retrieve. This parameter cannot be used together with WorkspaceID. - The value for WorkspaceState is a string. An example of a string is 'active'. The values that - can be used are 'active' and 'deleted'. - -.EXAMPLE - Get-FabricWorkspace - - This example will retrieve all Workspaces. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' - - This example will retrieve the Workspace with the ID '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceName 'MyWorkspace' - - This example will retrieve the Workspace with the name 'MyWorkspace'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceCapacityId '12345678-1234-1234-1234-123456789012' - - This example will retrieve the Workspaces with the Capacity ID '12345678-1234-1234-1234-123456789012'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceType 'Personal' - - This example will retrieve the Workspaces with the type 'Personal'. - -.EXAMPLE - Get-FabricWorkspace ` - -WorkspaceState 'active' - - This example will retrieve the Workspaces with the state 'active'. - -.NOTES - - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/get-workspace?tabs=HTTP - - -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces?tabs=HTTP -#> - -[CmdletBinding()] - param ( - - [Alias("Id")] - [string]$WorkspaceId, - - [Alias("Name")] - [string]$WorkspaceName, - - [Alias("CapacityId")] - [string]$WorkspaceCapacityId, - - [ValidateSet("Personal", "Workspace", "Adminworkspace")] - [Alias("Type")] - [string]$WorkspaceType, - - [ValidateSet("active", "deleted")] - [Alias("State")] - [string]$WorkspaceState - ) - -begin { - - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "WorkspaceID has to be used alone" - if ($PSBoundParameters.ContainsKey("WorkspaceName") -and - ($PSBoundParameters.ContainsKey("WorkspaceID") ` - -or $PSBoundParameters.ContainsKey("WorkspaceCapcityId") ` - -or $PSBoundParameters.ContainsKey("WorkspaceType") ` - -or $PSBoundParameters.ContainsKey("WorkspaceState"))) { - throw "Parameters WorkspaceName, WorkspaceCapacityId, WorkspaceType or WorkspaceState and WorkspaceID cannot be used together!" - } - - # Create Workspace API URL - $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces" - - # Create URL for WebAPI Call if WorkspaceID is provided - if ($PSBoundParameters.ContainsKey("WorkspaceID")) { - $workspaceApiUrlId = $workspaceApiUrl + '/' + $WorkspaceID - } - - Write-Verbose "If there are any parameters, we need to filter the API call, the URL will be constructed here." - $workspaceApiFilter = $workspaceApiUrl - - if ($PSBoundParameters.ContainsKey("WorkspaceName")) { - $workspaceApiFilter = $workspaceApiFilter + "?name=$WorkspaceName" - } - - if ($PSBoundParameters.ContainsKey("WorkspaceCapacityId")) { - $workspaceApiFilter = $workspaceApiFilter + "?capacityId=$WorkspaceCapacityId" - } - - if ($PSBoundParameters.ContainsKey("WorkspaceType")) { - $workspaceApiFilter = $workspaceApiFilter + "?type=$WorkspaceType" - } - - if ($PSBoundParameters.ContainsKey("WorkspaceState")) { - $workspaceApiFilter = $workspaceApiFilter + "?state=$WorkspaceState" - } - Write-Verbose "Workspace API URL: $workspaceApiFilter" - -} - -process { - - Write-Verbose "Providing a WorkspaceID is so specific that this will have precedence over any other parameter" - if ($PSBoundParameters.ContainsKey("WorkspaceID")) { - Write-Verbose "Calling Workspace API with WorkspaceId $WorkspaceId" - Write-Verbose "---------------------------------------------------" - Write-Verbose "Sending the following values to the Workspace API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $workspaceApiUrlId" - Write-Verbose "ContentType: application/json" - # Call Workspace API for WorkspaceID - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $workspaceApiUrlId ` - -ContentType "application/json" - - $response - } - else { - Write-Verbose "Calling Workspace API with Filter" - Write-Verbose "---------------------------------" - Write-Verbose "Sending the following values to the Workspace API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: GET" - Write-Verbose "URI: $workspaceApiFilter" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $workspaceApiFilter ` - -ContentType "application/json" - - $response.Workspaces - } - -} - -end {} - -} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 index 119a219d..02b2f585 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 @@ -1,56 +1,153 @@ -function Get-FabricWorkspaceRoleAssignment { -#Requires -Version 7.1 - <# .SYNOPSIS - Retrieves Fabric Workspace Role Assignments +Retrieves role assignments for a specified Fabric workspace. .DESCRIPTION - Retrieves Fabric Workspace Role Assignments. Without the WorkspaceName or WorkspaceID parameter, +The `Get-FabricWorkspaceRoleAssignments` function fetches the role assignments associated with a Fabric workspace by making a GET request to the API. If `WorkspaceRoleAssignmentId` is provided, it retrieves the specific role assignment. .PARAMETER WorkspaceId - Id of the Fabric Workspace for which the Role Assignments should be retrieved. - The value for WorkspaceId is a GUID. An example of a GUID is '12345678-1234-1234-1234-123456789012'. +The unique identifier of the workspace to fetch role assignments for. + +.PARAMETER WorkspaceRoleAssignmentId +(Optional) The unique identifier of a specific role assignment to retrieve. .EXAMPLE - Get-FabricWorkspaceRoleAssignment ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' +Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" - This example will retrieve all Role Assignments for the Workspace with the ID '12345678-1234-1234-1234-123456789012'. +Fetches all role assignments for the workspace with the ID "workspace123". -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/get-workspace-role-assignment?tabs=HTTP +.EXAMPLE +Get-FabricWorkspaceRoleAssignments -WorkspaceId "workspace123" -WorkspaceRoleAssignmentId "role123" -.LINK - https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/list-workspace-role-assignments?tabs=HTTP +Fetches the role assignment with the ID "role123" for the workspace "workspace123". + +.NOTES +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch #> -[CmdletBinding()] +function Get-FabricWorkspaceRoleAssignment { + [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, - [Alias("Id")] - [string]$WorkspaceId + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceRoleAssignmentId ) -begin { - Confirm-FabricAuthToken | Out-Null - - # Create Workspace API URL - $workspaceApiUrl = "$($FabricSession.BaseApiUrl)/admin/workspaces/$WorkspaceId/roleassignments" + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 3: Initialize variables + $continuationToken = $null + $workspaceRoles = @() + + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { + Add-Type -AssemblyName System.Web + } + + # Step 4: Loop to retrieve all capacities with continuation token + Write-Message -Message "Loop started to get continuation token" -Level Debug + $baseApiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId + + do { + # Step 5: Construct the API URL + $apiEndpointUrl = $baseApiEndpointUrl + + if ($null -ne $continuationToken) { + # URL-encode the continuation token + $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) + $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken + } + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 7: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + # Step 8: Add data to the list + if ($null -ne $response) { + Write-Message -Message "Adding data to the list" -Level Debug + $workspaceRoles += $response.value + + # Update the continuation token if present + if ($response.PSObject.Properties.Match("continuationToken")) { + Write-Message -Message "Updating the continuation token" -Level Debug + $continuationToken = $response.continuationToken + Write-Message -Message "Continuation token: $continuationToken" -Level Debug + } + else { + Write-Message -Message "Updating the continuation token to null" -Level Debug + $continuationToken = $null + } + } + else { + Write-Message -Message "No data received from the API." -Level Warning + break + } + } while ($null -ne $continuationToken) + Write-Message -Message "Loop finished and all data added to the list" -Level Debug + # Step 8: Filter results based on provided parameters + $roleAssignments = if ($WorkspaceRoleAssignmentId) { + $workspaceRoles | Where-Object { $_.Id -eq $WorkspaceRoleAssignmentId } + } + else { + $workspaceRoles + } + + # Step 9: Handle results + if ($roleAssignments) { + Write-Message -Message "Found $($roleAssignments.Count) role assignments for WorkspaceId '$WorkspaceId'." -Level Debug + # Transform data into custom objects + $results = foreach ($obj in $roleAssignments) { + [PSCustomObject]@{ + ID = $obj.id + PrincipalId = $obj.principal.id + DisplayName = $obj.principal.displayName + Type = $obj.principal.type + UserPrincipalName = $obj.principal.userDetails.userPrincipalName + aadAppId = $obj.principal.servicePrincipalDetails.aadAppId + Role = $obj.role + } + } + return $results + } + else { + if ($WorkspaceRoleAssignmentId) { + Write-Message -Message "No role assignment found with ID '$WorkspaceRoleAssignmentId' for WorkspaceId '$WorkspaceId'." -Level Warning + } + else { + Write-Message -Message "No role assignments found for WorkspaceId '$WorkspaceId'." -Level Warning + } + return @() + } + } + catch { + # Step 10: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error + } } - -process { - # Call Workspace API for WorkspaceID - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method GET ` - -Uri $workspaceApiUrl ` - -ContentType "application/json" - - $response.Workspaces - -} - -end {} - -} \ No newline at end of file diff --git a/source/Public/Workspace/New-FabricWorkspace.ps1 b/source/Public/Workspace/New-FabricWorkspace.ps1 index 931ca5f6..4da24893 100644 --- a/source/Public/Workspace/New-FabricWorkspace.ps1 +++ b/source/Public/Workspace/New-FabricWorkspace.ps1 @@ -1,80 +1,122 @@ <# .SYNOPSIS - Creates a new Fabric workspace. +Creates a new Fabric workspace with the specified display name. .DESCRIPTION - The New-FabricWorkspace function creates a new Fabric workspace. It uses the provided name to create the workspace. If the workspace already exists and the skipErrorIfExists switch is provided, it does not throw an error. +The `Add-FabricWorkspace` function creates a new workspace in the Fabric platform by sending a POST request to the API. It validates the display name and handles both success and error responses. -.PARAMETER Name - The name of the new Fabric workspace. This is a mandatory parameter. - -.PARAMETER SkipErrorIfExists - A switch that indicates whether to skip the error if the workspace already exists. If provided, the function does not throw an error if the workspace already exists. +.PARAMETER WorkspaceName +The display name of the workspace to be created. Must only contain alphanumeric characters, spaces, and underscores. .EXAMPLE - New-FabricWorkspace -Name "NewWorkspace" -SkipErrorIfExists - - This command creates a new Fabric workspace named "NewWorkspace". If the workspace already exists, it does not throw an error. +Add-FabricWorkspace -WorkspaceName "NewWorkspace" -.INPUTS - String, Switch. You can pipe a string that contains the name and a switch that indicates whether to skip the error if the workspace already exists to New-FabricWorkspace. - -.OUTPUTS - String. This function returns the ID of the new Fabric workspace. +Creates a workspace named "NewWorkspace". .NOTES - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. + +Author: Tiago Balabuch #> -Function New-FabricWorkspace { - <# - .SYNOPSIS - Creates a new Fabric workspace. - #> - [CmdletBinding(SupportsShouldProcess)] - param - ( - [string]$name, - [switch]$skipErrorIfExists +function New-FabricWorkspace { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [string]$WorkspaceName, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceDescription, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CapacityId ) - # Create a request body for the Fabric API - $itemRequest = @{ - displayName = $name - } | ConvertTo-Json - try { - # Invoke the Fabric API to create the workspace - if ($PSCmdlet.ShouldProcess("workspaces", "create")) { - $createResult = Invoke-FabricAPIRequest -Uri "workspaces" -Method Post -Body $itemRequest - - # Display a message indicating the workspace was created - Write-Output "Workspace created" - - # Output the ID of the new workspace - Write-Output $createResult.id + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $WorkspaceName } - } - catch { - $ex = $_.Exception - - if ($skipErrorIfExists) { - if ($ex.Message -ilike "*409*") { - Write-Output "Workspace already exists" - $listWorkspaces = Invoke-FabricAPIRequest -Uri "workspaces" -Method Get + if ($WorkspaceDescription) { + $body.description = $WorkspaceDescription + } - $workspace = $listWorkspaces | Where-Object { $_.displayName -ieq $name } + if ($CapacityId) { + $body.capacityId = $CapacityId + } - if (!$workspace) { - throw "Cannot find workspace '$name'" + # Convert the body to JSON + $bodyJson = $body | ConvertTo-Json -Depth 2 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + switch ($statusCode) { + 201 { + Write-Message -Message "Workspace '$WorkspaceName' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "Workspace '$WorkspaceName' creation accepted. Provisioning in progress!" -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + + return $operationResult } - Write-Output $workspace.id + else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } } - else { - throw + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." } } } + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create workspace. Error: $errorDetails" -Level Error + + } } diff --git a/source/Public/Workspace/New-FabricWorkspace2.ps1 b/source/Public/Workspace/New-FabricWorkspace2.ps1 deleted file mode 100644 index 16369223..00000000 --- a/source/Public/Workspace/New-FabricWorkspace2.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -function New-FabricWorkspace2 { -#Requires -Version 7.1 - -<# -.SYNOPSIS - Creates a new Fabric Workspace - -.DESCRIPTION - Creates a new Fabric Workspace - -.PARAMETER CapacityID - Id of the Fabric Capacity for which the Workspace should be created. The value for CapacityID is a GUID. - An example of a GUID is '12345678-1234-1234-1234-123456789012'. - -.PARAMETER WorkspaceName - The name of the Workspace to create. This parameter is mandatory. - -.PARAMETER WorkspaceDescription - The description of the Workspace to create. - -.EXAMPLE - New-FabricWorkspace ` - -CapacityID '12345678-1234-1234-1234-123456789012' ` - -WorkspaceName 'TestWorkspace' ` - -WorkspaceDescription 'This is a Test Workspace' - - This example will create a new Workspace with the name 'TestWorkspace' and the description 'This is a test workspace'. - -.NOTES - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output -#> - -[CmdletBinding(SupportsShouldProcess)] - param ( - - [Parameter(Mandatory=$true)] - [string]$CapacityId, - - [Parameter(Mandatory=$true)] - [Alias("Name", "DisplayName")] - [string]$WorkspaceName, - - [ValidateLength(0, 4000)] - [Alias("Description")] - [string]$WorkspaceDescription - ) - -begin { - Confirm-FabricAuthToken | Out-Null - - Write-Verbose "Create body of request" - $body = @{ - 'displayName' = $WorkspaceName - 'description' = $WorkspaceDescription - 'capacityId' = $CapacityId - } | ConvertTo-Json ` - -Depth 1 - - Write-Verbose "Create Workspace API URL" - $WorkspaceApiUrl = "$($FabricSession.BaseApiUrl)/workspaces" - } - -process { - - if($PSCmdlet.ShouldProcess($WorkspaceName)) { - Write-Verbose "Calling Workspace API" - Write-Verbose "---------------------" - Write-Verbose "Sending the following values to the Workspace API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $WorkspaceApiUrl" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - $response = Invoke-RestMethod ` - -Headers $FabricSession.headerParams ` - -Method POST ` - -Uri $WorkspaceApiUrl ` - -Body ($body) ` - -ContentType "application/json" - - $response - } -} - -end {} - -} \ No newline at end of file diff --git a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 index afd7f312..e54d8fd4 100644 --- a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 +++ b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 @@ -1,7 +1,6 @@ -function Register-FabricWorkspaceToCapacity { - <# +<# .SYNOPSIS -This function Sets a PowerBI workspace to a capacity. +Sets a PowerBI workspace to a capacity. .DESCRIPTION The Register-FabricWorkspaceToCapacity function Sets a PowerBI workspace to a capacity. It supports multiple aliases for flexibility. @@ -29,6 +28,10 @@ This example Sets the workspace object stored in the $workspace variable to the The function makes a POST request to the PowerBI API to Set the workspace to the capacity. The PowerBI access token is retrieved using the Get-PowerBIAccessToken function. #> + +# This function Sets a PowerBI workspace to a capacity. +# It supports multiple aliases for flexibility. +function Register-FabricWorkspaceToCapacity { [Alias("Register-FabWorkspaceToCapacity")] [CmdletBinding(SupportsShouldProcess)] param( @@ -54,8 +57,8 @@ The function makes a POST request to the PowerBI API to Set the workspace to the # The body of the request is created. It contains the capacity ID. $body = @{ capacityId = $CapacityId - } - + } + Confirm-FabricAuthToken | Out-Null # The workspace is Seted to the capacity by making a POST request to the PowerBI API. @@ -65,4 +68,4 @@ The function makes a POST request to the PowerBI API to Set the workspace to the return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/assignToCapacity" -Body $body } } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Remove-FabricWorkspace.ps1 b/source/Public/Workspace/Remove-FabricWorkspace.ps1 index aec748af..309a3828 100644 --- a/source/Public/Workspace/Remove-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspace.ps1 @@ -1,37 +1,68 @@ <# .SYNOPSIS -Removes a workspace. +Deletes an existing Fabric workspace by its workspace ID. .DESCRIPTION -The Remove-FabricWorkspace function removes a workspace. It supports multiple aliases for flexibility. +The `Remove-FabricWorkspace` function deletes a workspace in the Fabric platform by sending a DELETE request to the API. It validates the workspace ID and handles both success and error responses. -.PARAMETER groupID -The ID of the group (workspace). This is a mandatory parameter. +.PARAMETER WorkspaceId +The unique identifier of the workspace to be deleted. .EXAMPLE -Remove-FabricWorkspace -groupID "your-group-id" +Remove-FabricWorkspace -WorkspaceId "workspace123" -This example removes a workspace given the group ID. +Deletes the workspace with the ID "workspace123". .NOTES -The function retrieves the PowerBI access token and makes a DELETE request to the PowerBI API to remove the workspace. -#> +- Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. +- Calls `Test-TokenExpired` to ensure token validity before making the API request. -# This function removes a workspace. +Author: Tiago Balabuch +#> function Remove-FabricWorkspace { [CmdletBinding(SupportsShouldProcess)] - # Define aliases for the function for flexibility. - - # Define a parameter for the group ID. - Param ( + param ( [Parameter(Mandatory = $true)] - [string]$workspaceID + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId ) - Confirm-FabricAuthToken | Out-Null + try { + # Step 1: Ensure token validity + Write-Message -Message "Validating token..." -Level Debug + Test-TokenExpired + Write-Message -Message "Token validation completed." -Level Debug + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Make a DELETE request to the PowerBI API to remove the workspace. - if ($PSCmdlet.ShouldProcess("Remove workspace $workspaceID")) { - return Invoke-FabricAPIRequest -Uri "workspaces/$workspaceID" -Method Delete + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 4: Validate the response code + if ($statusCode -ne 200) { + Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error + Write-Message -Message "Error: $($response.message)" -Level Error + Write-Message "Error Code: $($response.errorCode)" -Level Error + return $null + } + + Write-Message -Message "Workspace '$WorkspaceId' deleted successfully!" -Level Info + return $null + + } + catch { + # Step 5: Capture and log error details + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error + return $null } -} \ No newline at end of file +} diff --git a/source/Public/Workspace copy/Remove-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 similarity index 97% rename from source/Public/Workspace copy/Remove-FabricWorkspaceIdentity.ps1 rename to source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 index 2a55d5d4..ae315bf3 100644 --- a/source/Public/Workspace copy/Remove-FabricWorkspaceIdentity.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 @@ -17,11 +17,11 @@ Deprovisions the Managed Identity for the workspace with ID "workspace123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Remove-FabricWorkspaceIdentity { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -60,24 +60,24 @@ function Remove-FabricWorkspaceIdentity { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error diff --git a/source/Public/Workspace copy/Remove-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 similarity index 97% rename from source/Public/Workspace copy/Remove-FabricWorkspaceRoleAssignment.ps1 rename to source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 index 36be3a18..d9bae6b7 100644 --- a/source/Public/Workspace copy/Remove-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 @@ -20,12 +20,12 @@ Removes the role assignment with the ID "role123" from the workspace "workspace1 - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Remove-FabricWorkspaceRoleAssignment { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -63,7 +63,7 @@ function Remove-FabricWorkspaceRoleAssignment { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + Write-Message -Message "Role assignment '$WorkspaceRoleAssignmentId' successfully removed from workspace '$WorkspaceId'." -Level Info } catch { diff --git a/source/Public/Workspace copy/Unassign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 similarity index 95% rename from source/Public/Workspace copy/Unassign-FabricWorkspaceCapacity.ps1 rename to source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 index 8c8ce8bf..baa36657 100644 --- a/source/Public/Workspace copy/Unassign-FabricWorkspaceCapacity.ps1 +++ b/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 @@ -21,7 +21,7 @@ Author: Tiago Balabuch #> function Unassign-FabricWorkspaceCapacity { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -57,7 +57,7 @@ function Unassign-FabricWorkspaceCapacity { return $null } - Write-Message -Message "Workspace capacity has been successfully unassigned from workspace '$WorkspaceId'." -Level Info + Write-Message -Message "Workspace capacity has been successfully unassigned from workspace '$WorkspaceId'." -Level Info } catch { # Step 5: Capture and log error details diff --git a/source/Public/Workspace copy/Update-FabricWorkspace.ps1 b/source/Public/Workspace/Update-FabricWorkspace.ps1 similarity index 98% rename from source/Public/Workspace copy/Update-FabricWorkspace.ps1 rename to source/Public/Workspace/Update-FabricWorkspace.ps1 index 77b1056a..4af8ecc0 100644 --- a/source/Public/Workspace copy/Update-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Update-FabricWorkspace.ps1 @@ -32,7 +32,7 @@ Author: Tiago Balabuch #> function Update-FabricWorkspace { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/Workspace copy/Update-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 similarity index 98% rename from source/Public/Workspace copy/Update-FabricWorkspaceRoleAssignment.ps1 rename to source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 index f619e461..9039d7bd 100644 --- a/source/Public/Workspace copy/Update-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 @@ -31,7 +31,7 @@ Author: Tiago Balabuch #> function Update-FabricWorkspaceRoleAssignment { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -85,16 +85,16 @@ function Update-FabricWorkspaceRoleAssignment { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 6: Handle empty response if (-not $response) { Write-Message -Message "No data returned from the API." -Level Warning return $null } - + Write-Message -Message "Role assignment $WorkspaceRoleAssignmentId updated successfully in workspace '$WorkspaceId'." -Level Info return $response - + } catch { # Step 7: Handle and log errors From e9825266f384c29f6a49f3c5bb70b2b39acd252d Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 11:12:07 +0100 Subject: [PATCH 37/76] Refactor Base64 conversion functions for clarity #16 #20 Updated the Convert-FromBase64 and Convert-ToBase64 functions to improve code readability and maintainability. Removed unnecessary author notes and ensured consistent formatting. This will help in easier understanding and usage of the functions for Pester Help Tests. Thank you! --- source/Public/Utils/Convert-FromBase64.ps1 | 16 ++++++---------- source/Public/Utils/Convert-ToBase64.ps1 | 14 +++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/source/Public/Utils/Convert-FromBase64.ps1 b/source/Public/Utils/Convert-FromBase64.ps1 index 5aa99e13..281a2100 100644 --- a/source/Public/Utils/Convert-FromBase64.ps1 +++ b/source/Public/Utils/Convert-FromBase64.ps1 @@ -1,10 +1,11 @@ +function Convert-FromBase64 { <# .SYNOPSIS Decodes a Base64-encoded string into its original text representation. .DESCRIPTION - The Convert-FromBase64 function takes a Base64-encoded string as input, decodes it into a byte array, - and converts it back into a UTF-8 encoded string. It is useful for reversing Base64 encoding applied + The Convert-FromBase64 function takes a Base64-encoded string as input, decodes it into a byte array, + and converts it back into a UTF-8 encoded string. It is useful for reversing Base64 encoding applied to text or other data. .PARAMETER Base64String @@ -22,15 +23,10 @@ Output: Some encoded text - .NOTES - - This function assumes the Base64 input is a valid UTF-8 encoded string. - - Any decoding errors will throw a descriptive error message. - -.AUTHOR -Tiago Balabuch +This function assumes the Base64 input is a valid UTF-8 encoded string. +Any decoding errors will throw a descriptive error message. #> -function Convert-FromBase64 { param ( [Parameter(Mandatory = $true)] [string]$Base64String @@ -52,4 +48,4 @@ function Convert-FromBase64 { Write-Message -Message "An error occurred while decoding from Base64: $errorDetails" -Level Error throw "An error occurred while decoding from Base64: $_" } -} \ No newline at end of file +} diff --git a/source/Public/Utils/Convert-ToBase64.ps1 b/source/Public/Utils/Convert-ToBase64.ps1 index 8b152567..29e1fab0 100644 --- a/source/Public/Utils/Convert-ToBase64.ps1 +++ b/source/Public/Utils/Convert-ToBase64.ps1 @@ -1,10 +1,11 @@ +function Convert-ToBase64 { <# .SYNOPSIS Encodes the content of a file into a Base64-encoded string. .DESCRIPTION - The Convert-ToBase64 function takes a file path as input, reads the file's content as a byte array, - and converts it into a Base64-encoded string. This is useful for embedding binary data (e.g., images, + The Convert-ToBase64 function takes a file path as input, reads the file's content as a byte array, + and converts it into a Base64-encoded string. This is useful for embedding binary data (e.g., images, documents) in text-based formats such as JSON or XML. .PARAMETER filePath @@ -26,10 +27,9 @@ - Ensure the file exists at the specified path before running this function. - Large files may cause memory constraints due to full loading into memory. -.AUTHOR -Tiago Balabuch + + Tiago Balabuch #> -function Convert-ToBase64 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -37,7 +37,7 @@ function Convert-ToBase64 { [string]$filePath ) try { - + # Step 1: Reading all the bytes from the file #$bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString) Write-Message -Message "Reading all the bytes from the file specified: $filePath" -Level Debug @@ -57,4 +57,4 @@ function Convert-ToBase64 { Write-Message -Message "An error occurred while encoding to Base64: $errorDetails" -Level Error throw "An error occurred while encoding to Base64: $_" } -} \ No newline at end of file +} From da0e234dcd84b5fcec4d7bbf98bc304258f149f7 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 11:14:53 +0100 Subject: [PATCH 38/76] Refactor Get-FabricCopyJob function for clarity #16 #20 Improved the structure and readability of the Get-FabricCopyJob function by removing unnecessary lines and ensuring consistent formatting. This change enhances maintainability and prepares the code for future updates. Thank you! --- source/Public/Copy Job/Get-FabricCopyJob.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/Public/Copy Job/Get-FabricCopyJob.ps1 b/source/Public/Copy Job/Get-FabricCopyJob.ps1 index 043ff8de..3649c343 100644 --- a/source/Public/Copy Job/Get-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJob.ps1 @@ -1,3 +1,4 @@ +function Get-FabricCopyJob { <# .SYNOPSIS Retrieves CopyJob details from a specified Microsoft Fabric workspace. @@ -24,12 +25,11 @@ This example retrieves the CopyJob details for the CopyJob named "My CopyJob" in the workspace with ID "workspace-12345". .NOTES - - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - - Calls `Test-TokenExpired` to ensure token validity before making the API request. + Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. + Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch #> -function FabricCopyJob { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -57,16 +57,16 @@ function FabricCopyJob { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Construct the API endpoint URL $apiEndpointURI = "{0}/workspaces/{1}/copyJobs" -f $FabricConfig.BaseUrl, $WorkspaceId - + # Invoke the Fabric API to retrieve capacity details $copyJobs = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Get + -Method Get # Filter results based on provided parameters $response = if ($CopyJobId) { @@ -95,6 +95,6 @@ function FabricCopyJob { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve CopyJob. Error: $errorDetails" -Level Error - } - + } + } From bba9b947d793fe5a79dec796eadac28be8755f13 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:22:26 +0100 Subject: [PATCH 39/76] Remove commented-out paths in build.yaml for clarity This change cleans up the build.yaml file by removing unnecessary commented-out paths. This helps improve readability and maintainability of the configuration file for Pester Help Tests. Thank you! --- build.yaml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/build.yaml b/build.yaml index f905f029..f05a2581 100644 --- a/build.yaml +++ b/build.yaml @@ -9,8 +9,7 @@ BuiltModuleSubdirectory: module CopyPaths: - en-US -# - DSCResources - # - Modules + Encoding: UTF8 # Can be used to manually specify module's semantic version if the preferred method of # using GitVersion is not available, and it is not possible to set the session environment @@ -34,7 +33,7 @@ NestedModule: # OutputDirectory: ///Modules/HelperSubmodule # VersionedOutputDirectory: false # AddToManifest: false -# SemVer: +# SemVer: # # suffix: # # prefix: @@ -153,9 +152,3 @@ TaskHeader: | Write-Build DarkGray " $Path" Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" "" - - - - - - From 2e062f7950c4b10d858e0415ef9e8f3cc5bc9e38 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:22:47 +0100 Subject: [PATCH 40/76] Add detailed documentation for PowerShell functions Incomplete Help #16 This commit enhances the documentation for various PowerShell functions in the Set-WellKnown.ps1 script. Each function now includes a synopsis, description, parameters, and examples to improve clarity and usability. This will help users understand the purpose and usage of the functions better, especially for Pester Help Tests. Thank you! --- source/Private/Set-WellKnown.ps1 | 319 +++++++++++++++++++++++++++++-- 1 file changed, 302 insertions(+), 17 deletions(-) diff --git a/source/Private/Set-WellKnown.ps1 b/source/Private/Set-WellKnown.ps1 index 57897389..db77eb3b 100644 --- a/source/Private/Set-WellKnown.ps1 +++ b/source/Private/Set-WellKnown.ps1 @@ -1,4 +1,26 @@ function Write-Log { +<# +.SYNOPSIS + Write a log message to the console with color coding based on the log level. + +.DESCRIPTION + This function writes a log message to the console with color coding based on the log level. It supports different log levels such as INFO, WARN, ERROR, and DEBUG. The function also has an option to stop execution if an error occurs. + +.PARAMETER Message + The log message to be written to the console. + +.PARAMETER Level + The log level for the message. Default is INFO. Other options are WARN, ERROR, and DEBUG. + +.PARAMETER Stop + A boolean value indicating whether to stop execution if an error occurs. Default is true. + +.EXAMPLE + Write-Log -Message "This is an info message." -Level "INFO" + Write-Log -Message "This is a warning message." -Level "WARN" + Write-Log -Message "This is an error message." -Level "ERROR" -Stop $true + Write-Log -Message "This is a debug message." -Level "DEBUG" -Stop $false +#> param ( [Parameter(Mandatory = $true)] [string]$Message, @@ -31,9 +53,21 @@ function Write-Log { if ($Stop -and $Level -eq 'ERROR') { exit 1 } - } +} - function Install-ModuleIfNotInstalled { +function Install-ModuleIfNotInstalled { +<# +.SYNOPSIS + Installs a PowerShell module if it is not already installed. +.DESCRIPTION + This function checks if a specified PowerShell module is installed. If the module is not found, it attempts to install it from the PSGallery repository. The function handles errors during installation and logs messages accordingly. +.PARAMETER ModuleName + The name of the PowerShell module to be installed. +.EXAMPLE + Install-ModuleIfNotInstalled -ModuleName "Az.Accounts" + This example installs the Az.Accounts module if it is not already installed. + +#> param ( [Parameter(Mandatory = $true)] [string]$ModuleName @@ -49,9 +83,21 @@ function Write-Log { Write-Log -Message "Unable to install module: $ModuleName" -Level 'ERROR' } } - } +} - function Import-ModuleIfNotImported { +function Import-ModuleIfNotImported { +<# +.SYNOPSIS + + Imports a PowerShell module if it is not already imported. +.DESCRIPTION + This function checks if a specified PowerShell module is already imported. If the module is not found, it attempts to import it. The function handles errors during import and logs messages accordingly. +.PARAMETER ModuleName + The name of the PowerShell module to be imported. +.EXAMPLE + Import-ModuleIfNotImported -ModuleName "Az.Accounts" + This example imports the Az.Accounts module if it is not already imported. +#> param ( [Parameter(Mandatory = $true)] [string]$ModuleName @@ -67,9 +113,32 @@ function Write-Log { Write-Log -Message "Unable to import module: $ModuleName" -Level 'ERROR' } } - } +} + +function Invoke-FabricRest { +<# +.SYNOPSIS + Invokes a REST API call to the Fabric API with retry logic. +.DESCRIPTION + This function invokes a REST API call to the Fabric API with retry logic. It handles throttling and long-running operations. The function retrieves the Fabric access token and constructs the request URI based on the provided endpoint. It supports GET, POST, PUT, and DELETE methods. +.PARAMETER Method + The HTTP method to use for the request. Default is GET. Other options are POST, PUT, and DELETE. +.PARAMETER Endpoint + The API endpoint to call. This is a required parameter. +.PARAMETER Payload + The payload to send with the request. This is an optional parameter. +.PARAMETER RetryCount + The number of retry attempts in case of throttling. Default is 3. +.PARAMETER RetryDelaySeconds + The delay in seconds between retry attempts. Default is 30 seconds. +.EXAMPLE + Invoke-FabricRest -Method 'GET' -Endpoint 'workspaces' + This example retrieves a list of workspaces from the Fabric API. +.EXAMPLE + Invoke-FabricRest -Method 'POST' -Endpoint 'workspaces' -Payload @{ displayName = 'MyWorkspace' } + This example creates a new workspace in the Fabric API with the specified display name. +#> - function Invoke-FabricRest { param ( [Parameter(Mandatory = $false)] [string]$Method = 'GET', @@ -161,9 +230,23 @@ function Write-Log { catch { Write-Log -Message $_.Exception.Message -Level 'ERROR' } - } +} + +function Get-LroResult { +<# +.SYNOPSIS + Retrieves the result of a long-running operation using the operation ID. + +.DESCRIPTION + This function retrieves the result of a long-running operation using the operation ID. It checks the status of the operation and waits for it to complete. If the operation fails, it logs an error message. - function Get-LroResult { +.PARAMETER OperationId + The ID of the long-running operation to retrieve the result for. + +.EXAMPLE + Get-LroResult -OperationId '12345678-1234-1234-1234-123456789012' + This example retrieves the result of a long-running operation using the specified operation ID. +#> param ( [Parameter(Mandatory = $true)] [string]$OperationId @@ -186,9 +269,38 @@ function Write-Log { } return Invoke-FabricRest -Method 'GET' -Endpoint "operations/$OperationId/result" - } +} + +function Set-FabricItem { +<# +.SYNOPSIS + Creates or retrieves a Fabric item (e.g., DataPipeline, Environment, Eventhouse, etc.) in a specified workspace. +.DESCRIPTION + This function creates or retrieves a Fabric item (e.g., DataPipeline, Environment, Eventhouse, etc.) in a specified workspace. It checks if the item already exists and creates it if it doesn't. The function also validates the input parameters and handles errors. + +.PARAMETER DisplayName + The display name of the item to be created or retrieved. + +.PARAMETER WorkspaceId + The ID of the workspace where the item will be created or retrieved. + +.PARAMETER Type + The type of the item to be created or retrieved. Valid options are DataPipeline, Environment, Eventhouse, Eventstream, GraphQLApi, KQLDashboard, KQLDatabase, KQLQueryset, Lakehouse, MirroredDatabase, MLExperiment, MLModel, Notebook, Reflex, Report, SemanticModel, SparkJobDefinition, SQLDatabase, Warehouse. + +.PARAMETER CreationPayload + The payload to be used for creating the item. This is an optional parameter. + +.PARAMETER Definition + The definition to be used for creating the item. This is an optional parameter. - function Set-FabricItem { +.EXAMPLE + Set-FabricItem -DisplayName 'MyDataPipeline' -WorkspaceId '12345678-1234-1234-1234-123456789012' -Type 'DataPipeline' + This example creates or retrieves a DataPipeline item with the specified display name in the specified workspace. + +.EXAMPLE + Set-FabricItem -DisplayName 'MyEnvironment' -WorkspaceId '12345678-1234-1234-1234-123456789012' -Type 'Environment' -CreationPayload @{} + This example creates or retrieves an Environment item with the specified display name in the specified workspace using the provided creation payload. +#> param ( [Parameter(Mandatory = $true)] [string]$DisplayName, @@ -305,9 +417,26 @@ function Write-Log { Write-Log -Message "${Type} - Name: $($result.displayName) / ID: $($result.id)" return $result - } +} + +function Get-DefinitionPartBase64 { +<# +.SYNOPSIS + + Converts a file to a Base64 string. +.DESCRIPTION + Converts a file to a Base64 string. It reads the content of the file, replaces specified values, and encodes it in Base64 format. + +.PARAMETER Path + The path to the file to be converted. - function Get-DefinitionPartBase64 { +.PARAMETER Values + An object containing key-value pairs to replace in the file content. + +.EXAMPLE + Get-DefinitionPartBase64 -Path 'C:\path\to\file.txt' -Values @{ key = 'oldValue'; value = 'newValue' } + This example converts the specified file to a Base64 string and replaces 'oldValue' with 'newValue' in the file content. +#> param ( [Parameter(Mandatory = $true)] [string]$Path, @@ -329,9 +458,23 @@ function Write-Log { } return [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($content)) - } +} - function Set-FabricDomain { +function Set-FabricDomain { +<# +.SYNOPSIS + Creates or retrieves a Fabric domain in a specified parent domain. +.DESCRIPTION + This function creates or retrieves a Fabric domain in a specified parent domain. It checks if the domain already exists and creates it if it doesn't. The function also handles errors and logs messages accordingly. +.PARAMETER DisplayName + The display name of the domain to be created or retrieved. +.PARAMETER ParentDomainId + The ID of the parent domain for the domain. This is an optional parameter. +.EXAMPLE + Set-FabricDomain -DisplayName 'MyDomain' -ParentDomainId '12345678-1234-1234-1234-123456789012' + This example creates or retrieves a Fabric domain with the specified display name in the specified parent domain. + +#> param ( [Parameter(Mandatory = $true)] [string]$DisplayName, @@ -364,9 +507,22 @@ function Write-Log { } return $result - } +} + +function Get-BaseName { +<# +.SYNOPSIS + Generates a base name based on the provided length or environment variable. +.DESCRIPTION + This function generates a base name based on the provided length or environment variable. If the environment variable FABRIC_TESTACC_WELLKNOWN_NAME_BASE is set, it uses that value. Otherwise, it generates a random string of the specified length. +.PARAMETER Length + The length of the random string to be generated. Default is 10. This is an optional parameter. - function Get-BaseName { +.EXAMPLE + Get-BaseName -Length 15 + This example generates a random string of length 15 if the environment variable FABRIC_TESTACC_WELLKNOWN_NAME_BASE is not set. + +#> param ( [Parameter(Mandatory = $false)] [int]$Length = 10 @@ -379,9 +535,26 @@ function Write-Log { } return $base - } +} function Get-DisplayName { +<# +.SYNOPSIS + Generates a display name based on the provided base, prefix, suffix, and separator. +.DESCRIPTION + This function generates a display name based on the provided base, prefix, suffix, and separator. It concatenates the prefix, base, and suffix with the specified separator. The function also retrieves the base name if not provided. +.PARAMETER Base + The base name to be used in the display name. This is a required parameter. +.PARAMETER Prefix + The prefix to be added to the base name. This is an optional parameter. +.PARAMETER Suffix + The suffix to be added to the base name. This is an optional parameter. +.PARAMETER Separator + The separator to be used between the prefix, base, and suffix. Default is '_'. This is an optional parameter. +.EXAMPLE + Get-DisplayName -Base 'MyBaseName' -Prefix 'MyPrefix' -Suffix 'MySuffix' -Separator '-' + This example generates a display name with the specified base, prefix, suffix, and separator. +#> param ( [Parameter(Mandatory = $true)] [string]$Base, @@ -411,6 +584,22 @@ function Write-Log { } function Set-FabricWorkspace { +<# +.SYNOPSIS + Creates or retrieves a Fabric workspace in a specified capacity. +.DESCRIPTION + This function creates or retrieves a Fabric workspace in a specified capacity. It checks if the workspace already exists and creates it if it doesn't. The function also handles errors and logs messages accordingly. +.PARAMETER DisplayName + The display name of the workspace to be created or retrieved. +.PARAMETER CapacityId + The ID of the capacity where the workspace will be created or retrieved. +.PARAMETER ParentDomainId + The ID of the parent domain for the workspace. This is an optional parameter. +.EXAMPLE + Set-FabricWorkspace -DisplayName 'MyWorkspace' -CapacityId '12345678-1234-1234-1234-123456789012' + This example creates or retrieves a Fabric workspace with the specified display name in the specified capacity. + +#> param ( [Parameter(Mandatory = $true)] [string]$DisplayName, @@ -435,6 +624,19 @@ function Write-Log { } function Set-FabricWorkspaceCapacity { +<# +.SYNOPSIS + Assigns a Fabric workspace to a specified capacity. +.DESCRIPTION + This function assigns a Fabric workspace to a specified capacity. It checks if the workspace is already assigned to the specified capacity and assigns it if not. The function also handles errors and logs messages accordingly. +.PARAMETER WorkspaceId + The ID of the Fabric workspace to be assigned to the capacity. +.PARAMETER CapacityId + The ID of the capacity to which the workspace will be assigned. +.EXAMPLE + Set-FabricWorkspaceCapacity -WorkspaceId '12345678-1234-1234-1234-123456789012' -CapacityId '87654321-4321-4321-4321-210987654321' + This example assigns the specified workspace to the specified capacity. +#> param ( [Parameter(Mandatory = $true)] [string]$WorkspaceId, @@ -457,6 +659,20 @@ function Write-Log { } function Set-FabricWorkspaceRoleAssignment { +<# +.SYNOPSIS + Assigns a Service Principal Name (SPN) to a Fabric workspace with the Admin role. +.DESCRIPTION + This function assigns a Service Principal Name (SPN) to a Fabric workspace with the Admin role. It checks if the SPN is already assigned to the workspace and assigns it if not. The function also handles errors and logs messages accordingly. +.PARAMETER WorkspaceId + The ID of the Fabric workspace where the SPN will be assigned. +.PARAMETER SPN + The Service Principal Name (SPN) to be assigned to the workspace. This should be an object containing the Id and DisplayName of the SPN. +.EXAMPLE + Set-FabricWorkspaceRoleAssignment -WorkspaceId '12345678-1234-1234-1234-123456789012' -SPN $spn + This example assigns the specified SPN to the Fabric workspace with the specified ID. + +#> param ( [Parameter(Mandatory = $true)] [string]$WorkspaceId, @@ -481,6 +697,32 @@ function Write-Log { } function Set-FabricGatewayVirtualNetwork { +<# +.SYNOPSIS + Creates or retrieves a Virtual Network gateway in the Fabric API. +.DESCRIPTION + This function creates or retrieves a Virtual Network gateway in the Fabric API. It checks if the gateway already exists and creates it if it doesn't. The function also validates the input parameters and handles errors. +.PARAMETER DisplayName + The display name of the gateway to be created or retrieved. +.PARAMETER CapacityId + The ID of the capacity to be used for the gateway. +.PARAMETER InactivityMinutesBeforeSleep + The inactivity time (in minutes) before the gateway goes to auto-sleep. Allowed values: 30, 60, 90, 120, 150, 240, 360, 480, 720, 1440. +.PARAMETER NumberOfMemberGateways + The number of member gateways (between 1 and 7). +.PARAMETER SubscriptionId + The Azure subscription ID where the gateway will be created. +.PARAMETER ResourceGroupName + The name of the Azure Resource Group where the gateway will be created. +.PARAMETER VirtualNetworkName + The name of the Azure Virtual Network where the gateway will be created. +.PARAMETER SubnetName + The name of the subnet where the gateway will be created. +.EXAMPLE + Set-FabricGatewayVirtualNetwork -DisplayName 'MyGateway' -CapacityId '12345678-1234-1234-1234-123456789012' -InactivityMinutesBeforeSleep 30 -NumberOfMemberGateways 2 -SubscriptionId '12345678-1234-1234-1234-123456789012' -ResourceGroupName 'MyResourceGroup' -VirtualNetworkName 'MyVNet' -SubnetName 'MySubnet' + This example creates or retrieves a Virtual Network gateway with the specified parameters. + +#> [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -545,6 +787,36 @@ function Write-Log { function Set-AzureVirtualNetwork { +<# +.SYNOPSIS + Creates or retrieves an Azure Virtual Network (VNet) with a specified subnet and delegation. +.DESCRIPTION + This function creates or retrieves an Azure Virtual Network (VNet) with a specified subnet and delegation. It checks if the VNet already exists and creates it if it doesn't. The function also checks for the existence of the subnet and adds it if necessary. + It assigns the "Network Contributor" role to the current user on the VNet. + +.PARAMETER ResourceGroupName + The name of the Azure Resource Group where the VNet will be created or retrieved. + +.PARAMETER VNetName + The name of the Azure Virtual Network. + +.PARAMETER Location + The Azure region where the VNet will be created. + +.PARAMETER AddressPrefixes + The address prefixes for the VNet. + +.PARAMETER SubnetName + The name of the subnet to be created or retrieved. + +.PARAMETER SubnetAddressPrefixes + The address prefixes for the subnet. + +.EXAMPLE + Set-AzureVirtualNetwork -ResourceGroupName 'MyResourceGroup' -VNetName 'MyVNet' -Location 'East US' -AddressPrefixes ' + Create a new Azure Virtual Network with the specified parameters. + +#> param( [Parameter(Mandatory = $true)] [string]$ResourceGroupName, @@ -643,6 +915,19 @@ function Write-Log { function Invoke-DontRunThisCode { +<# +.SYNOPSIS + This function is used to set up the environment for the script. It installs required modules, imports the .env file, and logs in to Azure and Azure DevOps. +.DESCRIPTION + This function is used to set up the environment for the script. It installs required modules, imports the .env file, and logs in to Azure and Azure DevOps. It also checks if the required environment variables are set and logs in to Azure if not already logged in. + It retrieves the Fabric Capacity ID and logs in to Azure DevOps using the provided access token. The function also retrieves the Service Principal Name (SPN) if provided and logs messages accordingly. +.PARAMETER None + This function does not take any parameters. +.EXAMPLE + Invoke-DontRunThisCode + This example sets up the environment for the script by installing required modules, importing the .env file, and logging in to Azure and Azure DevOps. It also retrieves the Fabric Capacity ID and logs in to Azure DevOps using the provided access token. +#> + # Define an array of modules to install $modules = @('Az.Accounts', 'Az.Resources', 'Az.Fabric', 'pwsh-dotenv', 'ADOPS', 'Az.Network') From 660f4026c5a1eaba71dee2858a3c8977a789ab3f Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:22:59 +0100 Subject: [PATCH 41/76] Rename function for consistency and clarity Updated the function name from Get-AllFabricDatasetRefreshes to Get-FabricDatasetRefreshes for better alignment with naming conventions. This change enhances readability and maintains consistency across the codebase. Thank you! --- source/Public/Get-AllFabricDatasetRefreshes.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Public/Get-AllFabricDatasetRefreshes.ps1 b/source/Public/Get-AllFabricDatasetRefreshes.ps1 index 94dab947..826b6867 100644 --- a/source/Public/Get-AllFabricDatasetRefreshes.ps1 +++ b/source/Public/Get-AllFabricDatasetRefreshes.ps1 @@ -1,12 +1,13 @@ +function Get-FabricDatasetRefreshes { <# .SYNOPSIS Retrieves all refreshes for all datasets in all PowerBI workspaces. .DESCRIPTION -The Get-AllFabricDatasetRefreshes function retrieves all refreshes for all datasets in all PowerBI workspaces. It supports multiple aliases for flexibility. +The Get-FabricDatasetRefreshes function retrieves all refreshes for all datasets in all PowerBI workspaces. It supports multiple aliases for flexibility. .EXAMPLE -Get-AllFabricDatasetRefreshes +Get-FabricDatasetRefreshes This example retrieves all refreshes for all datasets in all PowerBI workspaces. @@ -15,7 +16,6 @@ The function makes a GET request to the PowerBI API to retrieve the refreshes. I #> # This function retrieves all refreshes for all datasets in all PowerBI workspaces. -function Get-AllFabricDatasetRefreshes { # Define aliases for the function for flexibility. Confirm-FabricAuthToken | Out-Null @@ -54,4 +54,4 @@ function Get-AllFabricDatasetRefreshes { } # Return the array of refreshes. return $refs -} \ No newline at end of file +} From 25d1a7a97c7618316bad27986910ec677a254465 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:23:12 +0100 Subject: [PATCH 42/76] Remove Get-AllFabricCapacities function for clarity This commit removes the Get-AllFabricCapacities function from the codebase. The function was deemed unnecessary and its removal helps to streamline the code and improve maintainability. Thank you! --- .../Capacity/Get-AllFabricCapacities.ps1 | 75 ------------------- 1 file changed, 75 deletions(-) delete mode 100644 source/Public/Capacity/Get-AllFabricCapacities.ps1 diff --git a/source/Public/Capacity/Get-AllFabricCapacities.ps1 b/source/Public/Capacity/Get-AllFabricCapacities.ps1 deleted file mode 100644 index 1e5434db..00000000 --- a/source/Public/Capacity/Get-AllFabricCapacities.ps1 +++ /dev/null @@ -1,75 +0,0 @@ -<# -.SYNOPSIS - This function retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. - -.DESCRIPTION - The Get-AllFabricCapacities function is used to retrieve all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. It uses the Az module to interact with Azure. - -.PARAMETER subscriptionID - An optional parameter that specifies the subscription ID. If this parameter is not provided, the function will retrieve resources from all subscriptions. - -.EXAMPLE - Get-AllFabricCapacities -subscriptionID "12345678-1234-1234-1234-123456789012" - - This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in the subscription with the ID "12345678-1234-1234-1234-123456789012". - -.EXAMPLE - Get-AllFabricCapacities - - This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in all subscriptions. - -.NOTES - Alias: Get-AllFabCapacities -#> -function Get-AllFabricCapacities { - # Define aliases for the function for flexibility. - - # Define parameters for the function - Param ( - # Optional parameter for subscription ID - [Parameter(Mandatory = $false)] - [string]$subscriptionID - ) - - # Initialize an array to store the results - $res = @() - - Get-FabricAuthToken | Out-Null - - # If a subscription ID is provided - if ($subscriptionID) { - # Set the context to the provided subscription ID - Set-AzContext -SubscriptionId $subscriptionID | Out-Null - - # Get all resource groups in the subscription - $rgs = Get-AzResourceGroup - - # For each resource group, get all resources of type "Microsoft.Fabric/capacities" - foreach ($r in $rgs) { - # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array - $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue - } - } - else { - # If no subscription ID is provided, get all subscriptions - $subscriptions = Get-AzSubscription - - # For each subscription, set the context to the subscription ID - foreach ($sub in $subscriptions) { - # Set the context to the subscription ID - Set-AzContext -SubscriptionId $sub.id | Out-Null - - # Get all resource groups in the subscription - $rgs = Get-AzResourceGroup - - # For each resource group, get all resources of type "Microsoft.Fabric/capacities" - foreach ($r in $rgs) { - # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array - $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue - } - } - } - - # Return the results - return $res -} \ No newline at end of file From 6405e9c445956b0be31ccae62a9e7b8f65f33e94 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:23:30 +0100 Subject: [PATCH 43/76] Add Get-FabricCapacities function for Pester Help Tests This commit introduces the Get-FabricCapacities function, which retrieves all resources of type "Microsoft.Fabric/capacities" from specified subscriptions or all subscriptions if no ID is provided. This enhancement improves the functionality and usability of the script for users needing to access capacity resources in Azure. Thank you! --- .../Public/Capacity/Get-FabricCapacities.ps1 | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 source/Public/Capacity/Get-FabricCapacities.ps1 diff --git a/source/Public/Capacity/Get-FabricCapacities.ps1 b/source/Public/Capacity/Get-FabricCapacities.ps1 new file mode 100644 index 00000000..265bc9fd --- /dev/null +++ b/source/Public/Capacity/Get-FabricCapacities.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS + This function retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. + +.DESCRIPTION + The Get-AllFabricCapacities function is used to retrieve all resources of type "Microsoft.Fabric/capacities" from all resource groups in a given subscription or all subscriptions if no subscription ID is provided. It uses the Az module to interact with Azure. + +.PARAMETER subscriptionID + An optional parameter that specifies the subscription ID. If this parameter is not provided, the function will retrieve resources from all subscriptions. + +.EXAMPLE + Get-AllFabricCapacities -subscriptionID "12345678-1234-1234-1234-123456789012" + + This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in the subscription with the ID "12345678-1234-1234-1234-123456789012". + +.EXAMPLE + Get-AllFabricCapacities + + This command retrieves all resources of type "Microsoft.Fabric/capacities" from all resource groups in all subscriptions. + +.NOTES + Alias: Get-AllFabCapacities +#> +function Get-FabricCapacities { + # Define aliases for the function for flexibility. + + # Define parameters for the function + Param ( + # Optional parameter for subscription ID + [Parameter(Mandatory = $false)] + [string]$subscriptionID + ) + + # Initialize an array to store the results + $res = @() + + Get-FabricAuthToken | Out-Null + + # If a subscription ID is provided + if ($subscriptionID) { + # Set the context to the provided subscription ID + Set-AzContext -SubscriptionId $subscriptionID | Out-Null + + # Get all resource groups in the subscription + $rgs = Get-AzResourceGroup + + # For each resource group, get all resources of type "Microsoft.Fabric/capacities" + foreach ($r in $rgs) { + # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array + $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue + } + } + else { + # If no subscription ID is provided, get all subscriptions + $subscriptions = Get-AzSubscription + + # For each subscription, set the context to the subscription ID + foreach ($sub in $subscriptions) { + # Set the context to the subscription ID + Set-AzContext -SubscriptionId $sub.id | Out-Null + + # Get all resource groups in the subscription + $rgs = Get-AzResourceGroup + + # For each resource group, get all resources of type "Microsoft.Fabric/capacities" + foreach ($r in $rgs) { + # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array + $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue + } + } + } + + # Return the results + return $res +} From aaf6d3190c6895f7f5a2b71a683a980802e31190 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:23:40 +0100 Subject: [PATCH 44/76] Refactor Help Tests to Filter by Exported Commands for Pester Help Tests Updated the Help tests to ensure they only iterate over functions that are exported by the module. This change enhances the accuracy of the tests and ensures compliance with the module's exported command structure. Thank you! --- tests/QA/module.tests.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/QA/module.tests.ps1 b/tests/QA/module.tests.ps1 index 2b8c69c7..fe686494 100644 --- a/tests/QA/module.tests.ps1 +++ b/tests/QA/module.tests.ps1 @@ -128,7 +128,7 @@ Describe 'Quality for module' -Tags 'TestQuality' { } Describe 'Help for module' -Tags 'helpQuality' { - It 'Should have .SYNOPSIS for ' -ForEach $testCases { + It 'Should have .SYNOPSIS for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName @@ -147,7 +147,7 @@ Describe 'Help for module' -Tags 'helpQuality' { $functionHelp.Synopsis | Should -Not -BeNullOrEmpty } - It 'Should have a .DESCRIPTION with length greater than 40 characters for ' -ForEach $testCases { + It 'Should have a .DESCRIPTION with length greater than 40 characters for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName @@ -166,7 +166,7 @@ Describe 'Help for module' -Tags 'helpQuality' { $functionHelp.Description.Length | Should -BeGreaterThan 40 } - It 'Should have at least one (1) example for ' -ForEach $testCases { + It 'Should have at least one (1) example for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName @@ -188,7 +188,7 @@ Describe 'Help for module' -Tags 'helpQuality' { } - It 'Should have described all parameters for ' -ForEach $testCases { + It 'Should have described all parameters for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName @@ -213,4 +213,3 @@ Describe 'Help for module' -Tags 'helpQuality' { } } } - From 912e73d60e101029e233493ae3a1f3e0d1404f4c Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:30:58 +0100 Subject: [PATCH 45/76] Refactor Functions for Consistency and Clarity for Help #16 #17 Updated multiple functions to ensure consistent formatting and clarity in their definitions. This change enhances readability and maintainability of the codebase for Pester Help Tests. Thank you! --- .../Capacity/Get-FabricCapacityRefreshables.ps1 | 4 ++-- .../Public/Capacity/Get-FabricCapacitySkus.ps1 | 7 +++---- .../Public/Capacity/Get-FabricCapacityState.ps1 | 4 ++-- .../Get-FabricCapacityTenantOverrides.ps1 | 4 ++-- .../Capacity/Get-FabricCapacityWorkload.ps1 | 6 +++--- source/Public/Get-FabricAPIClusterURI.ps1 | 16 +++++++--------- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 b/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 index fa21ff63..632ff58f 100644 --- a/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 @@ -1,3 +1,4 @@ +function Get-FabricCapacityRefreshables { <# .SYNOPSIS Retrieves the top refreshable capacities for the tenant. @@ -18,7 +19,6 @@ The function retrieves the PowerBI access token and makes a GET request to the P #> # This function retrieves the top refreshable capacities for the tenant. -function Get-FabricCapacityRefreshables { # Define aliases for the function for flexibility. [Alias("Get-FabCapacityRefreshables")] @@ -33,4 +33,4 @@ function Get-FabricCapacityRefreshables { # Make a GET request to the PowerBI API to retrieve the top refreshable capacities. # The function returns the 'value' property of the response. return (Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/capacities/refreshables?`$top=$top" -Headers $FabricSession.HeaderParams -Method GET).value -} \ No newline at end of file +} diff --git a/source/Public/Capacity/Get-FabricCapacitySkus.ps1 b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 index 06e27a5f..84f5a3d8 100644 --- a/source/Public/Capacity/Get-FabricCapacitySkus.ps1 +++ b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 @@ -1,3 +1,5 @@ + +function Get-FabricCapacitySkus { <# .SYNOPSIS Retrieves the fabric capacity information. @@ -11,10 +13,7 @@ Specifies the capacity to retrieve information for. If not provided, all capacit .EXAMPLE Get-FabricCapacitySkus -capacity "exampleCapacity" Retrieves the fabric capacity information for the specified capacity. - #> - -function Get-FabricCapacitySkus { # Define aliases for the function for flexibility. Param( @@ -34,4 +33,4 @@ function Get-FabricCapacitySkus { return $result.value -} \ No newline at end of file +} diff --git a/source/Public/Capacity/Get-FabricCapacityState.ps1 b/source/Public/Capacity/Get-FabricCapacityState.ps1 index 7b8f5307..d9eace57 100644 --- a/source/Public/Capacity/Get-FabricCapacityState.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityState.ps1 @@ -1,3 +1,4 @@ +function Get-FabricCapacityState { <# .SYNOPSIS Retrieves the state of a specific capacity. @@ -24,7 +25,6 @@ The function checks if the Azure token is null. If it is, it connects to the Azu #> # This function retrieves the state of a specific capacity. -function Get-FabricCapacityState { # Define aliases for the function for flexibility. [Alias("Get-FabCapacityState")] @@ -45,4 +45,4 @@ function Get-FabricCapacityState { # Make the GET request and return the response. return Invoke-RestMethod -Method GET -Uri $getCapacityState -Headers $script:AzureSession.HeaderParams -ErrorAction Stop -} \ No newline at end of file +} diff --git a/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 b/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 index ac6862bd..510ea63d 100644 --- a/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 @@ -1,3 +1,4 @@ +function Get-FabricCapacityTenantOverrides { <# .SYNOPSIS Retrieves the tenant overrides for all capacities. @@ -18,7 +19,6 @@ The function retrieves the PowerBI access token and makes a GET request to the F #> # This function retrieves the tenant overrides for all capacities. -function Get-FabricCapacityTenantOverrides { # Define aliases for the function for flexibility. [Alias("Get-FabCapacityTenantOverrides")] @@ -30,4 +30,4 @@ function Get-FabricCapacityTenantOverrides { # Make a GET request to the Fabric API to retrieve the tenant overrides for all capacities. # The function returns the response of the GET request. return Invoke-RestMethod -uri "$($FabricSession.BaseApiUrl)/admin/capacities/delegatedTenantSettingOverrides" -Headers $FabricSession.HeaderParams -Method GET -} \ No newline at end of file +} diff --git a/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 b/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 index f7036ae3..b5763179 100644 --- a/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 @@ -1,4 +1,5 @@ -<# +function Get-FabricCapacityWorkload { + <# .SYNOPSIS Retrieves the workloads for a specific capacity. @@ -21,7 +22,6 @@ The function retrieves the PowerBI access token and makes a GET request to the P #> # This function retrieves the workloads for a specific capacity. -function Get-FabricCapacityWorkload { # Define aliases for the function for flexibility. [Alias("Get-FabCapacityWorkload")] @@ -39,4 +39,4 @@ function Get-FabricCapacityWorkload { } -#https://learn.microsoft.com/en-us/rest/api/power-bi/capacities/get-workloads \ No newline at end of file +#https://learn.microsoft.com/en-us/rest/api/power-bi/capacities/get-workloads diff --git a/source/Public/Get-FabricAPIClusterURI.ps1 b/source/Public/Get-FabricAPIClusterURI.ps1 index 73cdf5f2..b59fb66e 100644 --- a/source/Public/Get-FabricAPIClusterURI.ps1 +++ b/source/Public/Get-FabricAPIClusterURI.ps1 @@ -1,24 +1,22 @@ +function Get-FabricAPIclusterURI { <# .SYNOPSIS -Retrieves the cluster URI for the tenant. + Retrieves the cluster URI for the tenant. .DESCRIPTION -The Get-FabricAPIclusterURI function retrieves the cluster URI for the tenant. It supports multiple aliases for flexibility. + The Get-FabricAPIclusterURI function retrieves the cluster URI for the tenant. It supports multiple aliases for flexibility. .EXAMPLE -Get-FabricAPIclusterURI + Get-FabricAPIclusterURI -This example retrieves the cluster URI for the tenant. + This example retrieves the cluster URI for the tenant. .NOTES -The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the datasets. It then extracts the '@odata.context' property from the response, splits it on the '/' character, and selects the third element. This element is used to construct the cluster URI, which is then returned by the function. + The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the datasets. It then extracts the '@odata.context' property from the response, splits it on the '/' character, and selects the third element. This element is used to construct the cluster URI, which is then returned by the function. #> #This function retrieves the cluster URI for the tenant. - - -function Get-FabricAPIclusterURI { # Define aliases for the function for flexibility. [Alias("Get-FabAPIClusterURI")] Param ( @@ -40,4 +38,4 @@ function Get-FabricAPIclusterURI { # Return the cluster URI. return $clusterURI -} \ No newline at end of file +} From e680eb31b62cae473669981fc08330e1740cf33d Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:47:56 +0100 Subject: [PATCH 46/76] Refactor Functions for Consistency and Clarity for Pester Help Tests #16 #24 This commit refactors multiple functions to improve consistency and clarity across the codebase. Changes include renaming functions, updating documentation, and ensuring uniform formatting. These enhancements aim to make the code more maintainable and easier to understand for future development. Thank you! --- ...t-FabricEnvironmentStagingSparkCompute.ps1 | 14 +++--- source/Public/Get-FabricConnection.ps1 | 6 +-- source/Public/Get-FabricUsageMetricsQuery.ps1 | 4 +- source/Public/Get-SHA256.ps1 | 5 +- source/Public/Item/Get-FabricItem.ps1 | 10 ++-- .../KQL Queryset/Invoke-FabricKQLCommand.ps1 | 30 +++--------- .../Lakehouse/Get-FabricLakehouseTable.ps1 | 31 ++++++++---- .../Lakehouse/Load-FabricLakehouseTable.ps1 | 48 +++++++++++++++---- .../Get-FabricMirroredDatabaseStatus.ps1 | 28 ++++++++--- .../Get-FabricMirroredDatabaseTableStatus.ps1 | 38 ++++++++++----- .../Utils/Get-FabricLongRunningOperation.ps1 | 17 ++++--- .../Get-FabricLongRunningOperationResult.ps1 | 15 +++--- source/Public/Utils/Set-FabricApiHeaders.ps1 | 16 +++---- .../New-FabricWorkspaceUsageMetricsReport.ps1 | 5 +- .../Register-FabricWorkspaceToCapacity.ps1 | 8 ++-- 15 files changed, 164 insertions(+), 111 deletions(-) diff --git a/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 b/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 index cbc94d55..dc0c38b1 100644 --- a/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 +++ b/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 @@ -1,9 +1,10 @@ +function Get-FabricEnvironmentStagingSparkCompute { <# .SYNOPSIS Retrieves staging Spark compute details for a specific environment in a Microsoft Fabric workspace. .DESCRIPTION -The Get-FabricEnvironmentStagingSparkCompute function interacts with the Microsoft Fabric API to fetch information +The Get-FabricEnvironmentStagingSparkCompute function interacts with the Microsoft Fabric API to fetch information about staging Spark compute configurations for a specified environment. It ensures token validity and handles API errors gracefully. .PARAMETER WorkspaceId @@ -21,11 +22,8 @@ Retrieves the staging Spark compute configurations for the specified environment - Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - Uses `Test-TokenExpired` to validate the token before making API calls. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - - -function Get-FabricEnvironmentStagingSparkCompute { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -65,7 +63,7 @@ function Get-FabricEnvironmentStagingSparkCompute { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 5: Handle results return $response } @@ -73,6 +71,6 @@ function Get-FabricEnvironmentStagingSparkCompute { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment spark compute. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Get-FabricConnection.ps1 b/source/Public/Get-FabricConnection.ps1 index b621a862..fbd8920c 100644 --- a/source/Public/Get-FabricConnection.ps1 +++ b/source/Public/Get-FabricConnection.ps1 @@ -1,3 +1,4 @@ +function Get-FabricConnection { <# .SYNOPSIS Retrieves Fabric connections. @@ -22,9 +23,6 @@ This example retrieves specific connection from Fabric with ID "12345". https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/get-connection?tabs=HTTP https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/list-connections?tabs=HTTP #> - - -Function Get-FabricConnection { [CmdletBinding()] param ( @@ -43,7 +41,7 @@ Function Get-FabricConnection { else { $result = Invoke-FabricAPIRequest -Uri "/connections" -Method GET } - + return $result.value } } diff --git a/source/Public/Get-FabricUsageMetricsQuery.ps1 b/source/Public/Get-FabricUsageMetricsQuery.ps1 index 14a57128..b1e82d17 100644 --- a/source/Public/Get-FabricUsageMetricsQuery.ps1 +++ b/source/Public/Get-FabricUsageMetricsQuery.ps1 @@ -1,3 +1,4 @@ +function Get-FabricUsageMetricsQuery { <# .SYNOPSIS Retrieves usage metrics for a specific dataset. @@ -33,7 +34,6 @@ The function defines the headers and body for a POST request to the PowerBI API #> # This function retrieves usage metrics for a specific dataset. -function Get-FabricUsageMetricsQuery { # Define aliases for the function for flexibility. [Alias("Get-FabUsageMetricsQuery")] @@ -81,4 +81,4 @@ function Get-FabricUsageMetricsQuery { # Make a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. # The function returns the response of the POST request. return Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/groups/$groupId/datasets/$DatasetID/executeQueries" -Headers $FabricSession.HeaderParams -Body $reportbody -Method Post -} \ No newline at end of file +} diff --git a/source/Public/Get-SHA256.ps1 b/source/Public/Get-SHA256.ps1 index 905b291d..aa8b1d0d 100644 --- a/source/Public/Get-SHA256.ps1 +++ b/source/Public/Get-SHA256.ps1 @@ -1,3 +1,5 @@ + +function Get-Sha256 ($string) { <# .SYNOPSIS Calculates the SHA256 hash of a string. @@ -18,7 +20,6 @@ The function creates a new SHA256CryptoServiceProvider object, converts the stri #> # This function calculates the SHA256 hash of a string. -function Get-Sha256 ($string) { # Create a new SHA256CryptoServiceProvider object. $sha256 = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider @@ -33,4 +34,4 @@ function Get-Sha256 ($string) { # Return the resulting hash. return $result -} \ No newline at end of file +} diff --git a/source/Public/Item/Get-FabricItem.ps1 b/source/Public/Item/Get-FabricItem.ps1 index e9261449..9bb0719d 100644 --- a/source/Public/Item/Get-FabricItem.ps1 +++ b/source/Public/Item/Get-FabricItem.ps1 @@ -1,3 +1,4 @@ +function Get-FabricItem { <# .SYNOPSIS Retrieves fabric items from a workspace. @@ -21,18 +22,15 @@ This function was originally written by Rui Romano. https://github.com/RuiRomano/fabricps-pbip #> - - -Function Get-FabricItem { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] [string]$workspaceId, - + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true )] $Workspace, - + [Parameter(Mandatory = $false)] [Alias("itemType")] [string]$type, @@ -64,4 +62,4 @@ Function Get-FabricItem { return $result.value } } -} \ No newline at end of file +} diff --git a/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 b/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 index 34720f2c..c31b2af9 100644 --- a/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 +++ b/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 @@ -1,23 +1,11 @@ function Invoke-FabricKQLCommand { -#Requires -Version 7.1 - <# .SYNOPSIS Executes a KQL command in a Kusto Database. .DESCRIPTION - Executes a KQL command in a Kusto Database. The KQL command is executed in the Kusto Database - that is specified by the KQLDatabaseName or KQLDatabaseId parameter. The KQL command is executed - in the context of the Fabric Real-Time Intelligence session that is established by the - Connect-RTISession cmdlet. The cmdlet distinguishes between management commands and query commands. - Management commands are executed in the management API, while query commands are executed in the query API. - The distinction is made by checking if the KQL command starts with a dot. If it does, it is a management command - else it is a query command. If the KQL command is a management command, it is crucial to have the - .execute database script <| in the beginning, otherwise the Kusto API will not execute the script. This cmdlet - will automatically add the .execute database script <| in the beginning of the KQL command if it is a management command - and if it is not already present. - If the KQL Command is a query command, the result is returned as an array of PowerShell objects by default. If the - parameter -ReturnRawResult is used, the raw result of the KQL query is returned which is a JSON object. + Executes a KQL command in a Kusto Database. The KQL command is executed in the Kusto Database that is specified by the KQLDatabaseName or KQLDatabaseId parameter. The KQL command is executed in the context of the Fabric Real-Time Intelligence session that is established by the Connect-RTISession cmdlet. The cmdlet distinguishes between management commands and query commands. Management commands are executed in the management API, while query commands are executed in the query API. The distinction is made by checking if the KQL command starts with a dot. If it does, it is a management command else it is a query command. If the KQL command is a management command, it is crucial to have the execute database script <| in the beginning, otherwise the Kusto API will not execute the script. This cmdlet will automatically add the .execute database script <| in the beginning of the KQL command if it is a management command and if it is not already present. If the KQL Command is a query command, the result is returned as an array of PowerShell objects by default. If the parameter -ReturnRawResult is used, the raw result of the KQL query is returned which is a JSON object. + .PARAMETER WorkspaceId Id of the Fabric Workspace for which the KQL command should be executed. The value for WorkspaceId is a GUID. @@ -39,10 +27,7 @@ function Invoke-FabricKQLCommand { a PowerShell object. .EXAMPLE - Invoke-FabricKQLCommand ` - -WorkspaceId '12345678-1234-1234-1234-123456789012' ` - -KQLDatabaseName 'MyKQLDatabase' ` - -KQLCommand '.create table MyTable (MyColumn: string)' + Invoke-FabricKQLCommand -WorkspaceId '12345678-1234-1234-1234-123456789012' -KQLDatabaseName 'MyKQLDatabase'-KQLCommand '.create table MyTable (MyColumn: string) This example will create a table named 'MyTable' with a column named 'MyColumn' in the KQLDatabase 'MyKQLDatabase'. @@ -69,11 +54,10 @@ function Invoke-FabricKQLCommand { and it will return the result as the raw result of the KQL command, which is a JSON object. .NOTES + Revision History: - Revsion History: - - - 2024-12-22 - FGE: Added Verbose Output - - 2024-12-27 - FGE: Major Update to support KQL Queries and Management Commands + 2024-12-22 - FGE: Added Verbose Output + 2024-12-27 - FGE: Major Update to support KQL Queries and Management Commands #> @@ -227,4 +211,4 @@ process { end {} -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 index e35ce0dd..20a9fa45 100644 --- a/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 +++ b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 @@ -1,6 +1,19 @@ +function Get-FabricLakehouseTable { +<# +.SYNOPSIS +Retrieves tables from a specified Lakehouse in a Fabric workspace. +.DESCRIPTION +This function retrieves tables from a specified Lakehouse in a Fabric workspace. It handles pagination using a continuation token to ensure all data is retrieved. +.PARAMETER WorkspaceId +The ID of the workspace containing the Lakehouse. +.PARAMETER LakehouseId +The ID of the Lakehouse from which to retrieve tables. +.EXAMPLE +Get-FabricLakehouseTable -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" +This example retrieves all tables from the specified Lakehouse in the specified workspace. -function Get-FabricLakehouseTable { +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -11,7 +24,7 @@ function Get-FabricLakehouseTable { [ValidateNotNullOrEmpty()] [string]$LakehouseId ) - + try { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug @@ -24,19 +37,19 @@ function Get-FabricLakehouseTable { $continuationToken = $null $tables = @() $maxResults = 100 - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } $baseApiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}/tables?maxResults={3}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId, $maxResults - + # Step 3: Loop to retrieve data with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug do { # Step 4: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) @@ -62,12 +75,12 @@ function Get-FabricLakehouseTable { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 7: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $tables += $response.data - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -99,6 +112,6 @@ function Get-FabricLakehouseTable { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 index b1ca0822..8db8c767 100644 --- a/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 +++ b/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 @@ -1,10 +1,42 @@ function Load-FabricLakehouseTable { +<# +.SYNOPSIS +Loads data into a specified table in a Lakehouse within a Fabric workspace. +.DESCRIPTION +Loads data into a specified table in a Lakehouse within a Fabric workspace. The function supports loading data from files or folders, with options for file format and CSV settings. +.PARAMETER WorkspaceId +The ID of the workspace containing the Lakehouse. +.PARAMETER LakehouseId +The ID of the Lakehouse where the table resides. +.PARAMETER TableName +The name of the table to load data into. +.PARAMETER PathType +The type of path to load data from (File or Folder). +.PARAMETER RelativePath +The relative path to the file or folder to load data from. +.PARAMETER FileFormat +The format of the file to load data from (CSV or Parquet). +.PARAMETER CsvDelimiter +The delimiter used in the CSV file (default is comma). +.PARAMETER CsvHeader +Indicates whether the CSV file has a header row (default is false). +.PARAMETER Mode +The mode for loading data (append or overwrite). +.PARAMETER Recursive +Indicates whether to load data recursively from subfolders (default is false). +.EXAMPLE +Load-FabricLakehouseTable -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" -TableName "your-table-name" -PathType "File" -RelativePath "path/to/your/file.csv" -FileFormat "CSV" -CsvDelimiter "," -CsvHeader $true -Mode "append" -Recursive $false +This example loads data from a CSV file into the specified table in the Lakehouse. +.EXAMPLE +Load-FabricLakehouseTable -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" -TableName "your-table-name" -PathType "Folder" -RelativePath "path/to/your/folder" -FileFormat "Parquet" -Mode "overwrite" -Recursive $true +This example loads data from a folder into the specified table in the Lakehouse, overwriting any existing data. +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$LakehouseId, @@ -31,7 +63,7 @@ function Load-FabricLakehouseTable { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$CsvDelimiter = ",", - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$CsvHeader = $false, @@ -40,7 +72,7 @@ function Load-FabricLakehouseTable { [ValidateNotNullOrEmpty()] [ValidateSet('append', 'overwrite')] [string]$Mode = "append", - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$Recursive = $false @@ -66,7 +98,7 @@ function Load-FabricLakehouseTable { format = $FileFormat } } - + if ($FileFormat -eq "CSV") { $body.formatOptions.delimiter = $CsvDelimiter $body.formatOptions.hasHeader = $CsvHeader @@ -101,11 +133,11 @@ function Load-FabricLakehouseTable { switch ($statusCode) { 202 { Write-Message -Message "Load table '$TableName' request accepted. Load table operation in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result @@ -118,7 +150,7 @@ function Load-FabricLakehouseTable { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 index 200788c3..7bd87fe8 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 @@ -1,4 +1,18 @@ function Get-FabricMirroredDatabaseStatus { +<# +.SYNOPSIS +Retrieves the status of a mirrored database in a specified workspace. +.DESCRIPTION +Retrieves the status of a mirrored database in a specified workspace. The function validates the authentication token, constructs the API endpoint URL, and makes a POST request to retrieve the mirroring status. +It handles errors and logs messages at various levels (Debug, Error). +.PARAMETER WorkspaceId +The ID of the workspace containing the mirrored database. +.PARAMETER MirroredDatabaseId +the ID of the mirrored database whose status is to be retrieved. +.EXAMPLE +Get-FabricMirroredDatabaseStatus -WorkspaceId "your-workspace-id" -MirroredDatabaseId "your-mirrored-database-id" +This example retrieves the status of a mirrored database with the specified ID in the specified workspace. +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -15,10 +29,10 @@ function Get-FabricMirroredDatabaseStatus { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getMirroringStatus" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -28,7 +42,7 @@ function Get-FabricMirroredDatabaseStatus { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -37,9 +51,9 @@ function Get-FabricMirroredDatabaseStatus { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 9: Handle results - + Write-Message -Message "Returning status of MirroredDatabases." -Level Debug return $response } @@ -47,6 +61,6 @@ function Get-FabricMirroredDatabaseStatus { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 index f37c4cfe..9bc1f3cc 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 @@ -1,4 +1,20 @@ function Get-FabricMirroredDatabaseTableStatus { +<# +.SYNOPSIS +Retrieves the status of tables in a mirrored database. +.DESCRIPTION +Retrieves the status of tables in a mirrored database. The function validates the authentication token, constructs the API endpoint URL, and makes a POST request to retrieve the mirroring status of tables. It handles errors and logs messages at various levels (Debug, Error). +.PARAMETER WorkspaceId +The ID of the workspace containing the mirrored database. +.PARAMETER MirroredDatabaseId +The ID of the mirrored database whose table status is to be retrieved. +.EXAMPLE +Get-FabricMirroredDatabaseTableStatus -WorkspaceId "your-workspace-id" -MirroredDatabaseId "your-mirrored-database-id" +This example retrieves the status of tables in a mirrored database with the specified ID in the specified workspace. +.NOTES +The function retrieves the PowerBI access token and makes a POST request to the PowerBI API to retrieve the status of tables in the specified mirrored database. It then returns the 'value' property of the response, which contains the table statuses. + +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -23,24 +39,24 @@ function Get-FabricMirroredDatabaseTableStatus { if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/getTablesMirroringStatus" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - + # Step 3: Loop to retrieve data with continuation token - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -50,7 +66,7 @@ function Get-FabricMirroredDatabaseTableStatus { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -59,12 +75,12 @@ function Get-FabricMirroredDatabaseTableStatus { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $MirroredDatabaseTableStatus += $response.data - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -84,7 +100,7 @@ function Get-FabricMirroredDatabaseTableStatus { Write-Message -Message "Loop finished and all data added to the list" -Level Debug # Step 9: Handle results - # Return all Mirrored Database Table Status + # Return all Mirrored Database Table Status Write-Message -Message "No filter provided. Returning all MirroredDatabases." -Level Debug $MirroredDatabaseTableStatus } @@ -92,6 +108,6 @@ function Get-FabricMirroredDatabaseTableStatus { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Utils/Get-FabricLongRunningOperation.ps1 b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 index 4fa2cc07..d73a2219 100644 --- a/source/Public/Utils/Get-FabricLongRunningOperation.ps1 +++ b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 @@ -1,9 +1,10 @@ +function Get-FabricLongRunningOperation { <# .SYNOPSIS Monitors the status of a long-running operation in Microsoft Fabric. .DESCRIPTION -The Get-FabricLongRunningOperation function queries the Microsoft Fabric API to check the status of a +The Get-FabricLongRunningOperation function queries the Microsoft Fabric API to check the status of a long-running operation. It periodically polls the operation until it reaches a terminal state (Succeeded or Failed). .PARAMETER operationId @@ -20,15 +21,13 @@ This command polls the status of the operation with the given operationId every .NOTES - Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. -.AUTHOR -Tiago Balabuch - + AUTHOR + Tiago Balabuch #> -function Get-FabricLongRunningOperation { param ( [Parameter(Mandatory = $false)] [string]$operationId, - + [Parameter(Mandatory = $false)] [string]$location, @@ -45,7 +44,7 @@ function Get-FabricLongRunningOperation { $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}" -f $operationId } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + try { do { @@ -73,7 +72,7 @@ function Get-FabricLongRunningOperation { # Log status for debugging Write-Message -Message "Operation Status: $($operation.status)" -Level Debug - + } while ($operation.status -notin @("Succeeded", "Completed", "Failed")) # Step 5: Return the operation result @@ -85,4 +84,4 @@ function Get-FabricLongRunningOperation { Write-Message -Message "An error occurred while checking the operation: $errorDetails" -Level Error throw } -} \ No newline at end of file +} diff --git a/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 b/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 index 00000c6c..98292203 100644 --- a/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 +++ b/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 @@ -1,9 +1,10 @@ +function Get-FabricLongRunningOperationResult { <# .SYNOPSIS Retrieves the result of a completed long-running operation from the Microsoft Fabric API. .DESCRIPTION -The Get-FabricLongRunningOperationResult function queries the Microsoft Fabric API to fetch the result +The Get-FabricLongRunningOperationResult function queries the Microsoft Fabric API to fetch the result of a specific long-running operation. This is typically used after confirming the operation has completed successfully. .PARAMETER operationId @@ -18,11 +19,9 @@ This command fetches the result of the operation with the specified operationId. - Ensure the Fabric API headers (e.g., authorization tokens) are defined in $FabricConfig.FabricHeaders. - This function does not handle polling. Ensure the operation is in a terminal state before calling this function. -.AUTHOR -Tiago Balabuch - + AUTHOR + Tiago Balabuch #> -function Get-FabricLongRunningOperationResult { param ( [Parameter(Mandatory = $true)] [string]$operationId @@ -42,7 +41,7 @@ function Get-FabricLongRunningOperationResult { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 3: Return the result Write-Message -Message "Result response code: $statusCode" -Level Debug @@ -56,7 +55,7 @@ function Get-FabricLongRunningOperationResult { Write-Message "Error Code: $($response.errorCode)" -Level Debug } - return $response + return $response } catch { # Step 3: Capture and log error details @@ -64,4 +63,4 @@ function Get-FabricLongRunningOperationResult { Write-Message -Message "An error occurred while returning the operation result: $errorDetails" -Level Error throw } -} \ No newline at end of file +} diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 index e1c7a43d..76b674e8 100644 --- a/source/Public/Utils/Set-FabricApiHeaders.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -1,9 +1,10 @@ +function Set-FabricApiHeaders { <# .SYNOPSIS Sets the Fabric API headers with a valid token for the specified Azure tenant. .DESCRIPTION -The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. +The `Set-FabricApiHeaders` function logs into the specified Azure tenant, retrieves an access token for the Fabric API, and sets the necessary headers for subsequent API requests. It also updates the token expiration time and global tenant ID. .PARAMETER TenantId @@ -33,11 +34,10 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the - Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). - Relies on a global `$FabricConfig` object for storing headers and token metadata. -.AUTHOR -Tiago Balabuch + AUTHOR + Tiago Balabuch #> -function Set-FabricApiHeaders { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -60,7 +60,7 @@ function Set-FabricApiHeaders { if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error throw "AppSecret is required when using AppId." - } + } if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { Write-Message -Message "AppId is required when using AppSecret." -Level Error throw "AppId is required when using AppId." @@ -68,16 +68,16 @@ function Set-FabricApiHeaders { # Step 3: Connect to the Azure account # Using AppId and AppSecret if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { - + Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug Write-Message -Message "Logging in using the AppId: $AppId" -Level Info $psCredential = [pscredential]::new($AppId, $AppSecret) Connect-AzAccount -ServicePrincipal -Credential $psCredential -Tenant $tenantId - } + } # Using the current user else { - + Write-Message -Message "Logging in using the current user" -Level Debug Write-Message -Message "Logging in using the current user" -Level Info Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null diff --git a/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 b/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 index 4f2a0084..cea1fee8 100644 --- a/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 +++ b/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 @@ -1,3 +1,4 @@ +function New-FabricWorkspaceUsageMetricsReport { <# .SYNOPSIS Retrieves the workspace usage metrics dataset ID. @@ -18,7 +19,7 @@ The function retrieves the PowerBI access token and the Fabric API cluster URI. #> # This function retrieves the workspace usage metrics dataset ID. -function New-FabricWorkspaceUsageMetricsReport { + # Define aliases for the function for flexibility. [Alias("New-FabWorkspaceUsageMetricsReport")] [CmdletBinding(SupportsShouldProcess)] @@ -45,4 +46,4 @@ function New-FabricWorkspaceUsageMetricsReport { else { return $null } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 index e54d8fd4..151778e0 100644 --- a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 +++ b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 @@ -1,3 +1,4 @@ +function Register-FabricWorkspaceToCapacity { <# .SYNOPSIS Sets a PowerBI workspace to a capacity. @@ -31,7 +32,6 @@ The function makes a POST request to the PowerBI API to Set the workspace to the # This function Sets a PowerBI workspace to a capacity. # It supports multiple aliases for flexibility. -function Register-FabricWorkspaceToCapacity { [Alias("Register-FabWorkspaceToCapacity")] [CmdletBinding(SupportsShouldProcess)] param( @@ -57,8 +57,8 @@ function Register-FabricWorkspaceToCapacity { # The body of the request is created. It contains the capacity ID. $body = @{ capacityId = $CapacityId - } - + } + Confirm-FabricAuthToken | Out-Null # The workspace is Seted to the capacity by making a POST request to the PowerBI API. @@ -68,4 +68,4 @@ function Register-FabricWorkspaceToCapacity { return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/assignToCapacity" -Body $body } } -} \ No newline at end of file +} From d7295eb4fcdd4501646208258b5e47477313da2b Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:49:59 +0100 Subject: [PATCH 47/76] Refactor Functions for Clarity and Consistency for Pester Help Tests Incomplete Help #16 #24 This commit refactors the Get-FabricWorkspaceDatasetRefreshes, Get-FabricWorkspaceUsageMetricsData, and Get-FabricWorkspaceUsers functions to improve clarity and consistency. The function definitions have been streamlined, and aliases have been maintained for flexibility. Thank you! --- .../Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 | 4 ++-- .../Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 | 4 ++-- source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 b/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 index 81188a66..f566d601 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 @@ -1,3 +1,4 @@ +function Get-FabricWorkspaceDatasetRefreshes { <# .SYNOPSIS Retrieves the refresh history of all datasets in a specified PowerBI workspace. @@ -25,7 +26,6 @@ #> # Define a function to get the refresh history of all datasets in a PowerBI workspace -function Get-FabricWorkspaceDatasetRefreshes { # Set aliases for the function [Alias("Get-FabWorkspaceDatasetRefreshes")] param( @@ -50,4 +50,4 @@ function Get-FabricWorkspaceDatasetRefreshes { } # Return the refresh history array return $refs -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 b/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 index f94d101a..e404971c 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 @@ -1,3 +1,4 @@ +function Get-FabricWorkspaceUsageMetricsData { <# .SYNOPSIS Retrieves workspace usage metrics data. @@ -21,7 +22,6 @@ The function retrieves the PowerBI access token and creates a new usage metrics #> # This function retrieves workspace usage metrics. -function Get-FabricWorkspaceUsageMetricsData { # Define aliases for the function for flexibility. [Alias("Get-FabWorkspaceUsageMetricsData")] @@ -57,4 +57,4 @@ function Get-FabricWorkspaceUsageMetricsData { } # Return the hashtable of reports. return $reports -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 b/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 index 38426fcd..1bab6d2b 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 @@ -1,3 +1,4 @@ +function Get-FabricWorkspaceUsers { <# .SYNOPSIS Retrieves the users of a workspace. @@ -26,7 +27,6 @@ The function defines parameters for the workspace ID and workspace object. If th #> # This function retrieves the users of a workspace. -function Get-FabricWorkspaceUsers { # Define aliases for the function for flexibility. [Alias("Get-FabWorkspaceUsers")] @@ -54,4 +54,4 @@ function Get-FabricWorkspaceUsers { return (Invoke-PowerBIRestMethod -Method get -Url ("groups/$($workspace.Id)/users") | ConvertFrom-Json).value } -} \ No newline at end of file +} From 9a611422e1cb1cae22de4ff0dd0776e2acf92606 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:54:39 +0100 Subject: [PATCH 48/76] Enhance Documentation for Import-FabricItem Function for Pester Help Tests Incomplete Help #16 Updated the function documentation to provide a clearer description of the Import-FabricItem function, including its capabilities and how it handles the import of datasets and reports. This change aims to improve clarity for users and maintain consistency across the codebase. Thank you! --- source/Public/Item/Import-FabricItem.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/Public/Item/Import-FabricItem.ps1 b/source/Public/Item/Import-FabricItem.ps1 index b885fb21..a13693d1 100644 --- a/source/Public/Item/Import-FabricItem.ps1 +++ b/source/Public/Item/Import-FabricItem.ps1 @@ -2,6 +2,9 @@ <# .SYNOPSIS Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source. + .DESCRIPTION + The Import-FabricItem function imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source. It supports multiple aliases for flexibility. + The function handles the import of datasets and reports, ensuring that the correct item type is used and that the items are created or updated as necessary. .PARAMETER fileOverrides This parameter let's you override a PBIP file without altering the local file. @@ -94,7 +97,7 @@ Function Import-FabricItem { if ($itemType -ieq "dataset") { $itemType = "SemanticModel" } - + $displayName = $itemMetadata.displayName @@ -161,11 +164,11 @@ Function Import-FabricItem { } } else { - + $fileContent = Get-Content -Path $filePath -AsByteStream -Raw } } - + $partPath = $filePath.Replace($itemPathAbs, "").TrimStart("\").Replace("\", "/") write-host "Processing part: '$partPath'" $fileEncodedContent = [Convert]::ToBase64String($fileContent) From 049f749caeb47821a776ab6a10665a3a83dbaf34 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 12:55:25 +0100 Subject: [PATCH 49/76] rename duplicate function This update refactors the Invoke-FabricAPIRequest function to improve clarity and consistency in the code. The changes include enhancing documentation, organizing parameters, and ensuring better readability. These improvements will facilitate easier testing and maintenance for Pester Help Tests. Thank you! --- source/Public/Invoke-FabricAPIRequest.ps1 | 21 ++--- .../Public/Utils/Invoke-FabricAPIRequest.ps1 | 86 +++++++++++++++---- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/source/Public/Invoke-FabricAPIRequest.ps1 b/source/Public/Invoke-FabricAPIRequest.ps1 index d54b46a9..0461101c 100644 --- a/source/Public/Invoke-FabricAPIRequest.ps1 +++ b/source/Public/Invoke-FabricAPIRequest.ps1 @@ -1,3 +1,4 @@ +Function Invoke-FabricAPIRequest { <# .SYNOPSIS @@ -46,14 +47,6 @@ This function was originally written by Rui Romano. https://github.com/RuiRomano/fabricps-pbip #> - - -Function Invoke-FabricAPIRequest { - <# - .SYNOPSIS - Sends an HTTP request to a Fabric API endpoint and retrieves the response. - Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response - #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [string] $authToken, @@ -72,7 +65,7 @@ Function Invoke-FabricAPIRequest { $requestUrl = "$($FabricSession.BaseApiUrl)/$uri" Write-Verbose "Calling $requestUrl" - $response = Invoke-WebRequest -Headers $fabricHeaders -Method $method -Uri $requestUrl -Body $body -TimeoutSec $timeoutSec + $response = Invoke-WebRequest -Headers $fabricHeaders -Method $method -Uri $requestUrl -Body $body -TimeoutSec $timeoutSec if ($response.StatusCode -eq 202) { if ($uri -match "jobType=Pipeline") { @@ -81,14 +74,14 @@ Function Invoke-FabricAPIRequest { else { do { $asyncUrl = [string]$response.Headers.Location - + Write-Output "Waiting for request to complete. Sleeping..." Start-Sleep -Seconds 5 $response2 = Invoke-WebRequest -Headers $fabricHeaders -Method Get -Uri $asyncUrl $lroStatusContent = $response2.Content | ConvertFrom-Json } while ($lroStatusContent.status -ine "succeeded" -and $lroStatusContent.status -ine "failed") - + try { $response = Invoke-WebRequest -Headers $fabricHeaders -Method Get -Uri "$asyncUrl/result" } @@ -118,7 +111,7 @@ Function Invoke-FabricAPIRequest { catch { $ex = $_.Exception $message = $null - + if ($null -ne $ex.Response) { $responseStatusCode = [int]$ex.Response.StatusCode @@ -169,7 +162,7 @@ Function Invoke-FabricAPIRequest { $message = "$($ex.Message)" } if ($message) { - throw $message + throw $message } } -} \ No newline at end of file +} diff --git a/source/Public/Utils/Invoke-FabricAPIRequest.ps1 b/source/Public/Utils/Invoke-FabricAPIRequest.ps1 index c2d4f894..f37590ad 100644 --- a/source/Public/Utils/Invoke-FabricAPIRequest.ps1 +++ b/source/Public/Utils/Invoke-FabricAPIRequest.ps1 @@ -1,21 +1,69 @@ -function Invoke-FabricAPIRequest { +function Invoke-FabricAPIRequest_duplicate { + +<# + .SYNOPSIS + Sends an HTTP request to a Fabric API endpoint and retrieves the response. + Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response + + .DESCRIPTION + The Invoke-FabricAPIRequest function is used to send an HTTP request to a Fabric API endpoint and retrieve the response. It handles various aspects such as authentication, 429 throttling, and Long-Running-Operation (LRO) response. + + .PARAMETER authToken + The authentication token to be used for the request. If not provided, it will be obtained using the Get-FabricAuthToken function. + + .PARAMETER uri + The URI of the Fabric API endpoint to send the request to. + + .PARAMETER method + The HTTP method to be used for the request. Valid values are 'Get', 'Post', 'Delete', 'Put', and 'Patch'. The default value is 'Get'. + + .PARAMETER body + The body of the request, if applicable. + + .PARAMETER contentType + The content type of the request. The default value is 'application/json; charset=utf-8'. + + .PARAMETER timeoutSec + The timeout duration for the request in seconds. The default value is 240 seconds. + + .PARAMETER outFile + The file path to save the response content to, if applicable. + + .PARAMETER retryCount + The number of times to retry the request in case of a 429 (Too Many Requests) error. The default value is 0. + + .EXAMPLE + Invoke-FabricAPIRequest -uri "/api/resource" -method "Get" + + This example sends a GET request to the "/api/resource" endpoint of the Fabric API. + + .EXAMPLE + Invoke-FabricAPIRequest -authToken "abc123" -uri "/api/resource" -method "Post" -body $requestBody + + This example sends a POST request to the "/api/resource" endpoint of the Fabric API with a request body. + + .NOTES + This function requires the Get-FabricAuthToken function to be defined in the same script or module. + This function was originally written by Rui Romano. + https://github.com/RuiRomano/fabricps-pbip +#> param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [hashtable]$Headers, - + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$BaseURI, - [Parameter(Mandatory = $true)] - [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] + [Parameter(Mandatory = $true)] + [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] [string] $Method, - - [Parameter(Mandatory = $false)] + + [Parameter(Mandatory = $false)] [string] $Body, - [Parameter(Mandatory = $false)] + [Parameter(Mandatory = $false)] [string] $ContentType = "application/json; charset=utf-8" ) @@ -62,7 +110,7 @@ function Invoke-FabricAPIRequest { switch ($statusCode) { 200 { - Write-Message -Message "API call succeeded." -Level Debug + Write-Message -Message "API call succeeded." -Level Debug # Step 5: Handle and log the response if ($response) { if ($response.PSObject.Properties.Name -contains 'value') { @@ -82,34 +130,34 @@ function Invoke-FabricAPIRequest { } } 201 { - Write-Message -Message "Resource created successfully." -Level Info + Write-Message -Message "Resource created successfully." -Level Info return $response - } + } 202 { - # Step 6: Handle long-running operations + # Step 6: Handle long-running operations Write-Message -Message "Request accepted. Provisioning in progress." -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - # Need to implement a retry mechanism for long running operations - # [string]$retryAfter = $responseHeader["Retry-After"] + # Need to implement a retry mechanism for long running operations + # [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId', Location: '$location'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - + # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation succeeded. Fetching result." -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug + Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug return $operationResult } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } 400 { $errorMsg = "Bad Request" } 401 { $errorMsg = "Unauthorized" } @@ -120,7 +168,7 @@ function Invoke-FabricAPIRequest { 500 { $errorMsg = "Internal Server Error" } default { $errorMsg = "Unexpected response code: $statusCode" } } - + if ($statusCode -notin 200, 201, 202) { Write-Message -Message "$errorMsg : $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error From ec2e6ec4d36b1a015517f6882b06c9e9e13a0a78 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 15:06:55 +0100 Subject: [PATCH 50/76] Enhance PowerShell scripts for Microsoft Fabric Incomplete Help #16 #24 - Added parameters for subscription ID and resource group name in Get-FabricCapacitySkus.ps1. - Renamed CopyJobName parameter to CopyJob in Get-FabricCopyJob.ps1 for clarity. - Updated Get-FabricDataPipeline.ps1 to include parameters for DataPipelineId and DataPipelineName. - Enhanced Get-FabricDatamart.ps1 with optional parameters for datamartId and datamartName. - Added EnvironmentId parameter to Get-FabricEnvironment.ps1 for specific environment retrieval. - Introduced Workspace parameter and itemID in Get-FabricItem.ps1 for improved item retrieval. - Added WorkspaceId parameter in Update-FabricKQLDashboard.ps1 for specifying the workspace. - Enhanced Get-FabricKQLDatabase.ps1 with optional KQLDatabaseId and KQLDatabaseName parameters. - Expanded New-FabricKQLDatabase.ps1 with additional parameters for KQL invitation token and source cluster URI. - Updated Get-FabricKQLQueryset.ps1 to include KQLQuerysetId for specific query set retrieval. - Enhanced Get-FabricLakehouse.ps1 with LakehouseId parameter for specific lakehouse retrieval. - Added MirroredDatabaseId parameter in Get-FabricMirroredDatabase.ps1 for specific mirrored database retrieval. - Introduced MirroredWarehouseId parameter in Get-FabricMirroredWarehouse.ps1 for specific mirrored warehouse retrieval. - Enhanced Get-FabricNotebook.ps1 with NotebookId parameter for specific notebook retrieval. - Added location parameter in Get-FabricLongRunningOperation.ps1 for operation status checking. - Expanded New-FabricWorkspace.ps1 with optional parameters for WorkspaceDescription and CapacityId. --- .../Capacity/Get-FabricCapacitySkus.ps1 | 6 +++ source/Public/Copy Job/Get-FabricCopyJob.ps1 | 2 +- .../Data Pipeline/Get-FabricDataPipeline.ps1 | 14 +++--- source/Public/Datamart/Get-FabricDatamart.ps1 | 18 ++++--- .../Environment/Get-FabricEnvironment.ps1 | 19 ++++---- source/Public/Item/Get-FabricItem.ps1 | 6 +++ .../Update-FabricKQLDashboard.ps1 | 12 +++-- .../KQL Database/Get-FabricKQLDatabase.ps1 | 27 ++++++----- .../KQL Database/New-FabricKQLDatabase.ps1 | 47 +++++++++++++------ .../KQL Queryset/Get-FabricKQLQueryset.ps1 | 26 +++++----- .../Public/Lakehouse/Get-FabricLakehouse.ps1 | 26 +++++----- .../Get-FabricMirroredDatabase.ps1 | 30 ++++++------ .../Get-FabricMirroredWarehouse.ps1 | 30 ++++++------ source/Public/Notebook/Get-FabricNotebook.ps1 | 30 ++++++------ .../Utils/Get-FabricLongRunningOperation.ps1 | 3 ++ .../Public/Workspace/New-FabricWorkspace.ps1 | 19 +++++--- 16 files changed, 188 insertions(+), 127 deletions(-) diff --git a/source/Public/Capacity/Get-FabricCapacitySkus.ps1 b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 index 84f5a3d8..9b6cd875 100644 --- a/source/Public/Capacity/Get-FabricCapacitySkus.ps1 +++ b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 @@ -7,6 +7,12 @@ Retrieves the fabric capacity information. .DESCRIPTION This function makes a GET request to the Fabric API to retrieve the tenant settings. +.PARAMETER subscriptionID +Specifies the subscription ID for the Azure subscription. + +.PARAMETER ResourceGroupName +Specifies the name of the resource group in which the Fabric capacity is located. + .PARAMETER capacity Specifies the capacity to retrieve information for. If not provided, all capacities will be retrieved. diff --git a/source/Public/Copy Job/Get-FabricCopyJob.ps1 b/source/Public/Copy Job/Get-FabricCopyJob.ps1 index 3649c343..30229e27 100644 --- a/source/Public/Copy Job/Get-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJob.ps1 @@ -13,7 +13,7 @@ function Get-FabricCopyJob { .PARAMETER CopyJobId The unique identifier of the CopyJob to retrieve. This parameter is optional. -.PARAMETER CopyJobName +.PARAMETER CopyJob The name of the CopyJob to retrieve. This parameter is optional. .EXAMPLE diff --git a/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 index 6f944615..7a8ca077 100644 --- a/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 @@ -1,3 +1,4 @@ +function Get-FabricDataPipeline { <# .SYNOPSIS Retrieves data pipelines from a specified Microsoft Fabric workspace. @@ -9,10 +10,10 @@ .PARAMETER WorkspaceId The unique identifier of the workspace where the Data Pipeline exists. This parameter is mandatory. -.PARAMETER Data PipelineId +.PARAMETER DataPipelineId The unique identifier of the Data Pipeline to retrieve. This parameter is optional. -.PARAMETER Data PipelineName +.PARAMETER DataPipelineName The name of the Data Pipeline to retrieve. This parameter is optional. .EXAMPLE @@ -29,7 +30,6 @@ Author: Tiago Balabuch #> -function Get-FabricDataPipeline { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -60,13 +60,13 @@ function Get-FabricDataPipeline { # Construct the API endpoint URL $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines" -f $FabricConfig.BaseUrl, $WorkspaceId - + # Invoke the Fabric API to retrieve capacity details $DataPipelines = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Get - + # Filter results based on provided parameters $response = if ($DataPipelineId) { $DataPipelines | Where-Object { $_.Id -eq $DataPipelineId } @@ -94,6 +94,6 @@ function Get-FabricDataPipeline { # Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve DataPipeline. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Datamart/Get-FabricDatamart.ps1 b/source/Public/Datamart/Get-FabricDatamart.ps1 index 55de12a3..e8dd4de3 100644 --- a/source/Public/Datamart/Get-FabricDatamart.ps1 +++ b/source/Public/Datamart/Get-FabricDatamart.ps1 @@ -1,3 +1,4 @@ +function Get-FabricDatamart { <# .SYNOPSIS Retrieves datamarts from a specified workspace. @@ -9,6 +10,12 @@ .PARAMETER WorkspaceId The ID of the workspace from which to retrieve datamarts. This parameter is mandatory. +.PARAMETER datamartId + The ID of the specific datamart to retrieve. This parameter is optional. + +.PARAMETER datamartName + The name of the specific datamart to retrieve. This parameter is optional. + .EXAMPLE Get-FabricDatamart -WorkspaceId "12345" This example retrieves all datamarts from the workspace with ID "12345". @@ -19,7 +26,6 @@ Author: Tiago Balabuch #> -function Get-FabricDatamart { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -41,7 +47,7 @@ function Get-FabricDatamart { Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug # Step 3: Initialize variables - + $apiEndpointURI = "{0}/workspaces/{1}/Datamarts" -f $FabricConfig.BaseUrl, $WorkspaceId $Datamarts = = Invoke-FabricAPIRequest ` @@ -49,7 +55,7 @@ function Get-FabricDatamart { -Headers $FabricConfig.FabricHeaders ` -Method Get # Step 9: Filter results based on provided parameters - + $response = if ($datamartId) { $Datamarts | Where-Object { $_.Id -eq $datamartId } } @@ -61,7 +67,7 @@ function Get-FabricDatamart { Write-Message -Message "No filter specified. Returning all datamarts." -Level Debug return $Datamarts } - + # Step 10: Handle results if ($response) { Write-Message -Message "Datamart found matching the specified criteria." -Level Debug @@ -76,5 +82,5 @@ function Get-FabricDatamart { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Datamart. Error: $errorDetails" -Level Error - } -} \ No newline at end of file + } +} diff --git a/source/Public/Environment/Get-FabricEnvironment.ps1 b/source/Public/Environment/Get-FabricEnvironment.ps1 index 48e497c8..cddb5443 100644 --- a/source/Public/Environment/Get-FabricEnvironment.ps1 +++ b/source/Public/Environment/Get-FabricEnvironment.ps1 @@ -1,3 +1,4 @@ +function Get-FabricEnvironment { <# .SYNOPSIS Retrieves an environment or a list of environments from a specified workspace in Microsoft Fabric. @@ -8,6 +9,9 @@ The `Get-FabricEnvironment` function sends a GET request to the Fabric API to re .PARAMETER WorkspaceId (Mandatory) The ID of the workspace to query environments. +.PARAMETER EnvironmentId +(Optional) The ID of a specific environment to retrieve. + .PARAMETER EnvironmentName (Optional) The name of the specific environment to retrieve. @@ -26,11 +30,10 @@ Retrieves all environments in workspace "12345". - Calls `Test-TokenExpired` to ensure token validity before making the API request. - Returns the matching environment details or all environments if no filter is provided. -Author: Tiago Balabuch +Author: Tiago Balabuch #> -function Get-FabricEnvironment { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -68,14 +71,14 @@ function Get-FabricEnvironment { } $baseApiEndpointUrl = "{0}/workspaces/{1}/environments" -f $FabricConfig.BaseUrl, $WorkspaceId - + # Step 4: Loop to retrieve data with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) @@ -101,7 +104,7 @@ function Get-FabricEnvironment { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug @@ -124,7 +127,7 @@ function Get-FabricEnvironment { } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $environment = if ($EnvironmentId) { $environments | Where-Object { $_.Id -eq $EnvironmentId } @@ -152,6 +155,6 @@ function Get-FabricEnvironment { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Item/Get-FabricItem.ps1 b/source/Public/Item/Get-FabricItem.ps1 index 9bb0719d..d2bd3855 100644 --- a/source/Public/Item/Get-FabricItem.ps1 +++ b/source/Public/Item/Get-FabricItem.ps1 @@ -9,6 +9,12 @@ The Get-FabricItem function retrieves fabric items from a specified workspace. I .PARAMETER workspaceId The ID of the workspace from which to retrieve the fabric items. +.PARAMETER Workspace +The workspace object from which to retrieve the fabric items. This parameter can be piped into the function. + +.PARAMETER itemID +The ID of the specific item to retrieve. If not specified, all items will be retrieved. + .PARAMETER type (Optional) The type of the fabric items to retrieve. If not specified, all items will be retrieved. diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 index 874a614b..03218957 100644 --- a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 @@ -1,3 +1,4 @@ +function Update-FabricKQLDashboard { <# .SYNOPSIS Updates the properties of a Fabric KQLDashboard. @@ -14,6 +15,9 @@ The new name for the KQLDashboard. .PARAMETER KQLDashboardDescription (Optional) The new description for the KQLDashboard. +.PARAMETER WorkspaceId +The unique identifier of the workspace where the KQLDashboard exists. + .EXAMPLE Update-FabricKQLDashboard -KQLDashboardId "KQLDashboard123" -KQLDashboardName "NewKQLDashboardName" @@ -28,17 +32,15 @@ Updates both the name and description of the KQLDashboard "KQLDashboard123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Update-FabricKQLDashboard { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLDashboardId, diff --git a/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 index 94677fe6..b71c9b65 100644 --- a/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 @@ -1,3 +1,4 @@ +function Get-FabricKQLDatabase { <# .SYNOPSIS Retrieves an KQLDatabase or a list of KQLDatabases from a specified workspace in Microsoft Fabric. @@ -8,6 +9,9 @@ The `Get-FabricKQLDatabase` function sends a GET request to the Fabric API to re .PARAMETER WorkspaceId (Mandatory) The ID of the workspace to query KQLDatabases. +.PARAMETER KQLDatabaseId +(Optional) The ID of a specific KQLDatabase to retrieve. + .PARAMETER KQLDatabaseName (Optional) The name of the specific KQLDatabase to retrieve. @@ -25,10 +29,9 @@ Retrieves all KQLDatabases in workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> -function Get-FabricKQLDatabase { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -59,11 +62,11 @@ function Get-FabricKQLDatabase { # Step 3: Initialize variables $continuationToken = $null $KQLDatabases = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId @@ -71,14 +74,14 @@ function Get-FabricKQLDatabase { do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -88,7 +91,7 @@ function Get-FabricKQLDatabase { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -97,12 +100,12 @@ function Get-FabricKQLDatabase { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $KQLDatabases += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -120,7 +123,7 @@ function Get-FabricKQLDatabase { } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $KQLDatabase = if ($KQLDatabaseId) { $KQLDatabases | Where-Object { $_.Id -eq $KQLDatabaseId } @@ -148,6 +151,6 @@ function Get-FabricKQLDatabase { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 index 21098872..898dd37f 100644 --- a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 @@ -1,15 +1,34 @@ +function New-FabricKQLDatabase { <# .SYNOPSIS Creates a new KQLDatabase in a specified Microsoft Fabric workspace. .DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLDatabase -in the specified workspace. It supports optional parameters for KQLDatabase description +This function sends a POST request to the Microsoft Fabric API to create a new KQLDatabase +in the specified workspace. It supports optional parameters for KQLDatabase description and path definitions for the KQLDatabase content. .PARAMETER WorkspaceId The unique identifier of the workspace where the KQLDatabase will be created. +.PARAMETER KQLInvitationToken +An optional invitation token for the KQLDatabase. + +.PARAMETER KQLSourceClusterUri +An optional source cluster URI for the KQLDatabase. + +.PARAMETER KQLDatabasePathSchemaDefinition +the path to the KQLDatabase schema definition file (e.g., .kql file) to upload. + +.PARAMETER KQLSourceDatabaseName +An optional source database name for the KQLDatabase. + +.PARAMETER parentEventhouseId +The ID of the parent Eventhouse item for the KQLDatabase. This is mandatory for ReadWrite type databases. + +.PARAMETER KQLDatabaseType +The type of KQLDatabase to create. Valid values are "ReadWrite" and "Shortcut". + .PARAMETER KQLDatabaseName The name of the KQLDatabase to be created. @@ -33,11 +52,9 @@ An optional path to the platform-specific definition (e.g., .platform file) to u - CreationPayload is evaluate only if Definition file is not provided. - invitationToken has priority over all other payload fields. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function New-FabricKQLDatabase { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -77,7 +94,7 @@ function New-FabricKQLDatabase { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLDatabasePathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLDatabasePathPlatformDefinition, @@ -112,9 +129,9 @@ function New-FabricKQLDatabase { $body.definition = @{ parts = @() } - + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { - + # Add new part to the parts array $body.definition.parts += @{ @@ -127,7 +144,7 @@ function New-FabricKQLDatabase { Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error return $null } - + if ($KQLDatabasePathPlatformDefinition) { $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition @@ -144,7 +161,7 @@ function New-FabricKQLDatabase { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } - + } if ($KQLDatabasePathSchemaDefinition) { $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition @@ -243,7 +260,7 @@ function New-FabricKQLDatabase { } 202 { Write-Message -Message "KQLDatabase '$KQLDatabaseName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] [string]$retryAfter = $responseHeader["Retry-After"] @@ -252,24 +269,24 @@ function New-FabricKQLDatabase { Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug - + return $operationResult } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error diff --git a/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 index b1c06d8b..71282e42 100644 --- a/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 @@ -1,3 +1,4 @@ +function Get-FabricKQLQueryset { <# .SYNOPSIS Retrieves an KQLQueryset or a list of KQLQuerysets from a specified workspace in Microsoft Fabric. @@ -8,6 +9,9 @@ The `Get-FabricKQLQueryset` function sends a GET request to the Fabric API to re .PARAMETER WorkspaceId (Mandatory) The ID of the workspace to query KQLQuerysets. +.PARAMETER KQLQuerysetId +(Optional) The ID of a specific KQLQueryset to retrieve. + .PARAMETER KQLQuerysetName (Optional) The name of the specific KQLQueryset to retrieve. @@ -25,11 +29,9 @@ Retrieves all KQLQuerysets in workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Get-FabricKQLQueryset { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -57,7 +59,7 @@ function Get-FabricKQLQueryset { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 3: Initialize variables $continuationToken = $null $KQLQuerysets = @() @@ -65,7 +67,7 @@ function Get-FabricKQLQueryset { if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets" -f $FabricConfig.BaseUrl, $WorkspaceId @@ -79,7 +81,7 @@ function Get-FabricKQLQueryset { $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -89,7 +91,7 @@ function Get-FabricKQLQueryset { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -98,12 +100,12 @@ function Get-FabricKQLQueryset { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $KQLQuerysets += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -121,7 +123,7 @@ function Get-FabricKQLQueryset { } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $KQLQueryset = if ($KQLQuerysetId) { $KQLQuerysets | Where-Object { $_.Id -eq $KQLQuerysetId } @@ -149,6 +151,6 @@ function Get-FabricKQLQueryset { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Lakehouse/Get-FabricLakehouse.ps1 b/source/Public/Lakehouse/Get-FabricLakehouse.ps1 index ecdeb17f..ce3658ee 100644 --- a/source/Public/Lakehouse/Get-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/Get-FabricLakehouse.ps1 @@ -1,3 +1,4 @@ +function Get-FabricLakehouse { <# .SYNOPSIS Retrieves an Lakehouse or a list of Lakehouses from a specified workspace in Microsoft Fabric. @@ -8,6 +9,9 @@ The `Get-FabricLakehouse` function sends a GET request to the Fabric API to retr .PARAMETER WorkspaceId (Mandatory) The ID of the workspace to query Lakehouses. +.PARAMETER LakehouseId +(Optional) The ID of a specific Lakehouse to retrieve. + .PARAMETER LakehouseName (Optional) The name of the specific Lakehouse to retrieve. @@ -25,11 +29,9 @@ Retrieves all Lakehouses in workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Get-FabricLakehouse { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -64,7 +66,7 @@ function Get-FabricLakehouse { if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/lakehouses" -f $FabricConfig.BaseUrl, $WorkspaceId @@ -72,14 +74,14 @@ function Get-FabricLakehouse { do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -89,7 +91,7 @@ function Get-FabricLakehouse { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -98,12 +100,12 @@ function Get-FabricLakehouse { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $lakehouses += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -121,7 +123,7 @@ function Get-FabricLakehouse { } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $lakehouse = if ($LakehouseId) { $lakehouses | Where-Object { $_.Id -eq $LakehouseId } @@ -149,6 +151,6 @@ function Get-FabricLakehouse { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 index e5d93177..2aef20d0 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 @@ -1,3 +1,4 @@ +function Get-FabricMirroredDatabase { <# .SYNOPSIS Retrieves an MirroredDatabase or a list of MirroredDatabases from a specified workspace in Microsoft Fabric. @@ -8,6 +9,9 @@ The `Get-FabricMirroredDatabase` function sends a GET request to the Fabric API .PARAMETER WorkspaceId (Mandatory) The ID of the workspace to query MirroredDatabases. +.PARAMETER MirroredDatabaseId +(Optional) The ID of a specific MirroredDatabase to retrieve. + .PARAMETER MirroredDatabaseName (Optional) The name of the specific MirroredDatabase to retrieve. @@ -25,11 +29,9 @@ Retrieves all MirroredDatabases in workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Get-FabricMirroredDatabase { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -64,24 +66,24 @@ function Get-FabricMirroredDatabase { if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - + # Step 3: Loop to retrieve data with continuation token - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +93,7 @@ function Get-FabricMirroredDatabase { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,12 +102,12 @@ function Get-FabricMirroredDatabase { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $MirroredDatabases += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -124,7 +126,7 @@ function Get-FabricMirroredDatabase { } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $MirroredDatabase = if ($MirroredDatabaseId) { $MirroredDatabases | Where-Object { $_.Id -eq $MirroredDatabaseId } @@ -152,6 +154,6 @@ function Get-FabricMirroredDatabase { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 b/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 index a5775d3f..1c4b53e3 100644 --- a/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 +++ b/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 @@ -1,3 +1,4 @@ +function Get-FabricMirroredWarehouse { <# .SYNOPSIS Retrieves an MirroredWarehouse or a list of MirroredWarehouses from a specified workspace in Microsoft Fabric. @@ -8,6 +9,9 @@ The `Get-FabricMirroredWarehouse` function sends a GET request to the Fabric API .PARAMETER WorkspaceId (Mandatory) The ID of the workspace to query MirroredWarehouses. +.PARAMETER MirroredWarehouseId +(Optional) The ID of a specific MirroredWarehouse to retrieve. + .PARAMETER MirroredWarehouseName (Optional) The name of the specific MirroredWarehouse to retrieve. @@ -25,11 +29,9 @@ Retrieves all MirroredWarehouses in workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Get-FabricMirroredWarehouse { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -64,24 +66,24 @@ function Get-FabricMirroredWarehouse { if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/MirroredWarehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - + # Step 3: Loop to retrieve data with continuation token - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +93,7 @@ function Get-FabricMirroredWarehouse { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,12 +102,12 @@ function Get-FabricMirroredWarehouse { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $MirroredWarehouses += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -124,7 +126,7 @@ function Get-FabricMirroredWarehouse { } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $MirroredWarehouse = if ($MirroredWarehouseId) { $MirroredWarehouses | Where-Object { $_.Id -eq $MirroredWarehouseId } @@ -152,6 +154,6 @@ function Get-FabricMirroredWarehouse { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredWarehouse. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Notebook/Get-FabricNotebook.ps1 b/source/Public/Notebook/Get-FabricNotebook.ps1 index bd759f9a..39e16b9d 100644 --- a/source/Public/Notebook/Get-FabricNotebook.ps1 +++ b/source/Public/Notebook/Get-FabricNotebook.ps1 @@ -1,3 +1,4 @@ +function Get-FabricNotebook { <# .SYNOPSIS Retrieves an Notebook or a list of Notebooks from a specified workspace in Microsoft Fabric. @@ -8,6 +9,9 @@ The `Get-FabricNotebook` function sends a GET request to the Fabric API to retri .PARAMETER WorkspaceId (Mandatory) The ID of the workspace to query Notebooks. +.PARAMETER NotebookId +(Optional) The ID of a specific Notebook to retrieve. + .PARAMETER NotebookName (Optional) The name of the specific Notebook to retrieve. @@ -25,11 +29,9 @@ Retrieves all Notebooks in workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> - -function Get-FabricNotebook { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -60,27 +62,27 @@ function Get-FabricNotebook { # Step 3: Initialize variables $continuationToken = $null $notebooks = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/notebooks" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -90,7 +92,7 @@ function Get-FabricNotebook { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -99,12 +101,12 @@ function Get-FabricNotebook { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $notebooks += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug @@ -122,7 +124,7 @@ function Get-FabricNotebook { } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $notebook = if ($NotebookId) { $notebooks | Where-Object { $_.Id -eq $NotebookId } @@ -150,6 +152,6 @@ function Get-FabricNotebook { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Notebook. Error: $errorDetails" -Level Error - } - + } + } diff --git a/source/Public/Utils/Get-FabricLongRunningOperation.ps1 b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 index d73a2219..e2637fd1 100644 --- a/source/Public/Utils/Get-FabricLongRunningOperation.ps1 +++ b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 @@ -10,6 +10,9 @@ long-running operation. It periodically polls the operation until it reaches a t .PARAMETER operationId The unique identifier of the long-running operation to be monitored. +.PARAMETER location +The URL provided in the Location header of the initial request. This is used to check the status of the operation. + .PARAMETER retryAfter The interval (in seconds) to wait between polling the operation status. The default is 5 seconds. diff --git a/source/Public/Workspace/New-FabricWorkspace.ps1 b/source/Public/Workspace/New-FabricWorkspace.ps1 index 4da24893..783328ed 100644 --- a/source/Public/Workspace/New-FabricWorkspace.ps1 +++ b/source/Public/Workspace/New-FabricWorkspace.ps1 @@ -1,3 +1,4 @@ +function New-FabricWorkspace { <# .SYNOPSIS Creates a new Fabric workspace with the specified display name. @@ -8,6 +9,12 @@ The `Add-FabricWorkspace` function creates a new workspace in the Fabric platfor .PARAMETER WorkspaceName The display name of the workspace to be created. Must only contain alphanumeric characters, spaces, and underscores. +.PARAMETER WorkspaceDescription +(Optional) A description for the workspace. This parameter is optional. + +.PARAMETER CapacityId +(Optional) The ID of the capacity to be associated with the workspace. This parameter is optional. + .EXAMPLE Add-FabricWorkspace -WorkspaceName "NewWorkspace" @@ -19,8 +26,6 @@ Creates a workspace named "NewWorkspace". Author: Tiago Balabuch #> - -function New-FabricWorkspace { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -87,24 +92,24 @@ function New-FabricWorkspace { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -117,6 +122,6 @@ function New-FabricWorkspace { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create workspace. Error: $errorDetails" -Level Error - + } } From ed0fc1a4583eec52088805574ae56fc03fa6b265 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 15:15:21 +0100 Subject: [PATCH 51/76] Refactor Script Analyzer Test for Pester Help Tests Updated the Script Analyzer test to filter test cases based on exported commands. This change ensures that only relevant functions are analyzed, improving the accuracy of the tests. Thank you! --- tests/QA/module.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/QA/module.tests.ps1 b/tests/QA/module.tests.ps1 index fe686494..8fcb660d 100644 --- a/tests/QA/module.tests.ps1 +++ b/tests/QA/module.tests.ps1 @@ -117,7 +117,7 @@ Describe 'Quality for module' -Tags 'TestQuality' { Get-ChildItem -Path 'tests\' -Recurse -Include "$Name.Tests.ps1" | Should -Not -BeNullOrEmpty } - It 'Should pass Script Analyzer for ' -ForEach $testCases -Skip:(-not $scriptAnalyzerRules) { + It 'Should pass Script Analyzer for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) -Skip:(-not $scriptAnalyzerRules) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $pssaResult = (Invoke-ScriptAnalyzer -Path $functionFile.FullName) From fe26b543e1916ae330a122b12c94a4230bc50589 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 12 May 2025 15:15:46 +0100 Subject: [PATCH 52/76] Refactor Add-FabricWorkspaceIdentity for Pester Help Tests Clean up whitespace and improve readability in the Add-FabricWorkspaceIdentity function. This enhances the code quality and maintains consistency for Pester Help Tests. Thank you! --- .../Public/Workspace/Add-FabricWorkspaceIdentity.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 index cc042432..d2bf942b 100644 --- a/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 +++ b/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 @@ -17,7 +17,7 @@ Provisions a Managed Identity for the workspace with ID "workspace123". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Add-FabricWorkspaceIdentity { @@ -59,14 +59,14 @@ function Add-FabricWorkspaceIdentity { Write-Message -Message "Workspace identity provisioning accepted for workspace '$WorkspaceId'. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug @@ -74,17 +74,17 @@ function Add-FabricWorkspaceIdentity { if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error From 4a3b92ffc8abcb3e03f05f5f4e8c99da240be028 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Mon, 12 May 2025 16:08:02 +0100 Subject: [PATCH 53/76] used the dbatools formatter --- .../Public/Capacity/Get-FabricCapacities.ps1 | 5 +- source/Public/Capacity/Get-FabricCapacity.ps1 | 22 +- .../Get-FabricCapacityRefreshables.ps1 | 12 +- .../Capacity/Get-FabricCapacitySkus.ps1 | 10 +- .../Capacity/Get-FabricCapacityState.ps1 | 14 +- .../Get-FabricCapacityTenantOverrides.ps1 | 10 +- .../Capacity/Get-FabricCapacityWorkload.ps1 | 10 +- .../Public/Capacity/Resume-FabricCapacity.ps1 | 8 +- .../Capacity/Suspend-FabricCapacity.ps1 | 6 +- source/Public/Confirm-FabricAuthToken.ps1 | 50 ++--- source/Public/Connect-FabricAccount.ps1 | 38 ++-- source/Public/Copy Job/Get-FabricCopyJob.ps1 | 26 +-- .../Copy Job/Get-FabricCopyJobDefinition.ps1 | 11 +- source/Public/Copy Job/New-FabricCopyJob.ps1 | 17 +- .../Public/Copy Job/Remove-FabricCopyJob.ps1 | 11 +- .../Public/Copy Job/Update-FabricCopyJob.ps1 | 15 +- .../Update-FabricCopyJobDefinition.ps1 | 29 ++- .../Public/Dashboard/Get-FabricDashboard.ps1 | 11 +- .../Data Pipeline/Get-FabricDataPipeline.ps1 | 18 +- .../Data Pipeline/New-FabricDataPipeline.ps1 | 15 +- .../Remove-FabricDataPipeline.ps1 | 9 +- .../Update-FabricDataPipeline.ps1 | 15 +- source/Public/Datamart/Get-FabricDatamart.ps1 | 24 +-- ...Assign-FabricDomainWorkspaceByCapacity.ps1 | 20 +- .../Assign-FabricDomainWorkspaceById.ps1 | 9 +- ...ssign-FabricDomainWorkspaceByPrincipal.ps1 | 12 +- ...gn-FabricDomainWorkspaceRoleAssignment.ps1 | 7 +- source/Public/Domain/Get-FabricDomain.ps1 | 20 +- .../Domain/Get-FabricDomainWorkspace.ps1 | 12 +- source/Public/Domain/New-FabricDomain.ps1 | 20 +- source/Public/Domain/Remove-FabricDomain.ps1 | 9 +- .../Domain/Unassign-FabricDomainWorkspace.ps1 | 16 +- ...gn-FabricDomainWorkspaceRoleAssignment.ps1 | 7 +- source/Public/Domain/Update-FabricDomain.ps1 | 12 +- .../Environment/Get-FabricEnvironment.ps1 | 24 +-- .../Get-FabricEnvironmentLibrary.ps1 | 17 +- .../Get-FabricEnvironmentSparkCompute.ps1 | 17 +- .../Get-FabricEnvironmentStagingLibrary.ps1 | 17 +- ...t-FabricEnvironmentStagingSparkCompute.ps1 | 9 +- .../Environment/New-FabricEnvironment.ps1 | 18 +- .../Environment/Publish-FabricEnvironment.ps1 | 20 +- .../Environment/Remove-FabricEnvironment.ps1 | 9 +- ...Remove-FabricEnvironmentStagingLibrary.ps1 | 11 +- .../Stop-FabricEnvironmentPublish.ps1 | 11 +- .../Environment/Update-FabricEnvironment.ps1 | 5 +- ...e-FabricEnvironmentStagingSparkCompute.ps1 | 13 +- ...Upload-FabricEnvironmentStagingLibrary.ps1 | 13 +- .../Eventhouse/Get-FabricEventhouse.ps1 | 24 +-- .../Get-FabricEventhouseDefinition.ps1 | 30 ++- .../Eventhouse/New-FabricEventhouse.ps1 | 22 +- .../Eventhouse/Remove-FabricEventhouse.ps1 | 11 +- .../Eventhouse/Update-FabricEventhouse.ps1 | 13 +- .../Update-FabricEventhouseDefinition.ps1 | 48 ++--- .../Eventstream/Get-FabricEventstream.ps1 | 24 +-- .../Get-FabricEventstreamDefinition.ps1 | 24 +-- .../Eventstream/New-FabricEventstream.ps1 | 18 +- .../Eventstream/Remove-FabricEventstream.ps1 | 13 +- .../Eventstream/Update-FabricEventstream.ps1 | 5 +- .../Update-FabricEventstreamDefinition.ps1 | 48 ++--- .../Get-FabricExternalDataShares.ps1 | 21 +- .../Revoke-FabricExternalDataShares.ps1 | 11 +- .../Public/Get-AllFabricDatasetRefreshes.ps1 | 8 +- source/Public/Get-FabricAPIClusterURI.ps1 | 10 +- source/Public/Get-FabricAuthToken.ps1 | 28 +-- source/Public/Get-FabricConnection.ps1 | 43 ++-- source/Public/Get-FabricDatasetRefreshes.ps1 | 6 +- source/Public/Get-FabricDebugInfo.ps1 | 26 +-- source/Public/Get-FabricUsageMetricsQuery.ps1 | 59 +++--- source/Public/Get-SHA256.ps1 | 8 +- source/Public/Invoke-FabricAPIRequest.ps1 | 37 ++-- source/Public/Invoke-FabricDatasetRefresh.ps1 | 3 +- source/Public/Item/Export-FabricItem.ps1 | 20 +- source/Public/Item/Get-FabricItem.ps1 | 72 ++++--- source/Public/Item/Import-FabricItem.ps1 | 55 +++-- source/Public/Item/Remove-FabricItem.ps1 | 64 +++--- .../KQL Dashboard/Get-FabricKQLDashboard.ps1 | 20 +- .../Get-FabricKQLDashboardDefinition.ps1 | 24 +-- .../KQL Dashboard/New-FabricKQLDashboard.ps1 | 32 ++- .../Remove-FabricKQLDashboard.ps1 | 9 +- .../Update-FabricKQLDashboard.ps1 | 9 +- .../Update-FabricKQLDashboardDefinition.ps1 | 40 ++-- .../KQL Database/Get-FabricKQLDatabase.ps1 | 24 +-- .../Get-FabricKQLDatabaseDefinition.ps1 | 22 +- .../KQL Database/New-FabricKQLDatabase.ps1 | 24 +-- .../KQL Database/Remove-FabricKQLDatabase.ps1 | 9 +- .../KQL Database/Update-FabricKQLDatabase.ps1 | 5 +- .../Update-FabricKQLDatabaseDefinition.ps1 | 21 +- .../KQL Queryset/Get-FabricKQLQueryset.ps1 | 24 +-- .../Get-FabricKQLQuerysetDefinition.ps1 | 24 +-- .../KQL Queryset/Invoke-FabricKQLCommand.ps1 | 195 +++++++++--------- .../KQL Queryset/New-FabricKQLQueryset.ps1 | 32 ++- .../KQL Queryset/Remove-FabricKQLQueryset.ps1 | 5 +- .../KQL Queryset/Update-FabricKQLQueryset.ps1 | 5 +- .../Update-FabricKQLQuerysetDefinition.ps1 | 40 ++-- .../Public/Lakehouse/Get-FabricLakehouse.ps1 | 24 +-- .../Lakehouse/Get-FabricLakehouseTable.ps1 | 18 +- .../Lakehouse/Load-FabricLakehouseTable.ps1 | 12 +- .../Public/Lakehouse/New-FabricLakehouse.ps1 | 12 +- .../Lakehouse/Remove-FabricLakehouse.ps1 | 9 +- .../Start-FabricLakehouseTableMaintenance.ps1 | 28 ++- .../Lakehouse/Update-FabricLakehouse.ps1 | 5 +- .../ML Experiment/Get-FabricMLExperiment.ps1 | 44 ++-- .../ML Experiment/New-FabricMLExperiment.ps1 | 24 +-- .../Remove-FabricMLExperiment.ps1 | 11 +- .../Update-FabricMLExperiment.ps1 | 13 +- source/Public/ML Model/Get-FabricMLModel.ps1 | 44 ++-- source/Public/ML Model/New-FabricMLModel.ps1 | 24 +-- .../Public/ML Model/Remove-FabricMLModel.ps1 | 11 +- .../Public/ML Model/Update-FabricMLModel.ps1 | 13 +- .../Get-FabricMirroredDatabase.ps1 | 24 +-- .../Get-FabricMirroredDatabaseDefinition.ps1 | 24 +-- .../Get-FabricMirroredDatabaseStatus.ps1 | 9 +- .../Get-FabricMirroredDatabaseTableStatus.ps1 | 15 +- .../New-FabricMirroredDatabase.ps1 | 34 ++- .../Remove-FabricMirroredDatabase.ps1 | 7 +- .../Start-FabricMirroredDatabaseMirroring.ps1 | 11 +- .../Stop-FabricMirroredDatabaseMirroring.ps1 | 11 +- .../Update-FabricMirroredDatabase.ps1 | 5 +- ...pdate-FabricMirroredDatabaseDefinition.ps1 | 42 ++-- .../Get-FabricMirroredWarehouse.ps1 | 24 +-- source/Public/Notebook/Get-FabricNotebook.ps1 | 24 +-- .../Notebook/Get-FabricNotebookDefinition.ps1 | 28 ++- source/Public/Notebook/New-FabricNotebook.ps1 | 34 ++- .../Public/Notebook/New-FabricNotebookNEW.ps1 | 28 ++- .../Public/Notebook/Remove-FabricNotebook.ps1 | 9 +- .../Public/Notebook/Update-FabricNotebook.ps1 | 5 +- .../Update-FabricNotebookDefinition.ps1 | 38 ++-- .../Get-FabricPaginatedReport.ps1 | 42 ++-- .../Update-FabricPaginatedReport.ps1 | 13 +- source/Public/Reflex/Get-FabricReflex.ps1 | 42 ++-- .../Reflex/Get-FabricReflexDefinition.ps1 | 28 ++- source/Public/Reflex/New-FabricReflex.ps1 | 40 ++-- source/Public/Reflex/Remove-FabricReflex.ps1 | 15 +- source/Public/Reflex/Update-FabricReflex.ps1 | 13 +- .../Reflex/Update-FabricReflexDefinition.ps1 | 48 ++--- source/Public/Report/Get-FabricReport.ps1 | 42 ++-- .../Report/Get-FabricReportDefinition.ps1 | 28 ++- source/Public/Report/New-FabricReport.ps1 | 34 ++- source/Public/Report/Remove-FabricReport.ps1 | 15 +- source/Public/Report/Update-FabricReport.ps1 | 13 +- .../Report/Update-FabricReportDefinition.ps1 | 36 ++-- .../SQL Database/Get-FabricSQLDatabase.ps1 | 10 +- .../SQL Database/New-FabricSQLDatabase.ps1 | 121 ++++++----- .../SQL Database/Remove-FabricSQLDatabase.ps1 | 9 +- .../SQL Endpoints/Get-FabricSQLEndpoint.ps1 | 46 ++--- .../Get-FabricSemanticModel.ps1 | 42 ++-- .../Get-FabricSemanticModelDefinition.ps1 | 28 ++- .../New-FabricSemanticModel.ps1 | 39 ++-- .../Remove-FabricSemanticModel.ps1 | 15 +- .../Update-FabricSemanticModel.ps1 | 13 +- .../Update-FabricSemanticModelDefinition.ps1 | 30 ++- source/Public/Set-FabricAuthToken.ps1 | 122 ++++++----- .../Get-FabricSparkJobDefinition.ps1 | 42 ++-- ...Get-FabricSparkJobDefinitionDefinition.ps1 | 26 ++- .../New-FabricSparkJobDefinition.ps1 | 34 ++- .../Remove-FabricSparkJobDefinition.ps1 | 13 +- ...Start-FabricSparkJobDefinitionOnDemand.ps1 | 22 +- .../Update-FabricSparkJobDefinition.ps1 | 11 +- ...ate-FabricSparkJobDefinitionDefinition.ps1 | 44 ++-- .../Spark/Get-FabricSparkCustomPool.ps1 | 42 ++-- .../Public/Spark/Get-FabricSparkSettings.ps1 | 40 ++-- .../Spark/New-FabricSparkCustomPool.ps1 | 26 ++- .../Spark/Remove-FabricSparkCustomPool.ps1 | 11 +- .../Spark/Update-FabricSparkCustomPool.ps1 | 31 ++- .../Spark/Update-FabricSparkSettings.ps1 | 17 +- ...t-FabricCapacityTenantSettingOverrides.ps1 | 8 +- ...Get-FabricDomainTenantSettingOverrides.ps1 | 8 +- .../Public/Tenant/Get-FabricTenantSetting.ps1 | 13 +- .../Tenant/Get-FabricTenantSettings.ps1 | 2 +- ...-FabricWorkspaceTenantSettingOverrides.ps1 | 8 +- ...e-FabricCapacityTenantSettingOverrides.ps1 | 5 +- ...e-FabricCapacityTenantSettingOverrides.ps1 | 10 +- .../Tenant/Update-FabricTenantSetting.ps1 | 10 +- .../Get-FabricUserListAccessEntities.ps1 | 13 +- source/Public/Utils/Convert-FromBase64.ps1 | 9 +- source/Public/Utils/Convert-ToBase64.ps1 | 9 +- .../Utils/Get-FabricLongRunningOperation.ps1 | 15 +- .../Get-FabricLongRunningOperationResult.ps1 | 23 +-- .../Public/Utils/Invoke-FabricAPIRequest.ps1 | 21 +- source/Public/Utils/Set-FabricApiHeaders.ps1 | 13 +- .../Public/Utils/Test-FabricApiResponse.ps1 | 89 ++++---- .../Public/Warehouse/Get-FabricWarehouse.ps1 | 32 ++- .../Public/Warehouse/New-FabricWarehouse.ps1 | 11 +- .../Warehouse/Remove-FabricWarehouse.ps1 | 9 +- .../Warehouse/Update-FabricWarehouse.ps1 | 23 +-- .../Workspace/Add-FabricWorkspaceIdentity.ps1 | 10 +- .../Add-FabricWorkspaceRoleAssignment.ps1 | 11 +- .../Assign-FabricWorkspaceCapacity.ps1 | 7 +- .../Public/Workspace/Get-FabricWorkspace.ps1 | 32 ++- .../Get-FabricWorkspaceDatasetRefreshes.ps1 | 10 +- .../Get-FabricWorkspaceRoleAssignment.ps1 | 38 ++-- .../Get-FabricWorkspaceUsageMetricsData.ps1 | 17 +- .../Workspace/Get-FabricWorkspaceUsers.ps1 | 8 +- .../Public/Workspace/New-FabricWorkspace.ps1 | 12 +- .../New-FabricWorkspaceUsageMetricsReport.ps1 | 13 +- .../Register-FabricWorkspaceToCapacity.ps1 | 12 +- .../Workspace/Remove-FabricWorkspace.ps1 | 5 +- .../Remove-FabricWorkspaceIdentity.ps1 | 10 +- .../Remove-FabricWorkspaceRoleAssignment.ps1 | 5 +- .../Unassign-FabricWorkspaceCapacity.ps1 | 5 +- .../Unregister-FabricWorkspaceToCapacity.ps1 | 6 +- .../Workspace/Update-FabricWorkspace.ps1 | 5 +- .../Update-FabricWorkspaceRoleAssignment.ps1 | 5 +- 203 files changed, 2021 insertions(+), 2457 deletions(-) diff --git a/source/Public/Capacity/Get-FabricCapacities.ps1 b/source/Public/Capacity/Get-FabricCapacities.ps1 index 265bc9fd..b2280eae 100644 --- a/source/Public/Capacity/Get-FabricCapacities.ps1 +++ b/source/Public/Capacity/Get-FabricCapacities.ps1 @@ -49,8 +49,7 @@ function Get-FabricCapacities { # Get all resources of type "Microsoft.Fabric/capacities" and add them to the results array $res += Get-AzResource -ResourceGroupName $r.ResourceGroupName -resourcetype "Microsoft.Fabric/capacities" -ErrorAction SilentlyContinue } - } - else { + } else { # If no subscription ID is provided, get all subscriptions $subscriptions = Get-AzSubscription @@ -72,4 +71,4 @@ function Get-FabricCapacities { # Return the results return $res -} +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacity.ps1 b/source/Public/Capacity/Get-FabricCapacity.ps1 index a36c88ca..40380787 100644 --- a/source/Public/Capacity/Get-FabricCapacity.ps1 +++ b/source/Public/Capacity/Get-FabricCapacity.ps1 @@ -49,44 +49,40 @@ function Get-FabricCapacity { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Construct the API endpoint URL $apiEndpointURI = "{0}/capacities" -f $FabricConfig.BaseUrl - + # Invoke the Fabric API to retrieve capacity details $capacities = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Get - + # Filter results based on provided parameters $response = if ($capacityId) { $capacities | Where-Object { $_.Id -eq $capacityId } - } - elseif ($capacityName) { + } elseif ($capacityName) { $capacities | Where-Object { $_.DisplayName -eq $capacityName } - } - else { + } else { # No filter, return all capacities Write-Message -Message "No filter specified. Returning all capacities." -Level Debug return $capacities } - + # Handle results if ($response) { Write-Message -Message "Capacity found matching the specified criteria." -Level Debug return $response - } - else { + } else { Write-Message -Message "No capacity found matching the specified criteria." -Level Warning return $null } - } - catch { + } catch { # Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error return $null } -} \ No newline at end of file +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 b/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 index 632ff58f..49888dea 100644 --- a/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityRefreshables.ps1 @@ -1,5 +1,5 @@ -function Get-FabricCapacityRefreshables { -<# +function Get-FabricCapacityRefreshables { + <# .SYNOPSIS Retrieves the top refreshable capacities for the tenant. @@ -16,15 +16,15 @@ This example retrieves the top 5 refreshable capacities for the tenant. .NOTES The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the top refreshable capacities. It then returns the 'value' property of the response, which contains the capacities. -#> + #> -# This function retrieves the top refreshable capacities for the tenant. + # This function retrieves the top refreshable capacities for the tenant. # Define aliases for the function for flexibility. [Alias("Get-FabCapacityRefreshables")] # Define a mandatory parameter for the number of top refreshable capacities to retrieve. Param ( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string]$top = 5 ) @@ -33,4 +33,4 @@ The function retrieves the PowerBI access token and makes a GET request to the P # Make a GET request to the PowerBI API to retrieve the top refreshable capacities. # The function returns the 'value' property of the response. return (Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/capacities/refreshables?`$top=$top" -Headers $FabricSession.HeaderParams -Method GET).value -} +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacitySkus.ps1 b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 index 9b6cd875..713f4ae7 100644 --- a/source/Public/Capacity/Get-FabricCapacitySkus.ps1 +++ b/source/Public/Capacity/Get-FabricCapacitySkus.ps1 @@ -1,6 +1,6 @@ -function Get-FabricCapacitySkus { -<# +function Get-FabricCapacitySkus { + <# .SYNOPSIS Retrieves the fabric capacity information. @@ -19,7 +19,7 @@ Specifies the capacity to retrieve information for. If not provided, all capacit .EXAMPLE Get-FabricCapacitySkus -capacity "exampleCapacity" Retrieves the fabric capacity information for the specified capacity. -#> + #> # Define aliases for the function for flexibility. Param( @@ -27,7 +27,7 @@ Retrieves the fabric capacity information for the specified capacity. [string]$subscriptionID, [Parameter(Mandatory = $true)] [string]$ResourceGroupName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$capacity ) @@ -39,4 +39,4 @@ Retrieves the fabric capacity information for the specified capacity. return $result.value -} +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityState.ps1 b/source/Public/Capacity/Get-FabricCapacityState.ps1 index d9eace57..4f779b34 100644 --- a/source/Public/Capacity/Get-FabricCapacityState.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityState.ps1 @@ -1,5 +1,5 @@ function Get-FabricCapacityState { -<# + <# .SYNOPSIS Retrieves the state of a specific capacity. @@ -22,19 +22,19 @@ This example retrieves the state of a specific capacity given the subscription I .NOTES The function checks if the Azure token is null. If it is, it connects to the Azure account and retrieves the token. It then defines the headers for the GET request and the URL for the GET request. Finally, it makes the GET request and returns the response. -#> + #> -# This function retrieves the state of a specific capacity. + # This function retrieves the state of a specific capacity. # Define aliases for the function for flexibility. [Alias("Get-FabCapacityState")] # Define mandatory parameters for the subscription ID, resource group, and capacity. Param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$subscriptionID, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$resourcegroup, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$capacity ) @@ -45,4 +45,4 @@ The function checks if the Azure token is null. If it is, it connects to the Azu # Make the GET request and return the response. return Invoke-RestMethod -Method GET -Uri $getCapacityState -Headers $script:AzureSession.HeaderParams -ErrorAction Stop -} +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 b/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 index 510ea63d..311ada92 100644 --- a/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityTenantOverrides.ps1 @@ -1,5 +1,5 @@ -function Get-FabricCapacityTenantOverrides { -<# +function Get-FabricCapacityTenantOverrides { + <# .SYNOPSIS Retrieves the tenant overrides for all capacities. @@ -16,9 +16,9 @@ This example retrieves the tenant overrides for all capacities. .NOTES The function retrieves the PowerBI access token and makes a GET request to the Fabric API to retrieve the tenant overrides for all capacities. It then returns the response of the GET request. -#> + #> -# This function retrieves the tenant overrides for all capacities. + # This function retrieves the tenant overrides for all capacities. # Define aliases for the function for flexibility. [Alias("Get-FabCapacityTenantOverrides")] @@ -30,4 +30,4 @@ The function retrieves the PowerBI access token and makes a GET request to the F # Make a GET request to the Fabric API to retrieve the tenant overrides for all capacities. # The function returns the response of the GET request. return Invoke-RestMethod -uri "$($FabricSession.BaseApiUrl)/admin/capacities/delegatedTenantSettingOverrides" -Headers $FabricSession.HeaderParams -Method GET -} +} \ No newline at end of file diff --git a/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 b/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 index b5763179..92cacceb 100644 --- a/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 +++ b/source/Public/Capacity/Get-FabricCapacityWorkload.ps1 @@ -1,4 +1,4 @@ -function Get-FabricCapacityWorkload { +function Get-FabricCapacityWorkload { <# .SYNOPSIS Retrieves the workloads for a specific capacity. @@ -19,15 +19,15 @@ This example retrieves the workloads for a specific capacity given the capacity .NOTES The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the workloads for the specified capacity. It then returns the 'value' property of the response, which contains the workloads. -#> + #> -# This function retrieves the workloads for a specific capacity. + # This function retrieves the workloads for a specific capacity. # Define aliases for the function for flexibility. [Alias("Get-FabCapacityWorkload")] # Define a mandatory parameter for the capacity ID. Param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$capacityID ) @@ -39,4 +39,4 @@ The function retrieves the PowerBI access token and makes a GET request to the P } -#https://learn.microsoft.com/en-us/rest/api/power-bi/capacities/get-workloads +#https://learn.microsoft.com/en-us/rest/api/power-bi/capacities/get-workloads \ No newline at end of file diff --git a/source/Public/Capacity/Resume-FabricCapacity.ps1 b/source/Public/Capacity/Resume-FabricCapacity.ps1 index d78a8f07..a1ee73ab 100644 --- a/source/Public/Capacity/Resume-FabricCapacity.ps1 +++ b/source/Public/Capacity/Resume-FabricCapacity.ps1 @@ -1,5 +1,5 @@ function Resume-FabricCapacity { -<# + <# .SYNOPSIS Resumes a capacity. @@ -22,9 +22,9 @@ This example resumes a capacity given the subscription ID, resource group, and c .NOTES The function defines parameters for the subscription ID, resource group, and capacity. If the 'azToken' environment variable is null, it connects to the Azure account and sets the 'azToken' environment variable. It then defines the headers for the request, defines the URI for the request, and makes a GET request to the URI. -#> + #> -# This function resumes a capacity. + # This function resumes a capacity. # Define aliases for the function for flexibility. [Alias("Resume-FabCapacity")] @@ -49,4 +49,4 @@ The function defines parameters for the subscription ID, resource group, and cap if ($PSCmdlet.ShouldProcess("Resume capacity $capacity")) { return Invoke-RestMethod -Method POST -Uri $resumeCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop } -} +} \ No newline at end of file diff --git a/source/Public/Capacity/Suspend-FabricCapacity.ps1 b/source/Public/Capacity/Suspend-FabricCapacity.ps1 index 9e665c9f..7461bf5a 100644 --- a/source/Public/Capacity/Suspend-FabricCapacity.ps1 +++ b/source/Public/Capacity/Suspend-FabricCapacity.ps1 @@ -1,6 +1,6 @@ # This function suspends a capacity. function Suspend-FabricCapacity { -<# + <# .SYNOPSIS Suspends a capacity. @@ -23,7 +23,7 @@ This example suspends a capacity given the subscription ID, resource group, and .NOTES The function defines parameters for the subscription ID, resource group, and capacity. If the 'azToken' environment variable is null, it connects to the Azure account and sets the 'azToken' environment variable. It then defines the headers for the request, defines the URI for the request, and makes a GET request to the URI. -#> + #> # Define aliases for the function for flexibility. [Alias("Suspend-PowerBICapacity", "Suspend-FabCapacity")] @@ -49,4 +49,4 @@ The function defines parameters for the subscription ID, resource group, and cap return Invoke-RestMethod -Method POST -Uri $suspendCapacity -Headers $script:AzureSession.HeaderParams -ErrorAction Stop } -} +} \ No newline at end of file diff --git a/source/Public/Confirm-FabricAuthToken.ps1 b/source/Public/Confirm-FabricAuthToken.ps1 index 14c7933b..e31b28ec 100644 --- a/source/Public/Confirm-FabricAuthToken.ps1 +++ b/source/Public/Confirm-FabricAuthToken.ps1 @@ -21,30 +21,30 @@ #> function Confirm-FabricAuthToken { - [CmdletBinding()] - param ( ) - - Write-Verbose "Check if session is established and token not expired." - - # Check if the Fabric token is already set - if (!$FabricSession.FabricToken -or !$AzureSession.AccessToken) { - Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken" - Set-FabricAuthToken | Out-Null - } - - $now = (Get-Date) - $s = Get-FabricDebugInfo - if ($FabricSession.AccessToken.ExpiresOn -lt $now ) { - Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#1" - Set-FabricAuthToken -reset | Out-Null - } - - if ($s.AzureSession.AccessToken.ExpiresOn -lt $now ) { - Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#2" - Set-FabricAuthToken -reset | Out-Null - } - - $s = Get-FabricDebugInfo - return $s + [CmdletBinding()] + param ( ) + + Write-Verbose "Check if session is established and token not expired." + + # Check if the Fabric token is already set + if (!$FabricSession.FabricToken -or !$AzureSession.AccessToken) { + Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken" + Set-FabricAuthToken | Out-Null + } + + $now = (Get-Date) + $s = Get-FabricDebugInfo + if ($FabricSession.AccessToken.ExpiresOn -lt $now ) { + Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#1" + Set-FabricAuthToken -reset | Out-Null + } + + if ($s.AzureSession.AccessToken.ExpiresOn -lt $now ) { + Write-Output "Confirm-FabricAuthToken::Set-FabricAuthToken#2" + Set-FabricAuthToken -reset | Out-Null + } + + $s = Get-FabricDebugInfo + return $s } \ No newline at end of file diff --git a/source/Public/Connect-FabricAccount.ps1 b/source/Public/Connect-FabricAccount.ps1 index a96a1cec..c840ff62 100644 --- a/source/Public/Connect-FabricAccount.ps1 +++ b/source/Public/Connect-FabricAccount.ps1 @@ -1,7 +1,7 @@ function Connect-FabricAccount { -#Requires -Version 7.1 + #Requires -Version 7.1 -<# + <# .SYNOPSIS Connects to the Fabric WebAPI. @@ -26,31 +26,31 @@ function Connect-FabricAccount { .LINK Connect-AzAccount https://learn.microsoft.com/de-de/powershell/module/az.accounts/connect-azaccount?view=azps-12.4.0 -#> + #> -[CmdletBinding()] + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$TenantId ) -begin { -} + begin { + } -process { - Write-Verbose "Connect to Azure Account" - Connect-AzAccount -TenantId $TenantId | Out-Null + process { + Write-Verbose "Connect to Azure Account" + Connect-AzAccount -TenantId $TenantId | Out-Null - Write-Verbose "Get authentication token" - $FabricSession.FabricToken = (Get-AzAccessToken -ResourceUrl $FabricSession.BaseApiUrl).Token - Write-Verbose "Token: $($FabricSession.FabricToken)" + Write-Verbose "Get authentication token" + $FabricSession.FabricToken = (Get-AzAccessToken -ResourceUrl $FabricSession.BaseApiUrl).Token + Write-Verbose "Token: $($FabricSession.FabricToken)" - Write-Verbose "Setup headers for API calls" - $FabricSession.HeaderParams = @{'Authorization'="Bearer {0}" -f $FabricSession.FabricToken} - Write-Verbose "HeaderParams: $($FabricSession.HeaderParams)" -} + Write-Verbose "Setup headers for API calls" + $FabricSession.HeaderParams = @{'Authorization' = "Bearer {0}" -f $FabricSession.FabricToken } + Write-Verbose "HeaderParams: $($FabricSession.HeaderParams)" + } -end { -} + end { + } } \ No newline at end of file diff --git a/source/Public/Copy Job/Get-FabricCopyJob.ps1 b/source/Public/Copy Job/Get-FabricCopyJob.ps1 index 30229e27..ae920267 100644 --- a/source/Public/Copy Job/Get-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJob.ps1 @@ -1,5 +1,5 @@ function Get-FabricCopyJob { -<# + <# .SYNOPSIS Retrieves CopyJob details from a specified Microsoft Fabric workspace. @@ -29,7 +29,7 @@ function Get-FabricCopyJob { Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -63,19 +63,17 @@ function Get-FabricCopyJob { $apiEndpointURI = "{0}/workspaces/{1}/copyJobs" -f $FabricConfig.BaseUrl, $WorkspaceId # Invoke the Fabric API to retrieve capacity details - $copyJobs = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get + $copyJobs = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get # Filter results based on provided parameters $response = if ($CopyJobId) { $copyJobs | Where-Object { $_.Id -eq $CopyJobId } - } - elseif ($CopyJobName) { + } elseif ($CopyJobName) { $copyJobs | Where-Object { $_.DisplayName -eq $CopyJobName } - } - else { + } else { # Return all CopyJobs if no filter is provided Write-Message -Message "No filter provided. Returning all CopyJobs." -Level Debug $copyJobs @@ -85,16 +83,14 @@ function Get-FabricCopyJob { if ($response) { Write-Message -Message "CopyJob found matching the specified criteria." -Level Debug return $response - } - else { + } else { Write-Message -Message "No CopyJob found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve CopyJob. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 b/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 index bfc2fcff..4a2ce844 100644 --- a/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 @@ -3,7 +3,7 @@ Retrieves the definition of a Copy Job from a specific workspace in Microsoft Fabric. .DESCRIPTION -This function fetches the Copy Job's content or metadata from a workspace. +This function fetches the Copy Job's content or metadata from a workspace. It supports both synchronous and asynchronous operations, with detailed logging and error handling. .PARAMETER WorkspaceId @@ -62,14 +62,13 @@ function Get-FabricCopyJobDefinition { $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointUrl ` -Headers $FabricConfig.FabricHeaders ` - -Method Post + -Method Post # Step 5: Return the API response containing the Copy Job definition. return $response - } - catch { + } catch { # Step 6: Capture and log detailed error information for troubleshooting. $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Copy Job definition. Error: $errorDetails" -Level Error - } -} + } +} \ No newline at end of file diff --git a/source/Public/Copy Job/New-FabricCopyJob.ps1 b/source/Public/Copy Job/New-FabricCopyJob.ps1 index 7ac76e1c..0d7a06b4 100644 --- a/source/Public/Copy Job/New-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/New-FabricCopyJob.ps1 @@ -49,7 +49,7 @@ function New-FabricCopyJob { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$CopyJobPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$CopyJobPathPlatformDefinition @@ -92,8 +92,7 @@ function New-FabricCopyJob { payload = $CopyJobEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Copy Job definition." -Level Error return $null } @@ -116,8 +115,7 @@ function New-FabricCopyJob { payload = $CopyJobEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -133,13 +131,12 @@ function New-FabricCopyJob { -Method Post ` -Body $bodyJson - Write-Message -Message "Copy Job created successfully!" -Level Info + Write-Message -Message "Copy Job created successfully!" -Level Info return $response - - } - catch { + + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Copy Job. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Copy Job/Remove-FabricCopyJob.ps1 b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 index d1613966..c1503c00 100644 --- a/source/Public/Copy Job/Remove-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 @@ -3,7 +3,7 @@ Deletes a Copy Job from a specified Microsoft Fabric workspace. .DESCRIPTION - This function performs a DELETE operation on the Microsoft Fabric API to remove a Copy Job + This function performs a DELETE operation on the Microsoft Fabric API to remove a Copy Job from the specified workspace using the provided WorkspaceId and CopyJobId parameters. .PARAMETER WorkspaceId @@ -47,15 +47,14 @@ function Remove-FabricCopyJob { $response = Invoke-FabricAPIRequest ` -Headers $FabricConfig.FabricHeaders ` -BaseURI $apiEndpointURI ` - -Method Delete - + -Method Delete + Write-Message -Message "Copy Job '$CopyJobId' deleted successfully from workspace '$WorkspaceId'." -Level Info return $response - } - catch { + } catch { # Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Copy Job '$CopyJobId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Copy Job/Update-FabricCopyJob.ps1 b/source/Public/Copy Job/Update-FabricCopyJob.ps1 index 0db436dc..4895ee14 100644 --- a/source/Public/Copy Job/Update-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Update-FabricCopyJob.ps1 @@ -3,7 +3,7 @@ Updates an existing Copy Job in a specified Microsoft Fabric workspace. .DESCRIPTION - Sends a PATCH request to the Microsoft Fabric API to update an existing Copy Job + Sends a PATCH request to the Microsoft Fabric API to update an existing Copy Job in the specified workspace. Allows updating the Copy Job's name and optionally its description. .PARAMETER WorkspaceId @@ -33,8 +33,8 @@ function Update-FabricCopyJob { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$CopyJobId, @@ -71,20 +71,19 @@ function Update-FabricCopyJob { # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - + # Make the API request $response = Invoke-FabricAPIRequest ` -Headers $FabricConfig.FabricHeaders ` -BaseURI $apiEndpointURI ` -Method Patch ` - -Body $bodyJson + -Body $bodyJson Write-Message -Message "Copy Job '$CopyJobName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 b/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 index 4fd57f0f..883cbb65 100644 --- a/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 +++ b/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of a Copy Job in a Microsoft Fabric workspace. .DESCRIPTION -This function updates the content or metadata of a Copy Job within a Microsoft Fabric workspace. +This function updates the content or metadata of a Copy Job within a Microsoft Fabric workspace. The Copy Job content and platform-specific definitions can be provided as file paths, which will be encoded as Base64 and sent in the request. .PARAMETER WorkspaceId @@ -51,7 +51,7 @@ function Update-FabricCopyJobDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$CopyJobPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$CopyJobPathPlatformDefinition @@ -67,7 +67,7 @@ function Update-FabricCopyJobDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/copyJobs/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId if ($CopyJobPathPlatformDefinition) { - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -75,12 +75,12 @@ function Update-FabricCopyJobDefinition { $body = @{ definition = @{ parts = @() - } + } } - + if ($CopyJobPathDefinition) { $CopyJobEncodedContent = Convert-ToBase64 -filePath $CopyJobPathDefinition - + if (-not [string]::IsNullOrEmpty($CopyJobEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -88,8 +88,7 @@ function Update-FabricCopyJobDefinition { payload = $CopyJobEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Copy Job definition." -Level Error return $null } @@ -104,8 +103,7 @@ function Update-FabricCopyJobDefinition { payload = $CopyJobEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -121,14 +119,13 @@ function Update-FabricCopyJobDefinition { -Method Post ` -Body $bodyJson - Write-Message -Message "Copy Job updated successfully!" -Level Info + Write-Message -Message "Copy Job updated successfully!" -Level Info return $response - - - } - catch { + + + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Dashboard/Get-FabricDashboard.ps1 b/source/Public/Dashboard/Get-FabricDashboard.ps1 index ad267a30..b8335ff6 100644 --- a/source/Public/Dashboard/Get-FabricDashboard.ps1 +++ b/source/Public/Dashboard/Get-FabricDashboard.ps1 @@ -17,7 +17,7 @@ - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. - Author: Tiago Balabuch + Author: Tiago Balabuch #> function Get-FabricDashboard { @@ -42,13 +42,12 @@ function Get-FabricDashboard { -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Get - + return $Dashboards - } - catch { + } catch { # Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Dashboard. Error: $errorDetails" -Level Error - } -} + } +} \ No newline at end of file diff --git a/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 index 7a8ca077..c1510999 100644 --- a/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 @@ -1,5 +1,5 @@ function Get-FabricDataPipeline { -<# + <# .SYNOPSIS Retrieves data pipelines from a specified Microsoft Fabric workspace. @@ -29,7 +29,7 @@ function Get-FabricDataPipeline { - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -70,11 +70,9 @@ function Get-FabricDataPipeline { # Filter results based on provided parameters $response = if ($DataPipelineId) { $DataPipelines | Where-Object { $_.Id -eq $DataPipelineId } - } - elseif ($DataPipelineName) { + } elseif ($DataPipelineName) { $DataPipelines | Where-Object { $_.DisplayName -eq $DataPipelineName } - } - else { + } else { # Return all DataPipelines if no filter is provided Write-Message -Message "No filter provided. Returning all DataPipelines." -Level Debug $DataPipelines @@ -84,16 +82,14 @@ function Get-FabricDataPipeline { if ($response) { Write-Message -Message "DataPipeline found matching the specified criteria." -Level Debug return $response - } - else { + } else { Write-Message -Message "No DataPipeline found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve DataPipeline. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 index 5daa2ff3..c380160a 100644 --- a/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 @@ -3,8 +3,8 @@ Creates a new DataPipeline in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new DataPipeline - in the specified workspace. It supports optional parameters for DataPipeline description + This function sends a POST request to the Microsoft Fabric API to create a new DataPipeline + in the specified workspace. It supports optional parameters for DataPipeline description and path definitions for the DataPipeline content. .PARAMETER WorkspaceId @@ -17,7 +17,7 @@ An optional description for the DataPipeline. .EXAMPLE - New-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineName "New DataPipeline" + New-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineName "New DataPipeline" This example creates a new DataPipeline named "New DataPipeline" in the workspace with ID "workspace-12345" and uploads the definition file from the specified path. .NOTES @@ -72,13 +72,12 @@ function New-FabricDataPipeline { -Headers $FabricConfig.FabricHeaders ` -Method Post ` -Body $bodyJson - - Write-Message -Message "Data Pipeline created successfully!" -Level Info + + Write-Message -Message "Data Pipeline created successfully!" -Level Info return $response - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create DataPipeline. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 index f0ef3f1d..2d44bdac 100644 --- a/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 @@ -3,7 +3,7 @@ Removes a DataPipeline from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove a DataPipeline + This function sends a DELETE request to the Microsoft Fabric API to remove a DataPipeline from the specified workspace using the provided WorkspaceId and DataPipelineId. .PARAMETER WorkspaceId @@ -48,13 +48,12 @@ function Remove-FabricDataPipeline { $response = Invoke-FabricAPIRequest ` -Headers $FabricConfig.FabricHeaders ` -BaseURI $apiEndpointURI ` - -Method Delete + -Method Delete Write-Message -Message "DataPipeline '$DataPipelineId' deleted successfully from workspace '$WorkspaceId'." -Level Info return $response - } - catch { + } catch { # Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete DataPipeline '$DataPipelineId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 index 557fcf6a..d4d9b738 100644 --- a/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 @@ -3,7 +3,7 @@ Updates an existing DataPipeline in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing DataPipeline + This function sends a PATCH request to the Microsoft Fabric API to update an existing DataPipeline in the specified workspace. It supports optional parameters for DataPipeline description. .PARAMETER WorkspaceId @@ -33,8 +33,8 @@ function Update-FabricDataPipeline { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DataPipelineId, @@ -77,14 +77,13 @@ function Update-FabricDataPipeline { -Headers $FabricConfig.FabricHeaders ` -BaseURI $apiEndpointURI ` -Method Patch ` - -Body $bodyJson - + -Body $bodyJson + Write-Message -Message "DataPipeline '$DataPipelineName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update DataPipeline. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Datamart/Get-FabricDatamart.ps1 b/source/Public/Datamart/Get-FabricDatamart.ps1 index e8dd4de3..2aa761e9 100644 --- a/source/Public/Datamart/Get-FabricDatamart.ps1 +++ b/source/Public/Datamart/Get-FabricDatamart.ps1 @@ -1,5 +1,5 @@ function Get-FabricDatamart { -<# + <# .SYNOPSIS Retrieves datamarts from a specified workspace. @@ -25,7 +25,7 @@ function Get-FabricDatamart { - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -51,18 +51,16 @@ function Get-FabricDatamart { $apiEndpointURI = "{0}/workspaces/{1}/Datamarts" -f $FabricConfig.BaseUrl, $WorkspaceId $Datamarts = = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get # Step 9: Filter results based on provided parameters $response = if ($datamartId) { $Datamarts | Where-Object { $_.Id -eq $datamartId } - } - elseif ($datamartName) { + } elseif ($datamartName) { $Datamarts | Where-Object { $_.DisplayName -eq $datamartName } - } - else { + } else { # No filter, return all datamarts Write-Message -Message "No filter specified. Returning all datamarts." -Level Debug return $Datamarts @@ -72,15 +70,13 @@ function Get-FabricDatamart { if ($response) { Write-Message -Message "Datamart found matching the specified criteria." -Level Debug return $response - } - else { + } else { Write-Message -Message "No Datamart found matching the specified criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Datamart. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 index 5669c142..6cf747c8 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 @@ -20,7 +20,7 @@ Assigns workspaces to the domain with ID "12345" based on the specified capaciti - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Assign-FabricDomainWorkspaceByCapacity { @@ -44,12 +44,12 @@ function Assign-FabricDomainWorkspaceByCapacity { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspacesByCapacities" -f $FabricConfig.BaseUrl, $DomainId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 3: Construct the request body $body = @{ capacitiesIds = $CapacitiesIds } - + # Convert the body to JSON $bodyJson = $body | ConvertTo-Json -Depth 2 Write-Message -Message "Request Body: $bodyJson" -Level Debug @@ -78,25 +78,24 @@ function Assign-FabricDomainWorkspaceByCapacity { [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug return $operationStatus - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -104,10 +103,9 @@ function Assign-FabricDomainWorkspaceByCapacity { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Error occurred while assigning workspaces by capacity for domain '$DomainId'. Details: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 index 7c20edb6..e17355c0 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 @@ -44,12 +44,12 @@ function Assign-FabricDomainWorkspaceById { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/admin/domains/{1}/assignWorkspaces" -f $FabricConfig.BaseUrl, $DomainId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 3: Construct the request body $body = @{ workspacesIds = $WorkspaceIds } - + # Convert the body to JSON $bodyJson = $body | ConvertTo-Json -Depth 2 Write-Message -Message "Request Body: $bodyJson" -Level Debug @@ -74,10 +74,9 @@ function Assign-FabricDomainWorkspaceById { return $null } Write-Message -Message "Successfully assigned workspaces to the domain with ID '$DomainId'." -Level Info - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 index 52f868e8..5a52351f 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 @@ -12,7 +12,7 @@ The ID of the domain to which workspaces will be assigned. This parameter is man An array representing the principals with their `id` and `type` properties. Must contain a `principals` key with an array of objects. .EXAMPLE -$PrincipalIds = @( +$PrincipalIds = @( @{id = "813abb4a-414c-4ac0-9c2c-bd17036fd58c"; type = "User"}, @{id = "b5b9495c-685a-447a-b4d3-2d8e963e6288"; type = "User"} ) @@ -90,15 +90,14 @@ function Assign-FabricDomainWorkspaceByPrincipal { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug return $operationStatus - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return operationStatus @@ -110,10 +109,9 @@ function Assign-FabricDomainWorkspaceByPrincipal { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign domain workspaces by principals. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 b/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 index da306f7d..3cbcb1df 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 +++ b/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 @@ -27,7 +27,7 @@ Assigns the `Admins` role to the specified principals in the domain with ID "123 - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Assign-FabricDomainWorkspaceRoleAssignment { @@ -91,10 +91,9 @@ function Assign-FabricDomainWorkspaceRoleAssignment { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + Write-Message -Message "Bulk role assignment for domain '$DomainId' completed successfully!" -Level Info - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error diff --git a/source/Public/Domain/Get-FabricDomain.ps1 b/source/Public/Domain/Get-FabricDomain.ps1 index 31c8c1c7..8ecf06cf 100644 --- a/source/Public/Domain/Get-FabricDomain.ps1 +++ b/source/Public/Domain/Get-FabricDomain.ps1 @@ -28,7 +28,7 @@ Fetches the domain with the display name "Finance". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Get-FabricDomain { @@ -59,7 +59,7 @@ function Get-FabricDomain { Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - # Step 3: Construct the API URL with filtering logic + # Step 3: Construct the API URL with filtering logic $apiEndpointUrl = "{0}/admin/domains" -f $FabricConfig.BaseUrl if ($NonEmptyDomainsOnly) { $apiEndpointUrl = "{0}?nonEmptyOnly=true" -f $apiEndpointUrl @@ -83,7 +83,7 @@ function Get-FabricDomain { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 6: Handle empty response if (-not $response) { Write-Message -Message "No data returned from the API." -Level Warning @@ -94,11 +94,9 @@ function Get-FabricDomain { # Step 7: Filter results based on provided parameters $domains = if ($DomainId) { $response.domains | Where-Object { $_.Id -eq $DomainId } - } - elseif ($DomainName) { + } elseif ($DomainName) { $response.domains | Where-Object { $_.DisplayName -eq $DomainName } - } - else { + } else { # Return all domains if no filter is provided Write-Message -Message "No filter provided. Returning all domains." -Level Debug return $response.domains @@ -107,15 +105,13 @@ function Get-FabricDomain { # Step 8: Handle results if ($domains) { return $domains - } - else { + } else { Write-Message -Message "No domain found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/Get-FabricDomainWorkspace.ps1 b/source/Public/Domain/Get-FabricDomainWorkspace.ps1 index 4094ab30..337ac15e 100644 --- a/source/Public/Domain/Get-FabricDomainWorkspace.ps1 +++ b/source/Public/Domain/Get-FabricDomainWorkspace.ps1 @@ -17,7 +17,7 @@ Fetches workspaces for the domain with ID "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -56,7 +56,7 @@ function Get-FabricDomainWorkspace { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 5: Handle empty response if (-not $response) { Write-Message -Message "No data returned from the API." -Level Warning @@ -65,16 +65,14 @@ function Get-FabricDomainWorkspace { # Step 6: Handle results if ($response) { return $response.value - } - else { + } else { Write-Message -Message "No workspace found for the '$DomainId'." -Level Warning return $null } - } - catch { + } catch { # Step 7: Capture and log error details $errorDetails = Get-ErrorResponse($_.Exception) Write-Message -Message "Failed to retrieve domain workspaces. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/New-FabricDomain.ps1 b/source/Public/Domain/New-FabricDomain.ps1 index e60c9048..20951ae2 100644 --- a/source/Public/Domain/New-FabricDomain.ps1 +++ b/source/Public/Domain/New-FabricDomain.ps1 @@ -23,7 +23,7 @@ Creates a "Finance" domain under the parent domain with ID "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -82,7 +82,7 @@ function New-FabricDomain { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 201 { @@ -94,24 +94,23 @@ function New-FabricDomain { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -119,10 +118,9 @@ function New-FabricDomain { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create domain. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/Remove-FabricDomain.ps1 b/source/Public/Domain/Remove-FabricDomain.ps1 index c7103158..272ead51 100644 --- a/source/Public/Domain/Remove-FabricDomain.ps1 +++ b/source/Public/Domain/Remove-FabricDomain.ps1 @@ -17,7 +17,7 @@ Deletes the domain with ID "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -58,11 +58,10 @@ function Remove-FabricDomain { } Write-Message -Message "Domain '$DomainId' deleted successfully!" -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete domain '$DomainId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 b/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 index d86afbdc..62b0b5fb 100644 --- a/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 +++ b/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 @@ -4,7 +4,7 @@ Unassign workspaces from a specified Fabric domain. .DESCRIPTION -The `Unassign -FabricDomainWorkspace` function allows you to Unassign specific workspaces from a given Fabric domain or unassign all workspaces if no workspace IDs are specified. +The `Unassign -FabricDomainWorkspace` function allows you to Unassign specific workspaces from a given Fabric domain or unassign all workspaces if no workspace IDs are specified. It makes a POST request to the relevant API endpoint for this operation. .PARAMETER DomainId @@ -28,7 +28,7 @@ Unassigns the specified workspaces from the domain with ID "12345". - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Unassign-FabricDomainWorkspace { @@ -37,7 +37,7 @@ function Unassign-FabricDomainWorkspace { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DomainId, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [array]$WorkspaceIds @@ -55,17 +55,16 @@ function Unassign-FabricDomainWorkspace { $apiEndpointUrl = "{0}/admin/domains/{1}/{2}" -f $FabricConfig.BaseUrl, $DomainId, $endpointSuffix Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 3: Construct the request body (if needed) $bodyJson = if ($WorkspaceIds) { $body = @{ workspacesIds = $WorkspaceIds } $body | ConvertTo-Json -Depth 2 - } - else { + } else { $null } - + Write-Message -Message "Request Body: $bodyJson" -Level Debug # Step 4: Make the API request to unassign specific workspaces $response = Invoke-RestMethod ` @@ -87,8 +86,7 @@ function Unassign-FabricDomainWorkspace { return $null } Write-Message -Message "Successfully unassigned workspaces to the domain with ID '$DomainId'." -Level Info - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to unassign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error diff --git a/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 b/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 index c818fbb7..d7bec206 100644 --- a/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 +++ b/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 @@ -27,7 +27,7 @@ Unassign the `Admins` role to the specified principals in the domain with ID "12 - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -93,9 +93,8 @@ function Unassign-FabricDomainWorkspaceRoleAssignment { return $null } Write-Message -Message "Bulk role unassignment for domain '$DomainId' completed successfully!" -Level Info - - } - catch { + + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error diff --git a/source/Public/Domain/Update-FabricDomain.ps1 b/source/Public/Domain/Update-FabricDomain.ps1 index 0ffad315..0ac66fe0 100644 --- a/source/Public/Domain/Update-FabricDomain.ps1 +++ b/source/Public/Domain/Update-FabricDomain.ps1 @@ -3,7 +3,7 @@ Updates a Fabric domain by its ID. .DESCRIPTION -The `Update-FabricDomain` function modifies a specified domain in Microsoft Fabric using the provided parameters. +The `Update-FabricDomain` function modifies a specified domain in Microsoft Fabric using the provided parameters. .PARAMETER DomainId The unique identifier of the domain to be updated. @@ -26,7 +26,7 @@ Updates the domain with ID "12345" with a new name, description, and contributor - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -98,15 +98,13 @@ function Update-FabricDomain { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 6: Handle results Write-Message -Message "Domain '$DomainName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update domain '$DomainId'. Error: $errorDetails" -Level Error } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/source/Public/Environment/Get-FabricEnvironment.ps1 b/source/Public/Environment/Get-FabricEnvironment.ps1 index cddb5443..05c16161 100644 --- a/source/Public/Environment/Get-FabricEnvironment.ps1 +++ b/source/Public/Environment/Get-FabricEnvironment.ps1 @@ -1,5 +1,5 @@ function Get-FabricEnvironment { -<# + <# .SYNOPSIS Retrieves an environment or a list of environments from a specified workspace in Microsoft Fabric. @@ -32,7 +32,7 @@ Retrieves all environments in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( @@ -115,13 +115,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -131,11 +129,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $environment = if ($EnvironmentId) { $environments | Where-Object { $_.Id -eq $EnvironmentId } - } - elseif ($EnvironmentName) { + } elseif ($EnvironmentName) { $environments | Where-Object { $_.DisplayName -eq $EnvironmentName } - } - else { + } else { # Return all workspaces if no filter is provided Write-Message -Message "No filter provided. Returning all environments." -Level Debug $environments @@ -145,16 +141,14 @@ Author: Tiago Balabuch if ($environment) { Write-Message -Message "Environment found in the Workspace '$WorkspaceId'." -Level Debug return $environment - } - else { + } else { Write-Message -Message "No environment found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 b/source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 index 75a650ab..41f4330d 100644 --- a/source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 +++ b/source/Public/Environment/Get-FabricEnvironmentLibrary.ps1 @@ -3,8 +3,8 @@ Retrieves the list of libraries associated with a specific environment in a Microsoft Fabric workspace. .DESCRIPTION -The Get-FabricEnvironmentLibrary function fetches library information for a given workspace and environment -using the Microsoft Fabric API. It ensures the authentication token is valid and validates the response +The Get-FabricEnvironmentLibrary function fetches library information for a given workspace and environment +using the Microsoft Fabric API. It ensures the authentication token is valid and validates the response to handle errors gracefully. .PARAMETER WorkspaceId @@ -22,7 +22,7 @@ Retrieves the libraries associated with the specified environment in the given w - Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - Uses `Test-TokenExpired` to validate the token before making API calls. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Get-FabricEnvironmentLibrary { [CmdletBinding()] @@ -63,14 +63,13 @@ function Get-FabricEnvironmentLibrary { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 5: Handle results return $response - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment libraries. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 b/source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 index 65cf74de..cf60c915 100644 --- a/source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 +++ b/source/Public/Environment/Get-FabricEnvironmentSparkCompute.ps1 @@ -3,8 +3,8 @@ Retrieves the Spark compute details for a specific environment in a Microsoft Fabric workspace. .DESCRIPTION -The Get-FabricEnvironmentSparkCompute function communicates with the Microsoft Fabric API to fetch information -about Spark compute resources associated with a specified environment. It ensures that the API token is valid +The Get-FabricEnvironmentSparkCompute function communicates with the Microsoft Fabric API to fetch information +about Spark compute resources associated with a specified environment. It ensures that the API token is valid and gracefully handles errors during the API call. .PARAMETER WorkspaceId @@ -22,7 +22,7 @@ Retrieves Spark compute details for the specified environment in the given works - Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - Uses `Test-TokenExpired` to validate the token before making API calls. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Get-FabricEnvironmentSparkCompute { [CmdletBinding()] @@ -63,14 +63,13 @@ function Get-FabricEnvironmentSparkCompute { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 5: Handle results return $response - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment Spark compute. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 index 0a8a5505..1e87be69 100644 --- a/source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 +++ b/source/Public/Environment/Get-FabricEnvironmentStagingLibrary.ps1 @@ -3,7 +3,7 @@ Retrieves the staging library details for a specific environment in a Microsoft Fabric workspace. .DESCRIPTION -The Get-FabricEnvironmentStagingLibrary function interacts with the Microsoft Fabric API to fetch information +The Get-FabricEnvironmentStagingLibrary function interacts with the Microsoft Fabric API to fetch information about staging libraries associated with a specified environment. It ensures token validity and handles API errors gracefully. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ Retrieves the staging libraries for the specified environment in the given works - Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - Uses `Test-TokenExpired` to validate the token before making API calls. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Get-FabricEnvironmentStagingLibrary { [CmdletBinding()] @@ -55,7 +55,7 @@ function Get-FabricEnvironmentStagingLibrary { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 4: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -63,14 +63,13 @@ function Get-FabricEnvironmentStagingLibrary { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 5: Handle results return $response.customLibraries - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment spark compute. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 b/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 index dc0c38b1..49075388 100644 --- a/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 +++ b/source/Public/Environment/Get-FabricEnvironmentStagingSparkCompute.ps1 @@ -1,5 +1,5 @@ function Get-FabricEnvironmentStagingSparkCompute { -<# + <# .SYNOPSIS Retrieves staging Spark compute details for a specific environment in a Microsoft Fabric workspace. @@ -23,7 +23,7 @@ Retrieves the staging Spark compute configurations for the specified environment - Uses `Test-TokenExpired` to validate the token before making API calls. Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -66,11 +66,10 @@ Author: Tiago Balabuch # Step 5: Handle results return $response - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment spark compute. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/New-FabricEnvironment.ps1 b/source/Public/Environment/New-FabricEnvironment.ps1 index 009a5aeb..5b016513 100644 --- a/source/Public/Environment/New-FabricEnvironment.ps1 +++ b/source/Public/Environment/New-FabricEnvironment.ps1 @@ -23,7 +23,7 @@ Creates an environment named "DevEnv" in workspace "12345" with the specified de - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -89,24 +89,23 @@ function New-FabricEnvironment { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -114,10 +113,9 @@ function New-FabricEnvironment { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create environment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Publish-FabricEnvironment.ps1 b/source/Public/Environment/Publish-FabricEnvironment.ps1 index bf9fffbc..03ed87fc 100644 --- a/source/Public/Environment/Publish-FabricEnvironment.ps1 +++ b/source/Public/Environment/Publish-FabricEnvironment.ps1 @@ -3,7 +3,7 @@ Publishes a staging environment in a specified Microsoft Fabric workspace. .DESCRIPTION -This function interacts with the Microsoft Fabric API to initiate the publishing process for a staging environment. +This function interacts with the Microsoft Fabric API to initiate the publishing process for a staging environment. It validates the authentication token, constructs the API request, and handles both immediate and long-running operations. @@ -22,7 +22,7 @@ Initiates the publishing process for the specified staging environment. - Requires the `$FabricConfig` global object, including `BaseUrl` and `FabricHeaders`. - Uses `Test-TokenExpired` to validate the token before making API calls. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Publish-FabricEnvironment { @@ -70,24 +70,23 @@ function Publish-FabricEnvironment { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -95,10 +94,9 @@ function Publish-FabricEnvironment { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create environment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Remove-FabricEnvironment.ps1 b/source/Public/Environment/Remove-FabricEnvironment.ps1 index 4e1f734e..ebf61f8e 100644 --- a/source/Public/Environment/Remove-FabricEnvironment.ps1 +++ b/source/Public/Environment/Remove-FabricEnvironment.ps1 @@ -20,7 +20,7 @@ Deletes the environment with ID "67890" from workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -64,11 +64,10 @@ function Remove-FabricEnvironment { return $null } Write-Message -Message "Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 index c5f3d5ad..87639e8b 100644 --- a/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 +++ b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 @@ -1,5 +1,5 @@ function Remove-FabricEnvironmentStagingLibrary { -<# + <# .SYNOPSIS Deletes a specified library from the staging environment in a Microsoft Fabric workspace. @@ -27,7 +27,7 @@ Deletes the specified library from the staging environment in the specified work - This function currently supports deleting one library at a time. Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -65,7 +65,7 @@ Author: Tiago Balabuch -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - # Step 4: Validate the response code + # Step 4: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -73,10 +73,9 @@ Author: Tiago Balabuch return $null } Write-Message -Message "Staging library $LibraryName for the Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 b/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 index 9276ea0a..f6ebd36b 100644 --- a/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 +++ b/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 @@ -21,10 +21,10 @@ Cancels the publish operation for the specified environment. - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> -function Stop-FabricEnvironmentPublish{ +function Stop-FabricEnvironmentPublish { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -66,11 +66,10 @@ function Stop-FabricEnvironmentPublish{ return $null } Write-Message -Message "Publication for environment '$EnvironmentId' has been successfully canceled." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to cancel publication for environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Update-FabricEnvironment.ps1 b/source/Public/Environment/Update-FabricEnvironment.ps1 index daa06a5b..7b530d96 100644 --- a/source/Public/Environment/Update-FabricEnvironment.ps1 +++ b/source/Public/Environment/Update-FabricEnvironment.ps1 @@ -102,10 +102,9 @@ function Update-FabricEnvironment { # Step 6: Handle results Write-Message -Message "Environment '$EnvironmentName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Environment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 b/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 index bd542623..e9b97672 100644 --- a/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 +++ b/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 @@ -52,7 +52,7 @@ Update-FabricEnvironmentStagingSparkCompute -WorkspaceId "workspace-12345" -Envi - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Update-FabricEnvironmentStagingSparkCompute { @@ -61,7 +61,7 @@ function Update-FabricEnvironmentStagingSparkCompute { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EnvironmentId, @@ -156,8 +156,8 @@ function Update-FabricEnvironmentStagingSparkCompute { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - - # Step 5: Validate the response code + + # Step 5: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -168,10 +168,9 @@ function Update-FabricEnvironmentStagingSparkCompute { # Step 6: Handle results Write-Message -Message "Environment staging Spark compute updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update environment staging Spark compute. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 index d8b09960..1d3dfe02 100644 --- a/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 +++ b/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 @@ -20,7 +20,7 @@ Upload-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -Environme - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Upload-FabricEnvironmentStagingLibrary { @@ -29,7 +29,7 @@ function Upload-FabricEnvironmentStagingLibrary { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$WorkspaceId, - + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EnvironmentId @@ -46,9 +46,9 @@ function Upload-FabricEnvironmentStagingLibrary { Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Construct the request body - + # Step 4: Make the API request - $response = Invoke-RestMethod ` + $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` -Uri $apiEndpointUrl ` -Method Post ` @@ -70,10 +70,9 @@ function Upload-FabricEnvironmentStagingLibrary { # Step 6: Handle results Write-Message -Message "Environment staging library uploaded successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to upload environment staging library. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Get-FabricEventhouse.ps1 b/source/Public/Eventhouse/Get-FabricEventhouse.ps1 index 467c3d30..e1a501f6 100644 --- a/source/Public/Eventhouse/Get-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Get-FabricEventhouse.ps1 @@ -1,5 +1,5 @@ function Get-FabricEventhouse { -<# + <# .SYNOPSIS Retrieves Fabric Eventhouses @@ -60,7 +60,7 @@ function Get-FabricEventhouse { - 2024-11-09 - FGE: Added DisplaName as Alias for EventhouseName - 2024-11-16 - FGE: Added Verbose Output - 2024-11-27 - FGE: Added more Verbose Output -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -141,13 +141,11 @@ function Get-FabricEventhouse { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -157,11 +155,9 @@ function Get-FabricEventhouse { # Step 8: Filter results based on provided parameters $eventhouse = if ($EventhouseId) { $eventhouses | Where-Object { $_.Id -eq $EventhouseId } - } - elseif ($EventhouseName) { + } elseif ($EventhouseName) { $eventhouses | Where-Object { $_.DisplayName -eq $EventhouseName } - } - else { + } else { # Return all eventhouses if no filter is provided Write-Message -Message "No filter provided. Returning all Eventhouses." -Level Debug $eventhouses @@ -171,16 +167,14 @@ function Get-FabricEventhouse { if ($eventhouse) { Write-Message -Message "Eventhouse found in the Workspace '$WorkspaceId'." -Level Debug return $eventhouse - } - else { + } else { Write-Message -Message "No Eventhouse found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 b/source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 index e42a8a17..1f8bc72d 100644 --- a/source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 +++ b/source/Public/Eventhouse/Get-FabricEventhouseDefinition.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricEventhouseDefinition { [CmdletBinding()] @@ -53,11 +53,11 @@ function Get-FabricEventhouseDefinition { # Step 3: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId - + if ($EventhouseFormat) { $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $EventhouseFormat } - + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 4: Make the API request @@ -81,30 +81,29 @@ function Get-FabricEventhouseDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -114,11 +113,10 @@ function Get-FabricEventhouseDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Eventhouse. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Eventhouse/New-FabricEventhouse.ps1 b/source/Public/Eventhouse/New-FabricEventhouse.ps1 index c88a0a12..9ff47190 100644 --- a/source/Public/Eventhouse/New-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/New-FabricEventhouse.ps1 @@ -1,5 +1,5 @@ function New-FabricEventhouse { -<# + <# .SYNOPSIS Creates a new Eventhouse in a specified Microsoft Fabric workspace. @@ -32,7 +32,7 @@ function New-FabricEventhouse { Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( @@ -82,7 +82,7 @@ function New-FabricEventhouse { # Initialize definition if it doesn't exist if (-not $body.definition) { $body.definition = @{ - parts = @() + parts = @() } } @@ -92,8 +92,7 @@ function New-FabricEventhouse { payload = $eventhouseEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error return $null } @@ -106,7 +105,7 @@ function New-FabricEventhouse { # Initialize definition if it doesn't exist if (-not $body.definition) { $body.definition = @{ - parts = @() + parts = @() } } @@ -116,8 +115,7 @@ function New-FabricEventhouse { payload = $eventhouseEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -170,8 +168,7 @@ function New-FabricEventhouse { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -185,10 +182,9 @@ function New-FabricEventhouse { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Eventhouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 index bcbf6487..9f0a0481 100644 --- a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 @@ -1,5 +1,5 @@ function Remove-FabricEventhouse { -<# + <# .SYNOPSIS Removes an Eventhouse from a specified Microsoft Fabric workspace. @@ -35,7 +35,7 @@ function Remove-FabricEventhouse { Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( @@ -67,7 +67,7 @@ function Remove-FabricEventhouse { -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - # Step 4: Handle response + # Step 4: Handle response if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -77,10 +77,9 @@ function Remove-FabricEventhouse { } Write-Message -Message "Eventhouse '$EventhouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Eventhouse '$EventhouseId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Update-FabricEventhouse.ps1 b/source/Public/Eventhouse/Update-FabricEventhouse.ps1 index 6968c89c..1de7e6c4 100644 --- a/source/Public/Eventhouse/Update-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Update-FabricEventhouse.ps1 @@ -3,7 +3,7 @@ Updates an existing Eventhouse in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Eventhouse + This function sends a PATCH request to the Microsoft Fabric API to update an existing Eventhouse in the specified workspace. It supports optional parameters for Eventhouse description. .PARAMETER WorkspaceId @@ -27,15 +27,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricEventhouse { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EventhouseId, @@ -96,10 +96,9 @@ function Update-FabricEventhouse { # Step 6: Handle results Write-Message -Message "Eventhouse '$EventhouseName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 b/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 index aa1bf713..71e0152f 100644 --- a/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 +++ b/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of an existing Eventhouse in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Eventhouse + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Eventhouse in the specified workspace. It supports optional parameters for Eventhouse definition and platform-specific definition. .PARAMETER WorkspaceId @@ -27,7 +27,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricEventhouseDefinition { [CmdletBinding()] @@ -43,7 +43,7 @@ function Update-FabricEventhouseDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EventhousePathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$EventhousePathPlatformDefinition @@ -58,21 +58,21 @@ function Update-FabricEventhouseDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId #if ($UpdateMetadata -eq $true) { - if($EventhousePathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + if ($EventhousePathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Construct the request body $body = @{ definition = @{ - parts = @() - } + parts = @() + } } - + if ($EventhousePathDefinition) { $EventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition - + if (-not [string]::IsNullOrEmpty($EventhouseEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -80,8 +80,7 @@ function Update-FabricEventhouseDefinition { payload = $EventhouseEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error return $null } @@ -96,8 +95,7 @@ function Update-FabricEventhouseDefinition { payload = $EventhouseEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -116,7 +114,7 @@ function Update-FabricEventhouseDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -125,44 +123,42 @@ function Update-FabricEventhouseDefinition { } 202 { Write-Message -Message "Update definition for Eventhouse '$EventhouseId' accepted. Operation in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventstream/Get-FabricEventstream.ps1 b/source/Public/Eventstream/Get-FabricEventstream.ps1 index 98c25e87..74319aed 100644 --- a/source/Public/Eventstream/Get-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Get-FabricEventstream.ps1 @@ -1,5 +1,5 @@ function Get-FabricEventstream { -<# + <# .SYNOPSIS Retrieves an Eventstream or a list of Eventstreams from a specified workspace in Microsoft Fabric. @@ -33,7 +33,7 @@ Retrieves all Eventstreams in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] @@ -119,13 +119,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -136,11 +134,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $eventstream = if ($EventstreamId) { $eventstreams | Where-Object { $_.Id -eq $EventstreamId } - } - elseif ($EventstreamName) { + } elseif ($EventstreamName) { $eventstreams | Where-Object { $_.DisplayName -eq $EventstreamName } - } - else { + } else { # Return all eventstreams if no filter is provided Write-Message -Message "No filter provided. Returning all Eventstreams." -Level Debug $eventstreams @@ -150,16 +146,14 @@ Author: Tiago Balabuch if ($eventstream) { Write-Message -Message "Eventstream found matching the specified criteria." -Level Debug return $eventstream - } - else { + } else { Write-Message -Message "No Eventstream found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 b/source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 index eb070a7b..411deb41 100644 --- a/source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 +++ b/source/Public/Eventstream/Get-FabricEventstreamDefinition.ps1 @@ -4,7 +4,7 @@ Retrieves the definition of a Eventstream from a specific workspace in Microsoft Fabric. .DESCRIPTION -This function fetches the Eventstream's content or metadata from a workspace. +This function fetches the Eventstream's content or metadata from a workspace. Handles both synchronous and asynchronous operations, with detailed logging and error handling. .PARAMETER WorkspaceId @@ -85,37 +85,35 @@ function Get-FabricEventstreamDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } - + } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Eventstream. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Eventstream/New-FabricEventstream.ps1 b/source/Public/Eventstream/New-FabricEventstream.ps1 index 117b6233..481ec846 100644 --- a/source/Public/Eventstream/New-FabricEventstream.ps1 +++ b/source/Public/Eventstream/New-FabricEventstream.ps1 @@ -1,5 +1,5 @@ function New-FabricEventstream { -<# + <# .SYNOPSIS Creates a new Eventstream in a specified Microsoft Fabric workspace. @@ -34,7 +34,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u Author: Tiago Balabuch #> -#TODO SupportsShouldProcess + #TODO SupportsShouldProcess [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -96,8 +96,7 @@ Author: Tiago Balabuch payload = $EventstreamEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error return $null } @@ -121,8 +120,7 @@ Author: Tiago Balabuch payload = $EventstreamEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -167,8 +165,7 @@ Author: Tiago Balabuch Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -180,10 +177,9 @@ Author: Tiago Balabuch throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Eventstream. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventstream/Remove-FabricEventstream.ps1 b/source/Public/Eventstream/Remove-FabricEventstream.ps1 index 30f68ae3..9e2470d5 100644 --- a/source/Public/Eventstream/Remove-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Remove-FabricEventstream.ps1 @@ -1,5 +1,5 @@ function Remove-FabricEventstream { -<# + <# .SYNOPSIS Deletes an Eventstream from a specified workspace in Microsoft Fabric. @@ -28,9 +28,9 @@ Deletes the Eventstream with ID "67890" from workspace "12345". Author: Tiago Balabuch -#> + #> -#TODO SupportsShouldProcess + #TODO SupportsShouldProcess [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -40,7 +40,7 @@ Author: Tiago Balabuch [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EventstreamId -#TODO Add EventstreamName parameter to validate the name of the Eventstream to delete. + #TODO Add EventstreamName parameter to validate the name of the Eventstream to delete. ) try { @@ -71,10 +71,9 @@ Author: Tiago Balabuch } Write-Message -Message "Eventstream '$EventstreamId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Eventstream '$EventstreamId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventstream/Update-FabricEventstream.ps1 b/source/Public/Eventstream/Update-FabricEventstream.ps1 index 2ca2e39f..c5059ec3 100644 --- a/source/Public/Eventstream/Update-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Update-FabricEventstream.ps1 @@ -101,10 +101,9 @@ function Update-FabricEventstream { # Step 6: Handle results Write-Message -Message "Eventstream '$EventstreamName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 b/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 index 0929734e..3fb1b5ce 100644 --- a/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 +++ b/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of a Eventstream in a Microsoft Fabric workspace. .DESCRIPTION -This function allows updating the content or metadata of a Eventstream in a Microsoft Fabric workspace. +This function allows updating the content or metadata of a Eventstream in a Microsoft Fabric workspace. The Eventstream content can be provided as file paths, and metadata updates can optionally be enabled. .PARAMETER WorkspaceId @@ -19,7 +19,7 @@ The Eventstream content can be provided as file paths, and metadata updates can (Optional) The file path to the Eventstream's platform-specific definition file. The content will be encoded as Base64 and sent in the request. .PARAMETER UpdateMetadata -(Optional)A boolean flag indicating whether to update the Eventstream's metadata. +(Optional)A boolean flag indicating whether to update the Eventstream's metadata. Default: `$false`. .EXAMPLE @@ -38,7 +38,7 @@ Updates both the content and metadata of the Eventstream with ID `67890` in the - The Eventstream content is encoded as Base64 before being sent to the Fabric API. - This function handles asynchronous operations and retrieves operation results if required. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -56,7 +56,7 @@ function Update-FabricEventstreamDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EventstreamPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$EventstreamPathPlatformDefinition @@ -71,21 +71,21 @@ function Update-FabricEventstreamDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId - if($EventstreamPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + if ($EventstreamPathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Construct the request body $body = @{ definition = @{ - parts = @() - } + parts = @() + } } - + if ($EventstreamPathDefinition) { $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition - + if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -93,8 +93,7 @@ function Update-FabricEventstreamDefinition { payload = $EventstreamEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error return $null } @@ -109,8 +108,7 @@ function Update-FabricEventstreamDefinition { payload = $EventstreamEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -129,7 +127,7 @@ function Update-FabricEventstreamDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -140,41 +138,39 @@ function Update-FabricEventstreamDefinition { Write-Message -Message "Update definition for Eventstream '$EventstreamId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/External Data Share/Get-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Get-FabricExternalDataShares.ps1 index d9434787..c79cf336 100644 --- a/source/Public/External Data Share/Get-FabricExternalDataShares.ps1 +++ b/source/Public/External Data Share/Get-FabricExternalDataShares.ps1 @@ -7,7 +7,7 @@ It handles token validation, constructs the API URL, makes the API request, and processes the response. .EXAMPLE - Get-FabricExternalDataShares + Get-FabricExternalDataShares This example retrieves the External Data Shares details .NOTES @@ -21,30 +21,29 @@ function Get-FabricExternalDataShares { param ( ) try { - - # Validate authentication token before proceeding + + # Validate authentication token before proceeding Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - # Construct the API endpoint URI for retrieving external data shares + # Construct the API endpoint URI for retrieving external data shares Write-Message -Message "Constructing API endpoint URI..." -Level Debug $apiEndpointURI = "{0}/admin/items/externalDataShares" -f $FabricConfig.BaseUrl, $WorkspaceId - + # Invoke the API request to retrieve external data shares $externalDataShares = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Get + -Method Get # Return the retrieved external data shares Write-Message -Message "Successfully retrieved external data shares." -Level Debug return $externalDataShares - } - catch { + } catch { # Capture and log detailed error information if the API request fails $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 index f165eea8..7e811a20 100644 --- a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 +++ b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 @@ -1,5 +1,5 @@ function Revoke-FabricExternalDataShares { -<# + <# .SYNOPSIS Retrieves External Data Shares details from a specified Microsoft Fabric. @@ -25,7 +25,7 @@ function Revoke-FabricExternalDataShares { - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( @@ -40,7 +40,7 @@ function Revoke-FabricExternalDataShares { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ExternalDataShareId - ) + ) try { @@ -61,11 +61,10 @@ function Revoke-FabricExternalDataShares { # Step 4: Return retrieved data Write-Message -Message "Successfully revoked external data shares." -Level Info return $externalDataShares - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Get-AllFabricDatasetRefreshes.ps1 b/source/Public/Get-AllFabricDatasetRefreshes.ps1 index 826b6867..12a6c81f 100644 --- a/source/Public/Get-AllFabricDatasetRefreshes.ps1 +++ b/source/Public/Get-AllFabricDatasetRefreshes.ps1 @@ -1,5 +1,5 @@ function Get-FabricDatasetRefreshes { -<# + <# .SYNOPSIS Retrieves all refreshes for all datasets in all PowerBI workspaces. @@ -13,9 +13,9 @@ This example retrieves all refreshes for all datasets in all PowerBI workspaces. .NOTES The function makes a GET request to the PowerBI API to retrieve the refreshes. It loops through each workspace and each dataset in each workspace. If a dataset is refreshable, it retrieves the refreshes for the dataset and selects the most recent one. It then creates a PSCustomObject with information about the refresh and adds it to an array of refreshes. Finally, it returns the array of refreshes. -#> + #> -# This function retrieves all refreshes for all datasets in all PowerBI workspaces. + # This function retrieves all refreshes for all datasets in all PowerBI workspaces. # Define aliases for the function for flexibility. Confirm-FabricAuthToken | Out-Null @@ -54,4 +54,4 @@ The function makes a GET request to the PowerBI API to retrieve the refreshes. I } # Return the array of refreshes. return $refs -} +} \ No newline at end of file diff --git a/source/Public/Get-FabricAPIClusterURI.ps1 b/source/Public/Get-FabricAPIClusterURI.ps1 index b59fb66e..78414307 100644 --- a/source/Public/Get-FabricAPIClusterURI.ps1 +++ b/source/Public/Get-FabricAPIClusterURI.ps1 @@ -1,5 +1,5 @@ -function Get-FabricAPIclusterURI { -<# +function Get-FabricAPIclusterURI { + <# .SYNOPSIS Retrieves the cluster URI for the tenant. @@ -14,9 +14,9 @@ function Get-FabricAPIclusterURI { .NOTES The function retrieves the PowerBI access token and makes a GET request to the PowerBI API to retrieve the datasets. It then extracts the '@odata.context' property from the response, splits it on the '/' character, and selects the third element. This element is used to construct the cluster URI, which is then returned by the function. -#> + #> -#This function retrieves the cluster URI for the tenant. + #This function retrieves the cluster URI for the tenant. # Define aliases for the function for flexibility. [Alias("Get-FabAPIClusterURI")] Param ( @@ -38,4 +38,4 @@ function Get-FabricAPIclusterURI { # Return the cluster URI. return $clusterURI -} +} \ No newline at end of file diff --git a/source/Public/Get-FabricAuthToken.ps1 b/source/Public/Get-FabricAuthToken.ps1 index 55707a69..3bbd3fcb 100644 --- a/source/Public/Get-FabricAuthToken.ps1 +++ b/source/Public/Get-FabricAuthToken.ps1 @@ -22,18 +22,18 @@ #> function Get-FabricAuthToken { - [Alias("Get-FabAuthToken")] - [CmdletBinding()] - param - ( - ) - - # Check if the Fabric token is already set - if (!$FabricSession.FabricToken) { - # If not, set the Fabric token - Set-FabricAuthToken - } - - # Output the Fabric token - return $FabricSession.FabricToken + [Alias("Get-FabAuthToken")] + [CmdletBinding()] + param + ( + ) + + # Check if the Fabric token is already set + if (!$FabricSession.FabricToken) { + # If not, set the Fabric token + Set-FabricAuthToken + } + + # Output the Fabric token + return $FabricSession.FabricToken } \ No newline at end of file diff --git a/source/Public/Get-FabricConnection.ps1 b/source/Public/Get-FabricConnection.ps1 index fbd8920c..a1056914 100644 --- a/source/Public/Get-FabricConnection.ps1 +++ b/source/Public/Get-FabricConnection.ps1 @@ -1,5 +1,5 @@ function Get-FabricConnection { -<# + <# .SYNOPSIS Retrieves Fabric connections. @@ -22,26 +22,25 @@ This example retrieves specific connection from Fabric with ID "12345". .NOTES https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/get-connection?tabs=HTTP https://learn.microsoft.com/en-us/rest/api/fabric/core/connections/list-connections?tabs=HTTP -#> - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $false)] - [string]$connectionId - ) - - begin { - Confirm-FabricAuthToken | Out-Null - } - - process { - if ($connectionId) { - $result = Invoke-FabricAPIRequest -Uri "/connections/$($connectionId)" -Method GET - } - else { - $result = Invoke-FabricAPIRequest -Uri "/connections" -Method GET + #> + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $false)] + [string]$connectionId + ) + + begin { + Confirm-FabricAuthToken | Out-Null } - return $result.value - } -} + process { + if ($connectionId) { + $result = Invoke-FabricAPIRequest -Uri "/connections/$($connectionId)" -Method GET + } else { + $result = Invoke-FabricAPIRequest -Uri "/connections" -Method GET + } + + return $result.value + } +} \ No newline at end of file diff --git a/source/Public/Get-FabricDatasetRefreshes.ps1 b/source/Public/Get-FabricDatasetRefreshes.ps1 index 7f979aae..dde942ba 100644 --- a/source/Public/Get-FabricDatasetRefreshes.ps1 +++ b/source/Public/Get-FabricDatasetRefreshes.ps1 @@ -31,14 +31,14 @@ function Get-FabricDatasetRefreshes { # Define a mandatory parameter for the dataset ID. Param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$DatasetID, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$workspaceId ) # Get the dataset information. - $di = get-powerbidataset -id $DatasetID -WorkspaceId $workspaceId + $di = Get-PowerBIDataset -id $DatasetID -WorkspaceId $workspaceId # Check if the dataset is refreshable. if ($di.isrefreshable -eq "True") { diff --git a/source/Public/Get-FabricDebugInfo.ps1 b/source/Public/Get-FabricDebugInfo.ps1 index 1c7704a0..d19e2177 100644 --- a/source/Public/Get-FabricDebugInfo.ps1 +++ b/source/Public/Get-FabricDebugInfo.ps1 @@ -1,7 +1,7 @@ function Get-FabricDebugInfo { #Requires -Version 7.1 -<# + <# .SYNOPSIS Shows internal debug information about the current session. @@ -21,26 +21,26 @@ function Get-FabricDebugInfo { - 2024-12-22 - FGE: Added Verbose Output -#> + #> -[CmdletBinding()] + [CmdletBinding()] param ( ) -begin {} + begin { } -process { - Write-Verbose "Show current session object" + process { + Write-Verbose "Show current session object" - return @{ - FabricSession = $script:FabricSession - AzureSession = $script:AzureSession - FabricConfig = $script:FabricConfig - } + return @{ + FabricSession = $script:FabricSession + AzureSession = $script:AzureSession + FabricConfig = $script:FabricConfig + } -} + } -end {} + end { } } \ No newline at end of file diff --git a/source/Public/Get-FabricUsageMetricsQuery.ps1 b/source/Public/Get-FabricUsageMetricsQuery.ps1 index b1e82d17..13b0357d 100644 --- a/source/Public/Get-FabricUsageMetricsQuery.ps1 +++ b/source/Public/Get-FabricUsageMetricsQuery.ps1 @@ -1,5 +1,5 @@ function Get-FabricUsageMetricsQuery { -<# + <# .SYNOPSIS Retrieves usage metrics for a specific dataset. @@ -31,30 +31,30 @@ This example retrieves the usage metrics for a specific dataset given the datase .NOTES The function defines the headers and body for a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. It then makes the POST request and returns the response. -#> + #> -# This function retrieves usage metrics for a specific dataset. - # Define aliases for the function for flexibility. - [Alias("Get-FabUsageMetricsQuery")] + # This function retrieves usage metrics for a specific dataset. + # Define aliases for the function for flexibility. + [Alias("Get-FabUsageMetricsQuery")] - # Define parameters for the dataset ID, group ID, report name, token, and impersonated user. - param ( - [Parameter(Mandatory = $true)] - [string]$DatasetID, - [Parameter(Mandatory = $true)] - [string]$groupId, - [Parameter(Mandatory = $true)] - $reportname, - [Parameter(Mandatory = $false)] - [string]$ImpersonatedUser = "" - ) + # Define parameters for the dataset ID, group ID, report name, token, and impersonated user. + param ( + [Parameter(Mandatory = $true)] + [string]$DatasetID, + [Parameter(Mandatory = $true)] + [string]$groupId, + [Parameter(Mandatory = $true)] + $reportname, + [Parameter(Mandatory = $false)] + [string]$ImpersonatedUser = "" + ) - # Confirm the authentication token. - Confirm-FabricAuthToken | Out-Null + # Confirm the authentication token. + Confirm-FabricAuthToken | Out-Null - # Define the body of the POST request. - if ($ImpersonatedUser -ne "") { - $reportbody = '{ + # Define the body of the POST request. + if ($ImpersonatedUser -ne "") { + $reportbody = '{ "queries": [ { "query": "EVALUATE VALUES(' + $reportname + ')" @@ -63,11 +63,10 @@ The function defines the headers and body for a POST request to the PowerBI API "serializerSettings": { "includeNulls": true }, - "impersonatedUserName": "'+ $ImpersonatedUser + '" + "impersonatedUserName": "' + $ImpersonatedUser + '" }' - } - else { - $reportbody = '{ + } else { + $reportbody = '{ "queries": [ { "query": "EVALUATE VALUES(' + $reportname + ')" @@ -77,8 +76,8 @@ The function defines the headers and body for a POST request to the PowerBI API "includeNulls": true } }' - } - # Make a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. - # The function returns the response of the POST request. - return Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/groups/$groupId/datasets/$DatasetID/executeQueries" -Headers $FabricSession.HeaderParams -Body $reportbody -Method Post -} + } + # Make a POST request to the PowerBI API to retrieve the usage metrics for the specified dataset. + # The function returns the response of the POST request. + return Invoke-RestMethod -uri "$($PowerBI.BaseApiUrl)/groups/$groupId/datasets/$DatasetID/executeQueries" -Headers $FabricSession.HeaderParams -Body $reportbody -Method Post +} \ No newline at end of file diff --git a/source/Public/Get-SHA256.ps1 b/source/Public/Get-SHA256.ps1 index aa8b1d0d..87e66724 100644 --- a/source/Public/Get-SHA256.ps1 +++ b/source/Public/Get-SHA256.ps1 @@ -1,6 +1,6 @@ function Get-Sha256 ($string) { -<# + <# .SYNOPSIS Calculates the SHA256 hash of a string. @@ -17,9 +17,9 @@ This example calculates the SHA256 hash of a string. .NOTES The function creates a new SHA256CryptoServiceProvider object, converts the string to a byte array using UTF8 encoding, computes the SHA256 hash of the byte array, converts the hash to a string and removes any hyphens, and returns the resulting hash. -#> + #> -# This function calculates the SHA256 hash of a string. + # This function calculates the SHA256 hash of a string. # Create a new SHA256CryptoServiceProvider object. $sha256 = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider @@ -34,4 +34,4 @@ The function creates a new SHA256CryptoServiceProvider object, converts the stri # Return the resulting hash. return $result -} +} \ No newline at end of file diff --git a/source/Public/Invoke-FabricAPIRequest.ps1 b/source/Public/Invoke-FabricAPIRequest.ps1 index 0461101c..387506bb 100644 --- a/source/Public/Invoke-FabricAPIRequest.ps1 +++ b/source/Public/Invoke-FabricAPIRequest.ps1 @@ -1,6 +1,6 @@ Function Invoke-FabricAPIRequest { -<# + <# .SYNOPSIS Sends an HTTP request to a Fabric API endpoint and retrieves the response. Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response @@ -69,9 +69,8 @@ Function Invoke-FabricAPIRequest { if ($response.StatusCode -eq 202) { if ($uri -match "jobType=Pipeline") { - write-output "Waiting for pipeline to complete. Please check the status in the Power BI Service." - } - else { + Write-Output "Waiting for pipeline to complete. Please check the status in the Power BI Service." + } else { do { $asyncUrl = [string]$response.Headers.Location @@ -84,9 +83,8 @@ Function Invoke-FabricAPIRequest { try { $response = Invoke-WebRequest -Headers $fabricHeaders -Method Get -Uri "$asyncUrl/result" - } - catch { - write-output "No result URL" + } catch { + Write-Output "No result URL" } } } @@ -97,18 +95,15 @@ Function Invoke-FabricAPIRequest { if ($contentBytes[0] -eq 0xef -and $contentBytes[1] -eq 0xbb -and $contentBytes[2] -eq 0xbf) { $contentText = [System.Text.Encoding]::UTF8.GetString($contentBytes[3..$contentBytes.Length]) - } - else { + } else { $contentText = $response.Content } $jsonResult = $contentText | ConvertFrom-Json Write-Output $jsonResult -NoEnumerate - } - else { + } else { Write-Output $response -NoEnumerate } - } - catch { + } catch { $ex = $_.Exception $message = $null @@ -133,14 +128,12 @@ Function Invoke-FabricAPIRequest { if ($retryCount -le $maxRetries) { Invoke-FabricAPIRequest -authToken $authToken -uri $uri -method $method -body $body -contentType $contentType -timeoutSec $timeoutSec -retryCount ($retryCount + 1) - } - else { + } else { throw "Exceeded the amount of retries ($maxRetries) after 429 error." } - } - else { - $apiErrorObj = $ex.Response.Headers | Where-Object { $_.key -ieq "x-ms-public-api-error-code" } | Select-object -First 1 + } else { + $apiErrorObj = $ex.Response.Headers | Where-Object { $_.key -ieq "x-ms-public-api-error-code" } | Select-Object -First 1 if ($apiErrorObj) { $apiError = $apiErrorObj.Value[0] @@ -148,8 +141,7 @@ Function Invoke-FabricAPIRequest { if ($apiError -ieq "ItemHasProtectedLabel") { Write-Warning "Item has a protected label." - } - else { + } else { throw } @@ -157,12 +149,11 @@ Function Invoke-FabricAPIRequest { #$errorContent = $ex.Response.Content.ReadAsStringAsync().Result; #$message = "$($ex.Message) - StatusCode: '$($ex.Response.StatusCode)'; Content: '$errorContent'" } - } - else { + } else { $message = "$($ex.Message)" } if ($message) { throw $message } } -} +} \ No newline at end of file diff --git a/source/Public/Invoke-FabricDatasetRefresh.ps1 b/source/Public/Invoke-FabricDatasetRefresh.ps1 index be979475..b34b79b6 100644 --- a/source/Public/Invoke-FabricDatasetRefresh.ps1 +++ b/source/Public/Invoke-FabricDatasetRefresh.ps1 @@ -33,8 +33,7 @@ function Invoke-FabricDatasetRefresh { if ((Get-FabricDataset -DatasetId $DatasetID).isrefreshable -eq $false) { # If the dataset is not refreshable, write an error Write-Error "Dataset is not refreshable" - } - else { + } else { # If the dataset is refreshable, invoke a PowerBI REST method to refresh the dataset # The URL for the request is constructed using the provided workspace ID and dataset ID. diff --git a/source/Public/Item/Export-FabricItem.ps1 b/source/Public/Item/Export-FabricItem.ps1 index a760653c..a1ceafe0 100644 --- a/source/Public/Item/Export-FabricItem.ps1 +++ b/source/Public/Item/Export-FabricItem.ps1 @@ -51,7 +51,7 @@ Function Export-FabricItem { $parts = $item.definition.parts - if (!(test-path $path)) { + if (!(Test-Path $path)) { New-Item -ItemType Directory -Force -Path $path } @@ -63,14 +63,13 @@ Function Export-FabricItem { } # Display a message indicating the items were exported - write-output "Items exported to $path" + Write-Output "Items exported to $path" } else { $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get if ($filter) { $items = $items.value | Where-Object $filter - } - else { + } else { $items = $items.value } @@ -83,22 +82,22 @@ Function Export-FabricItem { $itemOutputPath = "$path\$workspaceId\$($itemName).$($itemType)" if ($itemType -in @("report", "semanticmodel", "notebook", "SparkJobDefinitionV1", "DataPipeline")) { - write-output "Getting definition of: $itemId / $itemName / $itemType" + Write-Output "Getting definition of: $itemId / $itemName / $itemType" $response = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/getDefinition" -Method Post $partCount = $response.definition.parts.Count - write-output "Parts: $partCount" + Write-Output "Parts: $partCount" if ($partCount -gt 0) { foreach ($part in $response.definition.parts) { - write-output "Saving part: $($part.path)" + Write-Output "Saving part: $($part.path)" $outputFilePath = "$itemOutputPath\$($part.path.Replace("/", "\"))" New-Item -ItemType Directory -Path (Split-Path $outputFilePath -Parent) -ErrorAction SilentlyContinue | Out-Null $payload = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($part.payload)) $payload | Out-File -FilePath "$outputFilePath" - + } @{ @@ -107,9 +106,8 @@ Function Export-FabricItem { } | ConvertTo-Json | Out-File "$itemOutputPath\item.metadata.json" } - } - else { - write-output "Type '$itemType' not available for export." + } else { + Write-Output "Type '$itemType' not available for export." } } } diff --git a/source/Public/Item/Get-FabricItem.ps1 b/source/Public/Item/Get-FabricItem.ps1 index d2bd3855..9fbf0c93 100644 --- a/source/Public/Item/Get-FabricItem.ps1 +++ b/source/Public/Item/Get-FabricItem.ps1 @@ -1,5 +1,5 @@ function Get-FabricItem { -<# + <# .SYNOPSIS Retrieves fabric items from a workspace. @@ -27,45 +27,43 @@ This example retrieves all fabric items of type "file" from the workspace with I This function was originally written by Rui Romano. https://github.com/RuiRomano/fabricps-pbip -#> - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] - [string]$workspaceId, + #> + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceId')] + [string]$workspaceId, - [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true )] - $Workspace, + [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true )] + $Workspace, - [Parameter(Mandatory = $false)] - [Alias("itemType")] - [string]$type, + [Parameter(Mandatory = $false)] + [Alias("itemType")] + [string]$type, - [Parameter(Mandatory = $false)] - [string]$itemID - ) + [Parameter(Mandatory = $false)] + [string]$itemID + ) - begin { - Confirm-FabricAuthToken | Out-Null - } - - process { - if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { - $workspaceID = $Workspace.id - } - if ($itemID) { - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Get + begin { + Confirm-FabricAuthToken | Out-Null } - else { - if ($type) { - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items?type=$type" -Method Get - } - else { - # Invoke the Fabric API to get the workspaces - $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items" -Method Get - } - # Output the result - return $result.value + + process { + if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { + $workspaceID = $Workspace.id + } + if ($itemID) { + $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Get + } else { + if ($type) { + $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items?type=$type" -Method Get + } else { + # Invoke the Fabric API to get the workspaces + $result = Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items" -Method Get + } + # Output the result + return $result.value + } } - } -} +} \ No newline at end of file diff --git a/source/Public/Item/Import-FabricItem.ps1 b/source/Public/Item/Import-FabricItem.ps1 index a13693d1..2ac35f34 100644 --- a/source/Public/Item/Import-FabricItem.ps1 +++ b/source/Public/Item/Import-FabricItem.ps1 @@ -27,7 +27,7 @@ This function requires the Invoke-FabricAPIRequest function to be available in the current session. This function was originally written by Rui Romano. https://github.com/RuiRomano/fabricps-pbip - #> +#> Function Import-FabricItem { <# @@ -40,7 +40,7 @@ Function Import-FabricItem { [CmdletBinding()] param ( - [string] $path = '.\pbipOutput' + [string] $path = '.\pbipOutput' , [string] $workspaceId , [string] $filter = $null , [hashtable] $fileOverrides @@ -53,27 +53,27 @@ Function Import-FabricItem { $itemsFolders = Get-ChildItem -Path $path -recurse -include *.pbir, *.pbidataset if ($filter) { - $itemsFolders = $itemsFolders | where-object { $_.Directory.FullName -like $filter } + $itemsFolders = $itemsFolders | Where-Object { $_.Directory.FullName -like $filter } } # Get existing items of the workspace $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get - write-output "Existing items: $($items.Count)" + Write-Output "Existing items: $($items.Count)" # Datasets first - $itemsFolders = $itemsFolders | Select-Object @{n = "Order"; e = { if ($_.Name -like "*.pbidataset") { 1 } else { 2 } } }, * | sort-object Order + $itemsFolders = $itemsFolders | Select-Object @{n = "Order"; e = { if ($_.Name -like "*.pbidataset") { 1 } else { 2 } } }, * | Sort-Object Order - $datasetReferences = @{} + $datasetReferences = @{ } foreach ($itemFolder in $itemsFolders) { # Get the parent folder $itemPath = $itemFolder.Directory.FullName - write-output "Processing item: '$itemPath'" + Write-Output "Processing item: '$itemPath'" $files = Get-ChildItem -Path $itemPath -Recurse -Attributes !Directory @@ -85,12 +85,12 @@ Function Import-FabricItem { $itemMetadataStr = Get-Content "$itemPath\item.metadata.json" if ($fileOverrides) { - $fileOverrideMatch = $fileOverrides.GetEnumerator() | where-object { "$itemPath\item.metadata.json" -ilike $_.Name } | select-object -First 1 + $fileOverrideMatch = $fileOverrides.GetEnumerator() | Where-Object { "$itemPath\item.metadata.json" -ilike $_.Name } | Select-Object -First 1 - if ($fileOverrideMatch) { - $itemMetadataStr = $fileOverrideMatch.Value + if ($fileOverrideMatch) { + $itemMetadataStr = $fileOverrideMatch.Value + } } - } $itemMetadata = $itemMetadataStr | ConvertFrom-Json $itemType = $itemMetadata.type @@ -108,7 +108,7 @@ Function Import-FabricItem { #$fileName = $_.Name $filePath = $_.FullName if ($fileOverrides) { - $fileOverrideMatch = $fileOverrides.GetEnumerator() | Where-Object { $filePath -ilike $_.Name } | select-object -First 1 + $fileOverrideMatch = $fileOverrides.GetEnumerator() | Where-Object { $filePath -ilike $_.Name } | Select-Object -First 1 } if ($fileOverrideMatch) { $fileContent = $fileOverrideMatch.Value @@ -117,12 +117,10 @@ Function Import-FabricItem { if ($fileContent -is [string]) { $fileContent = [system.Text.Encoding]::UTF8.GetBytes($fileContent) - } - elseif (!($fileContent -is [byte[]])) { + } elseif (!($fileContent -is [byte[]])) { throw "FileOverrides value type must be string or byte[]" } - } - else { + } else { if ($filePath -like "*.pbir") { $pbirJson = Get-Content -Path $filePath | ConvertFrom-Json @@ -131,7 +129,7 @@ Function Import-FabricItem { # try to swap byPath to byConnection - $reportDatasetPath = (Resolve-path (Join-Path $itemPath $pbirJson.datasetReference.byPath.path.Replace("/", "\"))).Path + $reportDatasetPath = (Resolve-Path (Join-Path $itemPath $pbirJson.datasetReference.byPath.path.Replace("/", "\"))).Path $datasetReference = $datasetReferences[$reportDatasetPath] @@ -155,22 +153,20 @@ Function Import-FabricItem { $fileContent = [system.Text.Encoding]::UTF8.GetBytes($newPBIR) - } - else { + } else { throw "Item API dont support byPath connection, switch the connection in the *.pbir file to 'byConnection'." } } else { $fileContent = Get-Content -Path $filePath -AsByteStream -Raw } - } - else { + } else { $fileContent = Get-Content -Path $filePath -AsByteStream -Raw } } $partPath = $filePath.Replace($itemPathAbs, "").TrimStart("\").Replace("\", "/") - write-host "Processing part: '$partPath'" + Write-Host "Processing part: '$partPath'" $fileEncodedContent = [Convert]::ToBase64String($fileContent) Write-Output @{ @@ -191,13 +187,13 @@ Function Import-FabricItem { throw "Found more than one item for displayName '$displayName'" } - Write-output "Item '$displayName' of type '$itemType' already exists." -ForegroundColor Yellow + Write-Output "Item '$displayName' of type '$itemType' already exists." -ForegroundColor Yellow $itemId = $foundItem.id } if ($null -eq $itemId) { - write-output "Creating a new item" + Write-Output "Creating a new item" # Prepare the request @@ -213,12 +209,11 @@ Function Import-FabricItem { $itemId = $createItemResult.id - write-output "Created a new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green + Write-Output "Created a new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green Write-Output $itemId - } - else { - write-output "Updating item definition" + } else { + Write-Output "Updating item definition" $itemRequest = @{ definition = @{ @@ -227,7 +222,7 @@ Function Import-FabricItem { } | ConvertTo-Json -Depth 3 Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/updateDefinition" -Method Post -Body $itemRequest - write-output "Updated new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green + Write-Output "Updated new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green Write-Output $itemId } @@ -239,4 +234,4 @@ Function Import-FabricItem { } } -} +} \ No newline at end of file diff --git a/source/Public/Item/Remove-FabricItem.ps1 b/source/Public/Item/Remove-FabricItem.ps1 index 9dbc4685..60b50b11 100644 --- a/source/Public/Item/Remove-FabricItem.ps1 +++ b/source/Public/Item/Remove-FabricItem.ps1 @@ -31,40 +31,40 @@ #> Function Remove-FabricItem { - [CmdletBinding(SupportsShouldProcess)] - param - ( - [Parameter(Mandatory = $true)] - [string]$workspaceId, - [Parameter(Mandatory = $false)] - [string]$filter, - [Parameter(Mandatory = $false)] - [string]$itemID - ) + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory = $true)] + [string]$workspaceId, + [Parameter(Mandatory = $false)] + [string]$filter, + [Parameter(Mandatory = $false)] + [string]$itemID + ) - Confirm-FabricAuthToken | Out-Null + Confirm-FabricAuthToken | Out-Null - # Invoke the Fabric API to get the items in the workspace - if ($PSCmdlet.ShouldProcess("Remove items from workspace $workspaceId")) { - if ($itemID) { - Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Delete - } else { - $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get + # Invoke the Fabric API to get the items in the workspace + if ($PSCmdlet.ShouldProcess("Remove items from workspace $workspaceId")) { + if ($itemID) { + Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/items/$($itemID)" -Method Delete + } else { + $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get - # Display the count of existing items - Write-Output "Existing items: $($items.Count)" + # Display the count of existing items + Write-Output "Existing items: $($items.Count)" - # If a filter is provided - if ($filter) { - # Filter the items whose DisplayName matches the filter - $items = $items | Where-Object { $_.DisplayName -like $filter } - } + # If a filter is provided + if ($filter) { + # Filter the items whose DisplayName matches the filter + $items = $items | Where-Object { $_.DisplayName -like $filter } + } - # For each item - foreach ($item in $items) { - # Remove the item - Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$($item.ID)" -Method Delete - } - } - } -} + # For each item + foreach ($item in $items) { + # Remove the item + Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$($item.ID)" -Method Delete + } + } + } +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 index 442a8a99..754fdd8e 100644 --- a/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboard.ps1 @@ -114,13 +114,11 @@ function Get-FabricKQLDashboard { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -130,11 +128,9 @@ function Get-FabricKQLDashboard { # Step 8: Filter results based on provided parameters $KQLDashboard = if ($KQLDashboardId) { $KQLDashboards | Where-Object { $_.Id -eq $KQLDashboardId } - } - elseif ($KQLDashboardName) { + } elseif ($KQLDashboardName) { $KQLDashboards | Where-Object { $_.DisplayName -eq $KQLDashboardName } - } - else { + } else { # Return all KQLDashboards if no filter is provided Write-Message -Message "No filter provided. Returning all KQLDashboards." -Level Debug $KQLDashboards @@ -144,16 +140,14 @@ function Get-FabricKQLDashboard { if ($KQLDashboard) { Write-Message -Message "KQLDashboard found matching the specified criteria." -Level Debug return $KQLDashboard - } - else { + } else { Write-Message -Message "No KQLDashboard found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 index 4f0cf5d1..48f81469 100644 --- a/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 +++ b/source/Public/KQL Dashboard/Get-FabricKQLDashboardDefinition.ps1 @@ -4,7 +4,7 @@ Retrieves the definition of a KQLDashboard from a specific workspace in Microsoft Fabric. .DESCRIPTION -This function fetches the KQLDashboard's content or metadata from a workspace. +This function fetches the KQLDashboard's content or metadata from a workspace. Handles both synchronous and asynchronous operations, with detailed logging and error handling. .PARAMETER WorkspaceId @@ -84,37 +84,35 @@ function Get-FabricKQLDashboardDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } - + } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLDashboard. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 index 50694d70..18768780 100644 --- a/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 @@ -3,8 +3,8 @@ Creates a new KQLDashboard in a specified Microsoft Fabric workspace. .DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLDashboard -in the specified workspace. It supports optional parameters for KQLDashboard description +This function sends a POST request to the Microsoft Fabric API to create a new KQLDashboard +in the specified workspace. It supports optional parameters for KQLDashboard description and path definitions for the KQLDashboard content. .PARAMETER WorkspaceId @@ -29,7 +29,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -52,7 +52,7 @@ function New-FabricKQLDashboard { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLDashboardPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLDashboardPathPlatformDefinition @@ -95,8 +95,7 @@ function New-FabricKQLDashboard { payload = $KQLDashboardEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error return $null } @@ -120,8 +119,7 @@ function New-FabricKQLDashboard { payload = $KQLDashboardEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -150,28 +148,27 @@ function New-FabricKQLDashboard { } 202 { Write-Message -Message "KQLDashboard '$KQLDashboardName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -179,10 +176,9 @@ function New-FabricKQLDashboard { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create KQLDashboard. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 index 166e7635..275c07c5 100644 --- a/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 @@ -20,7 +20,7 @@ Deletes the KQLDashboard with ID "67890" from workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -63,11 +63,10 @@ function Remove-FabricKQLDashboard { return $null } Write-Message -Message "KQLDashboard '$KQLDashboardId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete KQLDashboard '$KQLDashboardId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 index 03218957..81371165 100644 --- a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 @@ -1,5 +1,5 @@ function Update-FabricKQLDashboard { -<# + <# .SYNOPSIS Updates the properties of a Fabric KQLDashboard. @@ -34,7 +34,7 @@ Updates both the name and description of the KQLDashboard "KQLDashboard123". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -100,10 +100,9 @@ Author: Tiago Balabuch # Step 6: Handle results Write-Message -Message "KQLDashboard '$KQLDashboardName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 index 14b423fc..be838874 100644 --- a/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of a KQLDashboard in a Microsoft Fabric workspace. .DESCRIPTION -This function allows updating the content or metadata of a KQLDashboard in a Microsoft Fabric workspace. +This function allows updating the content or metadata of a KQLDashboard in a Microsoft Fabric workspace. The KQLDashboard content can be provided as file paths, and metadata updates can optionally be enabled. .PARAMETER WorkspaceId @@ -25,7 +25,7 @@ Update-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" Updates the content of the KQLDashboard with ID `67890` in the workspace `12345` using the specified KQLDashboard file. .EXAMPLE -Update-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" -KQLDashboardPathDefinition "C:\KQLDashboards\KQLDashboard.ipynb" +Update-FabricKQLDashboardDefinition -WorkspaceId "12345" -KQLDashboardId "67890" -KQLDashboardPathDefinition "C:\KQLDashboards\KQLDashboard.ipynb" Updates both the content and metadata of the KQLDashboard with ID `67890` in the workspace `12345`. @@ -35,7 +35,7 @@ Updates both the content and metadata of the KQLDashboard with ID `67890` in the - The KQLDashboard content is encoded as Base64 before being sent to the Fabric API. - This function handles asynchronous operations and retrieves operation results if required. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -53,7 +53,7 @@ function Update-FabricKQLDashboardDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLDashboardPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLDashboardPathPlatformDefinition @@ -68,8 +68,8 @@ function Update-FabricKQLDashboardDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/KQLDashboards/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - if($KQLDashboardPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + if ($KQLDashboardPathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -78,12 +78,12 @@ function Update-FabricKQLDashboardDefinition { definition = @{ format = $null parts = @() - } + } } - + if ($KQLDashboardPathDefinition) { $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition - + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -91,8 +91,7 @@ function Update-FabricKQLDashboardDefinition { payload = $KQLDashboardEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error return $null } @@ -107,8 +106,7 @@ function Update-FabricKQLDashboardDefinition { payload = $KQLDashboardEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -127,7 +125,7 @@ function Update-FabricKQLDashboardDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -142,25 +140,23 @@ function Update-FabricKQLDashboardDefinition { # Handle operation result if ($operationResult.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug - + $result = Get-FabricLongRunningOperationResult -operationId $operationId return $result.definition.parts - } - else { + } else { Write-Message -Message "Operation Failed" -Level Debug return $operationResult.definition.parts - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 index b71c9b65..144aa890 100644 --- a/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Get-FabricKQLDatabase.ps1 @@ -1,5 +1,5 @@ function Get-FabricKQLDatabase { -<# + <# .SYNOPSIS Retrieves an KQLDatabase or a list of KQLDatabases from a specified workspace in Microsoft Fabric. @@ -31,7 +31,7 @@ Retrieves all KQLDatabases in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -111,13 +111,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -127,11 +125,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $KQLDatabase = if ($KQLDatabaseId) { $KQLDatabases | Where-Object { $_.Id -eq $KQLDatabaseId } - } - elseif ($KQLDatabaseName) { + } elseif ($KQLDatabaseName) { $KQLDatabases | Where-Object { $_.DisplayName -eq $KQLDatabaseName } - } - else { + } else { # Return all KQLDatabases if no filter is provided Write-Message -Message "No filter provided. Returning all KQLDatabases." -Level Debug $KQLDatabases @@ -141,16 +137,14 @@ Author: Tiago Balabuch if ($KQLDatabase) { Write-Message -Message "KQLDatabase found matching the specified criteria." -Level Debug return $KQLDatabase - } - else { + } else { Write-Message -Message "No KQLDatabase found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 b/source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 index a3d56115..95c016ea 100644 --- a/source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 +++ b/source/Public/KQL Database/Get-FabricKQLDatabaseDefinition.ps1 @@ -4,7 +4,7 @@ Retrieves the definition of a KQLDatabase from a specific workspace in Microsoft Fabric. .DESCRIPTION -This function fetches the KQLDatabase's content or metadata from a workspace. +This function fetches the KQLDatabase's content or metadata from a workspace. It supports retrieving KQLDatabase definitions in the Jupyter KQLDatabase (`ipynb`) format. Handles both synchronous and asynchronous operations, with detailed logging and error handling. @@ -92,24 +92,23 @@ function Get-FabricKQLDatabaseDefinition { Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -118,12 +117,11 @@ function Get-FabricKQLDatabaseDefinition { Write-Message "Error Code: $($response.errorCode)" -Level Error throw "API request failed with status code $statusCode." } - + } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLDatabase. Error: $errorDetails" -Level Error - } -} + } +} \ No newline at end of file diff --git a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 index 898dd37f..9505147b 100644 --- a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 @@ -1,5 +1,5 @@ function New-FabricKQLDatabase { -<# + <# .SYNOPSIS Creates a new KQLDatabase in a specified Microsoft Fabric workspace. @@ -54,7 +54,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -139,8 +139,7 @@ Author: Tiago Balabuch payload = $KQLDatabaseEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error return $null } @@ -156,8 +155,7 @@ Author: Tiago Balabuch payload = $KQLDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -174,15 +172,13 @@ Author: Tiago Balabuch payload = $KQLDatabaseEncodedSchemaContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in schema definition." -Level Error return $null } } - } - else { + } else { if ($KQLDatabaseType -eq "Shortcut") { if (-not $parentEventhouseId) { Write-Message -Message "Error: 'parentEventhouseId' is required for Shortcut type." -Level Error @@ -281,8 +277,7 @@ Author: Tiago Balabuch Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -296,10 +291,9 @@ Author: Tiago Balabuch throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create KQLDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 index 2b5d69f2..86c1d1a5 100644 --- a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 @@ -20,7 +20,7 @@ Deletes the KQLDatabase with ID "67890" from workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -64,11 +64,10 @@ function Remove-FabricKQLDatabase { return $null } Write-Message -Message "KQLDatabase '$KQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete KQLDatabase '$KQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 index 35b2544e..79e172b3 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 @@ -102,10 +102,9 @@ function Update-FabricKQLDatabase { # Step 6: Handle results Write-Message -Message "KQLDatabase '$KQLDatabaseName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 index 4ff0155b..b516aad2 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 @@ -77,7 +77,7 @@ function Update-FabricKQLDatabaseDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId - if($KQLDatabasePathPlatformDefinition){ + if ($KQLDatabasePathPlatformDefinition) { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -85,7 +85,7 @@ function Update-FabricKQLDatabaseDefinition { # Step 3: Construct the request body $body = @{ definition = @{ - parts = @() + parts = @() } } @@ -99,8 +99,7 @@ function Update-FabricKQLDatabaseDefinition { payload = $KQLDatabaseEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error return $null } @@ -115,8 +114,7 @@ function Update-FabricKQLDatabaseDefinition { payload = $KQLDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -133,8 +131,7 @@ function Update-FabricKQLDatabaseDefinition { payload = $KQLDatabaseEncodedSchemaContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in schema definition." -Level Error return $null } @@ -181,8 +178,7 @@ function Update-FabricKQLDatabaseDefinition { $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -196,10 +192,9 @@ function Update-FabricKQLDatabaseDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 index 71282e42..49591b5f 100644 --- a/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Get-FabricKQLQueryset.ps1 @@ -1,5 +1,5 @@ function Get-FabricKQLQueryset { -<# + <# .SYNOPSIS Retrieves an KQLQueryset or a list of KQLQuerysets from a specified workspace in Microsoft Fabric. @@ -31,7 +31,7 @@ Retrieves all KQLQuerysets in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -111,13 +111,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -127,11 +125,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $KQLQueryset = if ($KQLQuerysetId) { $KQLQuerysets | Where-Object { $_.Id -eq $KQLQuerysetId } - } - elseif ($KQLQuerysetName) { + } elseif ($KQLQuerysetName) { $KQLQuerysets | Where-Object { $_.DisplayName -eq $KQLQuerysetName } - } - else { + } else { # Return all KQLQuerysets if no filter is provided Write-Message -Message "No filter provided. Returning all KQLQuerysets." -Level Debug $KQLQuerysets @@ -141,16 +137,14 @@ Author: Tiago Balabuch if ($KQLQueryset) { Write-Message -Message "KQLQueryset found matching the specified criteria." -Level Debug return $KQLQueryset - } - else { + } else { Write-Message -Message "No KQLQueryset found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 b/source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 index 79e88196..d1340167 100644 --- a/source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 +++ b/source/Public/KQL Queryset/Get-FabricKQLQuerysetDefinition.ps1 @@ -4,7 +4,7 @@ Retrieves the definition of a KQLQueryset from a specific workspace in Microsoft Fabric. .DESCRIPTION -This function fetches the KQLQueryset's content or metadata from a workspace. +This function fetches the KQLQueryset's content or metadata from a workspace. Handles both synchronous and asynchronous operations, with detailed logging and error handling. .PARAMETER WorkspaceId @@ -84,37 +84,35 @@ function Get-FabricKQLQuerysetDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } - + } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve KQLQueryset. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 b/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 index c31b2af9..00ab9f5c 100644 --- a/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 +++ b/source/Public/KQL Queryset/Invoke-FabricKQLCommand.ps1 @@ -1,5 +1,5 @@ function Invoke-FabricKQLCommand { -<# + <# .SYNOPSIS Executes a KQL command in a Kusto Database. @@ -59,156 +59,153 @@ function Invoke-FabricKQLCommand { 2024-12-22 - FGE: Added Verbose Output 2024-12-27 - FGE: Major Update to support KQL Queries and Management Commands -#> + #> -[CmdletBinding()] + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$WorkspaceId, [string]$KQLDatabaseName, [string]$KQLDatabaseId, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$KQLCommand, [switch]$ReturnRawResult ) -begin { + begin { - Confirm-FabricAuthToken | Out-Null + Confirm-FabricAuthToken | Out-Null - Write-Verbose "Check if KQLDatabaseName and KQLDatabaseId are used together" - if ($PSBoundParameters.ContainsKey("KQLDatabaseName") -and $PSBoundParameters.ContainsKey("KQLDatabaseId")) { - throw "Parameters KQLDatabaseName and KQLDatabaseId cannot be used together" - } + Write-Verbose "Check if KQLDatabaseName and KQLDatabaseId are used together" + if ($PSBoundParameters.ContainsKey("KQLDatabaseName") -and $PSBoundParameters.ContainsKey("KQLDatabaseId")) { + throw "Parameters KQLDatabaseName and KQLDatabaseId cannot be used together" + } - Write-Verbose "Get Kusto Database" - if ($PSBoundParameters.ContainsKey("KQLDatabaseName")) { - Write-Verbose "Getting Kusto Database by Name: $KQLDatabaseName" - $kustDB = Get-FabricKQLDatabase ` - -WorkspaceId $WorkspaceId ` - -KQLDatabaseName $KQLDatabaseName - } + Write-Verbose "Get Kusto Database" + if ($PSBoundParameters.ContainsKey("KQLDatabaseName")) { + Write-Verbose "Getting Kusto Database by Name: $KQLDatabaseName" + $kustDB = Get-FabricKQLDatabase ` + -WorkspaceId $WorkspaceId ` + -KQLDatabaseName $KQLDatabaseName + } - if ($PSBoundParameters.ContainsKey("KQLDatabaseId")) { - Write-Verbose "Getting Kusto Database by Id: $KQLDatabaseId" - $kustDB = Get-FabricKQLDatabase ` - -WorkspaceId $WorkspaceId ` - -KQLDatabaseId $KQLDatabaseId - } + if ($PSBoundParameters.ContainsKey("KQLDatabaseId")) { + Write-Verbose "Getting Kusto Database by Id: $KQLDatabaseId" + $kustDB = Get-FabricKQLDatabase ` + -WorkspaceId $WorkspaceId ` + -KQLDatabaseId $KQLDatabaseId + } - Write-Verbose "Check if Kusto Database was found" - if ($null -eq $kustDB) { - throw "Kusto Database not found" - } + Write-Verbose "Check if Kusto Database was found" + if ($null -eq $kustDB) { + throw "Kusto Database not found" + } - Write-Verbose "Generate the Management API URL" - $mgmtAPI = "$($kustDB.queryServiceUri)/v1/rest/mgmt" + Write-Verbose "Generate the Management API URL" + $mgmtAPI = "$($kustDB.queryServiceUri)/v1/rest/mgmt" - Write-Verbose "Generate the query API URL" - $queryAPI = "$($kustDB.queryServiceUri)/v1/rest/query" + Write-Verbose "Generate the query API URL" + $queryAPI = "$($kustDB.queryServiceUri)/v1/rest/query" - $KQLCommand = $KQLCommand | Out-String + $KQLCommand = $KQLCommand | Out-String - Write-Verbose "Check if the KQL command starts with a dot so it is a management command. Otherwise it is a query command" - if (-not ($KQLCommand -match "^\.")) { - $isManamgentCommand = $false - Write-Verbose "The command is a query command." - } - else { - $isManamgentCommand = $true - Write-Verbose "The command is a management command. It is crucial to have the .execute database script <| in the beginning, otherwise the Kusto API will not execute the script." - if (-not ($KQLCommand -match "\.execute database script <\|")) { - $KQLCommand = ".execute database script <| $KQLCommand" + Write-Verbose "Check if the KQL command starts with a dot so it is a management command. Otherwise it is a query command" + if (-not ($KQLCommand -match "^\.")) { + $isManamgentCommand = $false + Write-Verbose "The command is a query command." + } else { + $isManamgentCommand = $true + Write-Verbose "The command is a management command. It is crucial to have the .execute database script <| in the beginning, otherwise the Kusto API will not execute the script." + if (-not ($KQLCommand -match "\.execute database script <\|")) { + $KQLCommand = ".execute database script <| $KQLCommand" + } } } -} -process { + process { - Write-Verbose "The KQL-Command is: $KQLCommand" + Write-Verbose "The KQL-Command is: $KQLCommand" - Write-Verbose "Create body of the request" - $body = @{ - 'csl' = $KQLCommand; - 'db'= $kustDB.displayName - } | ConvertTo-Json -Depth 1 + Write-Verbose "Create body of the request" + $body = @{ + 'csl' = $KQLCommand; + 'db' = $kustDB.displayName + } | ConvertTo-Json -Depth 1 - if ($isManamgentCommand) { - Write-Verbose "Calling Management API" - Write-Verbose "----------------------" - Write-Verbose "Sending the following values to the Query API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $mgmtAPI" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" + if ($isManamgentCommand) { + Write-Verbose "Calling Management API" + Write-Verbose "----------------------" + Write-Verbose "Sending the following values to the Query API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $mgmtAPI" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" - $result = Invoke-RestMethod ` - -Headers $headerParams ` - -Method POST ` - -Uri $mgmtAPI ` - -Body ($body) ` - -ContentType "application/json; charset=utf-8" + $result = Invoke-RestMethod ` + -Headers $headerParams ` + -Method POST ` + -Uri $mgmtAPI ` + -Body ($body) ` + -ContentType "application/json; charset=utf-8" - Write-Verbose "Result of the Management API: $($result | ` + Write-Verbose "Result of the Management API: $($result | ` ConvertTo-Json ` -Depth 10)" - $result - } - else { - Write-Verbose "Calling Query API" - Write-Verbose "-----------------" - Write-Verbose "Sending the following values to the Query API:" - Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" - Write-Verbose "Method: POST" - Write-Verbose "URI: $queryAPI" - Write-Verbose "Body of request: $body" - Write-Verbose "ContentType: application/json" - - $result = Invoke-RestMethod ` - -Headers $headerParams ` - -Method POST ` - -Uri $queryAPI ` - -Body ($body) ` - -ContentType "application/json; charset=utf-8" - Write-Verbose "Result of the Query API: $($result | ` + $result + } else { + Write-Verbose "Calling Query API" + Write-Verbose "-----------------" + Write-Verbose "Sending the following values to the Query API:" + Write-Verbose "Headers: $($FabricSession.headerParams | Format-List | Out-String)" + Write-Verbose "Method: POST" + Write-Verbose "URI: $queryAPI" + Write-Verbose "Body of request: $body" + Write-Verbose "ContentType: application/json" + + $result = Invoke-RestMethod ` + -Headers $headerParams ` + -Method POST ` + -Uri $queryAPI ` + -Body ($body) ` + -ContentType "application/json; charset=utf-8" + Write-Verbose "Result of the Query API: $($result | ` ConvertTo-Json ` -Depth 10)" - if ($ReturnRawResult) { - $result - } - else { + if ($ReturnRawResult) { + $result + } else { $myRecords = @() for ($j = 0; $j -lt $Result.tables[0].rows.Count; $j++) { - $myTableRow = [PSCustomObject]@{} + $myTableRow = [PSCustomObject]@{ } for ($i = 0; $i -lt $Result.tables[0].rows[0].Count; $i++) { - $myTableRow | ` + $myTableRow | ` Add-Member ` - -MemberType NoteProperty ` - -Name $Result.Tables[0].Columns[$i].ColumnName ` - -Value $Result.Tables[0].rows[$j][$i] + -MemberType NoteProperty ` + -Name $Result.Tables[0].Columns[$i].ColumnName ` + -Value $Result.Tables[0].rows[$j][$i] } $myRecords += $myTableRow } $myRecords - } + } + } } -} -end {} + end { } -} +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 index fea1665c..92ca18a9 100644 --- a/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 @@ -3,8 +3,8 @@ Creates a new KQLQueryset in a specified Microsoft Fabric workspace. .DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new KQLQueryset -in the specified workspace. It supports optional parameters for KQLQueryset description +This function sends a POST request to the Microsoft Fabric API to create a new KQLQueryset +in the specified workspace. It supports optional parameters for KQLQueryset description and path definitions for the KQLQueryset content. .PARAMETER WorkspaceId @@ -29,7 +29,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -52,7 +52,7 @@ function New-FabricKQLQueryset { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLQuerysetPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLQuerysetPathPlatformDefinition @@ -95,8 +95,7 @@ function New-FabricKQLQueryset { payload = $KQLQuerysetEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in KQLQueryset definition." -Level Error return $null } @@ -120,8 +119,7 @@ function New-FabricKQLQueryset { payload = $KQLQuerysetEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -150,28 +148,27 @@ function New-FabricKQLQueryset { } 202 { Write-Message -Message "KQLQueryset '$KQLQuerysetName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -179,10 +176,9 @@ function New-FabricKQLQueryset { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create KQLQueryset. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 index f33273fb..c36be042 100644 --- a/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 @@ -64,10 +64,9 @@ function Remove-FabricKQLQueryset { } Write-Message -Message "KQLQueryset '$KQLQuerysetId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete KQLQueryset '$KQLQuerysetId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 index 868bd231..35a0fdc0 100644 --- a/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 @@ -101,10 +101,9 @@ function Update-FabricKQLQueryset { # Step 6: Handle results Write-Message -Message "KQLQueryset '$KQLQuerysetName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 b/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 index 6189f659..91aea620 100644 --- a/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 +++ b/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of a KQLQueryset in a Microsoft Fabric workspace. .DESCRIPTION -This function allows updating the content or metadata of a KQLQueryset in a Microsoft Fabric workspace. +This function allows updating the content or metadata of a KQLQueryset in a Microsoft Fabric workspace. The KQLQueryset content can be provided as file paths, and metadata updates can optionally be enabled. .PARAMETER WorkspaceId @@ -25,7 +25,7 @@ Update-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" - Updates the content of the KQLQueryset with ID `67890` in the workspace `12345` using the specified KQLQueryset file. .EXAMPLE -Update-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" -KQLQuerysetPathDefinition "C:\KQLQuerysets\KQLQueryset.ipynb" +Update-FabricKQLQuerysetDefinition -WorkspaceId "12345" -KQLQuerysetId "67890" -KQLQuerysetPathDefinition "C:\KQLQuerysets\KQLQueryset.ipynb" Updates both the content and metadata of the KQLQueryset with ID `67890` in the workspace `12345`. @@ -35,7 +35,7 @@ Updates both the content and metadata of the KQLQueryset with ID `67890` in the - The KQLQueryset content is encoded as Base64 before being sent to the Fabric API. - This function handles asynchronous operations and retrieves operation results if required. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -53,7 +53,7 @@ function Update-FabricKQLQuerysetDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KQLQuerysetPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$KQLQuerysetPathPlatformDefinition @@ -68,8 +68,8 @@ function Update-FabricKQLQuerysetDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId - if($KQLQuerysetPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + if ($KQLQuerysetPathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -78,12 +78,12 @@ function Update-FabricKQLQuerysetDefinition { definition = @{ format = $null parts = @() - } + } } - + if ($KQLQuerysetPathDefinition) { $KQLQuerysetEncodedContent = Convert-ToBase64 -filePath $KQLQuerysetPathDefinition - + if (-not [string]::IsNullOrEmpty($KQLQuerysetEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -91,8 +91,7 @@ function Update-FabricKQLQuerysetDefinition { payload = $KQLQuerysetEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in KQLQueryset definition." -Level Error return $null } @@ -107,8 +106,7 @@ function Update-FabricKQLQuerysetDefinition { payload = $KQLQuerysetEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -127,7 +125,7 @@ function Update-FabricKQLQuerysetDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -142,25 +140,23 @@ function Update-FabricKQLQuerysetDefinition { # Handle operation result if ($operationResult.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug - + $result = Get-FabricLongRunningOperationResult -operationId $operationId return $result.definition.parts - } - else { + } else { Write-Message -Message "Operation Failed" -Level Debug return $operationResult.definition.parts - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Lakehouse/Get-FabricLakehouse.ps1 b/source/Public/Lakehouse/Get-FabricLakehouse.ps1 index ce3658ee..ad33e1b0 100644 --- a/source/Public/Lakehouse/Get-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/Get-FabricLakehouse.ps1 @@ -1,5 +1,5 @@ function Get-FabricLakehouse { -<# + <# .SYNOPSIS Retrieves an Lakehouse or a list of Lakehouses from a specified workspace in Microsoft Fabric. @@ -31,7 +31,7 @@ Retrieves all Lakehouses in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -111,13 +111,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -127,11 +125,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $lakehouse = if ($LakehouseId) { $lakehouses | Where-Object { $_.Id -eq $LakehouseId } - } - elseif ($LakehouseName) { + } elseif ($LakehouseName) { $lakehouses | Where-Object { $_.DisplayName -eq $LakehouseName } - } - else { + } else { # Return all lakehouses if no filter is provided Write-Message -Message "No filter provided. Returning all Lakehouses." -Level Debug $lakehouses @@ -141,16 +137,14 @@ Author: Tiago Balabuch if ($Lakehouse) { Write-Message -Message "Lakehouse found matching the specified criteria." -Level Debug return $Lakehouse - } - else { + } else { Write-Message -Message "No Lakehouse found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 index 20a9fa45..f608e63b 100644 --- a/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 +++ b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 @@ -1,5 +1,5 @@ function Get-FabricLakehouseTable { -<# + <# .SYNOPSIS Retrieves tables from a specified Lakehouse in a Fabric workspace. @@ -13,7 +13,7 @@ The ID of the Lakehouse from which to retrieve tables. Get-FabricLakehouseTable -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" This example retrieves all tables from the specified Lakehouse in the specified workspace. -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -86,13 +86,11 @@ This example retrieves all tables from the specified Lakehouse in the specified Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -102,16 +100,14 @@ This example retrieves all tables from the specified Lakehouse in the specified if ($tables) { Write-Message -Message "Tables found in the Lakehouse '$LakehouseId'." -Level Debug return $tables - } - else { + } else { Write-Message -Message "No tables found matching in the Lakehouse '$LakehouseId'." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 index 8db8c767..638cfc82 100644 --- a/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 +++ b/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 @@ -1,5 +1,5 @@ function Load-FabricLakehouseTable { -<# + <# .SYNOPSIS Loads data into a specified table in a Lakehouse within a Fabric workspace. .DESCRIPTION @@ -30,7 +30,7 @@ This example loads data from a CSV file into the specified table in the Lakehous .EXAMPLE Load-FabricLakehouseTable -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" -TableName "your-table-name" -PathType "Folder" -RelativePath "path/to/your/folder" -FileFormat "Parquet" -Mode "overwrite" -Recursive $true This example loads data from a folder into the specified table in the Lakehouse, overwriting any existing data. -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -145,8 +145,7 @@ This example loads data from a folder into the specified table in the Lakehouse, Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Load table '$TableName' operation complete successfully!" -Level Info return $operationStatus - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -162,10 +161,9 @@ This example loads data from a folder into the specified table in the Lakehouse, # Step 6: Handle results - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Lakehouse/New-FabricLakehouse.ps1 b/source/Public/Lakehouse/New-FabricLakehouse.ps1 index 7f594cb4..e88d7eb9 100644 --- a/source/Public/Lakehouse/New-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/New-FabricLakehouse.ps1 @@ -63,8 +63,8 @@ function New-FabricLakehouse { # Step 3: Construct the request body $body = @{ - displayName = $LakehouseName - } + displayName = $LakehouseName + } if ($LakehouseDescription) { $body.description = $LakehouseDescription @@ -114,8 +114,7 @@ function New-FabricLakehouse { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -127,10 +126,9 @@ function New-FabricLakehouse { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Lakehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 b/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 index 254bdde6..d16b9f1b 100644 --- a/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 @@ -20,7 +20,7 @@ Deletes the Lakehouse with ID "67890" from workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -63,11 +63,10 @@ function Remove-FabricLakehouse { return $null } Write-Message -Message "Lakehouse '$LakehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Lakehouse '$LakehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 index f6db24d9..a494df3a 100644 --- a/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 +++ b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 @@ -1,5 +1,5 @@ function Start-FabricLakehouseTableMaintenance { -<# + <# .SYNOPSIS Initiates a table maintenance job for a specified Lakehouse in a Fabric workspace. .DESCRIPTION @@ -41,7 +41,7 @@ function Start-FabricLakehouseTableMaintenance { .NOTES -#> + #> [CmdletBinding()] param ( @@ -106,7 +106,7 @@ function Start-FabricLakehouseTableMaintenance { $body = @{ executionData = @{ tableName = $TableName - optimizeSettings = @{} + optimizeSettings = @{ } } } if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and $SchemaName) { @@ -117,14 +117,14 @@ function Start-FabricLakehouseTableMaintenance { $body.executionData.optimizeSettings.vOrder = $IsVOrder } - if ($ColumnsZOrderBy) { - # Ensure $ColumnsZOrderBy is an array - if (-not ($ColumnsZOrderBy -is [array])) { - $ColumnsZOrderBy = $ColumnsZOrderBy -split "," + if ($ColumnsZOrderBy) { + # Ensure $ColumnsZOrderBy is an array + if (-not ($ColumnsZOrderBy -is [array])) { + $ColumnsZOrderBy = $ColumnsZOrderBy -split "," + } + # Add it to the optimizeSettings in the request body + $body.executionData.optimizeSettings.zOrderBy = $ColumnsZOrderBy } - # Add it to the optimizeSettings in the request body - $body.executionData.optimizeSettings.zOrderBy = $ColumnsZOrderBy - } @@ -176,8 +176,7 @@ function Start-FabricLakehouseTableMaintenance { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug return $operationStatus - } - else { + } else { Write-Message -Message "The operation is running asynchronously." -Level Info Write-Message -Message "Use the returned details to check the operation status." -Level Info Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info @@ -195,10 +194,9 @@ function Start-FabricLakehouseTableMaintenance { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to start table maintenance job. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Lakehouse/Update-FabricLakehouse.ps1 b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 index ed251ca7..5f95b744 100644 --- a/source/Public/Lakehouse/Update-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 @@ -102,10 +102,9 @@ function Update-FabricLakehouse { # Step 6: Handle results Write-Message -Message "Lakehouse '$LakehouseName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/ML Experiment/Get-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Get-FabricMLExperiment.ps1 index 52e57f8e..4e607c4f 100644 --- a/source/Public/ML Experiment/Get-FabricMLExperiment.ps1 +++ b/source/Public/ML Experiment/Get-FabricMLExperiment.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricMLExperiment { [CmdletBinding()] @@ -61,27 +61,27 @@ function Get-FabricMLExperiment { # Step 3: Initialize variables $continuationToken = $null $MLExperiments = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/mlExperiments" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +91,7 @@ function Get-FabricMLExperiment { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,38 +100,34 @@ function Get-FabricMLExperiment { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $MLExperiments += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $MLExperiment = if ($MLExperimentId) { $MLExperiments | Where-Object { $_.Id -eq $MLExperimentId } - } - elseif ($MLExperimentName) { + } elseif ($MLExperimentName) { $MLExperiments | Where-Object { $_.DisplayName -eq $MLExperimentName } - } - else { + } else { # Return all MLExperiments if no filter is provided Write-Message -Message "No filter provided. Returning all MLExperiments." -Level Debug $MLExperiments @@ -141,16 +137,14 @@ function Get-FabricMLExperiment { if ($MLExperiment) { Write-Message -Message "ML Experiment found matching the specified criteria." -Level Debug return $MLExperiment - } - else { + } else { Write-Message -Message "No ML Experiment found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve ML Experiment. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/ML Experiment/New-FabricMLExperiment.ps1 b/source/Public/ML Experiment/New-FabricMLExperiment.ps1 index 0a4d28be..f8f26544 100644 --- a/source/Public/ML Experiment/New-FabricMLExperiment.ps1 +++ b/source/Public/ML Experiment/New-FabricMLExperiment.ps1 @@ -3,7 +3,7 @@ Creates a new ML Experiment in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new ML Experiment + This function sends a POST request to the Microsoft Fabric API to create a new ML Experiment in the specified workspace. It supports optional parameters for ML Experiment description. .PARAMETER WorkspaceId @@ -24,7 +24,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function New-FabricMLExperiment { [CmdletBinding()] @@ -85,33 +85,32 @@ function New-FabricMLExperiment { } 202 { Write-Message -Message "ML Experiment '$MLExperimentName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -121,10 +120,9 @@ function New-FabricMLExperiment { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create ML Experiment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 index 7c8cbb20..2289f4f4 100644 --- a/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 +++ b/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 @@ -3,7 +3,7 @@ Removes an ML Experiment from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an ML Experiment + This function sends a DELETE request to the Microsoft Fabric API to remove an ML Experiment from the specified workspace using the provided WorkspaceId and MLExperimentId. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Remove-FabricMLExperiment { [CmdletBinding()] @@ -62,11 +62,10 @@ function Remove-FabricMLExperiment { return $null } Write-Message -Message "ML Experiment '$MLExperimentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete ML Experiment '$MLExperimentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 index f9c0a9e5..16c4c922 100644 --- a/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 +++ b/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 @@ -3,7 +3,7 @@ Updates an existing ML Experiment in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Experiment + This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Experiment in the specified workspace. It supports optional parameters for ML Experiment description. .PARAMETER WorkspaceId @@ -27,15 +27,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricMLExperiment { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$MLExperimentId, @@ -96,10 +96,9 @@ function Update-FabricMLExperiment { # Step 6: Handle results Write-Message -Message "ML Experiment '$MLExperimentName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update ML Experiment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/ML Model/Get-FabricMLModel.ps1 b/source/Public/ML Model/Get-FabricMLModel.ps1 index 7d15c5e8..e7b728cc 100644 --- a/source/Public/ML Model/Get-FabricMLModel.ps1 +++ b/source/Public/ML Model/Get-FabricMLModel.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricMLModel { [CmdletBinding()] @@ -61,27 +61,27 @@ function Get-FabricMLModel { # Step 3: Initialize variables $continuationToken = $null $MLModels = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/mlModels" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +91,7 @@ function Get-FabricMLModel { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,38 +100,34 @@ function Get-FabricMLModel { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $MLModels += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $MLModel = if ($MLModelId) { $MLModels | Where-Object { $_.Id -eq $MLModelId } - } - elseif ($MLModelName) { + } elseif ($MLModelName) { $MLModels | Where-Object { $_.DisplayName -eq $MLModelName } - } - else { + } else { # Return all MLModels if no filter is provided Write-Message -Message "No filter provided. Returning all MLModels." -Level Debug $MLModels @@ -141,16 +137,14 @@ function Get-FabricMLModel { if ($MLModel) { Write-Message -Message "ML Model found matching the specified criteria." -Level Debug return $MLModel - } - else { + } else { Write-Message -Message "No ML Model found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve ML Model. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/ML Model/New-FabricMLModel.ps1 b/source/Public/ML Model/New-FabricMLModel.ps1 index cf4ffac0..757fe679 100644 --- a/source/Public/ML Model/New-FabricMLModel.ps1 +++ b/source/Public/ML Model/New-FabricMLModel.ps1 @@ -3,7 +3,7 @@ Creates a new ML Model in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new ML Model + This function sends a POST request to the Microsoft Fabric API to create a new ML Model in the specified workspace. It supports optional parameters for ML Model description. .PARAMETER WorkspaceId @@ -24,7 +24,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function New-FabricMLModel { [CmdletBinding()] @@ -85,33 +85,32 @@ function New-FabricMLModel { } 202 { Write-Message -Message "ML Model '$MLModelName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -121,10 +120,9 @@ function New-FabricMLModel { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create ML Model. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/ML Model/Remove-FabricMLModel.ps1 b/source/Public/ML Model/Remove-FabricMLModel.ps1 index f1491d73..79c85218 100644 --- a/source/Public/ML Model/Remove-FabricMLModel.ps1 +++ b/source/Public/ML Model/Remove-FabricMLModel.ps1 @@ -3,7 +3,7 @@ Removes an ML Model from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an ML Model + This function sends a DELETE request to the Microsoft Fabric API to remove an ML Model from the specified workspace using the provided WorkspaceId and MLModelId. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Remove-FabricMLModel { [CmdletBinding()] @@ -62,11 +62,10 @@ function Remove-FabricMLModel { return $null } Write-Message -Message "ML Model '$MLModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete ML Model '$MLModelId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/ML Model/Update-FabricMLModel.ps1 b/source/Public/ML Model/Update-FabricMLModel.ps1 index 50e2d6a0..eff3ecd1 100644 --- a/source/Public/ML Model/Update-FabricMLModel.ps1 +++ b/source/Public/ML Model/Update-FabricMLModel.ps1 @@ -3,7 +3,7 @@ Updates an existing ML Model in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Model + This function sends a PATCH request to the Microsoft Fabric API to update an existing ML Model in the specified workspace. It supports optional parameters for ML Model description. .PARAMETER WorkspaceId @@ -24,15 +24,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricMLModel { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$MLModelId, @@ -84,10 +84,9 @@ function Update-FabricMLModel { # Step 6: Handle results Write-Message -Message "ML Model '$MLModelId' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update ML Model. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 index 2aef20d0..0129b89d 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabase.ps1 @@ -1,5 +1,5 @@ function Get-FabricMirroredDatabase { -<# + <# .SYNOPSIS Retrieves an MirroredDatabase or a list of MirroredDatabases from a specified workspace in Microsoft Fabric. @@ -31,7 +31,7 @@ Retrieves all MirroredDatabases in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -113,13 +113,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -130,11 +128,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $MirroredDatabase = if ($MirroredDatabaseId) { $MirroredDatabases | Where-Object { $_.Id -eq $MirroredDatabaseId } - } - elseif ($MirroredDatabaseName) { + } elseif ($MirroredDatabaseName) { $MirroredDatabases | Where-Object { $_.DisplayName -eq $MirroredDatabaseName } - } - else { + } else { # Return all MirroredDatabases if no filter is provided Write-Message -Message "No filter provided. Returning all MirroredDatabases." -Level Debug $MirroredDatabases @@ -144,16 +140,14 @@ Author: Tiago Balabuch if ($MirroredDatabase) { Write-Message -Message "MirroredDatabase found matching the specified criteria." -Level Debug return $MirroredDatabase - } - else { + } else { Write-Message -Message "No MirroredDatabase found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 index 2a7700cf..c1df17c8 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseDefinition.ps1 @@ -4,7 +4,7 @@ Retrieves the definition of a MirroredDatabase from a specific workspace in Microsoft Fabric. .DESCRIPTION -This function fetches the MirroredDatabase's content or metadata from a workspace. +This function fetches the MirroredDatabase's content or metadata from a workspace. Handles both synchronous and asynchronous operations, with detailed logging and error handling. .PARAMETER WorkspaceId @@ -73,37 +73,35 @@ function Get-FabricMirroredDatabaseDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } - + } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 index 7bd87fe8..c2583190 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseStatus.ps1 @@ -1,5 +1,5 @@ function Get-FabricMirroredDatabaseStatus { -<# + <# .SYNOPSIS Retrieves the status of a mirrored database in a specified workspace. .DESCRIPTION @@ -12,7 +12,7 @@ the ID of the mirrored database whose status is to be retrieved. .EXAMPLE Get-FabricMirroredDatabaseStatus -WorkspaceId "your-workspace-id" -MirroredDatabaseId "your-mirrored-database-id" This example retrieves the status of a mirrored database with the specified ID in the specified workspace. -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -56,11 +56,10 @@ This example retrieves the status of a mirrored database with the specified ID i Write-Message -Message "Returning status of MirroredDatabases." -Level Debug return $response - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 index 9bc1f3cc..7e4efaa7 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 @@ -1,5 +1,5 @@ function Get-FabricMirroredDatabaseTableStatus { -<# + <# .SYNOPSIS Retrieves the status of tables in a mirrored database. .DESCRIPTION @@ -14,7 +14,7 @@ This example retrieves the status of tables in a mirrored database with the spec .NOTES The function retrieves the PowerBI access token and makes a POST request to the PowerBI API to retrieve the status of tables in the specified mirrored database. It then returns the 'value' property of the response, which contains the table statuses. -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -86,13 +86,11 @@ The function retrieves the PowerBI access token and makes a POST request to the Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -103,11 +101,10 @@ The function retrieves the PowerBI access token and makes a POST request to the # Return all Mirrored Database Table Status Write-Message -Message "No filter provided. Returning all MirroredDatabases." -Level Debug $MirroredDatabaseTableStatus - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 index 70b4710d..e603d050 100644 --- a/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 @@ -3,8 +3,8 @@ Creates a new MirroredDatabase in a specified Microsoft Fabric workspace. .DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new MirroredDatabase -in the specified workspace. It supports optional parameters for MirroredDatabase description +This function sends a POST request to the Microsoft Fabric API to create a new MirroredDatabase +in the specified workspace. It supports optional parameters for MirroredDatabase description and path definitions for the MirroredDatabase content. .PARAMETER WorkspaceId @@ -29,7 +29,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -52,7 +52,7 @@ function New-FabricMirroredDatabase { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$MirroredDatabasePathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$MirroredDatabasePathPlatformDefinition @@ -84,7 +84,7 @@ function New-FabricMirroredDatabase { # Initialize definition if it doesn't exist if (-not $body.definition) { $body.definition = @{ - parts = @() + parts = @() } } @@ -94,8 +94,7 @@ function New-FabricMirroredDatabase { payload = $MirroredDatabaseEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error return $null } @@ -119,8 +118,7 @@ function New-FabricMirroredDatabase { payload = $MirroredDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -149,27 +147,26 @@ function New-FabricMirroredDatabase { } 202 { Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation Failed" -Level Debug return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -177,10 +174,9 @@ function New-FabricMirroredDatabase { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 index 701e513d..0a44a64b 100644 --- a/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 @@ -62,11 +62,10 @@ function Remove-FabricMirroredDatabase { return $null } Write-Message -Message "MirroredDatabase '$MirroredDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete MirroredDatabase '$MirroredDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 index 844bb4a8..d3936ffe 100644 --- a/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 +++ b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 @@ -1,5 +1,5 @@ -function Start-FabricMirroredDatabaseMirroring{ -<# +function Start-FabricMirroredDatabaseMirroring { + <# .SYNOPSIS Starts the mirroring of a specified mirrored database in a given workspace. .DESCRIPTION @@ -17,7 +17,7 @@ function Start-FabricMirroredDatabaseMirroring{ - Calls `Test-TokenExpired` to ensure token validity before making the API request. - This function handles asynchronous operations and retrieves operation results if required. -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -60,11 +60,10 @@ function Start-FabricMirroredDatabaseMirroring{ # Step 9: Handle results Write-Message -Message "Database mirroring started successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info return - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to start MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 index a88e8613..d5201c9d 100644 --- a/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 +++ b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 @@ -1,5 +1,5 @@ -function Stop-FabricMirroredDatabaseMirroring{ -<# +function Stop-FabricMirroredDatabaseMirroring { + <# .SYNOPSIS Stops the mirroring of a specified mirrored database in a given workspace. .DESCRIPTION @@ -21,7 +21,7 @@ function Stop-FabricMirroredDatabaseMirroring{ - Calls `Test-TokenExpired` to ensure token validity before making the API request. - This function handles asynchronous operations and retrieves operation results if required. -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -64,11 +64,10 @@ function Stop-FabricMirroredDatabaseMirroring{ # Step 9: Handle results Write-Message -Message "Database mirroring stopped successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info return - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to stop MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 index a5f86f49..33f57b41 100644 --- a/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 @@ -101,10 +101,9 @@ function Update-FabricMirroredDatabase { # Step 6: Handle results Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 b/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 index 5fc7fb71..1ef351e6 100644 --- a/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 +++ b/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of a MirroredDatabase in a Microsoft Fabric workspace. .DESCRIPTION -This function allows updating the content or metadata of a MirroredDatabase in a Microsoft Fabric workspace. +This function allows updating the content or metadata of a MirroredDatabase in a Microsoft Fabric workspace. The MirroredDatabase content can be provided as file paths, and metadata updates can optionally be enabled. .PARAMETER WorkspaceId @@ -19,7 +19,7 @@ The MirroredDatabase content can be provided as file paths, and metadata updates (Optional) The file path to the MirroredDatabase's platform-specific definition file. The content will be encoded as Base64 and sent in the request. .PARAMETER UpdateMetadata -(Optional)A boolean flag indicating whether to update the MirroredDatabase's metadata. +(Optional)A boolean flag indicating whether to update the MirroredDatabase's metadata. Default: `$false`. .EXAMPLE @@ -38,7 +38,7 @@ Updates both the content and metadata of the MirroredDatabase with ID `67890` in - The MirroredDatabase content is encoded as Base64 before being sent to the Fabric API. - This function handles asynchronous operations and retrieves operation results if required. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -56,7 +56,7 @@ function Update-FabricMirroredDatabaseDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$MirroredDatabasePathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$MirroredDatabasePathPlatformDefinition @@ -71,21 +71,21 @@ function Update-FabricMirroredDatabaseDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - if($MirroredDatabasePathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + if ($MirroredDatabasePathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Construct the request body $body = @{ definition = @{ - parts = @() - } + parts = @() + } } - + if ($MirroredDatabasePathDefinition) { $MirroredDatabaseEncodedContent = Convert-ToBase64 -filePath $MirroredDatabasePathDefinition - + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -93,8 +93,7 @@ function Update-FabricMirroredDatabaseDefinition { payload = $MirroredDatabaseEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error return $null } @@ -109,8 +108,7 @@ function Update-FabricMirroredDatabaseDefinition { payload = $MirroredDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -129,7 +127,7 @@ function Update-FabricMirroredDatabaseDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -144,25 +142,23 @@ function Update-FabricMirroredDatabaseDefinition { # Handle operation result if ($operationResult.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug - + $result = Get-FabricLongRunningOperationResult -operationId $operationId return $result.definition.parts - } - else { + } else { Write-Message -Message "Operation Failed" -Level Debug return $operationResult.definition.parts - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 b/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 index 1c4b53e3..8e32b5dc 100644 --- a/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 +++ b/source/Public/Mirrored Warehouse/Get-FabricMirroredWarehouse.ps1 @@ -1,5 +1,5 @@ function Get-FabricMirroredWarehouse { -<# + <# .SYNOPSIS Retrieves an MirroredWarehouse or a list of MirroredWarehouses from a specified workspace in Microsoft Fabric. @@ -31,7 +31,7 @@ Retrieves all MirroredWarehouses in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -113,13 +113,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -130,11 +128,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $MirroredWarehouse = if ($MirroredWarehouseId) { $MirroredWarehouses | Where-Object { $_.Id -eq $MirroredWarehouseId } - } - elseif ($MirroredWarehouseName) { + } elseif ($MirroredWarehouseName) { $MirroredWarehouses | Where-Object { $_.DisplayName -eq $MirroredWarehouseName } - } - else { + } else { # Return all MirroredWarehouses if no filter is provided Write-Message -Message "No filter provided. Returning all MirroredWarehouses." -Level Debug $MirroredWarehouses @@ -144,16 +140,14 @@ Author: Tiago Balabuch if ($MirroredWarehouse) { Write-Message -Message "MirroredWarehouse found matching the specified criteria." -Level Debug return $MirroredWarehouse - } - else { + } else { Write-Message -Message "No MirroredWarehouse found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve MirroredWarehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Notebook/Get-FabricNotebook.ps1 b/source/Public/Notebook/Get-FabricNotebook.ps1 index 39e16b9d..8f9785ad 100644 --- a/source/Public/Notebook/Get-FabricNotebook.ps1 +++ b/source/Public/Notebook/Get-FabricNotebook.ps1 @@ -1,5 +1,5 @@ function Get-FabricNotebook { -<# + <# .SYNOPSIS Retrieves an Notebook or a list of Notebooks from a specified workspace in Microsoft Fabric. @@ -31,7 +31,7 @@ Retrieves all Notebooks in workspace "12345". Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -112,13 +112,11 @@ Author: Tiago Balabuch Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -128,11 +126,9 @@ Author: Tiago Balabuch # Step 8: Filter results based on provided parameters $notebook = if ($NotebookId) { $notebooks | Where-Object { $_.Id -eq $NotebookId } - } - elseif ($NotebookName) { + } elseif ($NotebookName) { $notebooks | Where-Object { $_.DisplayName -eq $NotebookName } - } - else { + } else { # Return all notebooks if no filter is provided Write-Message -Message "No filter provided. Returning all Notebooks." -Level Debug $notebooks @@ -142,16 +138,14 @@ Author: Tiago Balabuch if ($notebook) { Write-Message -Message "Notebook found matching the specified criteria." -Level Debug return $notebook - } - else { + } else { Write-Message -Message "No notebook found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Notebook. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Notebook/Get-FabricNotebookDefinition.ps1 b/source/Public/Notebook/Get-FabricNotebookDefinition.ps1 index 009eb88d..b7235bbb 100644 --- a/source/Public/Notebook/Get-FabricNotebookDefinition.ps1 +++ b/source/Public/Notebook/Get-FabricNotebookDefinition.ps1 @@ -4,7 +4,7 @@ Retrieves the definition of a notebook from a specific workspace in Microsoft Fabric. .DESCRIPTION -This function fetches the notebook's content or metadata from a workspace. +This function fetches the notebook's content or metadata from a workspace. It supports retrieving notebook definitions in the Jupyter Notebook (`ipynb`) format. Handles both synchronous and asynchronous operations, with detailed logging and error handling. @@ -88,13 +88,13 @@ function Get-FabricNotebookDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] #[string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug @@ -102,30 +102,28 @@ function Get-FabricNotebookDefinition { if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } - + } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Notebook. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Notebook/New-FabricNotebook.ps1 b/source/Public/Notebook/New-FabricNotebook.ps1 index eec94a4e..0b607990 100644 --- a/source/Public/Notebook/New-FabricNotebook.ps1 +++ b/source/Public/Notebook/New-FabricNotebook.ps1 @@ -3,8 +3,8 @@ Creates a new notebook in a specified Microsoft Fabric workspace. .DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new notebook -in the specified workspace. It supports optional parameters for notebook description +This function sends a POST request to the Microsoft Fabric API to create a new notebook +in the specified workspace. It supports optional parameters for notebook description and path definitions for the notebook content. .PARAMETER WorkspaceId @@ -29,7 +29,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -52,7 +52,7 @@ function New-FabricNotebook { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$NotebookPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$NotebookPathPlatformDefinition @@ -95,8 +95,7 @@ function New-FabricNotebook { payload = $notebookEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in notebook definition." -Level Error return $null } @@ -120,8 +119,7 @@ function New-FabricNotebook { payload = $notebookEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -150,33 +148,32 @@ function New-FabricNotebook { } 202 { Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -184,10 +181,9 @@ function New-FabricNotebook { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Notebook/New-FabricNotebookNEW.ps1 b/source/Public/Notebook/New-FabricNotebookNEW.ps1 index c29b71f7..2fb7356c 100644 --- a/source/Public/Notebook/New-FabricNotebookNEW.ps1 +++ b/source/Public/Notebook/New-FabricNotebookNEW.ps1 @@ -3,8 +3,8 @@ Creates a new notebook in a specified Microsoft Fabric workspace. .DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new notebook -in the specified workspace. It supports optional parameters for notebook description +This function sends a POST request to the Microsoft Fabric API to create a new notebook +in the specified workspace. It supports optional parameters for notebook description and path definitions for the notebook content. .PARAMETER WorkspaceId @@ -29,7 +29,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -115,33 +115,32 @@ function New-FabricNotebookNEW { } 202 { Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error @@ -149,10 +148,9 @@ function New-FabricNotebookNEW { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Notebook/Remove-FabricNotebook.ps1 b/source/Public/Notebook/Remove-FabricNotebook.ps1 index ec22fad3..8da9ae55 100644 --- a/source/Public/Notebook/Remove-FabricNotebook.ps1 +++ b/source/Public/Notebook/Remove-FabricNotebook.ps1 @@ -20,7 +20,7 @@ Deletes the Notebook with ID "67890" from workspace "12345". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Validates token expiration before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -63,11 +63,10 @@ function Remove-FabricNotebook { return $null } Write-Message -Message "Notebook '$NotebookId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete notebook '$NotebookId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Notebook/Update-FabricNotebook.ps1 b/source/Public/Notebook/Update-FabricNotebook.ps1 index 8f1195ff..8d967ce1 100644 --- a/source/Public/Notebook/Update-FabricNotebook.ps1 +++ b/source/Public/Notebook/Update-FabricNotebook.ps1 @@ -101,10 +101,9 @@ function Update-FabricNotebook { # Step 6: Handle results Write-Message -Message "Notebook '$NotebookName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 b/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 index e6272644..b1f765d5 100644 --- a/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 +++ b/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of a notebook in a Microsoft Fabric workspace. .DESCRIPTION -This function allows updating the content or metadata of a notebook in a Microsoft Fabric workspace. +This function allows updating the content or metadata of a notebook in a Microsoft Fabric workspace. The notebook content can be provided as file paths, and metadata updates can optionally be enabled. .PARAMETER WorkspaceId @@ -34,7 +34,7 @@ Updates both the content and metadata of the notebook with ID `67890` in the wor - The notebook content is encoded as Base64 before being sent to the Fabric API. - This function handles asynchronous operations and retrieves operation results if required. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -52,7 +52,7 @@ function Update-FabricNotebookDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$NotebookPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$NotebookPathPlatformDefinition @@ -68,7 +68,7 @@ function Update-FabricNotebookDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId if ($NotebookPathPlatformDefinition) { - $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl + $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -77,12 +77,12 @@ function Update-FabricNotebookDefinition { definition = @{ format = "ipynb" parts = @() - } + } } - + if ($NotebookPathDefinition) { $notebookEncodedContent = Convert-ToBase64 -filePath $NotebookPathDefinition - + if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -90,8 +90,7 @@ function Update-FabricNotebookDefinition { payload = $notebookEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in notebook definition." -Level Error return $null } @@ -106,8 +105,7 @@ function Update-FabricNotebookDefinition { payload = $notebookEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -126,7 +124,7 @@ function Update-FabricNotebookDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -150,18 +148,17 @@ function Update-FabricNotebookDefinition { if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -170,10 +167,9 @@ function Update-FabricNotebookDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 b/source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 index fc52257d..4ffe89b8 100644 --- a/source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 +++ b/source/Public/Paginated Reports/Get-FabricPaginatedReport.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricPaginatedReport { [CmdletBinding()] @@ -61,27 +61,27 @@ function Get-FabricPaginatedReport { # Step 3: Initialize variables $continuationToken = $null $PaginatedReports = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/paginatedReports" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +91,7 @@ function Get-FabricPaginatedReport { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,38 +100,34 @@ function Get-FabricPaginatedReport { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $PaginatedReports += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $PaginatedReport = if ($PaginatedReportId) { $PaginatedReports | Where-Object { $_.Id -eq $PaginatedReportId } - } - elseif ($PaginatedReportName) { + } elseif ($PaginatedReportName) { $PaginatedReports | Where-Object { $_.DisplayName -eq $PaginatedReportName } - } - else { + } else { # Return all PaginatedReports if no filter is provided Write-Message -Message "No filter provided. Returning all Paginated Reports." -Level Debug $PaginatedReports @@ -141,15 +137,13 @@ function Get-FabricPaginatedReport { if ($PaginatedReport) { Write-Message -Message "Paginated Report found matching the specified criteria." -Level Debug return $PaginatedReport - } - else { + } else { Write-Message -Message "No Paginated Report found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Paginated Report. Error: $errorDetails" -Level Error - } -} + } +} \ No newline at end of file diff --git a/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 b/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 index 00d0c59d..4a8bfa31 100644 --- a/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 +++ b/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 @@ -3,7 +3,7 @@ Updates an existing paginated report in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing paginated report + This function sends a PATCH request to the Microsoft Fabric API to update an existing paginated report in the specified workspace. It supports optional parameters for paginated report description. .PARAMETER WorkspaceId @@ -27,15 +27,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricPaginatedReport { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$PaginatedReportId, @@ -96,10 +96,9 @@ function Update-FabricPaginatedReport { # Step 6: Handle results Write-Message -Message "Paginated Report '$PaginatedReportName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Paginated Report. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Reflex/Get-FabricReflex.ps1 b/source/Public/Reflex/Get-FabricReflex.ps1 index 9e274d3c..b7d0a707 100644 --- a/source/Public/Reflex/Get-FabricReflex.ps1 +++ b/source/Public/Reflex/Get-FabricReflex.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricReflex { [CmdletBinding()] @@ -58,15 +58,15 @@ function Get-FabricReflex { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 3: Initialize variables $continuationToken = $null $Reflexes = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/reflexes" -f $FabricConfig.BaseUrl, $WorkspaceId @@ -81,7 +81,7 @@ function Get-FabricReflex { $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +91,7 @@ function Get-FabricReflex { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,38 +100,34 @@ function Get-FabricReflex { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $Reflexes += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $Reflex = if ($ReflexId) { $Reflexes | Where-Object { $_.Id -eq $ReflexId } - } - elseif ($ReflexName) { + } elseif ($ReflexName) { $Reflexes | Where-Object { $_.DisplayName -eq $ReflexName } - } - else { + } else { # Return all Reflexes if no filter is provided Write-Message -Message "No filter provided. Returning all Reflexes." -Level Debug $Reflexes @@ -141,16 +137,14 @@ function Get-FabricReflex { if ($Reflex) { Write-Message -Message "Reflex found in the Workspace '$WorkspaceId'." -Level Debug return $Reflex - } - else { + } else { Write-Message -Message "No Reflex found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Reflex. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Reflex/Get-FabricReflexDefinition.ps1 b/source/Public/Reflex/Get-FabricReflexDefinition.ps1 index 121677e4..4dde23c7 100644 --- a/source/Public/Reflex/Get-FabricReflexDefinition.ps1 +++ b/source/Public/Reflex/Get-FabricReflexDefinition.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricReflexDefinition { [CmdletBinding()] @@ -53,11 +53,11 @@ function Get-FabricReflexDefinition { # Step 3: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId - + if ($ReflexFormat) { $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $ReflexFormat } - + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 4: Make the API request @@ -81,30 +81,29 @@ function Get-FabricReflexDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -114,11 +113,10 @@ function Get-FabricReflexDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Reflex. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Reflex/New-FabricReflex.ps1 b/source/Public/Reflex/New-FabricReflex.ps1 index e71b6502..ec792de0 100644 --- a/source/Public/Reflex/New-FabricReflex.ps1 +++ b/source/Public/Reflex/New-FabricReflex.ps1 @@ -3,7 +3,7 @@ Creates a new Reflex in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Reflex + This function sends a POST request to the Microsoft Fabric API to create a new Reflex in the specified workspace. It supports optional parameters for Reflex description and path definitions. .PARAMETER WorkspaceId @@ -30,7 +30,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function New-FabricReflex { [CmdletBinding()] @@ -51,7 +51,7 @@ function New-FabricReflex { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$ReflexPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$ReflexPathPlatformDefinition @@ -81,7 +81,7 @@ function New-FabricReflex { # Initialize definition if it doesn't exist if (-not $body.definition) { $body.definition = @{ - parts = @() + parts = @() } } @@ -91,8 +91,7 @@ function New-FabricReflex { payload = $ReflexEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error return $null } @@ -105,7 +104,7 @@ function New-FabricReflex { # Initialize definition if it doesn't exist if (-not $body.definition) { $body.definition = @{ - parts = @() + parts = @() } } @@ -115,8 +114,7 @@ function New-FabricReflex { payload = $ReflexEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -137,9 +135,9 @@ function New-FabricReflex { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + Write-Message -Message "Response Code: $statusCode" -Level Debug - + # Step 5: Handle and log the response switch ($statusCode) { 201 { @@ -148,33 +146,32 @@ function New-FabricReflex { } 202 { Write-Message -Message "Reflex '$ReflexName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -184,10 +181,9 @@ function New-FabricReflex { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Reflex. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Reflex/Remove-FabricReflex.ps1 b/source/Public/Reflex/Remove-FabricReflex.ps1 index 884f4ab5..ff259aab 100644 --- a/source/Public/Reflex/Remove-FabricReflex.ps1 +++ b/source/Public/Reflex/Remove-FabricReflex.ps1 @@ -3,7 +3,7 @@ Removes an Reflex from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an Reflex + This function sends a DELETE request to the Microsoft Fabric API to remove an Reflex from the specified workspace using the provided WorkspaceId and ReflexId. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Remove-FabricReflex { [CmdletBinding()] @@ -53,8 +53,8 @@ function Remove-FabricReflex { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - - # Step 4: Handle response + + # Step 4: Handle response if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -63,11 +63,10 @@ function Remove-FabricReflex { return $null } - Write-Message -Message "Reflex '$ReflexId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + Write-Message -Message "Reflex '$ReflexId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Reflex '$ReflexId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Reflex/Update-FabricReflex.ps1 b/source/Public/Reflex/Update-FabricReflex.ps1 index a600d2fc..2b3ab89c 100644 --- a/source/Public/Reflex/Update-FabricReflex.ps1 +++ b/source/Public/Reflex/Update-FabricReflex.ps1 @@ -3,7 +3,7 @@ Updates an existing Reflex in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Reflex + This function sends a PATCH request to the Microsoft Fabric API to update an existing Reflex in the specified workspace. It supports optional parameters for Reflex description. .PARAMETER WorkspaceId @@ -27,15 +27,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricReflex { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ReflexId, @@ -96,10 +96,9 @@ function Update-FabricReflex { # Step 6: Handle results Write-Message -Message "Reflex '$ReflexName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Reflex/Update-FabricReflexDefinition.ps1 b/source/Public/Reflex/Update-FabricReflexDefinition.ps1 index 45582d9a..24730ac1 100644 --- a/source/Public/Reflex/Update-FabricReflexDefinition.ps1 +++ b/source/Public/Reflex/Update-FabricReflexDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of an existing Reflex in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Reflex + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Reflex in the specified workspace. It supports optional parameters for Reflex definition and platform-specific definition. .PARAMETER WorkspaceId @@ -27,7 +27,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricReflexDefinition { [CmdletBinding()] @@ -43,7 +43,7 @@ function Update-FabricReflexDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ReflexPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$ReflexPathPlatformDefinition @@ -58,21 +58,21 @@ function Update-FabricReflexDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId #if ($UpdateMetadata -eq $true) { - if($ReflexPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + if ($ReflexPathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Construct the request body $body = @{ definition = @{ - parts = @() - } + parts = @() + } } - + if ($ReflexPathDefinition) { $ReflexEncodedContent = Convert-ToBase64 -filePath $ReflexPathDefinition - + if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -80,8 +80,7 @@ function Update-FabricReflexDefinition { payload = $ReflexEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error return $null } @@ -96,8 +95,7 @@ function Update-FabricReflexDefinition { payload = $ReflexEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -116,7 +114,7 @@ function Update-FabricReflexDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -125,44 +123,42 @@ function Update-FabricReflexDefinition { } 202 { Write-Message -Message "Update definition for Reflex '$ReflexId' accepted. Operation in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Report/Get-FabricReport.ps1 b/source/Public/Report/Get-FabricReport.ps1 index 0f655be2..ee09cba1 100644 --- a/source/Public/Report/Get-FabricReport.ps1 +++ b/source/Public/Report/Get-FabricReport.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricReport { [CmdletBinding()] @@ -58,15 +58,15 @@ function Get-FabricReport { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 3: Initialize variables $continuationToken = $null $Reports = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/reports" -f $FabricConfig.BaseUrl, $WorkspaceId @@ -81,7 +81,7 @@ function Get-FabricReport { $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +91,7 @@ function Get-FabricReport { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,38 +100,34 @@ function Get-FabricReport { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $Reports += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $Report = if ($ReportId) { $Reports | Where-Object { $_.Id -eq $ReportId } - } - elseif ($ReportName) { + } elseif ($ReportName) { $Reports | Where-Object { $_.DisplayName -eq $ReportName } - } - else { + } else { # Return all Reports if no filter is provided Write-Message -Message "No filter provided. Returning all Reports." -Level Debug $Reports @@ -141,16 +137,14 @@ function Get-FabricReport { if ($Report) { Write-Message -Message "Report found in the Workspace '$WorkspaceId'." -Level Debug return $Report - } - else { + } else { Write-Message -Message "No Report found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Report. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Report/Get-FabricReportDefinition.ps1 b/source/Public/Report/Get-FabricReportDefinition.ps1 index ef06367d..4e3e8cf9 100644 --- a/source/Public/Report/Get-FabricReportDefinition.ps1 +++ b/source/Public/Report/Get-FabricReportDefinition.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricReportDefinition { [CmdletBinding()] @@ -53,11 +53,11 @@ function Get-FabricReportDefinition { # Step 3: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId - + if ($ReportFormat) { $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $ReportFormat } - + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 4: Make the API request @@ -81,30 +81,29 @@ function Get-FabricReportDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -114,11 +113,10 @@ function Get-FabricReportDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Report. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Report/New-FabricReport.ps1 b/source/Public/Report/New-FabricReport.ps1 index 9e61167c..151b9e9d 100644 --- a/source/Public/Report/New-FabricReport.ps1 +++ b/source/Public/Report/New-FabricReport.ps1 @@ -3,7 +3,7 @@ Creates a new Report in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Report + This function sends a POST request to the Microsoft Fabric API to create a new Report in the specified workspace. It supports optional parameters for Report description and path definitions. .PARAMETER WorkspaceId @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function New-FabricReport { [CmdletBinding()] @@ -49,7 +49,7 @@ function New-FabricReport { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ReportPathDefinition - ) + ) try { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug @@ -71,14 +71,14 @@ function New-FabricReport { if ($ReportPathDefinition) { if (-not $body.definition) { $body.definition = @{ - parts = @() + parts = @() } } $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $ReportPathDefinition # Add new part to the parts array $body.definition.parts = $jsonObjectParts.parts } - + # Convert the body to JSON $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug @@ -94,9 +94,9 @@ function New-FabricReport { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + Write-Message -Message "Response Code: $statusCode" -Level Debug - + # Step 5: Handle and log the response switch ($statusCode) { 201 { @@ -105,33 +105,32 @@ function New-FabricReport { } 202 { Write-Message -Message "Report '$ReportName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -141,10 +140,9 @@ function New-FabricReport { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Report. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Report/Remove-FabricReport.ps1 b/source/Public/Report/Remove-FabricReport.ps1 index 3ab69774..3a3ef692 100644 --- a/source/Public/Report/Remove-FabricReport.ps1 +++ b/source/Public/Report/Remove-FabricReport.ps1 @@ -3,7 +3,7 @@ Removes an Report from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an Report + This function sends a DELETE request to the Microsoft Fabric API to remove an Report from the specified workspace using the provided WorkspaceId and ReportId. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Remove-FabricReport { [CmdletBinding()] @@ -53,8 +53,8 @@ function Remove-FabricReport { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - - # Step 4: Handle response + + # Step 4: Handle response if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -63,11 +63,10 @@ function Remove-FabricReport { return $null } - Write-Message -Message "Report '$ReportId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + Write-Message -Message "Report '$ReportId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Report '$ReportId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Report/Update-FabricReport.ps1 b/source/Public/Report/Update-FabricReport.ps1 index 08b852ea..0d5a9536 100644 --- a/source/Public/Report/Update-FabricReport.ps1 +++ b/source/Public/Report/Update-FabricReport.ps1 @@ -3,7 +3,7 @@ Updates an existing Report in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Report + This function sends a PATCH request to the Microsoft Fabric API to update an existing Report in the specified workspace. It supports optional parameters for Report description. .PARAMETER WorkspaceId @@ -27,15 +27,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricReport { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ReportId, @@ -96,10 +96,9 @@ function Update-FabricReport { # Step 6: Handle results Write-Message -Message "Report '$ReportName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Report/Update-FabricReportDefinition.ps1 b/source/Public/Report/Update-FabricReportDefinition.ps1 index 1a7bb73f..db0f06f0 100644 --- a/source/Public/Report/Update-FabricReportDefinition.ps1 +++ b/source/Public/Report/Update-FabricReportDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of an existing Report in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Report + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing Report in the specified workspace. It supports optional parameters for Report definition and platform-specific definition. .PARAMETER WorkspaceId @@ -24,7 +24,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricReportDefinition { [CmdletBinding()] @@ -56,14 +56,14 @@ function Update-FabricReportDefinition { # Step 3: Construct the request body $body = @{ definition = @{ - parts = @() - } + parts = @() + } } - + if ($ReportPathDefinition) { if (-not $body.definition) { $body.definition = @{ - parts = @() + parts = @() } } $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $ReportPathDefinition @@ -78,8 +78,8 @@ function Update-FabricReportDefinition { } } - if($hasPlatformFile -eq $true) { - $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl + if ($hasPlatformFile -eq $true) { + $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -97,7 +97,7 @@ function Update-FabricReportDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -106,16 +106,16 @@ function Update-FabricReportDefinition { } 202 { Write-Message -Message "Update definition for Report '$ReportId' accepted. Operation in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result @@ -123,23 +123,21 @@ function Update-FabricReportDefinition { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Update definition operation for Report '$ReportId' succeeded!" -Level Info return $operationStatus - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/SQL Database/Get-FabricSQLDatabase.ps1 b/source/Public/SQL Database/Get-FabricSQLDatabase.ps1 index 6ba60a10..ddbfdbe8 100644 --- a/source/Public/SQL Database/Get-FabricSQLDatabase.ps1 +++ b/source/Public/SQL Database/Get-FabricSQLDatabase.ps1 @@ -1,6 +1,6 @@ function Get-FabricSQLDatabase { -<# + <# .SYNOPSIS Retrieves Fabric SQLDatabases @@ -43,15 +43,15 @@ function Get-FabricSQLDatabase { .NOTES Revision History: - 2025-03-06 - KNO: Init version of the function -#> + #> -[CmdletBinding()] + [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$WorkspaceId, - [Alias("Name","DisplayName")] + [Alias("Name", "DisplayName")] [string]$SQLDatabaseName, [Alias("Id")] diff --git a/source/Public/SQL Database/New-FabricSQLDatabase.ps1 b/source/Public/SQL Database/New-FabricSQLDatabase.ps1 index e980f04e..ce9f1332 100644 --- a/source/Public/SQL Database/New-FabricSQLDatabase.ps1 +++ b/source/Public/SQL Database/New-FabricSQLDatabase.ps1 @@ -3,8 +3,8 @@ Creates a new SQL Database in a specified Microsoft Fabric workspace. .DESCRIPTION -This function sends a POST request to the Microsoft Fabric API to create a new SQL Database -in the specified workspace. It supports optional parameters for SQL Database description +This function sends a POST request to the Microsoft Fabric API to create a new SQL Database +in the specified workspace. It supports optional parameters for SQL Database description and path definitions for the SQL Database content. .PARAMETER WorkspaceId @@ -18,7 +18,7 @@ An optional description for the SQL Database. .EXAMPLE - New-FabricSQLDatabase -WorkspaceId "workspace-12345" -Name "NewDatabase" + New-FabricSQLDatabase -WorkspaceId "workspace-12345" -Name "NewDatabase" .NOTES - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. @@ -29,61 +29,60 @@ Author: Kamil Nowinski #> function New-FabricSQLDatabase { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_]*$')] - [string]$Name, - - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$Description - ) - - try { - # Step 1: Ensure token validity - Test-TokenExpired - - # Step 2: Construct the API URL - $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases" -f $FabricConfig.BaseUrl, $WorkspaceId - Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Construct the request body - $body = @{ - displayName = $Name - } - - if ($Description) { - $body.description = $Description - } - - $bodyJson = $body | ConvertTo-Json -Depth 10 - Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - # Step 5: Handle and log the response - Write-Message "RESPONSE: $response" -Level Debug - Test-FabricApiResponse -response $response -responseHeader $responseHeader -statusCode $statusCode -Name $Name -TypeName 'SQL Database' - } - catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to create SQL Database. Error: $errorDetails" -Level Error - } -} + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^[a-zA-Z0-9_]*$')] + [string]$Name, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$Description + ) + + try { + # Step 1: Ensure token validity + Test-TokenExpired + + # Step 2: Construct the API URL + $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases" -f $FabricConfig.BaseUrl, $WorkspaceId + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + # Step 3: Construct the request body + $body = @{ + displayName = $Name + } + + if ($Description) { + $body.description = $Description + } + + $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + # Step 5: Handle and log the response + Write-Message "RESPONSE: $response" -Level Debug + Test-FabricApiResponse -response $response -responseHeader $responseHeader -statusCode $statusCode -Name $Name -TypeName 'SQL Database' + } catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to create SQL Database. Error: $errorDetails" -Level Error + } +} \ No newline at end of file diff --git a/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 index 799f07d4..4f72cf67 100644 --- a/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 +++ b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 @@ -40,7 +40,7 @@ function Remove-FabricSQLDatabase { # Step 1: Ensure token validity Confirm-FabricAuthToken | Out-Null Test-TokenExpired - + # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SQLDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -62,11 +62,10 @@ function Remove-FabricSQLDatabase { return $null } Write-Message -Message "SQL Database '$SQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SQL Database '$SQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 b/source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 index 6b760c91..8639f525 100644 --- a/source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 +++ b/source/Public/SQL Endpoints/Get-FabricSQLEndpoint.ps1 @@ -3,9 +3,9 @@ Retrieves SQL Endpoints from a specified workspace in Fabric. .DESCRIPTION -The Get-FabricSQLEndpoint function retrieves SQL Endpoints from a specified workspace in Fabric. -It supports filtering by SQL Endpoint ID or SQL Endpoint Name. If both filters are provided, -an error message is returned. The function handles token validation, API requests with continuation +The Get-FabricSQLEndpoint function retrieves SQL Endpoints from a specified workspace in Fabric. +It supports filtering by SQL Endpoint ID or SQL Endpoint Name. If both filters are provided, +an error message is returned. The function handles token validation, API requests with continuation tokens, and processes the response to return the desired SQL Endpoint(s). .PARAMETER WorkspaceId @@ -60,27 +60,27 @@ function Get-FabricSQLEndpoint { # Step 3: Initialize variables $continuationToken = $null $SQLEndpoints = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/SQLEndpoints" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -90,7 +90,7 @@ function Get-FabricSQLEndpoint { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -99,38 +99,34 @@ function Get-FabricSQLEndpoint { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $SQLEndpoints += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $SQLEndpoint = if ($SQLEndpointId) { $SQLEndpoints | Where-Object { $_.Id -eq $SQLEndpointId } - } - elseif ($SQLEndpointName) { + } elseif ($SQLEndpointName) { $SQLEndpoints | Where-Object { $_.DisplayName -eq $SQLEndpointName } - } - else { + } else { # Return all SQLEndpoints if no filter is provided Write-Message -Message "No filter provided. Returning all Paginated Reports." -Level Debug $SQLEndpoints @@ -140,15 +136,13 @@ function Get-FabricSQLEndpoint { if ($SQLEndpoint) { Write-Message -Message "Paginated Report found matching the specified criteria." -Level Debug return $SQLEndpoint - } - else { + } else { Write-Message -Message "No Paginated Report found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Paginated Report. Error: $errorDetails" -Level Error - } -} + } +} \ No newline at end of file diff --git a/source/Public/Semantic Model/Get-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Get-FabricSemanticModel.ps1 index f0611bdf..653dcc22 100644 --- a/source/Public/Semantic Model/Get-FabricSemanticModel.ps1 +++ b/source/Public/Semantic Model/Get-FabricSemanticModel.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricSemanticModel { [CmdletBinding()] @@ -58,15 +58,15 @@ function Get-FabricSemanticModel { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 3: Initialize variables $continuationToken = $null $SemanticModels = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/semanticModels" -f $FabricConfig.BaseUrl, $WorkspaceId @@ -81,7 +81,7 @@ function Get-FabricSemanticModel { $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +91,7 @@ function Get-FabricSemanticModel { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,38 +100,34 @@ function Get-FabricSemanticModel { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $SemanticModels += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $SemanticModel = if ($SemanticModelId) { $SemanticModels | Where-Object { $_.Id -eq $SemanticModelId } - } - elseif ($SemanticModelName) { + } elseif ($SemanticModelName) { $SemanticModels | Where-Object { $_.DisplayName -eq $SemanticModelName } - } - else { + } else { # Return all SemanticModels if no filter is provided Write-Message -Message "No filter provided. Returning all SemanticModels." -Level Debug $SemanticModels @@ -141,16 +137,14 @@ function Get-FabricSemanticModel { if ($SemanticModel) { Write-Message -Message "SemanticModel found in the Workspace '$WorkspaceId'." -Level Debug return $SemanticModel - } - else { + } else { Write-Message -Message "No SemanticModel found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve SemanticModel. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 b/source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 index fa38d546..d0083936 100644 --- a/source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 +++ b/source/Public/Semantic Model/Get-FabricSemanticModelDefinition.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricSemanticModelDefinition { [CmdletBinding()] @@ -54,11 +54,11 @@ function Get-FabricSemanticModelDefinition { # Step 3: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId - + if ($SemanticModelFormat) { $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $SemanticModelFormat } - + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 4: Make the API request @@ -82,30 +82,29 @@ function Get-FabricSemanticModelDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -115,11 +114,10 @@ function Get-FabricSemanticModelDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve SemanticModel. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Semantic Model/New-FabricSemanticModel.ps1 b/source/Public/Semantic Model/New-FabricSemanticModel.ps1 index 4ff05a02..0a0cd70d 100644 --- a/source/Public/Semantic Model/New-FabricSemanticModel.ps1 +++ b/source/Public/Semantic Model/New-FabricSemanticModel.ps1 @@ -3,7 +3,7 @@ Creates a new SemanticModel in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new SemanticModel + This function sends a POST request to the Microsoft Fabric API to create a new SemanticModel in the specified workspace. It supports optional parameters for SemanticModel description and path definitions. .PARAMETER WorkspaceId @@ -27,7 +27,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function New-FabricSemanticModel { [CmdletBinding()] @@ -48,7 +48,7 @@ function New-FabricSemanticModel { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SemanticModelPathDefinition - ) + ) try { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug @@ -62,9 +62,10 @@ function New-FabricSemanticModel { # Step 3: Construct the request body $body = @{ displayName = $SemanticModelName - definition = @{ - parts = @() - }} + definition = @{ + parts = @() + } + } $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $SemanticModelPathDefinition # Add new part to the parts array @@ -73,7 +74,7 @@ function New-FabricSemanticModel { if ($SemanticModelDescription) { $body.description = $SemanticModelDescription } - + # Convert the body to JSON $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug @@ -89,9 +90,9 @@ function New-FabricSemanticModel { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + Write-Message -Message "Response Code: $statusCode" -Level Debug - + # Step 5: Handle and log the response switch ($statusCode) { 201 { @@ -100,33 +101,32 @@ function New-FabricSemanticModel { } 202 { Write-Message -Message "SemanticModel '$SemanticModelName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -136,10 +136,9 @@ function New-FabricSemanticModel { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create SemanticModel. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 index 4e12b229..b1025a38 100644 --- a/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 +++ b/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 @@ -3,7 +3,7 @@ Removes an SemanticModel from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an SemanticModel + This function sends a DELETE request to the Microsoft Fabric API to remove an SemanticModel from the specified workspace using the provided WorkspaceId and SemanticModelId. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Remove-FabricSemanticModel { [CmdletBinding()] @@ -53,8 +53,8 @@ function Remove-FabricSemanticModel { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - - # Step 4: Handle response + + # Step 4: Handle response if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -63,11 +63,10 @@ function Remove-FabricSemanticModel { return $null } - Write-Message -Message "SemanticModel '$SemanticModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + Write-Message -Message "SemanticModel '$SemanticModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SemanticModel '$SemanticModelId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 index 12974b58..0082d596 100644 --- a/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 +++ b/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 @@ -3,7 +3,7 @@ Updates an existing SemanticModel in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing SemanticModel + This function sends a PATCH request to the Microsoft Fabric API to update an existing SemanticModel in the specified workspace. It supports optional parameters for SemanticModel description. .PARAMETER WorkspaceId @@ -27,15 +27,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricSemanticModel { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SemanticModelId, @@ -96,10 +96,9 @@ function Update-FabricSemanticModel { # Step 6: Handle results Write-Message -Message "SemanticModel '$SemanticModelName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 b/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 index 67e146ea..2c98e150 100644 --- a/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 +++ b/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of an existing SemanticModel in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SemanticModel + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SemanticModel in the specified workspace. It supports optional parameters for SemanticModel definition and platform-specific definition. .PARAMETER WorkspaceId @@ -24,7 +24,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricSemanticModelDefinition { [CmdletBinding()] @@ -54,9 +54,9 @@ function Update-FabricSemanticModelDefinition { $body = @{ definition = @{ parts = @() - } + } } - + $jsonObjectParts = Get-FileDefinitionParts -sourceDirectory $SemanticModelPathDefinition # Add new part to the parts array $body.definition.parts = $jsonObjectParts.parts @@ -69,7 +69,7 @@ function Update-FabricSemanticModelDefinition { } if ($hasPlatformFile -eq $true) { - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -87,7 +87,7 @@ function Update-FabricSemanticModelDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -96,16 +96,16 @@ function Update-FabricSemanticModelDefinition { } 202 { Write-Message -Message "Update definition for SemanticModel '$SemanticModelId' accepted. Operation in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result @@ -113,23 +113,21 @@ function Update-FabricSemanticModelDefinition { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Update definition operation for Semantic Model '$SemanticModelId' succeeded!" -Level Info return $operationStatus - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Set-FabricAuthToken.ps1 b/source/Public/Set-FabricAuthToken.ps1 index 8d5910c8..ac5f5dd1 100644 --- a/source/Public/Set-FabricAuthToken.ps1 +++ b/source/Public/Set-FabricAuthToken.ps1 @@ -1,5 +1,5 @@ function Set-FabricAuthToken { -<# + <# .SYNOPSIS Sets the Fabric authentication token. @@ -41,66 +41,64 @@ function Set-FabricAuthToken { .NOTES This function was originally written by Rui Romano. https://github.com/RuiRomano/fabricps-pbip -#> + #> - [CmdletBinding(SupportsShouldProcess)] - param - ( + [CmdletBinding(SupportsShouldProcess)] + param + ( [string] $servicePrincipalId - , [string] $servicePrincipalSecret - , [PSCredential] $credential - , [string] $tenantId - , [switch] $reset - , [string] $apiUrl - ) - - if (!$reset) { - $azContext = Get-AzContext - } - - if ($apiUrl) { - $FabricSession.BaseApiUrl = $apiUrl - } - - if (!$azContext) { - Write-Output "Getting authentication token" - if ($servicePrincipalId) { - $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $servicePrincipalId, ($servicePrincipalSecret | ConvertTo-SecureString -AsPlainText -Force) - - Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential | Out-Null - - Set-AzContext -Tenant $tenantId | Out-Null - } - elseif ($null -ne $credential) { - Connect-AzAccount -Credential $credential -Tenant $tenantId | Out-Null - } - else { - Connect-AzAccount | Out-Null - } - $azContext = Get-AzContext - } - if ($PSCmdlet.ShouldProcess("Setting Fabric authentication token for $($azContext.Account)")) { - Write-Output "Connnected: $($azContext.Account)" - Write-Output "Fabric ResourceUrl: $($FabricSession.ResourceUrl)" - - $FabricSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $FabricSession.ResourceUrl) - $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($FabricSession.AccessToken.Token) - $FabricSession.FabricToken = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) - Write-Verbose "Setup headers for API calls" - $FabricSession.HeaderParams = @{ Authorization = $FabricSession.AccessToken.Type + ' ' + $FabricSession.FabricToken } - - Write-Output "Azure BaseApiUrl: $($FabricSession.ResourceUrl)" - $script:AzureSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $AzureSession.BaseApiUrl) - $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzureSession.AccessToken.Token) - $script:AzureSession.Token = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) - $AzureSession.HeaderParams = @{ Authorization = $AzureSession.AccessToken.Type + ' ' + $AzureSession.Token } - - # Copy session values to exposed $FabricConfig - $FabricConfig.TenantIdGlobal = $FabricSession.AccessToken.TenantId - $FabricConfig.TokenExpiresOn = $FabricSession.AccessToken.ExpiresOn - $FabricConfig.FabricHeaders = $FabricSession.HeaderParams - - return $($FabricSession.FabricToken) - - } -} + , [string] $servicePrincipalSecret + , [PSCredential] $credential + , [string] $tenantId + , [switch] $reset + , [string] $apiUrl + ) + + if (!$reset) { + $azContext = Get-AzContext + } + + if ($apiUrl) { + $FabricSession.BaseApiUrl = $apiUrl + } + + if (!$azContext) { + Write-Output "Getting authentication token" + if ($servicePrincipalId) { + $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $servicePrincipalId, ($servicePrincipalSecret | ConvertTo-SecureString -AsPlainText -Force) + + Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential | Out-Null + + Set-AzContext -Tenant $tenantId | Out-Null + } elseif ($null -ne $credential) { + Connect-AzAccount -Credential $credential -Tenant $tenantId | Out-Null + } else { + Connect-AzAccount | Out-Null + } + $azContext = Get-AzContext + } + if ($PSCmdlet.ShouldProcess("Setting Fabric authentication token for $($azContext.Account)")) { + Write-Output "Connnected: $($azContext.Account)" + Write-Output "Fabric ResourceUrl: $($FabricSession.ResourceUrl)" + + $FabricSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $FabricSession.ResourceUrl) + $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($FabricSession.AccessToken.Token) + $FabricSession.FabricToken = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) + Write-Verbose "Setup headers for API calls" + $FabricSession.HeaderParams = @{ Authorization = $FabricSession.AccessToken.Type + ' ' + $FabricSession.FabricToken } + + Write-Output "Azure BaseApiUrl: $($FabricSession.ResourceUrl)" + $script:AzureSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $AzureSession.BaseApiUrl) + $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzureSession.AccessToken.Token) + $script:AzureSession.Token = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) + $AzureSession.HeaderParams = @{ Authorization = $AzureSession.AccessToken.Type + ' ' + $AzureSession.Token } + + # Copy session values to exposed $FabricConfig + $FabricConfig.TenantIdGlobal = $FabricSession.AccessToken.TenantId + $FabricConfig.TokenExpiresOn = $FabricSession.AccessToken.ExpiresOn + $FabricConfig.FabricHeaders = $FabricSession.HeaderParams + + return $($FabricSession.FabricToken) + + } +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 index 09524fb4..13000c7b 100644 --- a/source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinition.ps1 @@ -28,7 +28,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricSparkJobDefinition { [CmdletBinding()] @@ -58,15 +58,15 @@ function Get-FabricSparkJobDefinition { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 3: Initialize variables $continuationToken = $null $SparkJobDefinitions = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions" -f $FabricConfig.BaseUrl, $WorkspaceId @@ -81,7 +81,7 @@ function Get-FabricSparkJobDefinition { $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -91,7 +91,7 @@ function Get-FabricSparkJobDefinition { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -100,38 +100,34 @@ function Get-FabricSparkJobDefinition { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $SparkJobDefinitions += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $SparkJobDefinition = if ($SparkJobDefinitionId) { $SparkJobDefinitions | Where-Object { $_.Id -eq $SparkJobDefinitionId } - } - elseif ($SparkJobDefinitionName) { + } elseif ($SparkJobDefinitionName) { $SparkJobDefinitions | Where-Object { $_.DisplayName -eq $SparkJobDefinitionName } - } - else { + } else { # Return all SparkJobDefinitions if no filter is provided Write-Message -Message "No filter provided. Returning all SparkJobDefinitions." -Level Debug $SparkJobDefinitions @@ -141,16 +137,14 @@ function Get-FabricSparkJobDefinition { if ($SparkJobDefinition) { Write-Message -Message "Spark Job Definition found in the Workspace '$WorkspaceId'." -Level Debug return $SparkJobDefinition - } - else { + } else { Write-Message -Message "No Spark Job Definition found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve SparkJobDefinition. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 index b1db8a91..11b2ea17 100644 --- a/source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 +++ b/source/Public/Spark Job Definition/Get-FabricSparkJobDefinitionDefinition.ps1 @@ -53,11 +53,11 @@ function Get-FabricSparkJobDefinitionDefinition { # Step 3: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId - + if ($SparkJobDefinitionFormat) { $apiEndpointUrl = "{0}?format={1}" -f $apiEndpointUrl, $SparkJobDefinitionFormat } - + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 4: Make the API request @@ -81,30 +81,29 @@ function Get-FabricSparkJobDefinitionDefinition { [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult.definition.parts - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -114,11 +113,10 @@ function Get-FabricSparkJobDefinitionDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 9: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Spark Job Definition. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 index 99d992ef..2c9e779d 100644 --- a/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 @@ -3,7 +3,7 @@ Creates a new SparkJobDefinition in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new SparkJobDefinition + This function sends a POST request to the Microsoft Fabric API to create a new SparkJobDefinition in the specified workspace. It supports optional parameters for SparkJobDefinition description and path definitions. .PARAMETER WorkspaceId @@ -50,7 +50,7 @@ function New-FabricSparkJobDefinition { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionPathPlatformDefinition @@ -91,8 +91,7 @@ function New-FabricSparkJobDefinition { payload = $SparkJobDefinitionEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error return $null } @@ -116,8 +115,7 @@ function New-FabricSparkJobDefinition { payload = $SparkJobDefinitionEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -138,9 +136,9 @@ function New-FabricSparkJobDefinition { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + Write-Message -Message "Response Code: $statusCode" -Level Debug - + # Step 5: Handle and log the response switch ($statusCode) { 201 { @@ -149,33 +147,32 @@ function New-FabricSparkJobDefinition { } 202 { Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -185,10 +182,9 @@ function New-FabricSparkJobDefinition { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Spark Job Definition. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 index 68f8e184..17a14a0f 100644 --- a/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 @@ -3,7 +3,7 @@ Removes an SparkJobDefinition from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove an SparkJobDefinition + This function sends a DELETE request to the Microsoft Fabric API to remove an SparkJobDefinition from the specified workspace using the provided WorkspaceId and SparkJobDefinitionId. .PARAMETER WorkspaceId @@ -52,8 +52,8 @@ function Remove-FabricSparkJobDefinition { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - - # Step 4: Handle response + + # Step 4: Handle response if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error @@ -62,11 +62,10 @@ function Remove-FabricSparkJobDefinition { return $null } - Write-Message -Message "Spark Job Definition '$SparkJobDefinitionId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } - catch { + Write-Message -Message "Spark Job Definition '$SparkJobDefinitionId' deleted successfully from workspace '$WorkspaceId'." -Level Info + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SparkJobDefinition '$SparkJobDefinitionId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 b/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 index 43bb3dac..83683a1c 100644 --- a/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 +++ b/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 @@ -3,7 +3,7 @@ Starts a Fabric Spark Job Definition on demand. .DESCRIPTION - This function initiates a Spark Job Definition on demand within a specified workspace. + This function initiates a Spark Job Definition on demand within a specified workspace. It constructs the appropriate API endpoint URL and makes a POST request to start the job. The function can optionally wait for the job to complete based on the 'waitForCompletion' parameter. @@ -52,7 +52,7 @@ function Start-FabricSparkJobDefinitionOnDemand { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/jobs/instances?jobType={3}" -f $FabricConfig.BaseUrl, $WorkspaceId , $SparkJobDefinitionId, $JobType Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -68,7 +68,7 @@ function Start-FabricSparkJobDefinitionOnDemand { -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - Write-Message -Message "Response Code: $statusCode" -Level Debug + Write-Message -Message "Response Code: $statusCode" -Level Debug # Step 5: Handle and log the response switch ($statusCode) { 201 { @@ -79,22 +79,21 @@ function Start-FabricSparkJobDefinitionOnDemand { Write-Message -Message "Spark Job Definition on demand accepted and is now running in the background. Job execution is in progress." -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - + if ($waitForCompletion -eq $true) { - Write-Message -Message "Getting Long Running Operation status" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug return $operationStatus - } - else { + } else { Write-Message -Message "The operation is running asynchronously." -Level Info Write-Message -Message "Use the returned details to check the operation status." -Level Info - Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info + Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info $operationDetails = [PSCustomObject]@{ OperationId = $operationId Location = $location @@ -109,10 +108,9 @@ function Start-FabricSparkJobDefinitionOnDemand { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to start Spark Job Definition on demand. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 index 9b2c641a..e6f8e476 100644 --- a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 @@ -3,7 +3,7 @@ Updates an existing SparkJobDefinition in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing SparkJobDefinition + This function sends a PATCH request to the Microsoft Fabric API to update an existing SparkJobDefinition in the specified workspace. It supports optional parameters for SparkJobDefinition description. .PARAMETER WorkspaceId @@ -33,8 +33,8 @@ function Update-FabricSparkJobDefinition { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionId, @@ -95,10 +95,9 @@ function Update-FabricSparkJobDefinition { # Step 6: Handle results Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SparkJobDefinition. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 index cca084d7..ef7c2651 100644 --- a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 +++ b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 @@ -3,7 +3,7 @@ Updates the definition of an existing SparkJobDefinition in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SparkJobDefinition + This function sends a PATCH request to the Microsoft Fabric API to update the definition of an existing SparkJobDefinition in the specified workspace. It supports optional parameters for SparkJobDefinition definition and platform-specific definition. .PARAMETER WorkspaceId @@ -42,7 +42,7 @@ function Update-FabricSparkJobDefinitionDefinition { [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionPathDefinition, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionPathPlatformDefinition @@ -57,8 +57,8 @@ function Update-FabricSparkJobDefinitionDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId #if ($UpdateMetadata -eq $true) { - if($SparkJobDefinitionPathPlatformDefinition){ - $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl + if ($SparkJobDefinitionPathPlatformDefinition) { + $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -67,12 +67,12 @@ function Update-FabricSparkJobDefinitionDefinition { definition = @{ format = "SparkJobDefinitionV1" parts = @() - } + } } - + if ($SparkJobDefinitionPathDefinition) { $SparkJobDefinitionEncodedContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathDefinition - + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) { # Add new part to the parts array $body.definition.parts += @{ @@ -80,8 +80,7 @@ function Update-FabricSparkJobDefinitionDefinition { payload = $SparkJobDefinitionEncodedContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error return $null } @@ -96,8 +95,7 @@ function Update-FabricSparkJobDefinitionDefinition { payload = $SparkJobDefinitionEncodedPlatformContent payloadType = "InlineBase64" } - } - else { + } else { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -116,7 +114,7 @@ function Update-FabricSparkJobDefinitionDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 5: Handle and log the response switch ($statusCode) { 200 { @@ -125,44 +123,42 @@ function Update-FabricSparkJobDefinitionDefinition { } 202 { Write-Message -Message "Update definition for Spark Job Definition '$SparkJobDefinitionId' accepted. Operation in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } - } + } + } default { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Spark Job Definition. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark/Get-FabricSparkCustomPool.ps1 b/source/Public/Spark/Get-FabricSparkCustomPool.ps1 index 6bf0441e..b2e1d221 100644 --- a/source/Public/Spark/Get-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/Get-FabricSparkCustomPool.ps1 @@ -66,27 +66,27 @@ function Get-FabricSparkCustomPool { # Step 3: Initialize variables $continuationToken = $null $SparkCustomPools = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/spark/pools" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -96,7 +96,7 @@ function Get-FabricSparkCustomPool { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -105,38 +105,34 @@ function Get-FabricSparkCustomPool { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $SparkCustomPools += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 8: Filter results based on provided parameters $SparkCustomPool = if ($SparkCustomPoolId) { $SparkCustomPools | Where-Object { $_.id -eq $SparkCustomPoolId } - } - elseif ($SparkCustomPoolName) { + } elseif ($SparkCustomPoolName) { $SparkCustomPools | Where-Object { $_.name -eq $SparkCustomPoolName } - } - else { + } else { # Return all SparkCustomPools if no filter is provided Write-Message -Message "No filter provided. Returning all SparkCustomPools." -Level Debug $SparkCustomPools @@ -146,16 +142,14 @@ function Get-FabricSparkCustomPool { if ($SparkCustomPool) { Write-Message -Message "SparkCustomPool found matching the specified criteria." -Level Debug return $SparkCustomPool - } - else { + } else { Write-Message -Message "No SparkCustomPool found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve SparkCustomPool. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Spark/Get-FabricSparkSettings.ps1 b/source/Public/Spark/Get-FabricSparkSettings.ps1 index 11c5c51f..6b094ec0 100644 --- a/source/Public/Spark/Get-FabricSparkSettings.ps1 +++ b/source/Public/Spark/Get-FabricSparkSettings.ps1 @@ -18,7 +18,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Get-FabricSparkSettings { [CmdletBinding()] @@ -37,26 +37,26 @@ function Get-FabricSparkSettings { # Step 3: Initialize variables $continuationToken = $null $SparkSettings = @() - + if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/spark/settings" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -66,7 +66,7 @@ function Get-FabricSparkSettings { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -75,45 +75,41 @@ function Get-FabricSparkSettings { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $SparkSettings += $response - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } } while ($null -ne $continuationToken) Write-Message -Message "Loop finished and all data added to the list" -Level Debug - + # Step 9: Handle results if ($SparkSettings) { Write-Message -Message " Returning all Spark Settings." -Level Debug - # Return all Spark Settings + # Return all Spark Settings return $SparkSettings - } - else { + } else { Write-Message -Message "No SparkSettings found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve SparkSettings. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Spark/New-FabricSparkCustomPool.ps1 b/source/Public/Spark/New-FabricSparkCustomPool.ps1 index 848677ce..75365fd0 100644 --- a/source/Public/Spark/New-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/New-FabricSparkCustomPool.ps1 @@ -3,7 +3,7 @@ Creates a new Spark custom pool in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new Spark custom pool + This function sends a POST request to the Microsoft Fabric API to create a new Spark custom pool in the specified workspace. It supports various parameters for Spark custom pool configuration. .PARAMETER WorkspaceId @@ -45,7 +45,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function New-FabricSparkCustomPool { @@ -64,7 +64,7 @@ function New-FabricSparkCustomPool { [ValidateNotNullOrEmpty()] [ValidateSet('MemoryOptimized')] [string]$NodeFamily, - + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidateSet('Large', 'Medium', 'Small', 'XLarge', 'XXLarge')] @@ -145,33 +145,32 @@ function New-FabricSparkCustomPool { } 202 { Write-Message -Message "SparkCustomPool '$SparkCustomPoolName' creation accepted. Provisioning in progress!" -Level Info - + [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] + [string]$retryAfter = $responseHeader["Retry-After"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug Write-Message -Message "Getting Long Running Operation status" -Level Debug - + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug - + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId, -location $location Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - + return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus - } + } } default { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -181,10 +180,9 @@ function New-FabricSparkCustomPool { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create SparkCustomPool. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 index 2b748e65..b1e546d3 100644 --- a/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 @@ -3,7 +3,7 @@ Removes a Spark custom pool from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove a Spark custom pool + This function sends a DELETE request to the Microsoft Fabric API to remove a Spark custom pool from the specified workspace using the provided WorkspaceId and SparkCustomPoolId. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Remove-FabricSparkCustomPool { [CmdletBinding()] @@ -62,11 +62,10 @@ function Remove-FabricSparkCustomPool { return $null } Write-Message -Message "Spark Custom Pool '$SparkCustomPoolId' deleted successfully from workspace '$WorkspaceId'." -Level Info - - } - catch { + + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SparkCustomPool '$SparkCustomPoolId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark/Update-FabricSparkCustomPool.ps1 b/source/Public/Spark/Update-FabricSparkCustomPool.ps1 index 9e4bcfd5..d42419f5 100644 --- a/source/Public/Spark/Update-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/Update-FabricSparkCustomPool.ps1 @@ -3,7 +3,7 @@ Updates an existing Spark custom pool in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool + This function sends a PATCH request to the Microsoft Fabric API to update an existing Spark custom pool in the specified workspace. It supports various parameters for Spark custom pool configuration. .PARAMETER WorkspaceId @@ -48,15 +48,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricSparkCustomPool { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SparkCustomPoolId, @@ -70,7 +70,7 @@ function Update-FabricSparkCustomPool { [ValidateNotNullOrEmpty()] [ValidateSet('MemoryOptimized')] [string]$NodeFamily, - + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidateSet('Large', 'Medium', 'Small', 'XLarge', 'XXLarge')] @@ -113,14 +113,14 @@ function Update-FabricSparkCustomPool { # Step 3: Construct the request body $body = @{ - name = $InstancePoolName - nodeFamily = $NodeFamily - nodeSize = $NodeSize - autoScale = @{ - enabled = $AutoScaleEnabled - minNodeCount = $AutoScaleMinNodeCount - maxNodeCount = $AutoScaleMaxNodeCount - } + name = $InstancePoolName + nodeFamily = $NodeFamily + nodeSize = $NodeSize + autoScale = @{ + enabled = $AutoScaleEnabled + minNodeCount = $AutoScaleMinNodeCount + maxNodeCount = $AutoScaleMaxNodeCount + } dynamicExecutorAllocation = @{ enabled = $DynamicExecutorAllocationEnabled minExecutors = $DynamicExecutorAllocationMinExecutors @@ -155,10 +155,9 @@ function Update-FabricSparkCustomPool { # Step 6: Handle results Write-Message -Message "Spark Custom Pool '$SparkCustomPoolName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SparkCustomPool. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Spark/Update-FabricSparkSettings.ps1 b/source/Public/Spark/Update-FabricSparkSettings.ps1 index 261cd0e6..a00c4806 100644 --- a/source/Public/Spark/Update-FabricSparkSettings.ps1 +++ b/source/Public/Spark/Update-FabricSparkSettings.ps1 @@ -136,7 +136,7 @@ function Update-FabricSparkSettings { # Step 3: Construct the request body # Construct the request body with optional properties - $body = @{} + $body = @{ } if ($PSBoundParameters.ContainsKey('automaticLogEnabled')) { $body.automaticLog = @{ @@ -157,12 +157,12 @@ function Update-FabricSparkSettings { } if ($PSBoundParameters.ContainsKey('defaultPoolName') -or $PSBoundParameters.ContainsKey('defaultPoolType')) { if ($PSBoundParameters.ContainsKey('defaultPoolName') -and $PSBoundParameters.ContainsKey('defaultPoolType')) { - $body.pool = @{ - defaultPool = @{ - name = $defaultPoolName - type = $defaultPoolType + $body.pool = @{ + defaultPool = @{ + name = $defaultPoolName + type = $defaultPoolType + } } - } } else { Write-Message -Message "Both 'defaultPoolName' and 'defaultPoolType' must be provided together." -Level Error throw @@ -207,10 +207,9 @@ function Update-FabricSparkSettings { # Step 6: Handle results Write-Message -Message "Spark Custom Pool '$SparkSettingsName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SparkSettings. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 index e36f1f3a..d4d50c61 100644 --- a/source/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Get-FabricCapacityTenantSettingOverrides.ps1 @@ -52,19 +52,17 @@ function Get-FabricCapacityTenantSettingOverrides { $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Get + -Method Get # Step 4: Check if any capacity tenant setting overrides were retrieved and handle results accordingly if ($response) { Write-Message -Message $message -Level Debug return $response - } - else { + } else { Write-Message -Message "No capacity tenant setting overrides found." -Level Warning return $null } - } - catch { + } catch { # Step 5: Log detailed error information if the API request fails $errorDetails = $_.Exception.Message Write-Message -Message "Error retrieving capacity tenant setting overrides: $errorDetails" -Level Error diff --git a/source/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 b/source/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 index 22ce3d3a..c5fb76b1 100644 --- a/source/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Get-FabricDomainTenantSettingOverrides.ps1 @@ -35,19 +35,17 @@ function Get-FabricDomainTenantSettingOverrides { $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Get + -Method Get # Step 4: Check if any domain tenant setting overrides were retrieved and handle results accordingly if ($response) { Write-Message -Message "Successfully retrieved domain tenant setting overrides." -Level Debug return $response - } - else { + } else { Write-Message -Message "No domain tenant setting overrides found." -Level Warning return $null } - } - catch { + } catch { # Step 5: Log detailed error information if the API request fails $errorDetails = $_.Exception.Message Write-Message -Message "Error retrieving domain tenant setting overrides: $errorDetails" -Level Error diff --git a/source/Public/Tenant/Get-FabricTenantSetting.ps1 b/source/Public/Tenant/Get-FabricTenantSetting.ps1 index 655e1c9c..7a83a33f 100644 --- a/source/Public/Tenant/Get-FabricTenantSetting.ps1 +++ b/source/Public/Tenant/Get-FabricTenantSetting.ps1 @@ -22,7 +22,7 @@ Returns the tenant setting with the title "SomeSetting". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Is-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> @@ -48,14 +48,13 @@ function Get-FabricTenantSetting { $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Get + -Method Get # Step 4: Filter tenant settings based on the provided SettingTitle parameter (if specified) $settings = if ($SettingTitle) { Write-Message -Message "Filtering tenant settings by title: '$SettingTitle'" -Level Debug $response.tenantSettings | Where-Object { $_.title -eq $SettingTitle } - } - else { + } else { Write-Message -Message "No filter specified. Retrieving all tenant settings." -Level Debug $response.tenantSettings } @@ -64,13 +63,11 @@ function Get-FabricTenantSetting { if ($settings) { Write-Message -Message "Tenant settings successfully retrieved." -Level Debug return $settings - } - else { + } else { Write-Message -Message "No tenant settings found matching the specified criteria." -Level Warning return $null } - } - catch { + } catch { # Step 6: Log detailed error information if the API request fails $errorDetails = $_.Exception.Message Write-Message -Message "Error retrieving tenant settings: $errorDetails" -Level Error diff --git a/source/Public/Tenant/Get-FabricTenantSettings.ps1 b/source/Public/Tenant/Get-FabricTenantSettings.ps1 index 51f88e64..b3372aad 100644 --- a/source/Public/Tenant/Get-FabricTenantSettings.ps1 +++ b/source/Public/Tenant/Get-FabricTenantSettings.ps1 @@ -15,7 +15,7 @@ Retrieves the tenant settings from the Fabric API. #> -function Get-FabricTenantSettings { +function Get-FabricTenantSettings { [Alias("Get-FabTenantSettings")] Param () diff --git a/source/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 b/source/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 index c04e8ed1..9ff733b1 100644 --- a/source/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Get-FabricWorkspaceTenantSettingOverrides.ps1 @@ -34,19 +34,17 @@ function Get-FabricWorkspaceTenantSettingOverrides { $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Get + -Method Get # Step 4: Check if any workspaces tenant setting overrides were retrieved and handle results accordingly if ($response) { Write-Message -Message "Successfully retrieved workspaces tenant setting overrides." -Level Debug return $response - } - else { + } else { Write-Message -Message "No workspaces tenant setting overrides found." -Level Warning return $null } - } - catch { + } catch { # Step 5: Log detailed error information if the API request fails $errorDetails = $_.Exception.Message Write-Message -Message "Error retrieving workspaces tenant setting overrides: $errorDetails" -Level Error diff --git a/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 index 7665ddea..8c41dfc7 100644 --- a/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 @@ -47,12 +47,11 @@ function Revoke-FabricCapacityTenantSettingOverrides { $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` - -Method Delete + -Method Delete Write-Message -Message "Successfully removed the tenant setting override '$tenantSettingName' from the capacity with ID '$capacityId'." -Level Info return $response - } - catch { + } catch { # Step 5: Log detailed error information if the API request fails $errorDetails = $_.Exception.Message Write-Message -Message "Error retrieving capacity tenant setting overrides: $errorDetails" -Level Error diff --git a/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 index f352b771..8fcf7d3e 100644 --- a/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 @@ -59,7 +59,7 @@ function Update-FabricCapacityTenantSettingOverrides { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$DelegateToWorkspace, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.Object]$EnabledSecurityGroups, @@ -123,14 +123,12 @@ function Update-FabricCapacityTenantSettingOverrides { -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Post ` - -Body $bodyJson + -Body $bodyJson Write-Message -Message "Successfully updated capacity tenant setting overrides for CapacityId: $CapacityId and SettingTitle: $SettingTitle." -Level Info return $response - } - catch { + } catch { $errorDetails = $_.Exception.Message Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error } -} - +} \ No newline at end of file diff --git a/source/Public/Tenant/Update-FabricTenantSetting.ps1 b/source/Public/Tenant/Update-FabricTenantSetting.ps1 index 0a701151..4e086ba8 100644 --- a/source/Public/Tenant/Update-FabricTenantSetting.ps1 +++ b/source/Public/Tenant/Update-FabricTenantSetting.ps1 @@ -63,7 +63,7 @@ function Update-FabricCapacityTenantSettingOverrides { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [bool]$DelegateToWorkspace, - + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.Object]$EnabledSecurityGroups, @@ -151,14 +151,12 @@ function Update-FabricCapacityTenantSettingOverrides { -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Post ` - -Body $bodyJson + -Body $bodyJson Write-Message -Message "Successfully updated tenant setting." -Level Info return $response - } - catch { + } catch { $errorDetails = $_.Exception.Message Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error } -} - +} \ No newline at end of file diff --git a/source/Public/Users/Get-FabricUserListAccessEntities.ps1 b/source/Public/Users/Get-FabricUserListAccessEntities.ps1 index 8d15cdaa..c39956ec 100644 --- a/source/Public/Users/Get-FabricUserListAccessEntities.ps1 +++ b/source/Public/Users/Get-FabricUserListAccessEntities.ps1 @@ -40,11 +40,11 @@ function Get-FabricUserListAccessEntities { ) try { - + Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 4: Loop to retrieve all capacities with continuation token $apiEndpointURI = "{0}admin/users/{1}/access" -f $FabricConfig.BaseUrl, $UserId @@ -58,11 +58,10 @@ function Get-FabricUserListAccessEntities { -Method Get return $response - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Warehouse. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Utils/Convert-FromBase64.ps1 b/source/Public/Utils/Convert-FromBase64.ps1 index 281a2100..7d2de3c9 100644 --- a/source/Public/Utils/Convert-FromBase64.ps1 +++ b/source/Public/Utils/Convert-FromBase64.ps1 @@ -1,5 +1,5 @@ function Convert-FromBase64 { -<# + <# .SYNOPSIS Decodes a Base64-encoded string into its original text representation. @@ -26,7 +26,7 @@ function Convert-FromBase64 { .NOTES This function assumes the Base64 input is a valid UTF-8 encoded string. Any decoding errors will throw a descriptive error message. -#> + #> param ( [Parameter(Mandatory = $true)] [string]$Base64String @@ -41,11 +41,10 @@ Any decoding errors will throw a descriptive error message. # Step 3: Return the decoded string return $decodedString - } - catch { + } catch { # Step 4: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "An error occurred while decoding from Base64: $errorDetails" -Level Error throw "An error occurred while decoding from Base64: $_" } -} +} \ No newline at end of file diff --git a/source/Public/Utils/Convert-ToBase64.ps1 b/source/Public/Utils/Convert-ToBase64.ps1 index 29e1fab0..5118aa91 100644 --- a/source/Public/Utils/Convert-ToBase64.ps1 +++ b/source/Public/Utils/Convert-ToBase64.ps1 @@ -1,5 +1,5 @@ function Convert-ToBase64 { -<# + <# .SYNOPSIS Encodes the content of a file into a Base64-encoded string. @@ -29,7 +29,7 @@ function Convert-ToBase64 { Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -50,11 +50,10 @@ function Convert-ToBase64 { # Step 3: Return the encoded string Write-Message -Message "Return the encoded string for the file: $filePath" -Level Debug return $base64String - } - catch { + } catch { # Step 4: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "An error occurred while encoding to Base64: $errorDetails" -Level Error throw "An error occurred while encoding to Base64: $_" } -} +} \ No newline at end of file diff --git a/source/Public/Utils/Get-FabricLongRunningOperation.ps1 b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 index e2637fd1..1238955f 100644 --- a/source/Public/Utils/Get-FabricLongRunningOperation.ps1 +++ b/source/Public/Utils/Get-FabricLongRunningOperation.ps1 @@ -1,5 +1,5 @@ function Get-FabricLongRunningOperation { -<# + <# .SYNOPSIS Monitors the status of a long-running operation in Microsoft Fabric. @@ -26,7 +26,7 @@ This command polls the status of the operation with the given operationId every AUTHOR Tiago Balabuch -#> + #> param ( [Parameter(Mandatory = $false)] [string]$operationId, @@ -42,8 +42,7 @@ This command polls the status of the operation with the given operationId every if ($location) { # Use the Location header to define the operationUrl $apiEndpointUrl = $location - } - else { + } else { $apiEndpointUrl = "https://api.fabric.microsoft.com/v1/operations/{0}" -f $operationId } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -54,8 +53,7 @@ This command polls the status of the operation with the given operationId every # Step 2: Wait before the next request if ($retryAfter) { Start-Sleep -Seconds $retryAfter - } - else { + } else { Start-Sleep -Seconds 5 # Default retry interval if no Retry-After header } @@ -80,11 +78,10 @@ This command polls the status of the operation with the given operationId every # Step 5: Return the operation result return $operation - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "An error occurred while checking the operation: $errorDetails" -Level Error throw } -} +} \ No newline at end of file diff --git a/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 b/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 index 98292203..7e9e81c8 100644 --- a/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 +++ b/source/Public/Utils/Get-FabricLongRunningOperationResult.ps1 @@ -1,5 +1,5 @@ function Get-FabricLongRunningOperationResult { -<# + <# .SYNOPSIS Retrieves the result of a completed long-running operation from the Microsoft Fabric API. @@ -21,7 +21,7 @@ This command fetches the result of the operation with the specified operationId. AUTHOR Tiago Balabuch -#> + #> param ( [Parameter(Mandatory = $true)] [string]$operationId @@ -34,13 +34,13 @@ This command fetches the result of the operation with the specified operationId. try { # Step 2: Make the API request $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Get ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Get ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" # Step 3: Return the result @@ -56,11 +56,10 @@ This command fetches the result of the operation with the specified operationId. } return $response - } - catch { + } catch { # Step 3: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "An error occurred while returning the operation result: $errorDetails" -Level Error throw } -} +} \ No newline at end of file diff --git a/source/Public/Utils/Invoke-FabricAPIRequest.ps1 b/source/Public/Utils/Invoke-FabricAPIRequest.ps1 index f37590ad..1f855ebc 100644 --- a/source/Public/Utils/Invoke-FabricAPIRequest.ps1 +++ b/source/Public/Utils/Invoke-FabricAPIRequest.ps1 @@ -1,6 +1,6 @@ function Invoke-FabricAPIRequest_duplicate { -<# + <# .SYNOPSIS Sends an HTTP request to a Fabric API endpoint and retrieves the response. Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response @@ -46,7 +46,7 @@ function Invoke-FabricAPIRequest_duplicate { This function requires the Get-FabricAuthToken function to be defined in the same script or module. This function was originally written by Rui Romano. https://github.com/RuiRomano/fabricps-pbip -#> + #> param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -82,8 +82,7 @@ function Invoke-FabricAPIRequest_duplicate { if ($BaseURI -like "*`?*") { # URI already has parameters, append with & $apiEndpointURI = "$BaseURI&continuationToken=$encodedToken" - } - else { + } else { # No existing parameters, append with ? $apiEndpointURI = "$BaseURI?continuationToken=$encodedToken" } @@ -115,16 +114,13 @@ function Invoke-FabricAPIRequest_duplicate { if ($response) { if ($response.PSObject.Properties.Name -contains 'value') { $results += $response.value - } - elseif ($response.PSObject.Properties.Name -contains 'accessEntities') { + } elseif ($response.PSObject.Properties.Name -contains 'accessEntities') { $results += $response.accessEntities - } - else { + } else { $results += $response } $continuationToken = $response.PSObject.Properties.Match("continuationToken") ? $response.continuationToken : $null - } - else { + } else { Write-Message -Message "No data in response" -Level Debug $continuationToken = $null } @@ -153,8 +149,7 @@ function Invoke-FabricAPIRequest_duplicate { $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } @@ -178,4 +173,4 @@ function Invoke-FabricAPIRequest_duplicate { } while ($null -ne $continuationToken) return $results -} +} \ No newline at end of file diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 index 76b674e8..f9e19d8f 100644 --- a/source/Public/Utils/Set-FabricApiHeaders.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -1,5 +1,5 @@ function Set-FabricApiHeaders { -<# + <# .SYNOPSIS Sets the Fabric API headers with a valid token for the specified Azure tenant. @@ -36,7 +36,7 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the AUTHOR Tiago Balabuch -#> + #> [CmdletBinding()] param ( @@ -81,9 +81,9 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the Write-Message -Message "Logging in using the current user" -Level Debug Write-Message -Message "Logging in using the current user" -Level Info Connect-AzAccount -Tenant $TenantId -ErrorAction Stop | Out-Null - } + } - ## Step 4: Retrieve the access token for the Fabric API + ## Step 4: Retrieve the access token for the Fabric API Write-Message -Message "Retrieve the access token for the Fabric API: $TenantId" -Level Debug $fabricToken = Get-AzAccessToken -AsSecureString -ResourceUrl $FabricConfig.ResourceUrl -ErrorAction Stop -WarningAction SilentlyContinue @@ -106,11 +106,10 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the $FabricConfig.TenantIdGlobal = $TenantId Write-Message -Message "Fabric token successfully configured." -Level Info - } - catch { + } catch { # Step 8: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to set Fabric token: $errorDetails" -Level Error throw "Unable to configure Fabric token. Ensure tenant and API configurations are correct." } -} +} \ No newline at end of file diff --git a/source/Public/Utils/Test-FabricApiResponse.ps1 b/source/Public/Utils/Test-FabricApiResponse.ps1 index 5b19c577..34878a11 100644 --- a/source/Public/Utils/Test-FabricApiResponse.ps1 +++ b/source/Public/Utils/Test-FabricApiResponse.ps1 @@ -1,5 +1,5 @@ function Test-FabricApiResponse { -<# + <# .SYNOPSIS Tests the response from a Fabric API call and handles long-running operations. .DESCRIPTION @@ -29,57 +29,56 @@ Handles the response from a Fabric API call with a 202 status code, indicating t - This function is designed to be used within the context of a Fabric API client. - It requires the `Write-Message` function to log messages at different levels (Info, Debug, Error). - The function handles long-running operations by checking the status of the operation and retrieving the result if it has succeeded. -#> + #> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - $statusCode, - [Parameter(Mandatory = $false)] - $response, - [Parameter(Mandatory = $false)] - $responseHeader, - [Parameter(Mandatory = $false)] - $Name, - [Parameter(Mandatory = $false)] - $typeName = 'Fabric Item' - ) + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + $statusCode, + [Parameter(Mandatory = $false)] + $response, + [Parameter(Mandatory = $false)] + $responseHeader, + [Parameter(Mandatory = $false)] + $Name, + [Parameter(Mandatory = $false)] + $typeName = 'Fabric Item' + ) - switch ($statusCode) { - 201 { - Write-Message -Message "$typeName '$Name' created successfully!" -Level Info - return $response - } - 202 { - Write-Message -Message "$typeName '$Name' creation accepted. Provisioning in progress!" -Level Info + switch ($statusCode) { + 201 { + Write-Message -Message "$typeName '$Name' created successfully!" -Level Info + return $response + } + 202 { + Write-Message -Message "$typeName '$Name' creation accepted. Provisioning in progress!" -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Getting Long Running Operation status" -Level Debug + [string]$operationId = $responseHeader["x-ms-operation-id"] + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Getting Long Running Operation status" -Level Debug - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - # Handle operation result - if ($operationStatus.status -eq "Succeeded") { - Write-Message -Message "Operation Succeeded" -Level Debug - Write-Message -Message "Getting Long Running Operation result" -Level Debug + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + # Handle operation result + if ($operationStatus.status -eq "Succeeded") { + Write-Message -Message "Operation Succeeded" -Level Debug + Write-Message -Message "Getting Long Running Operation result" -Level Debug - $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId - Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug + $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId + Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug - return $operationResult + return $operationResult + } else { + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug + Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error + return $operationStatus + } } - else { - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug - Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error - return $operationStatus + default { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." } } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } - } -} +} \ No newline at end of file diff --git a/source/Public/Warehouse/Get-FabricWarehouse.ps1 b/source/Public/Warehouse/Get-FabricWarehouse.ps1 index 304ede6f..48447f37 100644 --- a/source/Public/Warehouse/Get-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/Get-FabricWarehouse.ps1 @@ -58,25 +58,23 @@ function Get-FabricWarehouse { Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug # Step 3: Initialize variables - + # Step 4: Loop to retrieve all capacities with continuation token $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId - - $Warehouses = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get ` - -Body $null + + $Warehouses = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -Method Get ` + -Body $null # Step 8: Filter results based on provided parameters $Warehouse = if ($WarehouseId) { $Warehouses | Where-Object { $_.Id -eq $WarehouseId } - } - elseif ($WarehouseName) { + } elseif ($WarehouseName) { $Warehouses | Where-Object { $_.DisplayName -eq $WarehouseName } - } - else { + } else { # Return all Warehouses if no filter is provided Write-Message -Message "No filter provided. Returning all Warehouses." -Level Debug $Warehouses @@ -86,16 +84,14 @@ function Get-FabricWarehouse { if ($Warehouse) { Write-Message -Message "Warehouse found matching the specified criteria." -Level Debug return $Warehouse - } - else { + } else { Write-Message -Message "No Warehouse found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Warehouse. Error: $errorDetails" -Level Error - } - -} + } + +} \ No newline at end of file diff --git a/source/Public/Warehouse/New-FabricWarehouse.ps1 b/source/Public/Warehouse/New-FabricWarehouse.ps1 index 2b8a49af..ab1d0462 100644 --- a/source/Public/Warehouse/New-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/New-FabricWarehouse.ps1 @@ -3,7 +3,7 @@ Creates a new warehouse in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a POST request to the Microsoft Fabric API to create a new warehouse + This function sends a POST request to the Microsoft Fabric API to create a new warehouse in the specified workspace. It supports optional parameters for warehouse description. .PARAMETER WorkspaceId @@ -71,13 +71,12 @@ function New-FabricWarehouse { -Method Post ` -Body $bodyJson - Write-Message -Message "Data Warehouse created successfully!" -Level Info + Write-Message -Message "Data Warehouse created successfully!" -Level Info return $response - - } - catch { + + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Warehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Warehouse/Remove-FabricWarehouse.ps1 b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 index 42b245f0..c9f2b191 100644 --- a/source/Public/Warehouse/Remove-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 @@ -3,7 +3,7 @@ Removes a warehouse from a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a DELETE request to the Microsoft Fabric API to remove a warehouse + This function sends a DELETE request to the Microsoft Fabric API to remove a warehouse from the specified workspace using the provided WorkspaceId and WarehouseId. .PARAMETER WorkspaceId @@ -48,14 +48,13 @@ function Remove-FabricWarehouse { -Headers $FabricConfig.FabricHeaders ` -BaseURI $apiEndpointURI ` -Method Delete ` - + Write-Message -Message "Warehouse '$WarehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info return $response - } - catch { + } catch { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Warehouse '$WarehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Warehouse/Update-FabricWarehouse.ps1 b/source/Public/Warehouse/Update-FabricWarehouse.ps1 index fd6e2856..18e8554b 100644 --- a/source/Public/Warehouse/Update-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/Update-FabricWarehouse.ps1 @@ -3,7 +3,7 @@ Updates an existing warehouse in a specified Microsoft Fabric workspace. .DESCRIPTION - This function sends a PATCH request to the Microsoft Fabric API to update an existing warehouse + This function sends a PATCH request to the Microsoft Fabric API to update an existing warehouse in the specified workspace. It supports optional parameters for warehouse description. .PARAMETER WorkspaceId @@ -27,15 +27,15 @@ - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch - + #> function Update-FabricWarehouse { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$WorkspaceId, - + [string]$WorkspaceId, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$WarehouseId, @@ -72,20 +72,19 @@ function Update-FabricWarehouse { # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - + $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Patch ` - -Body $bodyJson + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -Method Patch ` + -Body $bodyJson # Step 6: Handle results Write-Message -Message "Warehouse '$WarehouseName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Warehouse. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 index d2bf942b..1dba6e27 100644 --- a/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 +++ b/source/Public/Workspace/Add-FabricWorkspaceIdentity.ps1 @@ -39,7 +39,7 @@ function Add-FabricWorkspaceIdentity { Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Make the API request - $response = Invoke-RestMethod ` + $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` -Uri $apiEndpointUrl ` -Method Post ` @@ -79,8 +79,7 @@ function Add-FabricWorkspaceIdentity { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -92,10 +91,9 @@ function Add-FabricWorkspaceIdentity { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 5: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to provision workspace identity. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 index 4d29053b..ec08ae69 100644 --- a/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 @@ -26,7 +26,7 @@ Assigns the Admin role to the user with ID "principal123" in the workspace "work - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Add-FabricWorkspaceRoleAssignment { @@ -93,7 +93,7 @@ function Add-FabricWorkspaceRoleAssignment { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 6: Handle empty response if (-not $response) { Write-Message -Message "No data returned from the API." -Level Warning @@ -102,11 +102,10 @@ function Add-FabricWorkspaceRoleAssignment { Write-Message -Message "Role '$WorkspaceRole' assigned to principal '$PrincipalId' successfully in workspace '$WorkspaceId'." -Level Info return $response - - } - catch { + + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign role. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 index 0754a94b..fe657b62 100644 --- a/source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 +++ b/source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 @@ -20,7 +20,7 @@ Assigns the workspace with ID "workspace123" to the capacity "capacity456". - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Assign-FabricWorkspaceCapacity { @@ -74,10 +74,9 @@ function Assign-FabricWorkspaceCapacity { return $null } Write-Message -Message "Successfully assigned workspace with ID '$WorkspaceId' to capacity with ID '$CapacityId'." -Level Info - } - catch { + } catch { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign workspace with ID '$WorkspaceId' to capacity with ID '$CapacityId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspace.ps1 b/source/Public/Workspace/Get-FabricWorkspace.ps1 index 77797006..1f0b91cc 100644 --- a/source/Public/Workspace/Get-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspace.ps1 @@ -26,7 +26,7 @@ Fetches details of the workspace with the name "MyWorkspace". - Calls `Test-TokenExpired` to ensure token validity before making the API request. - Returns the matching workspace details or all workspaces if no filter is provided. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Get-FabricWorkspace { @@ -61,7 +61,7 @@ function Get-FabricWorkspace { if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces" -f $FabricConfig.BaseUrl @@ -75,7 +75,7 @@ function Get-FabricWorkspace { $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -85,7 +85,7 @@ function Get-FabricWorkspace { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -94,24 +94,22 @@ function Get-FabricWorkspace { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $workspaces += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -121,27 +119,23 @@ function Get-FabricWorkspace { # Step 8: Filter results based on provided parameters $workspace = if ($WorkspaceId) { $workspaces | Where-Object { $_.Id -eq $WorkspaceId } - } - elseif ($WorkspaceName) { + } elseif ($WorkspaceName) { $workspaces | Where-Object { $_.DisplayName -eq $WorkspaceName } - } - else { + } else { # Return all workspaces if no filter is provided Write-Message -Message "No filter provided. Returning all workspaces." -Level Debug $workspaces } - + # Step 9: Handle results if ($workspace) { Write-Message -Message "Workspace found matching the specified criteria." -Level Debug return $workspace - } - else { + } else { Write-Message -Message "No workspace found matching the provided criteria." -Level Warning return $null } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve workspace. Error: $errorDetails" -Level Error diff --git a/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 b/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 index f566d601..76c040d8 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceDatasetRefreshes.ps1 @@ -1,5 +1,5 @@ function Get-FabricWorkspaceDatasetRefreshes { -<# + <# .SYNOPSIS Retrieves the refresh history of all datasets in a specified PowerBI workspace. @@ -23,14 +23,14 @@ function Get-FabricWorkspaceDatasetRefreshes { .NOTES Alias: Get-PowerBIWorkspaceDatasetRefreshes, Get-FabWorkspaceDatasetRefreshes -#> + #> -# Define a function to get the refresh history of all datasets in a PowerBI workspace + # Define a function to get the refresh history of all datasets in a PowerBI workspace # Set aliases for the function [Alias("Get-FabWorkspaceDatasetRefreshes")] param( # Define a mandatory parameter for the workspace ID - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string]$WorkspaceID ) @@ -50,4 +50,4 @@ function Get-FabricWorkspaceDatasetRefreshes { } # Return the refresh history array return $refs -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 index 02b2f585..bc139908 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 @@ -25,7 +25,7 @@ Fetches the role assignment with the ID "role123" for the workspace "workspace12 - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. -Author: Tiago Balabuch +Author: Tiago Balabuch #> function Get-FabricWorkspaceRoleAssignment { @@ -45,7 +45,7 @@ function Get-FabricWorkspaceRoleAssignment { Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired Write-Message -Message "Token validation completed." -Level Debug - + # Step 3: Initialize variables $continuationToken = $null $workspaceRoles = @() @@ -53,22 +53,22 @@ function Get-FabricWorkspaceRoleAssignment { if (-not ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq "System.Web" })) { Add-Type -AssemblyName System.Web } - + # Step 4: Loop to retrieve all capacities with continuation token Write-Message -Message "Loop started to get continuation token" -Level Debug $baseApiEndpointUrl = "{0}/workspaces/{1}/roleAssignments" -f $FabricConfig.BaseUrl, $WorkspaceId - + do { # Step 5: Construct the API URL $apiEndpointUrl = $baseApiEndpointUrl - + if ($null -ne $continuationToken) { # URL-encode the continuation token $encodedToken = [System.Web.HttpUtility]::UrlEncode($continuationToken) $apiEndpointUrl = "{0}?continuationToken={1}" -f $apiEndpointUrl, $encodedToken } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - + # Step 6: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -78,7 +78,7 @@ function Get-FabricWorkspaceRoleAssignment { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" - + # Step 7: Validate the response code if ($statusCode -ne 200) { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error @@ -87,24 +87,22 @@ function Get-FabricWorkspaceRoleAssignment { Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } - + # Step 8: Add data to the list if ($null -ne $response) { Write-Message -Message "Adding data to the list" -Level Debug $workspaceRoles += $response.value - + # Update the continuation token if present if ($response.PSObject.Properties.Match("continuationToken")) { Write-Message -Message "Updating the continuation token" -Level Debug $continuationToken = $response.continuationToken Write-Message -Message "Continuation token: $continuationToken" -Level Debug - } - else { + } else { Write-Message -Message "Updating the continuation token to null" -Level Debug $continuationToken = $null } - } - else { + } else { Write-Message -Message "No data received from the API." -Level Warning break } @@ -113,8 +111,7 @@ function Get-FabricWorkspaceRoleAssignment { # Step 8: Filter results based on provided parameters $roleAssignments = if ($WorkspaceRoleAssignmentId) { $workspaceRoles | Where-Object { $_.Id -eq $WorkspaceRoleAssignmentId } - } - else { + } else { $workspaceRoles } @@ -134,20 +131,17 @@ function Get-FabricWorkspaceRoleAssignment { } } return $results - } - else { + } else { if ($WorkspaceRoleAssignmentId) { Write-Message -Message "No role assignment found with ID '$WorkspaceRoleAssignmentId' for WorkspaceId '$WorkspaceId'." -Level Warning - } - else { + } else { Write-Message -Message "No role assignments found for WorkspaceId '$WorkspaceId'." -Level Warning } return @() } - } - catch { + } catch { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 b/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 index e404971c..422eeee4 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceUsageMetricsData.ps1 @@ -1,5 +1,5 @@ function Get-FabricWorkspaceUsageMetricsData { -<# + <# .SYNOPSIS Retrieves workspace usage metrics data. @@ -19,9 +19,9 @@ This example retrieves the workspace usage metrics for a specific workspace give .NOTES The function retrieves the PowerBI access token and creates a new usage metrics report. It then defines the names of the reports to retrieve, initializes an empty hashtable to store the reports, and for each report name, retrieves the report and adds it to the hashtable. It then returns the hashtable of reports. -#> + #> -# This function retrieves workspace usage metrics. + # This function retrieves workspace usage metrics. # Define aliases for the function for flexibility. [Alias("Get-FabWorkspaceUsageMetricsData")] @@ -40,21 +40,20 @@ The function retrieves the PowerBI access token and creates a new usage metrics $reportnames = @("'Workspace views'", "'Report pages'", "Users", "Reports", "'Report views'", "'Report page views'", "'Report load times'") # Initialize an empty hashtable to store the reports. - $reports = @{} + $reports = @{ } # For each report name, retrieve the report and add it to the hashtable. if ($username -eq "") { foreach ($reportname in $reportnames) { - $report = Get-FabricUsagemetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname + $report = Get-FabricUsageMetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname $reports += @{ $reportname.replace("'", "") = $report } } - } - else { + } else { foreach ($reportname in $reportnames) { - $report = Get-FabricUsagemetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname -ImpersonatedUser $username + $report = Get-FabricUsageMetricsQuery -DatasetID $datasetId -groupId $workspaceId -reportname $reportname -ImpersonatedUser $username $reports += @{ $reportname.replace("'", "") = $report } } } # Return the hashtable of reports. return $reports -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 b/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 index 1bab6d2b..8f5e90ce 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceUsers.ps1 @@ -1,5 +1,5 @@ function Get-FabricWorkspaceUsers { -<# + <# .SYNOPSIS Retrieves the users of a workspace. @@ -24,9 +24,9 @@ This example retrieves the users of a workspace given a workspace object. .NOTES The function defines parameters for the workspace ID and workspace object. If the parameter set name is 'WorkspaceId', it retrieves the workspace object. It then makes a GET request to the PowerBI API to retrieve the users of the workspace and returns the 'value' property of the response, which contains the users. -#> + #> -# This function retrieves the users of a workspace. + # This function retrieves the users of a workspace. # Define aliases for the function for flexibility. [Alias("Get-FabWorkspaceUsers")] @@ -54,4 +54,4 @@ The function defines parameters for the workspace ID and workspace object. If th return (Invoke-PowerBIRestMethod -Method get -Url ("groups/$($workspace.Id)/users") | ConvertFrom-Json).value } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/New-FabricWorkspace.ps1 b/source/Public/Workspace/New-FabricWorkspace.ps1 index 783328ed..77e5462d 100644 --- a/source/Public/Workspace/New-FabricWorkspace.ps1 +++ b/source/Public/Workspace/New-FabricWorkspace.ps1 @@ -1,5 +1,5 @@ function New-FabricWorkspace { -<# + <# .SYNOPSIS Creates a new Fabric workspace with the specified display name. @@ -25,7 +25,7 @@ Creates a workspace named "NewWorkspace". - Calls `Test-TokenExpired` to ensure token validity before making the API request. Author: Tiago Balabuch -#> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -104,8 +104,7 @@ Author: Tiago Balabuch Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -117,11 +116,10 @@ Author: Tiago Balabuch throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create workspace. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 b/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 index cea1fee8..893f8387 100644 --- a/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 +++ b/source/Public/Workspace/New-FabricWorkspaceUsageMetricsReport.ps1 @@ -1,5 +1,5 @@ function New-FabricWorkspaceUsageMetricsReport { -<# + <# .SYNOPSIS Retrieves the workspace usage metrics dataset ID. @@ -16,9 +16,9 @@ This example retrieves the workspace usage metrics dataset ID for a specific wor .NOTES The function retrieves the PowerBI access token and the Fabric API cluster URI. It then makes a GET request to the Fabric API to retrieve the workspace usage metrics dataset ID, parses the response and replaces certain keys to match the expected format, and returns the 'dbName' property of the first model in the response, which is the dataset ID. -#> + #> -# This function retrieves the workspace usage metrics dataset ID. + # This function retrieves the workspace usage metrics dataset ID. # Define aliases for the function for flexibility. [Alias("New-FabWorkspaceUsageMetricsReport")] @@ -32,7 +32,7 @@ The function retrieves the PowerBI access token and the Fabric API cluster URI. Confirm-FabricAuthToken | Out-Null # Retrieve the Fabric API cluster URI. - $url = get-FabricAPIclusterURI + $url = Get-FabricAPIclusterURI # Make a GET request to the Fabric API to retrieve the workspace usage metrics dataset ID. if ($PSCmdlet.ShouldProcess("Workspace Usage Metrics Report", "Retrieve")) { @@ -42,8 +42,7 @@ The function retrieves the PowerBI access token and the Fabric API cluster URI. # Return the 'dbName' property of the first model in the response, which is the dataset ID. return $response.models[0].dbName - } - else { + } else { return $null } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 index 151778e0..e7d6d6f1 100644 --- a/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 +++ b/source/Public/Workspace/Register-FabricWorkspaceToCapacity.ps1 @@ -1,5 +1,5 @@ function Register-FabricWorkspaceToCapacity { -<# + <# .SYNOPSIS Sets a PowerBI workspace to a capacity. @@ -27,11 +27,11 @@ This example Sets the workspace object stored in the $workspace variable to the .NOTES The function makes a POST request to the PowerBI API to Set the workspace to the capacity. The PowerBI access token is retrieved using the Get-PowerBIAccessToken function. -#> + #> -# This function Sets a PowerBI workspace to a capacity. -# It supports multiple aliases for flexibility. + # This function Sets a PowerBI workspace to a capacity. + # It supports multiple aliases for flexibility. [Alias("Register-FabWorkspaceToCapacity")] [CmdletBinding(SupportsShouldProcess)] param( @@ -67,5 +67,5 @@ The function makes a POST request to the PowerBI API to Set the workspace to the #return (Invoke-FabricAPIRequest -Uri "workspaces/$($workspaceID)/assignToCapacity" -Method POST -Body $body).value return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/assignToCapacity" -Body $body } - } -} + } +} \ No newline at end of file diff --git a/source/Public/Workspace/Remove-FabricWorkspace.ps1 b/source/Public/Workspace/Remove-FabricWorkspace.ps1 index 309a3828..2057b995 100644 --- a/source/Public/Workspace/Remove-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspace.ps1 @@ -58,11 +58,10 @@ function Remove-FabricWorkspace { Write-Message -Message "Workspace '$WorkspaceId' deleted successfully!" -Level Info return $null - } - catch { + } catch { # Step 5: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error return $null } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 index ae315bf3..52137138 100644 --- a/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 @@ -39,7 +39,7 @@ function Remove-FabricWorkspaceIdentity { Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Make the API request - $response = Invoke-RestMethod ` + $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` -Uri $apiEndpointUrl ` -Method Post ` @@ -72,8 +72,7 @@ function Remove-FabricWorkspaceIdentity { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } - else { + } else { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus @@ -85,10 +84,9 @@ function Remove-FabricWorkspaceIdentity { throw "API request failed with status code $statusCode." } } - } - catch { + } catch { # Step 5: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to deprovision workspace identity. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 index d9bae6b7..95f26caf 100644 --- a/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 @@ -65,10 +65,9 @@ function Remove-FabricWorkspaceRoleAssignment { } Write-Message -Message "Role assignment '$WorkspaceRoleAssignmentId' successfully removed from workspace '$WorkspaceId'." -Level Info - } - catch { + } catch { # Step 5: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to remove role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 index baa36657..decd5247 100644 --- a/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 +++ b/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 @@ -58,10 +58,9 @@ function Unassign-FabricWorkspaceCapacity { } Write-Message -Message "Workspace capacity has been successfully unassigned from workspace '$WorkspaceId'." -Level Info - } - catch { + } catch { # Step 5: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to unassign workspace from capacity. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 b/source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 index 9b4f0ae4..f9fad83d 100644 --- a/source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 +++ b/source/Public/Workspace/Unregister-FabricWorkspaceToCapacity.ps1 @@ -40,7 +40,7 @@ function Unregister-FabricWorkspaceToCapacity { [Parameter(Mandatory = $true, ParameterSetName = 'WorkspaceObject', ValueFromPipeline = $true)] $Workspace ) - + begin { Confirm-FabricAuthToken | Out-Null } @@ -49,10 +49,10 @@ function Unregister-FabricWorkspaceToCapacity { if ($PSCmdlet.ParameterSetName -eq 'WorkspaceObject') { $workspaceid = $workspace.id } - + if ($PSCmdlet.ShouldProcess("Unassigns workspace $workspaceid from a capacity")) { return Invoke-WebRequest -Headers $FabricSession.HeaderParams -Method POST -Uri "$($FabricSession.BaseApiUrl)/workspaces/$($workspaceID)/unassignFromCapacity" #return (Invoke-FabricAPIRequest -Uri "workspaces/$workspaceid/unassignFromCapacity" -Method POST).value } - } + } } \ No newline at end of file diff --git a/source/Public/Workspace/Update-FabricWorkspace.ps1 b/source/Public/Workspace/Update-FabricWorkspace.ps1 index 4af8ecc0..ab2dbd9e 100644 --- a/source/Public/Workspace/Update-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Update-FabricWorkspace.ps1 @@ -94,10 +94,9 @@ function Update-FabricWorkspace { # Step 6: Handle results Write-Message -Message "Workspace '$WorkspaceName' updated successfully!" -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update workspace. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file diff --git a/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 index 9039d7bd..8c14afe3 100644 --- a/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 @@ -95,10 +95,9 @@ function Update-FabricWorkspaceRoleAssignment { Write-Message -Message "Role assignment $WorkspaceRoleAssignmentId updated successfully in workspace '$WorkspaceId'." -Level Info return $response - } - catch { + } catch { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update role assignment. Error: $errorDetails" -Level Error } -} +} \ No newline at end of file From 2442161e9c7070c4cc757f0bcb1ccad442942563 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Mon, 12 May 2025 16:38:32 +0100 Subject: [PATCH 54/76] extra space in help --- source/Public/Copy Job/Get-FabricCopyJob.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Public/Copy Job/Get-FabricCopyJob.ps1 b/source/Public/Copy Job/Get-FabricCopyJob.ps1 index ae920267..4686a72c 100644 --- a/source/Public/Copy Job/Get-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJob.ps1 @@ -17,11 +17,11 @@ function Get-FabricCopyJob { The name of the CopyJob to retrieve. This parameter is optional. .EXAMPLE - FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "CopyJob-67890" + FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobId "CopyJob-67890" This example retrieves the CopyJob details for the CopyJob with ID "CopyJob-67890" in the workspace with ID "workspace-12345". .EXAMPLE - FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobName "My CopyJob" + FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobName "My CopyJob" This example retrieves the CopyJob details for the CopyJob named "My CopyJob" in the workspace with ID "workspace-12345". .NOTES @@ -93,4 +93,4 @@ function Get-FabricCopyJob { Write-Message -Message "Failed to retrieve CopyJob. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} From e7ac5464e1e38ab1d0f97676ad8d523c77f687d5 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Mon, 12 May 2025 16:42:24 +0100 Subject: [PATCH 55/76] Fix parameter naming in Get-FabricCopyJob function and update examples --- source/Public/Copy Job/Get-FabricCopyJob.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/Public/Copy Job/Get-FabricCopyJob.ps1 b/source/Public/Copy Job/Get-FabricCopyJob.ps1 index 4686a72c..d96cd709 100644 --- a/source/Public/Copy Job/Get-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJob.ps1 @@ -4,7 +4,7 @@ function Get-FabricCopyJob { Retrieves CopyJob details from a specified Microsoft Fabric workspace. .DESCRIPTION - This function retrieves CopyJob details from a specified workspace using either the provided CopyJobId or CopyJobName. + This function retrieves CopyJob details from a specified workspace using either the provided CopyJobId or CopyJob. It handles token validation, constructs the API URL, makes the API request, and processes the response. .PARAMETER WorkspaceId @@ -21,7 +21,7 @@ function Get-FabricCopyJob { This example retrieves the CopyJob details for the CopyJob with ID "CopyJob-67890" in the workspace with ID "workspace-12345". .EXAMPLE - FabricCopyJob -WorkspaceId "workspace-12345" -CopyJobName "My CopyJob" + FabricCopyJob -WorkspaceId "workspace-12345" -CopyJob "My CopyJob" This example retrieves the CopyJob details for the CopyJob named "My CopyJob" in the workspace with ID "workspace-12345". .NOTES @@ -48,8 +48,8 @@ function Get-FabricCopyJob { try { # Handle ambiguous input - if ($CopyJobId -and $CopyJobName) { - Write-Message -Message "Both 'CopyJobId' and 'CopyJobName' were provided. Please specify only one." -Level Error + if ($CopyJobId -and $CopyJob) { + Write-Message -Message "Both 'CopyJobId' and 'CopyJob' were provided. Please specify only one." -Level Error return $null } @@ -71,8 +71,8 @@ function Get-FabricCopyJob { # Filter results based on provided parameters $response = if ($CopyJobId) { $copyJobs | Where-Object { $_.Id -eq $CopyJobId } - } elseif ($CopyJobName) { - $copyJobs | Where-Object { $_.DisplayName -eq $CopyJobName } + } elseif ($CopyJob) { + $copyJobs | Where-Object { $_.DisplayName -eq $CopyJob } } else { # Return all CopyJobs if no filter is provided Write-Message -Message "No filter provided. Returning all CopyJobs." -Level Debug From f66a2dc2c8c9705ca2271fd8663f0853b05f4923 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Tue, 13 May 2025 11:24:55 +0100 Subject: [PATCH 56/76] Update CONTRIBUTING.md with proper code formatting Signed-off-by: Rob Sewell --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1726dd7..f81f56cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ Find-Module Microsoft.PowerShell.PSResourceGet Install-Module Microsoft.PowerShell.PSResourceGet -Force ``` -1. Develop your updates in the source repository +1. Develop your updates in the source directory You should also resolve all dependencies before you start developing. This will ensure that you have all the required modules installed and loaded into your session. ```PowerShell @@ -60,7 +60,7 @@ Add this to your VS Code settings to enable it: "file": ".github/copilot-commit-message-instructions.md" } ], - ``` +``` 3. Build the module. From the root of the repository run the following command: From ff43d7b2ce9b50d6f02468dba6cf5525a4cdefc5 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Tue, 13 May 2025 17:31:36 +0100 Subject: [PATCH 57/76] test pushing conflicted files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 17c483df..f43368bb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ output/ markdownissues.txt node_modules package-lock.json + +# just a comment From 55ce5883f9338bf4b8f00f355d079e8d465e70ff Mon Sep 17 00:00:00 2001 From: jpomfret Date: Tue, 13 May 2025 17:42:02 +0100 Subject: [PATCH 58/76] lets run some tests on PR --- .github/workflows/pr.yml | 174 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..98be6be2 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,174 @@ +on: + pull_request: + branches: + - main + - sampler # temporary branch for testing + - develop + paths-ignore: + - CHANGELOG.md +env: + buildFolderName: output + buildArtifactName: output + testResultFolderName: testResults +jobs: + Build_Stage_Package_Module: + name: Package Module + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} # checkout the correct branch name + fetch-depth: 0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v0.9.15 + with: + versionSpec: 5.x + - name: Evaluate Next Version + uses: gittools/actions/gitversion/execute@v0.9.15 + with: + configFilePath: GitVersion.yml + - name: Build & Package Module + shell: pwsh + run: ./build.ps1 -ResolveDependency -tasks pack -Verbose + env: + ModuleVersion: ${{ env.gitVersion.NuGetVersionV2 }} + - name: Publish Build Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.buildArtifactName }} + path: ${{ env.buildFolderName }}/ + Test_Stage_test_linux: + name: Linux + runs-on: ubuntu-latest + needs: + - Build_Stage_Package_Module + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} # checkout the correct branch name + fetch-depth: 0 + - name: Download Build Artifact + uses: actions/download-artifact@v4 + with: + name: ${{ env.buildArtifactName }} + path: ${{ env.buildFolderName }} + - name: Run Tests + shell: pwsh + run: ./build.ps1 -tasks noop ; ./build.ps1 -tasks test # to get around dbatools failing to load XE.core.dll if + - name: Publish Test Artifact + uses: actions/upload-artifact@v4 + with: + path: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/ + name: CodeCoverageLinux + if: success() || failure() + Test_Stage_test_windows_core: + name: Windows (PowerShell) + runs-on: windows-2019 + needs: + - Build_Stage_Package_Module + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} # checkout the correct branch name + fetch-depth: 0 + - name: Download Build Artifact + uses: actions/download-artifact@v4 + with: + name: ${{ env.buildArtifactName }} + path: ${{ env.buildFolderName }} + - name: Run Tests + shell: pwsh + run: ./build.ps1 -tasks noop; ipmo dbatools ; ./build.ps1 -tasks test # to get around dbatools failing to load XE.core.dll if + + - name: Publish Test Artifact + uses: actions/upload-artifact@v4 + with: + path: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/ + name: CodeCoverageWinPS7 + if: success() || failure() + Test_Stage_test_windows_ps: + name: Windows (Windows PowerShell) + runs-on: windows-2019 + needs: + - Build_Stage_Package_Module + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} # checkout the correct branch name + fetch-depth: 0 + - name: Download Build Artifact + uses: actions/download-artifact@v4 + with: + name: ${{ env.buildArtifactName }} + path: ${{ env.buildFolderName }} + - name: Run Tests + shell: pwsh + run: ./build.ps1 -ResolveDependency -tasks test + - name: Publish Test Artifact + uses: actions/upload-artifact@v4 + with: + path: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/ + name: CodeCoverageWinPS51 + if: success() || failure() + Test_Stage_Code_Coverage: + name: Publish Code Coverage + if: success() || failure() + runs-on: ubuntu-latest + needs: + - Build_Stage_Package_Module + - Test_Stage_test_linux + - Test_Stage_test_windows_core + - Test_Stage_test_windows_ps + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{github.event.pull_request.head.ref}} # checkout the correct branch name + repository: ${{github.event.pull_request.head.repo.full_name}} # checkout the correct branch name + fetch-depth: 0 + + - name: Download Test Artifact Linux + uses: actions/download-artifact@v4 + with: + name: CodeCoverageLinux + path: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/CodeCoverageLinux/ + - name: Download Test Artifact Windows (PS 5.1) + uses: actions/download-artifact@v4 + with: + name: CodeCoverageWinPS51 + path: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/CodeCoverageWinPS51/ + - name: Download Test Artifact Windows (PS7) + uses: actions/download-artifact@v4 + with: + name: CodeCoverageWinPS7 + path: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/CodeCoverageWinPS7/ + + - name: Publish Linux Test Results + id: linux-test-results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + nunit_files: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/CodeCoverageLinux/NUnit*.xml + check_name: Linux Test Results + - name: Publish WinPS51 Test Results + id: winps51-test-results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + nunit_files: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/CodeCoverageWinPS51/NUnit*.xml + check_name: WinPS51 Test Results + - name: Publish WinPS71 Test Results + id: winps71-test-results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + nunit_files: ${{ env.buildFolderName }}/${{ env.testResultFolderName }}/CodeCoverageWinPS7/NUnit*.xml + check_name: WinPS71 Test Results From 0fb7272bda6c9029e6d5f2dcb876a8e696181789 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Tue, 13 May 2025 17:49:21 +0100 Subject: [PATCH 59/76] sample branches to test with --- .github/workflows/pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 98be6be2..ff605a70 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -3,6 +3,7 @@ on: branches: - main - sampler # temporary branch for testing + - testBranch # temporary branch for testing - develop paths-ignore: - CHANGELOG.md From edce5ba222ade6763801d5c6d98946f3be797574 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Tue, 13 May 2025 17:53:02 +0100 Subject: [PATCH 60/76] Updated the test command in the workflow to ensure proper execution by removing the unnecessary module import. This change helps avoid issues with loading dependencies during testing. --- .github/workflows/pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ff605a70..83b939b4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -59,7 +59,7 @@ jobs: path: ${{ env.buildFolderName }} - name: Run Tests shell: pwsh - run: ./build.ps1 -tasks noop ; ./build.ps1 -tasks test # to get around dbatools failing to load XE.core.dll if + run: ./build.ps1 -tasks noop ; ./build.ps1 -tasks test - name: Publish Test Artifact uses: actions/upload-artifact@v4 with: @@ -85,7 +85,7 @@ jobs: path: ${{ env.buildFolderName }} - name: Run Tests shell: pwsh - run: ./build.ps1 -tasks noop; ipmo dbatools ; ./build.ps1 -tasks test # to get around dbatools failing to load XE.core.dll if + run: ./build.ps1 -tasks noop; ./build.ps1 -tasks test - name: Publish Test Artifact uses: actions/upload-artifact@v4 From f04f59320b1946b3726152761ca99bbb55becb1c Mon Sep 17 00:00:00 2001 From: jpomfret Date: Tue, 13 May 2025 18:02:15 +0100 Subject: [PATCH 61/76] Remove temporary test branch from workflow for Pester Help Tests This change simplifies the workflow configuration by removing the unnecessary 'testBranch' entry. This helps maintain clarity and focus on the relevant branches for testing. Thank you! --- .github/workflows/pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 83b939b4..e8a452a1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -3,7 +3,6 @@ on: branches: - main - sampler # temporary branch for testing - - testBranch # temporary branch for testing - develop paths-ignore: - CHANGELOG.md From 076cda7515c5df6f5b8038267140599899c992c2 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Wed, 14 May 2025 09:51:18 +0100 Subject: [PATCH 62/76] fix for #25 add config variables to private function so executed on build --- source/Private/Set-FabConfig.ps1 | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 source/Private/Set-FabConfig.ps1 diff --git a/source/Private/Set-FabConfig.ps1 b/source/Private/Set-FabConfig.ps1 new file mode 100644 index 00000000..56429327 --- /dev/null +++ b/source/Private/Set-FabConfig.ps1 @@ -0,0 +1,37 @@ +# this is a workaround to get the variables set for now +# TODO: change to use PSFConfig? + +$script:FabricSession = [ordered]@{ + BaseApiUrl = 'https://api.fabric.microsoft.com/v1' + ResourceUrl = 'https://api.fabric.microsoft.com' + FabricToken = $null + HeaderParams = $null + ContentType = @{'Content-Type' = "application/json" } + KustoURL = "https://api.kusto.windows.net" + AccessToken = $null +} + +$script:AzureSession = [ordered]@{ + BaseApiUrl = "https://management.azure.com" + AccessToken = $null + Token = $null + HeaderParams = $null +} + +$script:PowerBI = [ordered]@{ + BaseApiUrl = "https://api.powerbi.com/v1.0/myorg" +} + +$FabricTools = @{ + FeatureFlags = @{ + AutoRenewExpiredToken = $true + } +} + +$FabricConfig = @{ + BaseUrl = "https://api.fabric.microsoft.com/v1" + ResourceUrl = "https://api.fabric.microsoft.com" + FabricHeaders = @{} + TenantIdGlobal = "" + TokenExpiresOn = "" +} From 61c85328b833e1a4bc1b44d017662fec8c17495b Mon Sep 17 00:00:00 2001 From: jpomfret Date: Thu, 15 May 2025 19:34:42 +0100 Subject: [PATCH 63/76] Fix formatting in Get-FabricDatamart example and add error logging message --- source/Public/Datamart/Get-FabricDatamart.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Public/Datamart/Get-FabricDatamart.ps1 b/source/Public/Datamart/Get-FabricDatamart.ps1 index 2aa761e9..b9a764e1 100644 --- a/source/Public/Datamart/Get-FabricDatamart.ps1 +++ b/source/Public/Datamart/Get-FabricDatamart.ps1 @@ -17,7 +17,7 @@ function Get-FabricDatamart { The name of the specific datamart to retrieve. This parameter is optional. .EXAMPLE - Get-FabricDatamart -WorkspaceId "12345" + Get-FabricDatamart -WorkspaceId "12345" This example retrieves all datamarts from the workspace with ID "12345". .NOTES @@ -79,4 +79,4 @@ function Get-FabricDatamart { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Datamart. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} From 089e00ad7ac71839749b3e93f2e304d2688f16ed Mon Sep 17 00:00:00 2001 From: Frank Geisler Date: Fri, 16 May 2025 09:05:44 +0200 Subject: [PATCH 64/76] Added an important box for important content. --- CONTRIBUTING.md | 66 ++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f81f56cd..74533c06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Welcome -Before we go any further, thanks for being here. Thanks for using the module and especially thanks +Before we go any further, thanks for being here. Thanks for using the module and especially thanks for being here and looking into how you can help! ## Important resources @@ -11,7 +11,6 @@ for being here and looking into how you can help! - bugs TODO: link to issues issue template - communicate via issues, PRs, and discussions as well as the project - ### Develop & Build We are using the [Sampler](https://github.com/gaelcolas/Sampler) Powershell Module to structure our module. @@ -20,9 +19,10 @@ This makes it easier to develop and test the module locally. The workflow for using this and developing the code is shown below. 1. Download or clone the repo locally and create a new branch to develop on - ```PowerShell - git checkout -b newStuff # give it a proper name! - ``` + + ```PowerShell + git checkout -b newStuff # give it a proper name! + ``` 1. Make sure you have the latest Microsoft.PowerShell.PSResourceGet module @@ -37,23 +37,28 @@ Install-Module Microsoft.PowerShell.PSResourceGet -Force 1. Develop your updates in the source directory You should also resolve all dependencies before you start developing. This will ensure that you have all the required modules installed and loaded into your session. + ```PowerShell .\build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet ``` -YOU MUST DEVELOP IN THE SOURCE DIRECTORY. -This is important because the build process will create a new folder in the root of the repository called `output` and this is where the module will be built and loaded from. +| :heavy_exclamation_mark: **Important** | +| :-------------------------------------------- | +| **YOU MUST DEVELOP IN THE SOURCE DIRECTORY.** | -If you change the code in the output folder and then build the module again, it will overwrite the changes you made. +This is important because the build process will create a new folder in the root of the repository called `output` and this is where the module will be built and loaded from. + +If you change the code in the output folder and then build the module again, it will overwrite the changes you made. Ask Rob how he knows this! -2. Use GitHub CoPilot to write your commit messages by clicking on the sparkles in the commit message box. This will generate a commit message based on the changes you made. You can then edit the message to make it more descriptive if you want. This uses the prompt in the `.github\copilot-commit-message-instructions.md` file. +2. Use GitHub CoPilot to write your commit messages by clicking on the sparkles in the commit message box. This will generate a commit message based on the changes you made. You can then edit the message to make it more descriptive if you want. This uses the prompt in the `.github\copilot-commit-message-instructions.md` file. Add this to your VS Code settings to enable it: + ```json "github.copilot.chat.commitMessageGeneration.instructions": [ - + { @@ -62,49 +67,54 @@ Add this to your VS Code settings to enable it: ], ``` - 3. Build the module. From the root of the repository run the following command: - ```PowerShell - ./build.ps1 -Tasks build - ``` - This will build the module and create a new folder in the root of the repository called `output`. It will also load the new module into your current session. - + ```PowerShell + ./build.ps1 -Tasks build + ``` + This will build the module and create a new folder in the root of the repository called `output`. It will also load the new module into your current session. 4. AFTER building, you can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: - ```PowerShell - Invoke-Pester ./tests/ - ``` - This will run all the tests in the `tests` folder and output the results to the console. - + ```PowerShell + Invoke-Pester ./tests/ + ``` + This will run all the tests in the `tests` folder and output the results to the console. + You can also run specific tags such as FunctionalQuality,TestQuality, HelpQuality - + 5. You can also simulate the deployment testing by running the following command: - ```PowerShell - ./build.ps1 -Tasks build,test - ``` + + ```PowerShell + ./build.ps1 -Tasks build,test + ``` 6. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. # Thanks! + We will review your PR and get back to you as soon as we can. We are all volunteers and do this in our spare time, so please be patient with us. We will try to get back to you within a week, but it may take longer if we are busy. If you have any questions or need help, please feel free to reach out to us on the [GitHub Discussions]( ) -## How to submit changes: +## How to submit changes: + TODO: Pull Request protocol etc. You might also include what response they'll get back from the team on submission, or any caveats about the speed of response. -## How to report a bug: +## How to report a bug: + TODO: Bugs are problems in code, in the functionality of an application or in its UI design; you can submit them through "bug trackers" and most projects invite you to do so, so that they may "debug" with more efficiency and the input of a contributor. Take a look at Atom's example for how to teach people to report bugs to your project. ## Templates: -TODO: + +TODO: in this section of your file, you might also want to link to a bug report "template" like this one here which contributors can copy and add context to; this will keep your bugs tidy and relevant. ## Style Guide + TODO: include extensions and vscode settings we use to keep things neat ## Code of Conduct + TODO: maybe beef this out - stolen from data sat repo for now. We expect and demand that you follow some basic rules. Nothing dramatic here. There will be a proper code of conduct for the websites added soon, but in this repository From bcd4afe4663498080e4b64b39cefa7004b701ced Mon Sep 17 00:00:00 2001 From: Frank Geisler Date: Fri, 16 May 2025 09:13:05 +0200 Subject: [PATCH 65/76] Update CONTRIBUTING.md for clarity and structure Refine the development workflow instructions for better readability and understanding. Adjusted the numbering and formatting for consistency. This will help contributors follow the guidelines more easily for Pester Help Tests. --- CONTRIBUTING.md | 68 +++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74533c06..d8b0e5c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,8 +13,7 @@ for being here and looking into how you can help! ### Develop & Build -We are using the [Sampler](https://github.com/gaelcolas/Sampler) Powershell Module to structure our module. -This makes it easier to develop and test the module locally. +We are using the [Sampler](https://github.com/gaelcolas/Sampler) Powershell Module to structure our module. This makes it easier to develop and test the module locally. The workflow for using this and developing the code is shown below. @@ -24,76 +23,79 @@ The workflow for using this and developing the code is shown below. git checkout -b newStuff # give it a proper name! ``` -1. Make sure you have the latest Microsoft.PowerShell.PSResourceGet module +2. Make sure you have the latest Microsoft.PowerShell.PSResourceGet module -```PowerShell --- Find the latest version on the gallery -Find-Module Microsoft.PowerShell.PSResourceGet + ```PowerShell + -- Find the latest version on the gallery + Find-Module Microsoft.PowerShell.PSResourceGet --- Install side-by-side -Install-Module Microsoft.PowerShell.PSResourceGet -Force -``` + -- Install side-by-side + Install-Module Microsoft.PowerShell.PSResourceGet -Force + ``` -1. Develop your updates in the source directory +3. Develop your updates in the source directory -You should also resolve all dependencies before you start developing. This will ensure that you have all the required modules installed and loaded into your session. + You should also resolve all dependencies before you start developing. This will ensure that you have all the required modules installed and loaded into your session. -```PowerShell -.\build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet -``` + ```PowerShell + .\build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet + ``` -| :heavy_exclamation_mark: **Important** | -| :-------------------------------------------- | -| **YOU MUST DEVELOP IN THE SOURCE DIRECTORY.** | + | :heavy_exclamation_mark: **Important** | + | :-------------------------------------------- | + | **YOU MUST DEVELOP IN THE SOURCE DIRECTORY.** | -This is important because the build process will create a new folder in the root of the repository called `output` and this is where the module will be built and loaded from. + This is important because the build process will create a new folder in the root of the repository called `output` and this is where the module will be built and loaded from. -If you change the code in the output folder and then build the module again, it will overwrite the changes you made. + If you change the code in the output folder and then build the module again, it will overwrite the changes you made. -Ask Rob how he knows this! + Ask Rob how he knows this! -2. Use GitHub CoPilot to write your commit messages by clicking on the sparkles in the commit message box. This will generate a commit message based on the changes you made. You can then edit the message to make it more descriptive if you want. This uses the prompt in the `.github\copilot-commit-message-instructions.md` file. +4. Use GitHub CoPilot to write your commit messages by clicking on the sparkles in the commit message box. This will generate a commit message based on the changes you made. You can then edit the message to make it more descriptive if you want. This uses the prompt in the `.github\copilot-commit-message-instructions.md` file. Add this to your VS Code settings to enable it: ```json "github.copilot.chat.commitMessageGeneration.instructions": [ - - - { "file": ".github/copilot-commit-message-instructions.md" } - ], +], ``` -3. Build the module. From the root of the repository run the following command: +5. Build the module. From the root of the repository run the following command: + ```PowerShell ./build.ps1 -Tasks build ``` + This will build the module and create a new folder in the root of the repository called `output`. It will also load the new module into your current session. -4. AFTER building, you can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: + +6. AFTER building, you can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: + ```PowerShell Invoke-Pester ./tests/ ``` + This will run all the tests in the `tests` folder and output the results to the console. -You can also run specific tags such as FunctionalQuality,TestQuality, HelpQuality + You can also run specific tags such as `FunctionalQuality`, `TestQuality`, `HelpQuality`. -5. You can also simulate the deployment testing by running the following command: +7. You can also simulate the deployment testing by running the following command: ```PowerShell ./build.ps1 -Tasks build,test ``` -6. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. +8. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. # Thanks! - We will review your PR and get back to you as soon as we can. We are all volunteers and do this in our spare time, so please be patient with us. We will try to get back to you within a week, but it may take longer if we are busy. - If you have any questions or need help, please feel free to reach out to us on the [GitHub Discussions]( ) +We will review your PR and get back to you as soon as we can. We are all volunteers and do this in our spare time, so please be patient with us. We will try to get back to you within a week, but it may take longer if we are busy. + +If you have any questions or need help, please feel free to reach out to us on the [GitHub Discussions](https://github.com/dataplat/FabricTools/discussions) -## How to submit changes: +## How to submit changes TODO: Pull Request protocol etc. You might also include what response they'll get back from the team on submission, or any caveats about the speed of response. From 5ea8de1e6f9fbf7f9b32e2ef839f3c3e76013e04 Mon Sep 17 00:00:00 2001 From: Frank Geisler Date: Fri, 16 May 2025 09:18:51 +0200 Subject: [PATCH 66/76] Update CONTRIBUTING.md for clarity and structure Refine the development and testing instructions for better understanding. Enhance the formatting of sections and emphasize important guidelines for contributors. Thank you! --- CONTRIBUTING.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8b0e5c4..c12ff173 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ for being here and looking into how you can help! - bugs TODO: link to issues issue template - communicate via issues, PRs, and discussions as well as the project -### Develop & Build +## Develop & Build We are using the [Sampler](https://github.com/gaelcolas/Sampler) Powershell Module to structure our module. This makes it easier to develop and test the module locally. @@ -53,15 +53,15 @@ The workflow for using this and developing the code is shown below. 4. Use GitHub CoPilot to write your commit messages by clicking on the sparkles in the commit message box. This will generate a commit message based on the changes you made. You can then edit the message to make it more descriptive if you want. This uses the prompt in the `.github\copilot-commit-message-instructions.md` file. -Add this to your VS Code settings to enable it: + Add this to your VS Code settings to enable it: -```json -"github.copilot.chat.commitMessageGeneration.instructions": [ - { - "file": ".github/copilot-commit-message-instructions.md" - } -], -``` + ```json + "github.copilot.chat.commitMessageGeneration.instructions": [ + { + "file": ".github/copilot-commit-message-instructions.md" + } + ], + ``` 5. Build the module. From the root of the repository run the following command: @@ -71,15 +71,13 @@ Add this to your VS Code settings to enable it: This will build the module and create a new folder in the root of the repository called `output`. It will also load the new module into your current session. -6. AFTER building, you can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: +6. **AFTER** building, you can then run the Pester tests to ensure that everything is working as expected. The tests are located in the `tests` folder and can be run using the following command: ```PowerShell Invoke-Pester ./tests/ ``` - This will run all the tests in the `tests` folder and output the results to the console. - - You can also run specific tags such as `FunctionalQuality`, `TestQuality`, `HelpQuality`. + This will run all the tests in the `tests` folder and output the results to the console. You can also run specific tags such as `FunctionalQuality`, `TestQuality`, `HelpQuality`. 7. You can also simulate the deployment testing by running the following command: @@ -89,7 +87,7 @@ Add this to your VS Code settings to enable it: 8. Once you are happy with your code, push your branch to GitHub and create a PR against the repo. -# Thanks! +## Thanks! We will review your PR and get back to you as soon as we can. We are all volunteers and do this in our spare time, so please be patient with us. We will try to get back to you within a week, but it may take longer if we are busy. @@ -100,12 +98,12 @@ If you have any questions or need help, please feel free to reach out to us on t TODO: Pull Request protocol etc. You might also include what response they'll get back from the team on submission, or any caveats about the speed of response. -## How to report a bug: +## How to report a bug TODO: Bugs are problems in code, in the functionality of an application or in its UI design; you can submit them through "bug trackers" and most projects invite you to do so, so that they may "debug" with more efficiency and the input of a contributor. Take a look at Atom's example for how to teach people to report bugs to your project. -## Templates: +## Templates TODO: in this section of your file, you might also want to link to a bug report "template" like this one here which contributors can copy and add context to; this will keep your bugs tidy and relevant. @@ -121,6 +119,8 @@ TODO: maybe beef this out - stolen from data sat repo for now. We expect and demand that you follow some basic rules. Nothing dramatic here. There will be a proper code of conduct for the websites added soon, but in this repository -BE EXCELLENT TO EACH OTHER +| :heavy_exclamation_mark: **Important** | +| :------------------------------------- | +| **BE EXCELLENT TO EACH OTHER** | Do I need to say more? If your behaviour or communication does not fit into this statement, we do not wish for you to help us. From 8688b35f11b034f2451397d1d26f49cdeb4329b7 Mon Sep 17 00:00:00 2001 From: Frank Geisler Date: Fri, 16 May 2025 09:28:36 +0200 Subject: [PATCH 67/76] Update Code of Conduct for clarity and inclusivity This update enhances the Code of Conduct to ensure a welcoming, respectful, and inclusive environment for all contributors. It outlines expected behaviors and the consequences of unacceptable actions. --- CONTRIBUTING.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c12ff173..b524ed48 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,7 @@ ## Welcome -Before we go any further, thanks for being here. Thanks for using the module and especially thanks -for being here and looking into how you can help! +Before we go any further, thanks for being here. Thanks for using the module and especially thanks for being here and looking into how you can help! ## Important resources @@ -115,9 +114,7 @@ include extensions and vscode settings we use to keep things neat ## Code of Conduct -TODO: maybe beef this out - stolen from data sat repo for now. - -We expect and demand that you follow some basic rules. Nothing dramatic here. There will be a proper code of conduct for the websites added soon, but in this repository +This project follows a Code of Conduct to ensure a welcoming, respectful, and inclusive environment for all contributors, regardless of background or identity. Contributors are expected to use inclusive language, respect differing viewpoints, and respond to feedback constructively. Unacceptable behaviors, such as harassment, discrimination, or publishing others' private information, will not be tolerated. Project maintainers are responsible for enforcing these standards fairly and may remove or ban individuals who violate them. This Code applies in all project spaces and in any public context where someone represents the project. Reports of misconduct will be handled confidentially and seriously. You can read the full code of conduct [here](https://github.com/dataplat/FabricTools/blob/sampler/CODE_OF_CONDUCT.md). | :heavy_exclamation_mark: **Important** | | :------------------------------------- | From b5d532fc5e27b90acab000c129008395a2c10ee4 Mon Sep 17 00:00:00 2001 From: Jess Pomfret Date: Fri, 16 May 2025 17:38:03 +0100 Subject: [PATCH 68/76] Add standard tests for each function (#35) * tests for all * Add OutputType attributes for improved type safety This update introduces OutputType attributes to several functions, enhancing type safety and providing better integration with tools that rely on type information. This change will help users understand the expected output of these functions more clearly, which is beneficial for Pester Help Tests. Thank you! * Add SuppressMessageAttribute for Pester Help Tests This change adds a SuppressMessageAttribute to the Set-FabricAuthToken function. This adjustment is necessary to allow the current unit tests to pass without warnings related to the use of ConvertToSecureString with plain text. Thank you! * So that the naming of functions matches approved PowerShell Verbs fixes #14 - Added Remove-FabricDomainWorkspaceAssignment to unassign workspaces from a Fabric domain. - Introduced Remove-FabricDomainWorkspaceRoleAssignment for bulk unassigning roles from principals in a Fabric domain. - Created Import-FabricEnvironmentStagingLibrary to upload libraries to the staging environment in a Fabric workspace. - Developed Write-FabricLakehouseTableData to load data into Lakehouse tables from files or folders. - Implemented Add-FabricWorkspaceCapacityAssignment to assign a workspace to a specified capacity. - Added Remove-FabricWorkspaceCapacityAssignment to unassign a workspace from its capacity. - Created unit tests for all new functions to ensure parameter validation and functionality. * change file and test name * remove file to match develop - missed commit * Refactor PowerShell functions to support ShouldProcess for safer execution fixes #12 - Updated multiple functions across various scripts to include [CmdletBinding(SupportsShouldProcess)] for better user confirmation before executing potentially destructive actions. - Enhanced error handling by restructuring try-catch blocks for improved readability and consistency. - Ensured that API requests are wrapped in ShouldProcess checks to prevent unintended changes. - Cleaned up code formatting and improved validation checks for input parameters. * Add WhatIf and Confirm parameters to various FabricTools update tests for Missing Pester Tests #7 - Updated expected parameters in the following test files to include "WhatIf" and "Confirm": - Update-FabricEventhouseDefinition.Tests.ps1 - Update-FabricEventstream.Tests.ps1 - Update-FabricEventstreamDefinition.Tests.ps1 - Update-FabricKQLDashboard.Tests.ps1 - Update-FabricKQLDashboardDefinition.Tests.ps1 - Update-FabricKQLDatabase.Tests.ps1 - Update-FabricKQLDatabaseDefinition.Tests.ps1 - Update-FabricKQLQueryset.Tests.ps1 - Update-FabricKQLQuerysetDefinition.Tests.ps1 - Update-FabricLakehouse.Tests.ps1 - Update-FabricMLExperiment.Tests.ps1 - Update-FabricMLModel.Tests.ps1 - Update-FabricMirroredDatabase.Tests.ps1 - Update-FabricMirroredDatabaseDefinition.Tests.ps1 - Update-FabricNotebook.Tests.ps1 - Update-FabricNotebookDefinition.Tests.ps1 - Update-FabricPaginatedReport.Tests.ps1 - Update-FabricReflex.Tests.ps1 - Update-FabricReflexDefinition.Tests.ps1 - Update-FabricReport.Tests.ps1 - Update-FabricReportDefinition.Tests.ps1 - Update-FabricSemanticModel.Tests.ps1 - Update-FabricSemanticModelDefinition.Tests.ps1 - Update-FabricSparkCustomPool.Tests.ps1 - Update-FabricSparkJobDefinition.Tests.ps1 - Update-FabricSparkJobDefinitionDefinition.Tests.ps1 - Update-FabricSparkSettings.Tests.ps1 - Update-FabricWarehouse.Tests.ps1 - Update-FabricWorkspace.Tests.ps1 - Update-FabricWorkspaceRoleAssignment.Tests.ps1 - Write-FabricLakehouseTableData.Tests.ps1 * Add Pester Help Tests for Get-FabricAPIclusterURI and Get-FabricCapacityTenantOverrides This commit introduces unit tests for the Get-FabricAPIclusterURI and Get-FabricCapacityTenantOverrides functions to ensure proper parameter validation and functionality. These tests will help maintain code quality and reliability as the project evolves. Thank you! * Update CodeCoverageThreshold for Pester Help Tests Adjust the CodeCoverageThreshold from 85 to 0.35 to better align with current testing requirements for Pester Help Tests. This change aims to improve the accuracy of code coverage metrics. Thank you! --------- Co-authored-by: Rob Sewell --- build.yaml | 2 +- source/Private/Set-WellKnown.ps1 | 1381 ----------------- source/Public/Copy Job/New-FabricCopyJob.ps1 | 7 +- .../Public/Copy Job/Remove-FabricCopyJob.ps1 | 8 +- .../Public/Copy Job/Update-FabricCopyJob.ps1 | 32 +- .../Update-FabricCopyJobDefinition.ps1 | 18 +- .../Data Pipeline/New-FabricDataPipeline.ps1 | 33 +- .../Remove-FabricDataPipeline.ps1 | 28 +- .../Update-FabricDataPipeline.ps1 | 33 +- ...icDomainWorkspaceAssignmentByCapacity.ps1} | 9 +- ...d-FabricDomainWorkspaceAssignmentById.ps1} | 9 +- ...cDomainWorkspaceAssignmentByPrincipal.ps1} | 9 +- ...d-FabricDomainWorkspaceRoleAssignment.ps1} | 5 +- source/Public/Domain/Get-FabricDomain.ps1 | 3 +- source/Public/Domain/New-FabricDomain.ps1 | 65 +- source/Public/Domain/Remove-FabricDomain.ps1 | 39 +- ...emove-FabricDomainWorkspaceAssignment.ps1} | 63 +- ...e-FabricDomainWorkspaceRoleAssignment.ps1} | 9 +- source/Public/Domain/Update-FabricDomain.ps1 | 49 +- ...mport-FabricEnvironmentStagingLibrary.ps1} | 7 +- .../Environment/New-FabricEnvironment.ps1 | 62 +- .../Environment/Remove-FabricEnvironment.ps1 | 39 +- ...Remove-FabricEnvironmentStagingLibrary.ps1 | 39 +- .../Stop-FabricEnvironmentPublish.ps1 | 43 +- .../Environment/Update-FabricEnvironment.ps1 | 45 +- ...e-FabricEnvironmentStagingSparkCompute.ps1 | 31 +- .../Eventhouse/New-FabricEventhouse.ps1 | 76 +- .../Eventhouse/Remove-FabricEventhouse.ps1 | 39 +- .../Eventhouse/Update-FabricEventhouse.ps1 | 45 +- .../Update-FabricEventhouseDefinition.ps1 | 80 +- .../Eventstream/New-FabricEventstream.ps1 | 87 +- .../Eventstream/Remove-FabricEventstream.ps1 | 38 +- .../Eventstream/Update-FabricEventstream.ps1 | 45 +- .../Update-FabricEventstreamDefinition.ps1 | 80 +- source/Public/Get-FabricAPIClusterURI.ps1 | 4 +- source/Public/Get-FabricDebugInfo.ps1 | 3 +- source/Public/Item/Import-FabricItem.ps1 | 13 +- .../KQL Dashboard/New-FabricKQLDashboard.ps1 | 88 +- .../Remove-FabricKQLDashboard.ps1 | 36 +- .../Update-FabricKQLDashboard.ps1 | 43 +- .../Update-FabricKQLDashboardDefinition.ps1 | 79 +- .../KQL Database/New-FabricKQLDatabase.ps1 | 132 +- .../KQL Database/Remove-FabricKQLDatabase.ps1 | 38 +- .../KQL Database/Update-FabricKQLDatabase.ps1 | 42 +- .../Update-FabricKQLDatabaseDefinition.ps1 | 87 +- .../KQL Queryset/New-FabricKQLQueryset.ps1 | 6 +- .../KQL Queryset/Remove-FabricKQLQueryset.ps1 | 36 +- .../KQL Queryset/Update-FabricKQLQueryset.ps1 | 44 +- .../Update-FabricKQLQuerysetDefinition.ps1 | 6 +- .../Lakehouse/Get-FabricLakehouseTable.ps1 | 3 +- .../Public/Lakehouse/New-FabricLakehouse.ps1 | 64 +- .../Lakehouse/Remove-FabricLakehouse.ps1 | 37 +- .../Start-FabricLakehouseTableMaintenance.ps1 | 80 +- .../Lakehouse/Update-FabricLakehouse.ps1 | 44 +- ...ps1 => Write-FabricLakehouseTableData.ps1} | 66 +- .../ML Experiment/New-FabricMLExperiment.ps1 | 62 +- .../Remove-FabricMLExperiment.ps1 | 37 +- .../Update-FabricMLExperiment.ps1 | 45 +- source/Public/ML Model/New-FabricMLModel.ps1 | 62 +- .../Public/ML Model/Remove-FabricMLModel.ps1 | 38 +- .../Public/ML Model/Update-FabricMLModel.ps1 | 42 +- .../Get-FabricMirroredDatabaseTableStatus.ps1 | 3 +- .../New-FabricMirroredDatabase.ps1 | 88 +- .../Remove-FabricMirroredDatabase.ps1 | 37 +- .../Start-FabricMirroredDatabaseMirroring.ps1 | 39 +- .../Stop-FabricMirroredDatabaseMirroring.ps1 | 39 +- .../Update-FabricMirroredDatabase.ps1 | 44 +- ...pdate-FabricMirroredDatabaseDefinition.ps1 | 80 +- source/Public/Notebook/New-FabricNotebook.ps1 | 89 +- .../Public/Notebook/New-FabricNotebookNEW.ps1 | 74 +- .../Public/Notebook/Remove-FabricNotebook.ps1 | 37 +- .../Public/Notebook/Update-FabricNotebook.ps1 | 45 +- .../Update-FabricNotebookDefinition.ps1 | 80 +- .../Update-FabricPaginatedReport.ps1 | 45 +- source/Public/Reflex/New-FabricReflex.ps1 | 87 +- source/Public/Reflex/Remove-FabricReflex.ps1 | 38 +- source/Public/Reflex/Update-FabricReflex.ps1 | 45 +- .../Reflex/Update-FabricReflexDefinition.ps1 | 81 +- source/Public/Report/New-FabricReport.ps1 | 66 +- source/Public/Report/Remove-FabricReport.ps1 | 38 +- source/Public/Report/Update-FabricReport.ps1 | 45 +- .../Report/Update-FabricReportDefinition.ps1 | 71 +- .../SQL Database/New-FabricSQLDatabase.ps1 | 44 +- .../SQL Database/Remove-FabricSQLDatabase.ps1 | 37 +- .../New-FabricSemanticModel.ps1 | 61 +- .../Remove-FabricSemanticModel.ps1 | 38 +- .../Update-FabricSemanticModel.ps1 | 45 +- .../Update-FabricSemanticModelDefinition.ps1 | 65 +- source/Public/Set-FabricAuthToken.ps1 | 136 +- .../New-FabricSparkJobDefinition.ps1 | 92 +- .../Remove-FabricSparkJobDefinition.ps1 | 40 +- ...Start-FabricSparkJobDefinitionOnDemand.ps1 | 119 +- .../Update-FabricSparkJobDefinition.ps1 | 47 +- ...ate-FabricSparkJobDefinitionDefinition.ps1 | 80 +- .../Public/Spark/Get-FabricSparkSettings.ps1 | 3 +- .../Spark/New-FabricSparkCustomPool.ps1 | 58 +- .../Spark/Remove-FabricSparkCustomPool.ps1 | 36 +- .../Spark/Update-FabricSparkCustomPool.ps1 | 41 +- .../Spark/Update-FabricSparkSettings.ps1 | 66 +- .../Tenant/Get-FabricTenantSettings.ps1 | 27 - ...e-FabricCapacityTenantSettingOverrides.ps1 | 56 +- .../Tenant/Update-FabricTenantSetting.ps1 | 75 +- source/Public/Utils/Convert-ToBase64.ps1 | 3 +- ... => Invoke-FabricAPIRequest_duplicate.ps1} | 2 +- source/Public/Utils/Set-FabricApiHeaders.ps1 | 47 +- .../Public/Warehouse/New-FabricWarehouse.ps1 | 32 +- .../Warehouse/Remove-FabricWarehouse.ps1 | 28 +- .../Warehouse/Update-FabricWarehouse.ps1 | 31 +- ...Add-FabricWorkspaceCapacityAssignment.ps1} | 9 +- .../Add-FabricWorkspaceRoleAssignment.ps1 | 3 +- .../Get-FabricWorkspaceRoleAssignment.ps1 | 3 +- .../Public/Workspace/New-FabricWorkspace.ps1 | 65 +- .../Workspace/Remove-FabricWorkspace.ps1 | 33 +- ...ove-FabricWorkspaceCapacityAssignment.ps1} | 45 +- .../Remove-FabricWorkspaceIdentity.ps1 | 55 +- .../Remove-FabricWorkspaceRoleAssignment.ps1 | 37 +- .../Workspace/Update-FabricWorkspace.ps1 | 45 +- .../Update-FabricWorkspaceRoleAssignment.ps1 | 44 +- tests/QA/module.tests.ps1 | 26 +- ...ainWorkspaceAssignmentByCapacity.Tests.ps1 | 46 + ...ricDomainWorkspaceAssignmentById.Tests.ps1 | 46 + ...inWorkspaceAssignmentByPrincipal.Tests.ps1 | 46 + ...ricDomainWorkspaceRoleAssignment.Tests.ps1 | 47 + ...abricWorkspaceCapacityAssignment.Tests.ps1 | 46 + .../Add-FabricWorkspaceIdentity.Tests.ps1 | 46 + ...dd-FabricWorkspaceRoleAssignment.Tests.ps1 | 49 + tests/Unit/Confirm-FabricAuthToken.Tests.ps1 | 45 + tests/Unit/Connect-FabricAccount.Tests.ps1 | 46 + tests/Unit/Convert-FromBase64.Tests.ps1 | 46 + tests/Unit/Convert-ToBase64.Tests.ps1 | 46 + tests/Unit/Export-FabricItem.Tests.ps1 | 49 + tests/Unit/Get-FabricAPIclusterURI.Tests.ps1 | 31 + tests/Unit/Get-FabricAuthToken.Tests.ps1 | 45 + tests/Unit/Get-FabricCapacities.Tests.ps1 | 46 + tests/Unit/Get-FabricCapacity.Tests.ps1 | 47 + .../Get-FabricCapacityRefreshables.Tests.ps1 | 46 + tests/Unit/Get-FabricCapacitySkus.Tests.ps1 | 48 + tests/Unit/Get-FabricCapacityState.Tests.ps1 | 48 + ...et-FabricCapacityTenantOverrides.Tests.ps1 | 33 + ...icCapacityTenantSettingOverrides.Tests.ps1 | 46 + .../Unit/Get-FabricCapacityWorkload.Tests.ps1 | 46 + tests/Unit/Get-FabricConnection.Tests.ps1 | 46 + tests/Unit/Get-FabricCopyJob.Tests.ps1 | 48 + .../Get-FabricCopyJobDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricDashboard.Tests.ps1 | 46 + tests/Unit/Get-FabricDataPipeline.Tests.ps1 | 48 + tests/Unit/Get-FabricDatamart.Tests.ps1 | 48 + .../Unit/Get-FabricDatasetRefreshes.Tests.ps1 | 47 + tests/Unit/Get-FabricDebugInfo.Tests.ps1 | 45 + tests/Unit/Get-FabricDomain.Tests.ps1 | 48 + ...bricDomainTenantSettingOverrides.Tests.ps1 | 45 + .../Unit/Get-FabricDomainWorkspace.Tests.ps1 | 46 + tests/Unit/Get-FabricEnvironment.Tests.ps1 | 48 + .../Get-FabricEnvironmentLibrary.Tests.ps1 | 47 + ...et-FabricEnvironmentSparkCompute.Tests.ps1 | 47 + ...-FabricEnvironmentStagingLibrary.Tests.ps1 | 47 + ...icEnvironmentStagingSparkCompute.Tests.ps1 | 47 + tests/Unit/Get-FabricEventhouse.Tests.ps1 | 48 + .../Get-FabricEventhouseDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricEventstream.Tests.ps1 | 48 + .../Get-FabricEventstreamDefinition.Tests.ps1 | 48 + .../Get-FabricExternalDataShares.Tests.ps1 | 45 + tests/Unit/Get-FabricItem.Tests.ps1 | 49 + tests/Unit/Get-FabricKQLDashboard.Tests.ps1 | 48 + ...Get-FabricKQLDashboardDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricKQLDatabase.Tests.ps1 | 48 + .../Get-FabricKQLDatabaseDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricKQLQueryset.Tests.ps1 | 48 + .../Get-FabricKQLQuerysetDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricLakehouse.Tests.ps1 | 48 + tests/Unit/Get-FabricLakehouseTable.Tests.ps1 | 47 + .../Get-FabricLongRunningOperation.Tests.ps1 | 48 + ...FabricLongRunningOperationResult.Tests.ps1 | 46 + tests/Unit/Get-FabricMLExperiment.Tests.ps1 | 48 + tests/Unit/Get-FabricMLModel.Tests.ps1 | 48 + .../Unit/Get-FabricMirroredDatabase.Tests.ps1 | 48 + ...FabricMirroredDatabaseDefinition.Tests.ps1 | 47 + ...Get-FabricMirroredDatabaseStatus.Tests.ps1 | 47 + ...abricMirroredDatabaseTableStatus.Tests.ps1 | 47 + .../Get-FabricMirroredWarehouse.Tests.ps1 | 48 + tests/Unit/Get-FabricNotebook.Tests.ps1 | 48 + .../Get-FabricNotebookDefinition.Tests.ps1 | 48 + .../Unit/Get-FabricPaginatedReport.Tests.ps1 | 48 + tests/Unit/Get-FabricReflex.Tests.ps1 | 48 + .../Unit/Get-FabricReflexDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricReport.Tests.ps1 | 48 + .../Unit/Get-FabricReportDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricSQLDatabase.Tests.ps1 | 48 + tests/Unit/Get-FabricSQLEndpoint.Tests.ps1 | 48 + tests/Unit/Get-FabricSemanticModel.Tests.ps1 | 48 + ...et-FabricSemanticModelDefinition.Tests.ps1 | 48 + .../Unit/Get-FabricSparkCustomPool.Tests.ps1 | 48 + .../Get-FabricSparkJobDefinition.Tests.ps1 | 48 + ...bricSparkJobDefinitionDefinition.Tests.ps1 | 48 + tests/Unit/Get-FabricSparkSettings.Tests.ps1 | 46 + tests/Unit/Get-FabricTenantSetting.Tests.ps1 | 45 + .../Get-FabricUsageMetricsQuery.Tests.ps1 | 49 + ...Get-FabricUserListAccessEntities.Tests.ps1 | 47 + tests/Unit/Get-FabricWarehouse.Tests.ps1 | 48 + tests/Unit/Get-FabricWorkspace.Tests.ps1 | 47 + ...-FabricWorkspaceDatasetRefreshes.Tests.ps1 | 46 + ...et-FabricWorkspaceRoleAssignment.Tests.ps1 | 47 + ...cWorkspaceTenantSettingOverrides.Tests.ps1 | 45 + ...-FabricWorkspaceUsageMetricsData.Tests.ps1 | 47 + tests/Unit/Get-FabricWorkspaceUsers.Tests.ps1 | 47 + tests/Unit/Get-Sha256.Tests.ps1 | 34 + ...-FabricEnvironmentStagingLibrary.Tests.ps1 | 45 + tests/Unit/Import-FabricItem.Tests.ps1 | 49 + tests/Unit/Invoke-FabricAPIRequest.Tests.ps1 | 52 + .../Invoke-FabricDatasetRefresh.Tests.ps1 | 46 + tests/Unit/Invoke-FabricKQLCommand.Tests.ps1 | 50 + tests/Unit/New-FabricCopyJob.Tests.ps1 | 50 + tests/Unit/New-FabricDataPipeline.Tests.ps1 | 48 + tests/Unit/New-FabricDomain.Tests.ps1 | 49 + tests/Unit/New-FabricEnvironment.Tests.ps1 | 48 + tests/Unit/New-FabricEventhouse.Tests.ps1 | 50 + tests/Unit/New-FabricEventstream.Tests.ps1 | 50 + tests/Unit/New-FabricKQLDashboard.Tests.ps1 | 50 + tests/Unit/New-FabricKQLDatabase.Tests.ps1 | 56 + tests/Unit/New-FabricKQLQueryset.Tests.ps1 | 50 + tests/Unit/New-FabricLakehouse.Tests.ps1 | 51 + tests/Unit/New-FabricMLExperiment.Tests.ps1 | 48 + tests/Unit/New-FabricMLModel.Tests.ps1 | 48 + .../Unit/New-FabricMirroredDatabase.Tests.ps1 | 50 + tests/Unit/New-FabricNotebook.Tests.ps1 | 50 + tests/Unit/New-FabricNotebookNEW.Tests.ps1 | 49 + tests/Unit/New-FabricReflex.Tests.ps1 | 50 + tests/Unit/New-FabricReport.Tests.ps1 | 49 + tests/Unit/New-FabricSQLDatabase.Tests.ps1 | 48 + tests/Unit/New-FabricSemanticModel.Tests.ps1 | 49 + .../Unit/New-FabricSparkCustomPool.Tests.ps1 | 55 + .../New-FabricSparkJobDefinition.Tests.ps1 | 50 + tests/Unit/New-FabricWarehouse.Tests.ps1 | 48 + tests/Unit/New-FabricWorkspace.Tests.ps1 | 48 + ...abricWorkspaceUsageMetricsReport.Tests.ps1 | 48 + .../Unit/Publish-FabricEnvironment.Tests.ps1 | 47 + ...gister-FabricWorkspaceToCapacity.Tests.ps1 | 50 + tests/Unit/Remove-FabricCopyJob.Tests.ps1 | 47 + .../Unit/Remove-FabricDataPipeline.Tests.ps1 | 47 + tests/Unit/Remove-FabricDomain.Tests.ps1 | 46 + ...-FabricDomainWorkspaceAssignment.Tests.ps1 | 48 + ...ricDomainWorkspaceRoleAssignment.Tests.ps1 | 49 + tests/Unit/Remove-FabricEnvironment.Tests.ps1 | 47 + ...-FabricEnvironmentStagingLibrary.Tests.ps1 | 48 + tests/Unit/Remove-FabricEventhouse.Tests.ps1 | 47 + tests/Unit/Remove-FabricEventstream.Tests.ps1 | 47 + tests/Unit/Remove-FabricItem.Tests.ps1 | 50 + .../Unit/Remove-FabricKQLDashboard.Tests.ps1 | 47 + tests/Unit/Remove-FabricKQLDatabase.Tests.ps1 | 47 + tests/Unit/Remove-FabricKQLQueryset.Tests.ps1 | 49 + tests/Unit/Remove-FabricLakehouse.Tests.ps1 | 48 + .../Unit/Remove-FabricMLExperiment.Tests.ps1 | 47 + tests/Unit/Remove-FabricMLModel.Tests.ps1 | 47 + .../Remove-FabricMirroredDatabase.Tests.ps1 | 48 + tests/Unit/Remove-FabricNotebook.Tests.ps1 | 48 + tests/Unit/Remove-FabricReflex.Tests.ps1 | 47 + tests/Unit/Remove-FabricReport.Tests.ps1 | 47 + tests/Unit/Remove-FabricSQLDatabase.Tests.ps1 | 47 + .../Unit/Remove-FabricSemanticModel.Tests.ps1 | 47 + .../Remove-FabricSparkCustomPool.Tests.ps1 | 47 + .../Remove-FabricSparkJobDefinition.Tests.ps1 | 47 + tests/Unit/Remove-FabricWarehouse.Tests.ps1 | 47 + tests/Unit/Remove-FabricWorkspace.Tests.ps1 | 48 + ...abricWorkspaceCapacityAssignment.Tests.ps1 | 48 + .../Remove-FabricWorkspaceIdentity.Tests.ps1 | 48 + ...ve-FabricWorkspaceRoleAssignment.Tests.ps1 | 49 + tests/Unit/Resume-FabricCapacity.Tests.ps1 | 50 + ...icCapacityTenantSettingOverrides.Tests.ps1 | 47 + .../Revoke-FabricExternalDataShares.Tests.ps1 | 48 + tests/Unit/Set-FabricApiHeaders.Tests.ps1 | 48 + tests/Unit/Set-FabricAuthToken.Tests.ps1 | 53 + ...-FabricLakehouseTableMaintenance.Tests.ps1 | 54 + ...-FabricMirroredDatabaseMirroring.Tests.ps1 | 47 + ...FabricSparkJobDefinitionOnDemand.Tests.ps1 | 49 + .../Stop-FabricEnvironmentPublish.Tests.ps1 | 47 + ...-FabricMirroredDatabaseMirroring.Tests.ps1 | 47 + tests/Unit/Suspend-FabricCapacity.Tests.ps1 | 50 + tests/Unit/Test-FabricApiResponse.Tests.ps1 | 50 + ...gister-FabricWorkspaceToCapacity.Tests.ps1 | 49 + ...icCapacityTenantSettingOverrides.Tests.ps1 | 53 + tests/Unit/Update-FabricCopyJob.Tests.ps1 | 49 + .../Update-FabricCopyJobDefinition.Tests.ps1 | 49 + .../Unit/Update-FabricDataPipeline.Tests.ps1 | 49 + tests/Unit/Update-FabricDomain.Tests.ps1 | 49 + tests/Unit/Update-FabricEnvironment.Tests.ps1 | 49 + ...icEnvironmentStagingSparkCompute.Tests.ps1 | 58 + tests/Unit/Update-FabricEventhouse.Tests.ps1 | 49 + ...pdate-FabricEventhouseDefinition.Tests.ps1 | 49 + tests/Unit/Update-FabricEventstream.Tests.ps1 | 49 + ...date-FabricEventstreamDefinition.Tests.ps1 | 49 + .../Unit/Update-FabricKQLDashboard.Tests.ps1 | 49 + ...ate-FabricKQLDashboardDefinition.Tests.ps1 | 50 + tests/Unit/Update-FabricKQLDatabase.Tests.ps1 | 50 + ...date-FabricKQLDatabaseDefinition.Tests.ps1 | 50 + tests/Unit/Update-FabricKQLQueryset.Tests.ps1 | 49 + ...date-FabricKQLQuerysetDefinition.Tests.ps1 | 49 + tests/Unit/Update-FabricLakehouse.Tests.ps1 | 49 + .../Unit/Update-FabricMLExperiment.Tests.ps1 | 49 + tests/Unit/Update-FabricMLModel.Tests.ps1 | 48 + .../Update-FabricMirroredDatabase.Tests.ps1 | 49 + ...FabricMirroredDatabaseDefinition.Tests.ps1 | 49 + tests/Unit/Update-FabricNotebook.Tests.ps1 | 49 + .../Update-FabricNotebookDefinition.Tests.ps1 | 49 + .../Update-FabricPaginatedReport.Tests.ps1 | 49 + tests/Unit/Update-FabricReflex.Tests.ps1 | 50 + .../Update-FabricReflexDefinition.Tests.ps1 | 49 + tests/Unit/Update-FabricReport.Tests.ps1 | 49 + .../Update-FabricReportDefinition.Tests.ps1 | 48 + .../Unit/Update-FabricSemanticModel.Tests.ps1 | 49 + ...te-FabricSemanticModelDefinition.Tests.ps1 | 48 + .../Update-FabricSparkCustomPool.Tests.ps1 | 56 + .../Update-FabricSparkJobDefinition.Tests.ps1 | 49 + ...bricSparkJobDefinitionDefinition.Tests.ps1 | 49 + .../Unit/Update-FabricSparkSettings.Tests.ps1 | 55 + tests/Unit/Update-FabricWarehouse.Tests.ps1 | 49 + tests/Unit/Update-FabricWorkspace.Tests.ps1 | 48 + ...te-FabricWorkspaceRoleAssignment.Tests.ps1 | 48 + .../Write-FabricLakehouseTableData.Tests.ps1 | 55 + 318 files changed, 12785 insertions(+), 3407 deletions(-) delete mode 100644 source/Private/Set-WellKnown.ps1 rename source/Public/Domain/{Assign-FabricDomainWorkspaceByCapacity.ps1 => Add-FabricDomainWorkspaceAssignmentByCapacity.ps1} (91%) rename source/Public/Domain/{Assign-FabricDomainWorkspaceById.ps1 => Add-FabricDomainWorkspaceAssignmentById.ps1} (87%) rename source/Public/Domain/{Assign-FabricDomainWorkspaceByPrincipal.ps1 => Add-FabricDomainWorkspaceAssignmentByPrincipal.ps1} (92%) rename source/Public/Domain/{Assign-FabricDomainWorkspaceRoleAssignment.ps1 => Add-FabricDomainWorkspaceRoleAssignment.ps1} (97%) rename source/Public/Domain/{Unassign-FabricDomainWorkspace.ps1 => Remove-FabricDomainWorkspaceAssignment.ps1} (67%) rename source/Public/Domain/{Unassign-FabricDomainWorkspaceRoleAssignment.ps1 => Remove-FabricDomainWorkspaceRoleAssignment.ps1} (94%) rename source/Public/Environment/{Upload-FabricEnvironmentStagingLibrary.ps1 => Import-FabricEnvironmentStagingLibrary.ps1} (94%) rename source/Public/Lakehouse/{Load-FabricLakehouseTable.ps1 => Write-FabricLakehouseTableData.ps1} (78%) delete mode 100644 source/Public/Tenant/Get-FabricTenantSettings.ps1 rename source/Public/Utils/{Invoke-FabricAPIRequest.ps1 => Invoke-FabricAPIRequest_duplicate.ps1} (99%) rename source/Public/Workspace/{Assign-FabricWorkspaceCapacity.ps1 => Add-FabricWorkspaceCapacityAssignment.ps1} (89%) rename source/Public/Workspace/{Unassign-FabricWorkspaceCapacity.ps1 => Remove-FabricWorkspaceCapacityAssignment.ps1} (65%) create mode 100644 tests/Unit/Add-FabricDomainWorkspaceAssignmentByCapacity.Tests.ps1 create mode 100644 tests/Unit/Add-FabricDomainWorkspaceAssignmentById.Tests.ps1 create mode 100644 tests/Unit/Add-FabricDomainWorkspaceAssignmentByPrincipal.Tests.ps1 create mode 100644 tests/Unit/Add-FabricDomainWorkspaceRoleAssignment.Tests.ps1 create mode 100644 tests/Unit/Add-FabricWorkspaceCapacityAssignment.Tests.ps1 create mode 100644 tests/Unit/Add-FabricWorkspaceIdentity.Tests.ps1 create mode 100644 tests/Unit/Add-FabricWorkspaceRoleAssignment.Tests.ps1 create mode 100644 tests/Unit/Confirm-FabricAuthToken.Tests.ps1 create mode 100644 tests/Unit/Connect-FabricAccount.Tests.ps1 create mode 100644 tests/Unit/Convert-FromBase64.Tests.ps1 create mode 100644 tests/Unit/Convert-ToBase64.Tests.ps1 create mode 100644 tests/Unit/Export-FabricItem.Tests.ps1 create mode 100644 tests/Unit/Get-FabricAPIclusterURI.Tests.ps1 create mode 100644 tests/Unit/Get-FabricAuthToken.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacities.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacity.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacityRefreshables.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacitySkus.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacityState.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacityTenantOverrides.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacityTenantSettingOverrides.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCapacityWorkload.Tests.ps1 create mode 100644 tests/Unit/Get-FabricConnection.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCopyJob.Tests.ps1 create mode 100644 tests/Unit/Get-FabricCopyJobDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDashboard.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDataPipeline.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDatamart.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDatasetRefreshes.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDebugInfo.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDomain.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDomainTenantSettingOverrides.Tests.ps1 create mode 100644 tests/Unit/Get-FabricDomainWorkspace.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEnvironment.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEnvironmentLibrary.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEnvironmentSparkCompute.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEnvironmentStagingLibrary.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEnvironmentStagingSparkCompute.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEventhouse.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEventhouseDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEventstream.Tests.ps1 create mode 100644 tests/Unit/Get-FabricEventstreamDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricExternalDataShares.Tests.ps1 create mode 100644 tests/Unit/Get-FabricItem.Tests.ps1 create mode 100644 tests/Unit/Get-FabricKQLDashboard.Tests.ps1 create mode 100644 tests/Unit/Get-FabricKQLDashboardDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricKQLDatabase.Tests.ps1 create mode 100644 tests/Unit/Get-FabricKQLDatabaseDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricKQLQueryset.Tests.ps1 create mode 100644 tests/Unit/Get-FabricKQLQuerysetDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricLakehouse.Tests.ps1 create mode 100644 tests/Unit/Get-FabricLakehouseTable.Tests.ps1 create mode 100644 tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 create mode 100644 tests/Unit/Get-FabricLongRunningOperationResult.Tests.ps1 create mode 100644 tests/Unit/Get-FabricMLExperiment.Tests.ps1 create mode 100644 tests/Unit/Get-FabricMLModel.Tests.ps1 create mode 100644 tests/Unit/Get-FabricMirroredDatabase.Tests.ps1 create mode 100644 tests/Unit/Get-FabricMirroredDatabaseDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricMirroredDatabaseStatus.Tests.ps1 create mode 100644 tests/Unit/Get-FabricMirroredDatabaseTableStatus.Tests.ps1 create mode 100644 tests/Unit/Get-FabricMirroredWarehouse.Tests.ps1 create mode 100644 tests/Unit/Get-FabricNotebook.Tests.ps1 create mode 100644 tests/Unit/Get-FabricNotebookDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricPaginatedReport.Tests.ps1 create mode 100644 tests/Unit/Get-FabricReflex.Tests.ps1 create mode 100644 tests/Unit/Get-FabricReflexDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricReport.Tests.ps1 create mode 100644 tests/Unit/Get-FabricReportDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSQLDatabase.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSQLEndpoint.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSemanticModel.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSemanticModelDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSparkCustomPool.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSparkJobDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSparkJobDefinitionDefinition.Tests.ps1 create mode 100644 tests/Unit/Get-FabricSparkSettings.Tests.ps1 create mode 100644 tests/Unit/Get-FabricTenantSetting.Tests.ps1 create mode 100644 tests/Unit/Get-FabricUsageMetricsQuery.Tests.ps1 create mode 100644 tests/Unit/Get-FabricUserListAccessEntities.Tests.ps1 create mode 100644 tests/Unit/Get-FabricWarehouse.Tests.ps1 create mode 100644 tests/Unit/Get-FabricWorkspace.Tests.ps1 create mode 100644 tests/Unit/Get-FabricWorkspaceDatasetRefreshes.Tests.ps1 create mode 100644 tests/Unit/Get-FabricWorkspaceRoleAssignment.Tests.ps1 create mode 100644 tests/Unit/Get-FabricWorkspaceTenantSettingOverrides.Tests.ps1 create mode 100644 tests/Unit/Get-FabricWorkspaceUsageMetricsData.Tests.ps1 create mode 100644 tests/Unit/Get-FabricWorkspaceUsers.Tests.ps1 create mode 100644 tests/Unit/Get-Sha256.Tests.ps1 create mode 100644 tests/Unit/Import-FabricEnvironmentStagingLibrary.Tests.ps1 create mode 100644 tests/Unit/Import-FabricItem.Tests.ps1 create mode 100644 tests/Unit/Invoke-FabricAPIRequest.Tests.ps1 create mode 100644 tests/Unit/Invoke-FabricDatasetRefresh.Tests.ps1 create mode 100644 tests/Unit/Invoke-FabricKQLCommand.Tests.ps1 create mode 100644 tests/Unit/New-FabricCopyJob.Tests.ps1 create mode 100644 tests/Unit/New-FabricDataPipeline.Tests.ps1 create mode 100644 tests/Unit/New-FabricDomain.Tests.ps1 create mode 100644 tests/Unit/New-FabricEnvironment.Tests.ps1 create mode 100644 tests/Unit/New-FabricEventhouse.Tests.ps1 create mode 100644 tests/Unit/New-FabricEventstream.Tests.ps1 create mode 100644 tests/Unit/New-FabricKQLDashboard.Tests.ps1 create mode 100644 tests/Unit/New-FabricKQLDatabase.Tests.ps1 create mode 100644 tests/Unit/New-FabricKQLQueryset.Tests.ps1 create mode 100644 tests/Unit/New-FabricLakehouse.Tests.ps1 create mode 100644 tests/Unit/New-FabricMLExperiment.Tests.ps1 create mode 100644 tests/Unit/New-FabricMLModel.Tests.ps1 create mode 100644 tests/Unit/New-FabricMirroredDatabase.Tests.ps1 create mode 100644 tests/Unit/New-FabricNotebook.Tests.ps1 create mode 100644 tests/Unit/New-FabricNotebookNEW.Tests.ps1 create mode 100644 tests/Unit/New-FabricReflex.Tests.ps1 create mode 100644 tests/Unit/New-FabricReport.Tests.ps1 create mode 100644 tests/Unit/New-FabricSQLDatabase.Tests.ps1 create mode 100644 tests/Unit/New-FabricSemanticModel.Tests.ps1 create mode 100644 tests/Unit/New-FabricSparkCustomPool.Tests.ps1 create mode 100644 tests/Unit/New-FabricSparkJobDefinition.Tests.ps1 create mode 100644 tests/Unit/New-FabricWarehouse.Tests.ps1 create mode 100644 tests/Unit/New-FabricWorkspace.Tests.ps1 create mode 100644 tests/Unit/New-FabricWorkspaceUsageMetricsReport.Tests.ps1 create mode 100644 tests/Unit/Publish-FabricEnvironment.Tests.ps1 create mode 100644 tests/Unit/Register-FabricWorkspaceToCapacity.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricCopyJob.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricDataPipeline.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricDomain.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricDomainWorkspaceAssignment.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricDomainWorkspaceRoleAssignment.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricEnvironment.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricEnvironmentStagingLibrary.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricEventhouse.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricEventstream.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricItem.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricKQLDashboard.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricKQLDatabase.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricKQLQueryset.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricLakehouse.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricMLExperiment.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricMLModel.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricMirroredDatabase.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricNotebook.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricReflex.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricReport.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricSQLDatabase.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricSemanticModel.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricSparkCustomPool.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricSparkJobDefinition.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricWarehouse.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricWorkspace.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricWorkspaceCapacityAssignment.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricWorkspaceIdentity.Tests.ps1 create mode 100644 tests/Unit/Remove-FabricWorkspaceRoleAssignment.Tests.ps1 create mode 100644 tests/Unit/Resume-FabricCapacity.Tests.ps1 create mode 100644 tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 create mode 100644 tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 create mode 100644 tests/Unit/Set-FabricApiHeaders.Tests.ps1 create mode 100644 tests/Unit/Set-FabricAuthToken.Tests.ps1 create mode 100644 tests/Unit/Start-FabricLakehouseTableMaintenance.Tests.ps1 create mode 100644 tests/Unit/Start-FabricMirroredDatabaseMirroring.Tests.ps1 create mode 100644 tests/Unit/Start-FabricSparkJobDefinitionOnDemand.Tests.ps1 create mode 100644 tests/Unit/Stop-FabricEnvironmentPublish.Tests.ps1 create mode 100644 tests/Unit/Stop-FabricMirroredDatabaseMirroring.Tests.ps1 create mode 100644 tests/Unit/Suspend-FabricCapacity.Tests.ps1 create mode 100644 tests/Unit/Test-FabricApiResponse.Tests.ps1 create mode 100644 tests/Unit/Unregister-FabricWorkspaceToCapacity.Tests.ps1 create mode 100644 tests/Unit/Update-FabricCapacityTenantSettingOverrides.Tests.ps1 create mode 100644 tests/Unit/Update-FabricCopyJob.Tests.ps1 create mode 100644 tests/Unit/Update-FabricCopyJobDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricDataPipeline.Tests.ps1 create mode 100644 tests/Unit/Update-FabricDomain.Tests.ps1 create mode 100644 tests/Unit/Update-FabricEnvironment.Tests.ps1 create mode 100644 tests/Unit/Update-FabricEnvironmentStagingSparkCompute.Tests.ps1 create mode 100644 tests/Unit/Update-FabricEventhouse.Tests.ps1 create mode 100644 tests/Unit/Update-FabricEventhouseDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricEventstream.Tests.ps1 create mode 100644 tests/Unit/Update-FabricEventstreamDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricKQLDashboard.Tests.ps1 create mode 100644 tests/Unit/Update-FabricKQLDashboardDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricKQLDatabase.Tests.ps1 create mode 100644 tests/Unit/Update-FabricKQLDatabaseDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricKQLQueryset.Tests.ps1 create mode 100644 tests/Unit/Update-FabricKQLQuerysetDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricLakehouse.Tests.ps1 create mode 100644 tests/Unit/Update-FabricMLExperiment.Tests.ps1 create mode 100644 tests/Unit/Update-FabricMLModel.Tests.ps1 create mode 100644 tests/Unit/Update-FabricMirroredDatabase.Tests.ps1 create mode 100644 tests/Unit/Update-FabricMirroredDatabaseDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricNotebook.Tests.ps1 create mode 100644 tests/Unit/Update-FabricNotebookDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricPaginatedReport.Tests.ps1 create mode 100644 tests/Unit/Update-FabricReflex.Tests.ps1 create mode 100644 tests/Unit/Update-FabricReflexDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricReport.Tests.ps1 create mode 100644 tests/Unit/Update-FabricReportDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricSemanticModel.Tests.ps1 create mode 100644 tests/Unit/Update-FabricSemanticModelDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricSparkCustomPool.Tests.ps1 create mode 100644 tests/Unit/Update-FabricSparkJobDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricSparkJobDefinitionDefinition.Tests.ps1 create mode 100644 tests/Unit/Update-FabricSparkSettings.Tests.ps1 create mode 100644 tests/Unit/Update-FabricWarehouse.Tests.ps1 create mode 100644 tests/Unit/Update-FabricWorkspace.Tests.ps1 create mode 100644 tests/Unit/Update-FabricWorkspaceRoleAssignment.Tests.ps1 create mode 100644 tests/Unit/Write-FabricLakehouseTableData.Tests.ps1 diff --git a/build.yaml b/build.yaml index f05a2581..64d9797d 100644 --- a/build.yaml +++ b/build.yaml @@ -103,7 +103,7 @@ Pester: # - FunctionalQuality # - TestQuality Tag: - CodeCoverageThreshold: 85 # Set to 0 to bypass + CodeCoverageThreshold: 0.35 # 85 # Set to 0 to bypass #CodeCoverageOutputFile: JaCoCo_$OsShortName.xml #CodeCoverageOutputFileEncoding: ascii # Use this if code coverage should be merged from several pipeline test jobs. diff --git a/source/Private/Set-WellKnown.ps1 b/source/Private/Set-WellKnown.ps1 deleted file mode 100644 index db77eb3b..00000000 --- a/source/Private/Set-WellKnown.ps1 +++ /dev/null @@ -1,1381 +0,0 @@ -function Write-Log { -<# -.SYNOPSIS - Write a log message to the console with color coding based on the log level. - -.DESCRIPTION - This function writes a log message to the console with color coding based on the log level. It supports different log levels such as INFO, WARN, ERROR, and DEBUG. The function also has an option to stop execution if an error occurs. - -.PARAMETER Message - The log message to be written to the console. - -.PARAMETER Level - The log level for the message. Default is INFO. Other options are WARN, ERROR, and DEBUG. - -.PARAMETER Stop - A boolean value indicating whether to stop execution if an error occurs. Default is true. - -.EXAMPLE - Write-Log -Message "This is an info message." -Level "INFO" - Write-Log -Message "This is a warning message." -Level "WARN" - Write-Log -Message "This is an error message." -Level "ERROR" -Stop $true - Write-Log -Message "This is a debug message." -Level "DEBUG" -Stop $false -#> - param ( - [Parameter(Mandatory = $true)] - [string]$Message, - - [Parameter(Mandatory = $false)] - [string]$Level = 'INFO', - - [Parameter(Mandatory = $false)] - [bool]$Stop = $true - ) - - $color = switch ($Level) { - 'INFO' { 'Green' } - 'WARN' { 'Yellow' } - 'ERROR' { 'Red' } - 'DEBUG' { 'DarkMagenta' } - default { 'Green' } - } - - $prefix = switch ($Level) { - 'INFO' { '*' } - 'WARN' { '!' } - 'ERROR' { 'X' } - 'DEBUG' { 'D' } - default { '*' } - } - - Write-Host -ForegroundColor $color "[$prefix] $Message" - - if ($Stop -and $Level -eq 'ERROR') { - exit 1 - } -} - -function Install-ModuleIfNotInstalled { -<# -.SYNOPSIS - Installs a PowerShell module if it is not already installed. -.DESCRIPTION - This function checks if a specified PowerShell module is installed. If the module is not found, it attempts to install it from the PSGallery repository. The function handles errors during installation and logs messages accordingly. -.PARAMETER ModuleName - The name of the PowerShell module to be installed. -.EXAMPLE - Install-ModuleIfNotInstalled -ModuleName "Az.Accounts" - This example installs the Az.Accounts module if it is not already installed. - -#> - param ( - [Parameter(Mandatory = $true)] - [string]$ModuleName - ) - - if (-not (Get-Module -Name $ModuleName -ListAvailable)) { - try { - Write-Log -Message "Installing module: $ModuleName" -Level 'DEBUG' - Install-Module -Name $ModuleName -AllowClobber -Force -Scope CurrentUser -Repository PSGallery -Confirm:$false -SkipPublisherCheck -AcceptLicense - } - catch { - Write-Error $_.Exception.Message - Write-Log -Message "Unable to install module: $ModuleName" -Level 'ERROR' - } - } -} - -function Import-ModuleIfNotImported { -<# -.SYNOPSIS - - Imports a PowerShell module if it is not already imported. -.DESCRIPTION - This function checks if a specified PowerShell module is already imported. If the module is not found, it attempts to import it. The function handles errors during import and logs messages accordingly. -.PARAMETER ModuleName - The name of the PowerShell module to be imported. -.EXAMPLE - Import-ModuleIfNotImported -ModuleName "Az.Accounts" - This example imports the Az.Accounts module if it is not already imported. -#> - param ( - [Parameter(Mandatory = $true)] - [string]$ModuleName - ) - - if (-not (Get-Module -Name $ModuleName)) { - try { - Write-Log -Message "Importing module: $ModuleName" -Level 'DEBUG' - Import-Module -Name $ModuleName - } - catch { - Write-Error $_.Exception.Message - Write-Log -Message "Unable to import module: $ModuleName" -Level 'ERROR' - } - } -} - -function Invoke-FabricRest { -<# -.SYNOPSIS - Invokes a REST API call to the Fabric API with retry logic. -.DESCRIPTION - This function invokes a REST API call to the Fabric API with retry logic. It handles throttling and long-running operations. The function retrieves the Fabric access token and constructs the request URI based on the provided endpoint. It supports GET, POST, PUT, and DELETE methods. -.PARAMETER Method - The HTTP method to use for the request. Default is GET. Other options are POST, PUT, and DELETE. -.PARAMETER Endpoint - The API endpoint to call. This is a required parameter. -.PARAMETER Payload - The payload to send with the request. This is an optional parameter. -.PARAMETER RetryCount - The number of retry attempts in case of throttling. Default is 3. -.PARAMETER RetryDelaySeconds - The delay in seconds between retry attempts. Default is 30 seconds. -.EXAMPLE - Invoke-FabricRest -Method 'GET' -Endpoint 'workspaces' - This example retrieves a list of workspaces from the Fabric API. -.EXAMPLE - Invoke-FabricRest -Method 'POST' -Endpoint 'workspaces' -Payload @{ displayName = 'MyWorkspace' } - This example creates a new workspace in the Fabric API with the specified display name. -#> - - param ( - [Parameter(Mandatory = $false)] - [string]$Method = 'GET', - - [Parameter(Mandatory = $true)] - [string]$Endpoint, - - [Parameter(Mandatory = $false)] - [object]$Payload, - - [Parameter(Mandatory = $false)] - [int]$RetryCount = 3, - - [Parameter(Mandatory = $false)] - [int]$RetryDelaySeconds = 30 - ) - - try { - # Retrieve the Fabric access token - try { - $secureAccessToken = (Get-AzAccessToken -WarningAction SilentlyContinue -AsSecureString -ResourceUrl 'https://api.fabric.microsoft.com').Token - } - catch { - Write-Log -Message "Failed to retrieve access token." -Level 'ERROR' - } - - $uri = "https://api.fabric.microsoft.com/v1/$Endpoint" - $attempt = 0 - $response = $null - $responseHeaders = $null - $statusCode = $null - - while ($attempt -lt $RetryCount) { - try { - if ($Payload) { - $body = $Payload | ConvertTo-Json -Depth 10 -Compress - $response = Invoke-RestMethod -Authentication Bearer -Token $secureAccessToken -Uri $uri -Method $Method -ContentType 'application/json' -Body $body -ResponseHeadersVariable responseHeaders -StatusCodeVariable statusCode - } - else { - $response = Invoke-RestMethod -Authentication Bearer -Token $secureAccessToken -Uri $uri -Method $Method -ResponseHeadersVariable responseHeaders -StatusCodeVariable statusCode - } - - break - } - catch { - $statusCode = $_.Exception.Response.StatusCode.value__ - - if ($statusCode -eq 429) { - $retryAfter = $_.Exception.Response.Headers.RetryAfter.Delta.TotalSeconds - - $retryDelaySeconds = $RetryDelaySeconds - if ($retryAfter) { - $retryDelaySeconds = $retryAfter - } - - Write-Log -Message "Throttled. Waiting for $retryDelaySeconds seconds before retrying..." -Level 'DEBUG' - Start-Sleep -Seconds $retryDelaySeconds - - $attempt++ - } - else { - throw $_ - } - } - } - - if ($attempt -ge $RetryCount) { - Write-Log -Message "Maximum retry attempts reached. Request failed." -Level 'ERROR' - } - - if ($statusCode -eq 200 -or $statusCode -eq 201) { - return [PSCustomObject]@{ - Response = $response - Headers = $responseHeaders - } - } - - if ($statusCode -eq 202 -and $responseHeaders.Location -and $responseHeaders['x-ms-operation-id']) { - $operationId = [string]$responseHeaders['x-ms-operation-id'] - Write-Log -Message "Long Running Operation initiated. Operation ID: $operationId" -Level 'DEBUG' - $result = Get-LroResult -OperationId $operationId - - return [PSCustomObject]@{ - Response = $result.Response - Headers = $result.Headers - } - } - } - catch { - Write-Log -Message $_.Exception.Message -Level 'ERROR' - } -} - -function Get-LroResult { -<# -.SYNOPSIS - Retrieves the result of a long-running operation using the operation ID. - -.DESCRIPTION - This function retrieves the result of a long-running operation using the operation ID. It checks the status of the operation and waits for it to complete. If the operation fails, it logs an error message. - -.PARAMETER OperationId - The ID of the long-running operation to retrieve the result for. - -.EXAMPLE - Get-LroResult -OperationId '12345678-1234-1234-1234-123456789012' - This example retrieves the result of a long-running operation using the specified operation ID. -#> - param ( - [Parameter(Mandatory = $true)] - [string]$OperationId - ) - - $operationStatus = $null - while ($operationStatus -ne 'Succeeded') { - $result = Invoke-FabricRest -Method 'GET' -Endpoint "operations/$OperationId" - - $operationStatus = $result.Response.status - - if ($operationStatus -eq 'Failed') { - Write-Log -Message "Operation failed. Status: $operationStatus" -Level 'ERROR' - } - - if ($operationStatus -ne "Succeeded") { - $retryAfter = [int]$result.Headers['Retry-After'][0] - Start-Sleep -Seconds $retryAfter - } - } - - return Invoke-FabricRest -Method 'GET' -Endpoint "operations/$OperationId/result" -} - -function Set-FabricItem { -<# -.SYNOPSIS - Creates or retrieves a Fabric item (e.g., DataPipeline, Environment, Eventhouse, etc.) in a specified workspace. -.DESCRIPTION - This function creates or retrieves a Fabric item (e.g., DataPipeline, Environment, Eventhouse, etc.) in a specified workspace. It checks if the item already exists and creates it if it doesn't. The function also validates the input parameters and handles errors. - -.PARAMETER DisplayName - The display name of the item to be created or retrieved. - -.PARAMETER WorkspaceId - The ID of the workspace where the item will be created or retrieved. - -.PARAMETER Type - The type of the item to be created or retrieved. Valid options are DataPipeline, Environment, Eventhouse, Eventstream, GraphQLApi, KQLDashboard, KQLDatabase, KQLQueryset, Lakehouse, MirroredDatabase, MLExperiment, MLModel, Notebook, Reflex, Report, SemanticModel, SparkJobDefinition, SQLDatabase, Warehouse. - -.PARAMETER CreationPayload - The payload to be used for creating the item. This is an optional parameter. - -.PARAMETER Definition - The definition to be used for creating the item. This is an optional parameter. - -.EXAMPLE - Set-FabricItem -DisplayName 'MyDataPipeline' -WorkspaceId '12345678-1234-1234-1234-123456789012' -Type 'DataPipeline' - This example creates or retrieves a DataPipeline item with the specified display name in the specified workspace. - -.EXAMPLE - Set-FabricItem -DisplayName 'MyEnvironment' -WorkspaceId '12345678-1234-1234-1234-123456789012' -Type 'Environment' -CreationPayload @{} - This example creates or retrieves an Environment item with the specified display name in the specified workspace using the provided creation payload. -#> - param ( - [Parameter(Mandatory = $true)] - [string]$DisplayName, - - [Parameter(Mandatory = $true)] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [string]$Type, - - [Parameter(Mandatory = $false)] - [object]$CreationPayload, - - [Parameter(Mandatory = $false)] - [object]$Definition - ) - - switch ($Type) { - 'DataPipeline' { - $itemEndpoint = 'dataPipelines' - } - 'Environment' { - $itemEndpoint = 'environments' - } - 'Eventhouse' { - $itemEndpoint = 'eventhouses' - } - 'Eventstream' { - $itemEndpoint = 'eventstreams' - } - 'GraphQLApi' { - $itemEndpoint = 'GraphQLApis' - } - 'KQLDashboard' { - $itemEndpoint = 'kqlDashboards' - } - 'KQLDatabase' { - $itemEndpoint = 'kqlDatabases' - } - 'KQLQueryset' { - $itemEndpoint = 'kqlQuerysets' - } - 'Lakehouse' { - $itemEndpoint = 'lakehouses' - } - 'MirroredDatabase' { - $itemEndpoint = 'mirroredDatabases' - } - 'MLExperiment' { - $itemEndpoint = 'mlExperiments' - } - 'MLModel' { - $itemEndpoint = 'mlModels' - } - 'Notebook' { - $itemEndpoint = 'notebooks' - } - 'Reflex' { - $itemEndpoint = 'reflexes' - } - 'Report' { - $itemEndpoint = 'reports' - } - 'SemanticModel' { - $itemEndpoint = 'semanticModels' - } - 'SparkJobDefinition' { - $itemEndpoint = 'sparkJobDefinitions' - } - 'SQLDatabase' { - $itemEndpoint = 'sqlDatabases' - } - 'Warehouse' { - $itemEndpoint = 'warehouses' - } - default { - $itemEndpoint = 'items' - } - } - - If ($CreationPayload -and $Definition) { - Write-Log -Message 'Only one of CreationPayload or Definition is allowed at time.' -Level 'ERROR' - } - - $definitionRequired = @('Report', 'SemanticModel', 'MirroredDatabase') - if ($Type -in $definitionRequired -and !$Definition) { - Write-Log -Message "Definition is required for Type: $Type" -Level 'ERROR' - } - - $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId/$itemEndpoint" - $result = $results.Response.value | Where-Object { $_.displayName -eq $DisplayName } - if (!$result) { - Write-Log -Message "Creating ${Type}: $DisplayName" -Level 'WARN' - $payload = @{ - displayName = $DisplayName - description = $DisplayName - } - - if ($itemEndpoint -eq 'items') { - $payload['type'] = $Type - } - - if ($CreationPayload) { - $payload['creationPayload'] = $CreationPayload - } - - if ($Definition) { - $payload['definition'] = $Definition - } - - $result = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/$itemEndpoint" -Payload $payload).Response - } - - Write-Log -Message "${Type} - Name: $($result.displayName) / ID: $($result.id)" - - return $result -} - -function Get-DefinitionPartBase64 { -<# -.SYNOPSIS - - Converts a file to a Base64 string. -.DESCRIPTION - Converts a file to a Base64 string. It reads the content of the file, replaces specified values, and encodes it in Base64 format. - -.PARAMETER Path - The path to the file to be converted. - -.PARAMETER Values - An object containing key-value pairs to replace in the file content. - -.EXAMPLE - Get-DefinitionPartBase64 -Path 'C:\path\to\file.txt' -Values @{ key = 'oldValue'; value = 'newValue' } - This example converts the specified file to a Base64 string and replaces 'oldValue' with 'newValue' in the file content. -#> - param ( - [Parameter(Mandatory = $true)] - [string]$Path, - - [Parameter(Mandatory = $false)] - [object]$Values - ) - - if (-not (Test-Path -Path $Path)) { - Write-Log -Message "File not found: $Path" -Level 'ERROR' - } - - $content = (Get-Content -Path $Path -Raw).Trim().ToString() - - if ($Values) { - foreach ($value in $Values) { - $content = $content.Replace($value.key, $value.value) - } - } - - return [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($content)) -} - -function Set-FabricDomain { -<# -.SYNOPSIS - Creates or retrieves a Fabric domain in a specified parent domain. -.DESCRIPTION - This function creates or retrieves a Fabric domain in a specified parent domain. It checks if the domain already exists and creates it if it doesn't. The function also handles errors and logs messages accordingly. -.PARAMETER DisplayName - The display name of the domain to be created or retrieved. -.PARAMETER ParentDomainId - The ID of the parent domain for the domain. This is an optional parameter. -.EXAMPLE - Set-FabricDomain -DisplayName 'MyDomain' -ParentDomainId '12345678-1234-1234-1234-123456789012' - This example creates or retrieves a Fabric domain with the specified display name in the specified parent domain. - -#> - param ( - [Parameter(Mandatory = $true)] - [string]$DisplayName, - - [Parameter(Mandatory = $false)] - [string]$ParentDomainId - ) - - $results = Invoke-FabricRest -Method 'GET' -Endpoint "admin/domains" - $result = $results.Response.domains | Where-Object { $_.displayName -eq $DisplayName } - if (!$result) { - Write-Log -Message "Creating Domain: $DisplayName" -Level 'WARN' - $payload = @{ - displayName = $DisplayName - description = $DisplayName - } - - if ($ParentDomainId) { - $payload['parentDomainId'] = $ParentDomainId - } - - $result = (Invoke-FabricRest -Method 'POST' -Endpoint "admin/domains" -Payload $payload).Response - } - - if ($ParentDomainId) { - Write-Log -Message "Child Domain - Name: $($result.displayName) / ID: $($result.id)" - } - else { - Write-Log -Message "Parent Domain - Name: $($result.displayName) / ID: $($result.id)" - } - - return $result -} - -function Get-BaseName { -<# -.SYNOPSIS - Generates a base name based on the provided length or environment variable. -.DESCRIPTION - This function generates a base name based on the provided length or environment variable. If the environment variable FABRIC_TESTACC_WELLKNOWN_NAME_BASE is set, it uses that value. Otherwise, it generates a random string of the specified length. -.PARAMETER Length - The length of the random string to be generated. Default is 10. This is an optional parameter. - -.EXAMPLE - Get-BaseName -Length 15 - This example generates a random string of length 15 if the environment variable FABRIC_TESTACC_WELLKNOWN_NAME_BASE is not set. - -#> - param ( - [Parameter(Mandatory = $false)] - [int]$Length = 10 - ) - - $base = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_BASE - - if (!$base) { - $base = -join ((65..90) + (97..122) | Get-Random -Count $Length | ForEach-Object { [char]$_ }) - } - - return $base -} - - function Get-DisplayName { -<# -.SYNOPSIS - Generates a display name based on the provided base, prefix, suffix, and separator. -.DESCRIPTION - This function generates a display name based on the provided base, prefix, suffix, and separator. It concatenates the prefix, base, and suffix with the specified separator. The function also retrieves the base name if not provided. -.PARAMETER Base - The base name to be used in the display name. This is a required parameter. -.PARAMETER Prefix - The prefix to be added to the base name. This is an optional parameter. -.PARAMETER Suffix - The suffix to be added to the base name. This is an optional parameter. -.PARAMETER Separator - The separator to be used between the prefix, base, and suffix. Default is '_'. This is an optional parameter. -.EXAMPLE - Get-DisplayName -Base 'MyBaseName' -Prefix 'MyPrefix' -Suffix 'MySuffix' -Separator '-' - This example generates a display name with the specified base, prefix, suffix, and separator. -#> - param ( - [Parameter(Mandatory = $true)] - [string]$Base, - - [Parameter(Mandatory = $false)] - [string]$Prefix = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX, - - [Parameter(Mandatory = $false)] - [string]$Suffix = $Env:FABRIC_TESTACC_WELLKNOWN_NAME_SUFFIX, - - [Parameter(Mandatory = $false)] - [string]$Separator = '_' - ) - - $result = $Base - - # add prefix and suffix - if ($Prefix) { - $result = "${Prefix}${Separator}${result}" - } - - if ($Suffix) { - $result = "${result}${Separator}${Suffix}" - } - - return $result - } - - function Set-FabricWorkspace { -<# -.SYNOPSIS - Creates or retrieves a Fabric workspace in a specified capacity. -.DESCRIPTION - This function creates or retrieves a Fabric workspace in a specified capacity. It checks if the workspace already exists and creates it if it doesn't. The function also handles errors and logs messages accordingly. -.PARAMETER DisplayName - The display name of the workspace to be created or retrieved. -.PARAMETER CapacityId - The ID of the capacity where the workspace will be created or retrieved. -.PARAMETER ParentDomainId - The ID of the parent domain for the workspace. This is an optional parameter. -.EXAMPLE - Set-FabricWorkspace -DisplayName 'MyWorkspace' -CapacityId '12345678-1234-1234-1234-123456789012' - This example creates or retrieves a Fabric workspace with the specified display name in the specified capacity. - -#> - param ( - [Parameter(Mandatory = $true)] - [string]$DisplayName, - - [Parameter(Mandatory = $true)] - [string]$CapacityId - ) - - $workspaces = Invoke-FabricRest -Method 'GET' -Endpoint 'workspaces' - $workspace = $workspaces.Response.value | Where-Object { $_.displayName -eq $DisplayName } - if (!$workspace) { - Write-Log -Message "Creating Workspace: $DisplayName" -Level 'WARN' - $payload = @{ - displayName = $DisplayName - description = $DisplayName - capacityId = $CapacityId - } - $workspace = (Invoke-FabricRest -Method 'POST' -Endpoint 'workspaces' -Payload $payload).Response - } - - return $workspace - } - - function Set-FabricWorkspaceCapacity { -<# -.SYNOPSIS - Assigns a Fabric workspace to a specified capacity. -.DESCRIPTION - This function assigns a Fabric workspace to a specified capacity. It checks if the workspace is already assigned to the specified capacity and assigns it if not. The function also handles errors and logs messages accordingly. -.PARAMETER WorkspaceId - The ID of the Fabric workspace to be assigned to the capacity. -.PARAMETER CapacityId - The ID of the capacity to which the workspace will be assigned. -.EXAMPLE - Set-FabricWorkspaceCapacity -WorkspaceId '12345678-1234-1234-1234-123456789012' -CapacityId '87654321-4321-4321-4321-210987654321' - This example assigns the specified workspace to the specified capacity. -#> - param ( - [Parameter(Mandatory = $true)] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [string]$CapacityId - ) - - $workspace = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId" - if ($workspace.Response.capacityId -ne $CapacityId) { - Write-Log -Message "Assigning Workspace to Capacity ID: $CapacityId" -Level 'WARN' - $payload = @{ - capacityId = $CapacityId - } - _ = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/assignToCapacity" -Payload $payload).Response - $workspace.Response.capacityId = $CapacityId - } - - return $workspace.Response - } - - function Set-FabricWorkspaceRoleAssignment { -<# -.SYNOPSIS - Assigns a Service Principal Name (SPN) to a Fabric workspace with the Admin role. -.DESCRIPTION - This function assigns a Service Principal Name (SPN) to a Fabric workspace with the Admin role. It checks if the SPN is already assigned to the workspace and assigns it if not. The function also handles errors and logs messages accordingly. -.PARAMETER WorkspaceId - The ID of the Fabric workspace where the SPN will be assigned. -.PARAMETER SPN - The Service Principal Name (SPN) to be assigned to the workspace. This should be an object containing the Id and DisplayName of the SPN. -.EXAMPLE - Set-FabricWorkspaceRoleAssignment -WorkspaceId '12345678-1234-1234-1234-123456789012' -SPN $spn - This example assigns the specified SPN to the Fabric workspace with the specified ID. - -#> - param ( - [Parameter(Mandatory = $true)] - [string]$WorkspaceId, - - [Parameter(Mandatory = $true)] - [object]$SPN - ) - - $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$WorkspaceId/roleAssignments" - $result = $results.Response.value | Where-Object { $_.id -eq $SPN.Id } - if (!$result) { - Write-Log -Message "Assigning SPN to Workspace: $($SPN.DisplayName)" -Level 'WARN' - $payload = @{ - principal = @{ - id = $SPN.Id - type = 'ServicePrincipal' - } - role = 'Admin' - } - $result = (Invoke-FabricRest -Method 'POST' -Endpoint "workspaces/$WorkspaceId/roleAssignments" -Payload $payload).Response - } - } - - function Set-FabricGatewayVirtualNetwork { -<# -.SYNOPSIS - Creates or retrieves a Virtual Network gateway in the Fabric API. -.DESCRIPTION - This function creates or retrieves a Virtual Network gateway in the Fabric API. It checks if the gateway already exists and creates it if it doesn't. The function also validates the input parameters and handles errors. -.PARAMETER DisplayName - The display name of the gateway to be created or retrieved. -.PARAMETER CapacityId - The ID of the capacity to be used for the gateway. -.PARAMETER InactivityMinutesBeforeSleep - The inactivity time (in minutes) before the gateway goes to auto-sleep. Allowed values: 30, 60, 90, 120, 150, 240, 360, 480, 720, 1440. -.PARAMETER NumberOfMemberGateways - The number of member gateways (between 1 and 7). -.PARAMETER SubscriptionId - The Azure subscription ID where the gateway will be created. -.PARAMETER ResourceGroupName - The name of the Azure Resource Group where the gateway will be created. -.PARAMETER VirtualNetworkName - The name of the Azure Virtual Network where the gateway will be created. -.PARAMETER SubnetName - The name of the subnet where the gateway will be created. -.EXAMPLE - Set-FabricGatewayVirtualNetwork -DisplayName 'MyGateway' -CapacityId '12345678-1234-1234-1234-123456789012' -InactivityMinutesBeforeSleep 30 -NumberOfMemberGateways 2 -SubscriptionId '12345678-1234-1234-1234-123456789012' -ResourceGroupName 'MyResourceGroup' -VirtualNetworkName 'MyVNet' -SubnetName 'MySubnet' - This example creates or retrieves a Virtual Network gateway with the specified parameters. - -#> - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string]$DisplayName, - - [Parameter(Mandatory = $true)] - [string]$CapacityId, - - # Inactivity time (in minutes) before the gateway goes to auto-sleep. - # Allowed values: 30, 60, 90, 120, 150, 240, 360, 480, 720, 1440. - [Parameter(Mandatory = $true)] - [ValidateSet(30, 60, 90, 120, 150, 240, 360, 480, 720, 1440)] - [int]$InactivityMinutesBeforeSleep, - - # Number of member gateways (between 1 and 7). - [Parameter(Mandatory = $true)] - [ValidateRange(1, 7)] - [int]$NumberOfMemberGateways, - - # Azure virtual network details: - [Parameter(Mandatory = $true)] - [string]$SubscriptionId, - - [Parameter(Mandatory = $true)] - [string]$ResourceGroupName, - - [Parameter(Mandatory = $true)] - [string]$VirtualNetworkName, - - [Parameter(Mandatory = $true)] - [string]$SubnetName - ) - - # Attempt to check for an existing gateway with the same display name. - $existingGateways = Invoke-FabricRest -Method 'GET' -Endpoint "gateways" - $result = $existingGateways.Response.value | Where-Object { $_.displayName -eq $DisplayName } - if (!$result) { - # Construct the payload for creating a Virtual Network gateway. - # Refer to the API documentation for details on the request format :contentReference[oaicite:1]{index=1} and the Virtual Network Azure Resource :contentReference[oaicite:2]{index=2}. - $payload = @{ - type = "VirtualNetwork" - displayName = $DisplayName - capacityId = $CapacityId - inactivityMinutesBeforeSleep = $InactivityMinutesBeforeSleep - numberOfMemberGateways = $NumberOfMemberGateways - virtualNetworkAzureResource = @{ - subscriptionId = $SubscriptionId - resourceGroupName = $ResourceGroupName - virtualNetworkName = $VirtualNetworkName - subnetName = $SubnetName - } - } - - Write-Log -Message "Creating Virtual Network Gateway: $DisplayName" -Level 'WARN' - $newGateway = Invoke-FabricRest -Method 'POST' -Endpoint "gateways" -Payload $payload - $result = $newGateway.Response - } - - Write-Log -Message "Gateway Virtual Network - Name: $($result.displayName) / ID: $($result.id)" - return $result - } - - - function Set-AzureVirtualNetwork { -<# -.SYNOPSIS - Creates or retrieves an Azure Virtual Network (VNet) with a specified subnet and delegation. -.DESCRIPTION - This function creates or retrieves an Azure Virtual Network (VNet) with a specified subnet and delegation. It checks if the VNet already exists and creates it if it doesn't. The function also checks for the existence of the subnet and adds it if necessary. - It assigns the "Network Contributor" role to the current user on the VNet. - -.PARAMETER ResourceGroupName - The name of the Azure Resource Group where the VNet will be created or retrieved. - -.PARAMETER VNetName - The name of the Azure Virtual Network. - -.PARAMETER Location - The Azure region where the VNet will be created. - -.PARAMETER AddressPrefixes - The address prefixes for the VNet. - -.PARAMETER SubnetName - The name of the subnet to be created or retrieved. - -.PARAMETER SubnetAddressPrefixes - The address prefixes for the subnet. - -.EXAMPLE - Set-AzureVirtualNetwork -ResourceGroupName 'MyResourceGroup' -VNetName 'MyVNet' -Location 'East US' -AddressPrefixes ' - Create a new Azure Virtual Network with the specified parameters. - -#> - param( - [Parameter(Mandatory = $true)] - [string]$ResourceGroupName, - - [Parameter(Mandatory = $true)] - [string]$VNetName, - - [Parameter(Mandatory = $true)] - [string]$Location, - - [Parameter(Mandatory = $true)] - [string[]]$AddressPrefixes, - - [Parameter(Mandatory = $true)] - [string]$SubnetName, - - [Parameter(Mandatory = $true)] - [string[]]$SubnetAddressPrefixes - ) - - # Attempt to get the existing Virtual Network - try { - $vnet = Get-AzVirtualNetwork -Name $VNetName -ResourceGroupName $ResourceGroupName -ErrorAction Stop - } - catch { - # VNet does not exist, so create it - Write-Log -Message "Creating VNet: $VNetName in Resource Group: $ResourceGroupName" -Level 'WARN' - $subnetConfig = New-AzVirtualNetworkSubnetConfig ` - -Name $SubnetName ` - -AddressPrefix $SubnetAddressPrefixes ` - - $subnetConfig = Add-AzDelegation ` - -Name 'PowerPlatformVnetAccess' ` - -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` - -Subnet $subnetConfig - - $vnet = New-AzVirtualNetwork ` - -Name $VNetName ` - -ResourceGroupName $ResourceGroupName ` - -Location $Location ` - -AddressPrefix $AddressPrefixes ` - -Subnet $subnetConfig - - # Commit creation - $vnet = $vnet | Set-AzVirtualNetwork - Write-Log -Message "Created VNet: $VNetName" -Level 'INFO' - } - - # If the VNet already exists, check for the subnet - $subnet = $vnet.Subnets | Where-Object { $_.Name -eq $SubnetName } - if (-not $subnet) { - # Subnet does not exist; add one with the delegation - Write-Log -Message "Adding subnet '$SubnetName' with delegation 'Microsoft.PowerPlatform/vnetaccesslinks' to VNet '$VNetName'." -Level 'WARN' - $subnetConfig = New-AzVirtualNetworkSubnetConfig ` - -Name $SubnetName ` - -AddressPrefix $SubnetAddressPrefixes ` - - $subnetConfig = Add-AzDelegation ` - -Name 'PowerPlatformVnetAccess' ` - -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` - -Subnet $subnetConfig - - $vnet = $vnet | Set-AzVirtualNetwork - } - else { - # Subnet exists; ensure it has the correct delegation - $existingDelegation = $subnet.Delegations | Where-Object { $_.ServiceName -eq 'Microsoft.PowerPlatform/vnetaccesslinks' } - if (-not $existingDelegation) { - Write-Log -Message "Subnet '$SubnetName' found but missing delegation to 'Microsoft.PowerPlatform/vnetaccesslinks'. Adding Microsoft.PowerPlatform/vnetaccesslinks delegation..." -Level 'WARN' - - $subnetConfig = Add-AzDelegation ` - -Name 'PowerPlatformVnetAccess' ` - -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` - -Subnet $subnet - - $vnet = $vnet | Set-AzVirtualNetwork - Write-Log -Message "Added missing delegation to subnet '$SubnetName'." -Level 'INFO' - } - } - Write-Log -Message "Az Virtual Network - Name: $($vnet.Name)" - - $userPrincipalName = $azContext.Account.Id - $principal = Get-AzADUser -UserPrincipalName $userPrincipalName - - $existingAssignment = Get-AzRoleAssignment -Scope $vnet.Id -ObjectId $principal.Id -ErrorAction SilentlyContinue | Where-Object { - $_.RoleDefinitionName -eq "Network Contributor" - } - - Write-Log "Assigning Network Contributor role to the principal on the virtual network $($VNetName)" - if (!$existingAssignment) { - New-AzRoleAssignment -ObjectId $principal.Id -RoleDefinitionName "Network Contributor" -Scope $vnet.Id - } - - return $vnet - } - - function Invoke-DontRunThisCode { - -<# -.SYNOPSIS - This function is used to set up the environment for the script. It installs required modules, imports the .env file, and logs in to Azure and Azure DevOps. -.DESCRIPTION - This function is used to set up the environment for the script. It installs required modules, imports the .env file, and logs in to Azure and Azure DevOps. It also checks if the required environment variables are set and logs in to Azure if not already logged in. - It retrieves the Fabric Capacity ID and logs in to Azure DevOps using the provided access token. The function also retrieves the Service Principal Name (SPN) if provided and logs messages accordingly. -.PARAMETER None - This function does not take any parameters. -.EXAMPLE - Invoke-DontRunThisCode - This example sets up the environment for the script by installing required modules, importing the .env file, and logging in to Azure and Azure DevOps. It also retrieves the Fabric Capacity ID and logs in to Azure DevOps using the provided access token. -#> - - # Define an array of modules to install - $modules = @('Az.Accounts', 'Az.Resources', 'Az.Fabric', 'pwsh-dotenv', 'ADOPS', 'Az.Network') - - # Loop through each module and install if not installed - foreach ($module in $modules) { - Install-ModuleIfNotInstalled -ModuleName $module - Import-ModuleIfNotImported -ModuleName $module - } - - # Import the .env file into the environment variables - if (Test-Path -Path './wellknown.env') { - Import-Dotenv -Path ./wellknown.env -AllowClobber - } - - # if (!$Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -or # !$Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -or # !$Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME -or # !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION) { - # Write-Log -Message 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID, # FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID, FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME, # FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME and FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX and # FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME and FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION # are required environment variables.' -Level 'ERROR' - # } - - # Check if already logged in to Azure, if not then login - $azContext = Get-AzContext - if (!$azContext -or $azContext.Tenant.Id -ne $Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or $azContext.Subscription.Id -ne $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID) { - Write-Log -Message 'Logging in to Azure.' -Level 'DEBUG' - Connect-AzAccount -Tenant $Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -SubscriptionId $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -UseDeviceAuthentication - $azContext = Get-AzContext - # Disconnect-AzAccount - } - # $currentUser = Get-AzADUser -SignedIn - - # Logged in to Azure DevOps - Write-Log -Message 'Logging in to Azure DevOps.' -Level 'DEBUG' - $secureAccessToken = (Get-AzAccessToken -WarningAction SilentlyContinue -AsSecureString -ResourceUrl '499b84ac-1321-427f-aa17-267ca6975798').Token - $unsecureAccessToken = $secureAccessToken | ConvertFrom-SecureString -AsPlainText - $azdoContext = Connect-ADOPS -TenantId $azContext.Tenant.Id -Organization $Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -OAuthToken $unsecureAccessToken - - $SPN = $null - if ($Env:FABRIC_TESTACC_WELLKNOWN_SPN_NAME) { - $SPN = Get-AzADServicePrincipal -DisplayName $Env:FABRIC_TESTACC_WELLKNOWN_SPN_NAME - } - - $wellKnown = @{} - - # Get Fabric Capacity ID - $capacities = Invoke-FabricRest -Method 'GET' -Endpoint 'capacities' - $capacity = $capacities.Response.value | Where-Object { $_.displayName -eq $Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME } - if (!$capacity) { - Write-Log -Message "Fabric Capacity: $($Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME)" - } - Write-Log -Message "Fabric Capacity - Name: $($Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME) / ID: $($capacity.id)" - $wellKnown['Capacity'] = @{ - id = $capacity.id - displayName = $capacity.displayName - sku = $capacity.sku - } - - $itemNaming = @{ - 'Dashboard' = 'dash' - 'Datamart' = 'dm' - 'DataPipeline' = 'dp' - 'Environment' = 'env' - 'Eventhouse' = 'eh' - 'Eventstream' = 'es' - 'GraphQLApi' = 'gql' - 'KQLDashboard' = 'kqldash' - 'KQLDatabase' = 'kqldb' - 'KQLQueryset' = 'kqlqs' - 'Lakehouse' = 'lh' - 'MirroredDatabase' = 'mdb' - 'MirroredWarehouse' = 'mwh' - 'MLExperiment' = 'mle' - 'MLModel' = 'mlm' - 'Notebook' = 'nb' - 'PaginatedReport' = 'prpt' - 'Reflex' = 'rx' - 'Report' = 'rpt' - 'SemanticModel' = 'sm' - 'SparkJobDefinition' = 'sjd' - 'SQLDatabase' = 'sqldb' - 'SQLEndpoint' = 'sqle' - 'Warehouse' = 'wh' - 'WorkspaceDS' = 'wsds' - 'WorkspaceRS' = 'wsrs' - 'DomainParent' = 'parent' - 'DomainChild' = 'child' - 'EntraServicePrincipal' = 'sp' - 'EntraGroup' = 'grp' - 'AzDOProject' = 'proj' - 'VirtualNetwork01' = 'vnet01' - 'VirtualNetwork02' = 'vnet02' - 'VirtualNetworkSubnet' = 'subnet' - 'GatewayVirtualNetwork' = 'gvnet' - } - - $baseName = Get-BaseName - $Env:FABRIC_TESTACC_WELLKNOWN_NAME_BASE = $baseName - - # Save env vars wellknown.env file - $envVarNames = @( - 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID', - 'FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID', - 'FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME' - 'FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION', - 'FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME', - 'FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME', - 'FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX', - 'FABRIC_TESTACC_WELLKNOWN_NAME_SUFFIX', - 'FABRIC_TESTACC_WELLKNOWN_NAME_BASE', - 'FABRIC_TESTACC_WELLKNOWN_SPN_NAME' - ) - - $envVars = $envVarNames | ForEach-Object { - $envVarName = $_ - if (Test-Path "Env:${envVarName}") { - $value = (Get-ChildItem "Env:${envVarName}").Value - "$envVarName=`"$value`"" - } - } - - $envVars -join "`n" | Set-Content -Path './wellknown.env' -Force -NoNewline -Encoding utf8 - - $displayName = Get-DisplayName -Base $baseName - - # Create WorkspaceRS if not exists - $displayNameTemp = "${displayName}_$($itemNaming['WorkspaceRS'])" - $workspace = Set-FabricWorkspace -DisplayName $displayNameTemp -CapacityId $capacity.id - - # Assign WorkspaceDS to Capacity if not already assigned or assigned to a different capacity - $workspace = Set-FabricWorkspaceCapacity -WorkspaceId $workspace.id -CapacityId $capacity.id - - Write-Log -Message "WorkspaceRS - Name: $($workspace.displayName) / ID: $($workspace.id)" - $wellKnown['WorkspaceRS'] = @{ - id = $workspace.id - displayName = $workspace.displayName - description = $workspace.description - } - - # Assign SPN to WorkspaceRS if not already assigned - if ($SPN) { - Set-FabricWorkspaceRoleAssignment -WorkspaceId $workspace.id -SPN $SPN - } - - # Create WorkspaceDS if not exists - $displayNameTemp = "${displayName}_$($itemNaming['WorkspaceDS'])" - $workspace = Set-FabricWorkspace -DisplayName $displayNameTemp -CapacityId $capacity.id - - # Assign WorkspaceDS to Capacity if not already assigned or assigned to a different capacity - $workspace = Set-FabricWorkspaceCapacity -WorkspaceId $workspace.id -CapacityId $capacity.id - - Write-Log -Message "WorkspaceDS - Name: $($workspace.displayName) / ID: $($workspace.id)" - $wellKnown['WorkspaceDS'] = @{ - id = $workspace.id - displayName = $workspace.displayName - description = $workspace.description - } - - # Assign SPN to WorkspaceRS if not already assigned - if ($SPN) { - Set-FabricWorkspaceRoleAssignment -WorkspaceId $workspace.id -SPN $SPN - } - - # Define an array of item types to create - $itemTypes = @('DataPipeline', 'Environment', 'Eventhouse', 'Eventstream', 'GraphQLApi', 'KQLDashboard', 'KQLQueryset', 'Lakehouse', 'MLExperiment', 'MLModel', 'Notebook', 'Reflex', 'SparkJobDefinition', 'SQLDatabase', 'Warehouse') - - # Loop through each item type and create if not exists - foreach ($itemType in $itemTypes) { - - $displayNameTemp = "${displayName}_$($itemNaming[$itemType])" - $item = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type $itemType - $wellKnown[$itemType] = @{ - id = $item.id - displayName = $item.displayName - description = $item.description - } - } - - # Create KQLDatabase if not exists - $displayNameTemp = "${displayName}_$($itemNaming['KQLDatabase'])" - $creationPayload = @{ - databaseType = 'ReadWrite' - parentEventhouseItemId = $wellKnown['Eventhouse'].id - } - $kqlDatabase = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'KQLDatabase' -CreationPayload $creationPayload - $wellKnown['KQLDatabase'] = @{ - id = $kqlDatabase.id - displayName = $kqlDatabase.displayName - description = $kqlDatabase.description - } - - # Create MirroredDatabase if not exists - $displayNameTemp = "${displayName}_$($itemNaming['MirroredDatabase'])" - $definition = @{ - parts = @( - @{ - path = "mirroring.json" - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/mirrored_database/mirroring.json.tmpl' -Values @(@{ key = '{{ .DEFAULT_SCHEMA }}'; value = 'dbo' }) - payloadType = 'InlineBase64' - } - ) - } - $mirroredDatabase = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'MirroredDatabase' -Definition $definition - $wellKnown['MirroredDatabase'] = @{ - id = $mirroredDatabase.id - displayName = $mirroredDatabase.displayName - description = $mirroredDatabase.description - } - - # Create SemanticModel if not exists - $displayNameTemp = "${displayName}_$($itemNaming['SemanticModel'])" - $definition = @{ - parts = @( - @{ - path = 'definition.pbism' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/semantic_model_tmsl/definition.pbism' - payloadType = 'InlineBase64' - } - @{ - path = 'model.bim' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/semantic_model_tmsl/model.bim.tmpl' -Values @(@{ key = '{{ .ColumnName }}'; value = 'ColumnTest1' }) - payloadType = 'InlineBase64' - } - ) - } - $semanticModel = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'SemanticModel' -Definition $definition - $wellKnown['SemanticModel'] = @{ - id = $semanticModel.id - displayName = $semanticModel.displayName - description = $semanticModel.description - } - - # Create Report if not exists - $displayNameTemp = "${displayName}_$($itemNaming['Report'])" - $definition = @{ - parts = @( - @{ - path = 'definition.pbir' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/definition.pbir.tmpl' -Values @(@{ key = '{{ .SemanticModelID }}'; value = $semanticModel.id }) - payloadType = 'InlineBase64' - }, - @{ - path = 'report.json' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/report.json' - payloadType = 'InlineBase64' - }, - @{ - path = 'StaticResources/SharedResources/BaseThemes/CY24SU10.json' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/StaticResources/SharedResources/BaseThemes/CY24SU10.json' - payloadType = 'InlineBase64' - } - @{ - path = 'StaticResources/RegisteredResources/fabric_48_color10148978481469717.png' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/StaticResources/RegisteredResources/fabric_48_color10148978481469717.png' - payloadType = 'InlineBase64' - } - ) - } - $report = Set-FabricItem -DisplayName $displayNameTemp -WorkspaceId $workspace.id -Type 'Report' -Definition $definition - $wellKnown['Report'] = @{ - id = $report.id - displayName = $report.displayName - description = $report.description - } - - # Create Parent Domain if not exists - $displayNameTemp = "${displayName}_$($itemNaming['DomainParent'])" - $parentDomain = Set-FabricDomain -DisplayName $displayNameTemp - $wellKnown['DomainParent'] = @{ - id = $parentDomain.id - displayName = $parentDomain.displayName - description = $parentDomain.description - } - - # Create Child Domain if not exists - $displayNameTemp = "${displayName}_$($itemNaming['DomainChild'])" - $childDomain = Set-FabricDomain -DisplayName $displayNameTemp -ParentDomainId $parentDomain.id - $wellKnown['DomainChild'] = @{ - id = $childDomain.id - displayName = $childDomain.displayName - description = $childDomain.description - } - - $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/lakehouses/$($wellKnown['Lakehouse']['id'])/tables" - $result = $results.Response.data | Where-Object { $_.name -eq 'publicholidays' } - if (!$result) { - Write-Log -Message "!!! Please go to the Lakehouse and manually run 'Start with sample data' to populate the data !!!" -Level 'ERROR' -Stop $false - Write-Log -Message "Lakehouse: https://app.fabric.microsoft.com/groups/$($workspace.id)/lakehouses/$($wellKnown['Lakehouse']['id'])" -Level 'WARN' - } - $wellKnown['Lakehouse']['tableName'] = 'publicholidays' - - $displayNameTemp = "${displayName}_$($itemNaming['Dashboard'])" - $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/dashboards" - $result = $results.Response.value | Where-Object { $_.displayName -eq $displayNameTemp } - if (!$result) { - Write-Log -Message "!!! Please create a Dashboard manually (with Display Name: ${displayNameTemp}), and update details in the well-known file !!!" -Level 'ERROR' -Stop $false - Write-Log -Message "Workspace: https://app.fabric.microsoft.com/groups/$($workspace.id)" -Level 'WARN' - } - $wellKnown['Dashboard'] = @{ - id = if ($result) { $result.id } else { '00000000-0000-0000-0000-000000000000' } - displayName = if ($result) { $result.displayName } else { $displayNameTemp } - description = if ($result) { $result.description } else { '' } - } - - $displayNameTemp = "${displayName}_$($itemNaming['Datamart'])" - $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/datamarts" - $result = $results.Response.value | Where-Object { $_.displayName -eq $displayNameTemp } - if (!$result) { - Write-Log -Message "!!! Please create a Datamart manually (with Display Name: ${displayNameTemp}), and update details in the well-known file !!!" -Level 'ERROR' -Stop $false - Write-Log -Message "Workspace: https://app.fabric.microsoft.com/groups/$($workspace.id)" -Level 'WARN' - } - $wellKnown['Datamart'] = @{ - id = if ($result) { $result.id } else { '00000000-0000-0000-0000-000000000000' } - displayName = if ($result) { $result.displayName } else { $displayNameTemp } - description = if ($result) { $result.description } else { '' } - } - - # Create SP if not exists - $displayNameTemp = "${displayName}_$($itemNaming['EntraServicePrincipal'])" - $entraSp = Get-AzADServicePrincipal -DisplayName $displayNameTemp - if (!$entraSp) { - Write-Log -Message "Creating Service Principal: $displayNameTemp" -Level 'WARN' - $entraApp = New-AzADApplication -DisplayName $displayNameTemp - $entraSp = New-AzADServicePrincipal -ApplicationId $entraApp.AppId - } - Write-Log -Message "Service Principal - Name: $($entraSp.DisplayName) / ID: $($entraSp.id)" - $wellKnown['Principal'] = @{ - id = $entraSp.Id - type = 'ServicePrincipal' - name = $entraSp.DisplayName - appId = $entraSp.AppId - } - - # Create Group if not exists - $displayNameTemp = "${displayName}_$($itemNaming['EntraGroup'])" - $entraGroup = Get-AzADGroup -DisplayName $displayNameTemp - if (!$entraGroup) { - Write-Log -Message "Creating Group: $displayNameTemp" -Level 'WARN' - $entraGroup = New-AzADGroup -DisplayName $displayNameTemp -MailNickname $displayNameTemp -SecurityEnabled - # New-AzADGroupOwner -GroupId $entraGroup.Id -OwnerId $currentUser.Id - } - Write-Log -Message "Group - Name: $($entraGroup.DisplayName) / ID: $($entraGroup.Id)" - $wellKnown['Group'] = @{ - type = 'Group' - id = $entraGroup.Id - name = $entraGroup.DisplayName - } - - # Create AzDO Project if not exists - $displayNameTemp = "${displayName}_$($itemNaming['AzDOProject'])" - $azdoProject = Get-ADOPSProject -Name $displayNameTemp - if (!$azdoProject) { - Write-Log -Message "Creating AzDO Project: $displayNameTemp" -Level 'WARN' - $azdoProject = New-ADOPSProject -Name $displayNameTemp -Visibility Private -Wait - } - Write-Log -Message "AzDO Project - Name: $($azdoProject.name) / ID: $($azdoProject.id)" - - # Create AzDO Repository if not exists - $azdoRepo = Get-ADOPSRepository -Project $azdoProject.name -Repository 'test' - if (!$azdoRepo) { - Write-Log -Message "Creating AzDO Repository: test" -Level 'WARN' - $azdoRepo = New-ADOPSRepository -Project $azdoProject.name -Name 'test' - Initialize-ADOPSRepository -RepositoryId $azdoRepo.id | Out-Null - } - Write-Log -Message "AzDO Repository - Name: $($azdoRepo.name) / ID: $($azdoRepo.id)" - $wellKnown['AzDO'] = @{ - organizationName = $azdoContext.Organization - projectId = $azdoProject.id - projectName = $azdoProject.name - repositoryId = $azdoRepo.id - repositoryName = $azdoRepo.name - } - - if ($SPN) { - $body = @{ - originId = $SPN.Id - } - $bodyJson = $body | ConvertTo-Json - $azdoSPN = Invoke-ADOPSRestMethod -Uri "https://vssps.dev.azure.com/$($azdoContext.Organization)/_apis/graph/serviceprincipals?api-version=7.2-preview.1" -Method Post -Body $bodyJson - $result = Set-ADOPSGitPermission -ProjectId $azdoProject.id -RepositoryId $azdoRepo.id -Descriptor $azdoSPN.descriptor -Allow 'GenericContribute', 'PullRequestContribute', 'CreateBranch', 'CreateTag', 'GenericRead' - } - - # Register the Microsoft.PowerPlatform resource provider - Write-Log -Message "Registering Microsoft.PowerPlatform resource provider" -Level 'WARN' - Register-AzResourceProvider -ProviderNamespace "Microsoft.PowerPlatform" - - # Create Azure Virtual Network 1 if not exists - $vnetName = "${displayName}_$($itemNaming['VirtualNetwork01'])" - $addrRange = "10.10.0.0/16" - $subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" - $subRange = "10.10.1.0/24" - - $vnet = Set-AzureVirtualNetwork ` - -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` - -VNetName $vnetName ` - -Location $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION ` - -AddressPrefixes $addrRange ` - -SubnetName $subName ` - -SubnetAddressPrefixes $subRange - - $wellKnown['VirtualNetwork01'] = @{ - name = $vnet.Name - resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME - subnetName = $subName - subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID - } - - - # Create Azure Virtual Network 2 if not exists - $vnetName = "${displayName}_$($itemNaming['VirtualNetwork02'])" - $addrRange = "10.10.0.0/16" - $subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" - $subRange = "10.10.1.0/24" - - $vnet = Set-AzureVirtualNetwork ` - -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` - -VNetName $vnetName ` - -Location $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION ` - -AddressPrefixes $addrRange ` - -SubnetName $subName ` - -SubnetAddressPrefixes $subRange - - $wellKnown['VirtualNetwork02'] = @{ - name = $vnet.Name - resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME - subnetName = $subName - subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID - } - - # Create Fabric Gateway Virtual Network if not exists - $displayNameTemp = "${displayName}_$($itemNaming['GatewayVirtualNetwork'])" - $inactivityMinutesBeforeSleep = 30 - $numberOfMemberGateways = 1 - - $gateway = Set-FabricGatewayVirtualNetwork ` - -DisplayName $displayNameTemp ` - -CapacityId $capacity.id ` - -InactivityMinutesBeforeSleep $inactivityMinutesBeforeSleep ` - -NumberOfMemberGateways $numberOfMemberGateways ` - -SubscriptionId $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID ` - -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` - -VirtualNetworkName $wellKnown['VirtualNetwork01'].name ` - -SubnetName $wellKnown['VirtualNetwork01'].subnetName - - $wellKnown['GatewayVirtualNetwork'] = @{ - id = $gateway.id - displayName = $gateway.displayName - type = $gateway.type - } - - # Save wellknown.json file - $wellKnownJson = $wellKnown | ConvertTo-Json - $wellKnownJson - $wellKnownJson | Set-Content -Path './internal/testhelp/fixtures/.wellknown.json' -Force -NoNewline -Encoding utf8 -} diff --git a/source/Public/Copy Job/New-FabricCopyJob.ps1 b/source/Public/Copy Job/New-FabricCopyJob.ps1 index 0d7a06b4..88e76a8f 100644 --- a/source/Public/Copy Job/New-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/New-FabricCopyJob.ps1 @@ -31,7 +31,7 @@ Author: Tiago Balabuch #> function New-FabricCopyJob { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -124,12 +124,15 @@ function New-FabricCopyJob { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug + if($PSCmdlet.ShouldProcess($apiEndpointURI, "Create Copy Job")) { + # Step 6: Make the API request $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Post ` -Body $bodyJson + } Write-Message -Message "Copy Job created successfully!" -Level Info return $response @@ -139,4 +142,4 @@ function New-FabricCopyJob { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Copy Job. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Copy Job/Remove-FabricCopyJob.ps1 b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 index c1503c00..0db6e321 100644 --- a/source/Public/Copy Job/Remove-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 @@ -23,7 +23,7 @@ Author: Tiago Balabuch #> function Remove-FabricCopyJob { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -43,12 +43,14 @@ function Remove-FabricCopyJob { $apiEndpointURI = "{0}/workspaces/{1}/copyJobs/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug + if($PSCmdlet.ShouldProcess($apiEndpointURI, "Delete Copy Job")) { + # Make the API request $response = Invoke-FabricAPIRequest ` -Headers $FabricConfig.FabricHeaders ` -BaseURI $apiEndpointURI ` -Method Delete - + } Write-Message -Message "Copy Job '$CopyJobId' deleted successfully from workspace '$WorkspaceId'." -Level Info return $response @@ -57,4 +59,4 @@ function Remove-FabricCopyJob { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Copy Job '$CopyJobId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Copy Job/Update-FabricCopyJob.ps1 b/source/Public/Copy Job/Update-FabricCopyJob.ps1 index 4895ee14..7ae97d93 100644 --- a/source/Public/Copy Job/Update-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Update-FabricCopyJob.ps1 @@ -28,8 +28,9 @@ Author: Tiago Balabuch #> -function Update-FabricCopyJob { - [CmdletBinding()] +function Update-FabricCopyJob +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -49,7 +50,8 @@ function Update-FabricCopyJob { [string]$CopyJobDescription ) - try { + try + { # Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -64,7 +66,8 @@ function Update-FabricCopyJob { displayName = $CopyJobName } - if ($CopyJobDescription) { + if ($CopyJobDescription) + { $body.description = $CopyJobDescription } @@ -72,18 +75,23 @@ function Update-FabricCopyJob { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Patch ` - -Body $bodyJson + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Update Copy Job")) + { + # Step 4: Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -method Patch ` + -body $bodyJson + } Write-Message -Message "Copy Job '$CopyJobName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 b/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 index 883cbb65..6a1de71a 100644 --- a/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 +++ b/source/Public/Copy Job/Update-FabricCopyJobDefinition.ps1 @@ -38,7 +38,7 @@ Author: Tiago Balabuch #> function Update-FabricCopyJobDefinition { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -112,12 +112,14 @@ function Update-FabricCopyJobDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson + if($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update Copy Job Definition")) { + # Step 4: Make the API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointUrl ` + -Headers $FabricConfig.FabricHeaders ` + -Method Post ` + -Body $bodyJson + } Write-Message -Message "Copy Job updated successfully!" -Level Info return $response @@ -128,4 +130,4 @@ function Update-FabricCopyJobDefinition { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Copy Job. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 index c380160a..2de33a13 100644 --- a/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 @@ -27,8 +27,9 @@ Author: Tiago Balabuch #> -function New-FabricDataPipeline { - [CmdletBinding()] +function New-FabricDataPipeline +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -44,7 +45,8 @@ function New-FabricDataPipeline { [string]$DataPipelineDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -59,25 +61,30 @@ function New-FabricDataPipeline { displayName = $DataPipelineName } - if ($DataPipelineDescription) { + if ($DataPipelineDescription) + { $body.description = $DataPipelineDescription } $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson - + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Create DataPipeline")) + { + # Step 4: Make the API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -method Post ` + -body $bodyJson + } Write-Message -Message "Data Pipeline created successfully!" -Level Info return $response - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create DataPipeline. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 index 2d44bdac..c1b17244 100644 --- a/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/Remove-FabricDataPipeline.ps1 @@ -23,8 +23,9 @@ Author: Tiago Balabuch #> -function Remove-FabricDataPipeline { - [CmdletBinding()] +function Remove-FabricDataPipeline +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -34,7 +35,8 @@ function Remove-FabricDataPipeline { [ValidateNotNullOrEmpty()] [string]$DataPipelineId ) - try { + try + { # Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -44,16 +46,22 @@ function Remove-FabricDataPipeline { $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $DataPipelineId Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Delete + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Delete DataPipeline")) + { + + # Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -method Delete + } Write-Message -Message "DataPipeline '$DataPipelineId' deleted successfully from workspace '$WorkspaceId'." -Level Info return $response - } catch { + } + catch + { # Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete DataPipeline '$DataPipelineId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 index d4d9b738..c286653f 100644 --- a/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/Update-FabricDataPipeline.ps1 @@ -28,8 +28,9 @@ Author: Tiago Balabuch #> -function Update-FabricDataPipeline { - [CmdletBinding()] +function Update-FabricDataPipeline +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -49,7 +50,8 @@ function Update-FabricDataPipeline { [string]$DataPipelineDescription ) - try { + try + { # Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -64,7 +66,8 @@ function Update-FabricDataPipeline { displayName = $DataPipelineName } - if ($DataPipelineDescription) { + if ($DataPipelineDescription) + { $body.description = $DataPipelineDescription } @@ -72,18 +75,24 @@ function Update-FabricDataPipeline { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Patch ` - -Body $bodyJson + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Update DataPipeline")) + { + + # Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -method Patch ` + -body $bodyJson + } Write-Message -Message "DataPipeline '$DataPipelineName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update DataPipeline. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 b/source/Public/Domain/Add-FabricDomainWorkspaceAssignmentByCapacity.ps1 similarity index 91% rename from source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 rename to source/Public/Domain/Add-FabricDomainWorkspaceAssignmentByCapacity.ps1 index 6cf747c8..84bf2dff 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceByCapacity.ps1 +++ b/source/Public/Domain/Add-FabricDomainWorkspaceAssignmentByCapacity.ps1 @@ -3,7 +3,7 @@ Assigns workspaces to a Fabric domain based on specified capacities. .DESCRIPTION -The `Assign-FabricDomainWorkspaceByCapacity` function assigns workspaces to a Fabric domain using a list of capacity IDs by making a POST request to the relevant API endpoint. +The `Add-FabricDomainWorkspaceAssignmentByCapacity` function assigns workspaces to a Fabric domain using a list of capacity IDs by making a POST request to the relevant API endpoint. .PARAMETER DomainId The unique identifier of the Fabric domain to which the workspaces will be assigned. @@ -12,7 +12,7 @@ The unique identifier of the Fabric domain to which the workspaces will be assig An array of capacity IDs used to assign workspaces to the domain. .EXAMPLE -Assign-FabricDomainWorkspaceByCapacity -DomainId "12345" -CapacitiesIds @("capacity1", "capacity2") +Add-FabricDomainWorkspaceAssignmentByCapacity -DomainId "12345" -CapacitiesIds @("capacity1", "capacity2") Assigns workspaces to the domain with ID "12345" based on the specified capacities. @@ -23,8 +23,9 @@ Assigns workspaces to the domain with ID "12345" based on the specified capaciti Author: Tiago Balabuch #> -function Assign-FabricDomainWorkspaceByCapacity { +function Add-FabricDomainWorkspaceAssignmentByCapacity { [CmdletBinding()] + [Alias("Assign-FabricDomainWorkspaceByCapacity")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -108,4 +109,4 @@ function Assign-FabricDomainWorkspaceByCapacity { $errorDetails = $_.Exception.Message Write-Message -Message "Error occurred while assigning workspaces by capacity for domain '$DomainId'. Details: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 b/source/Public/Domain/Add-FabricDomainWorkspaceAssignmentById.ps1 similarity index 87% rename from source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 rename to source/Public/Domain/Add-FabricDomainWorkspaceAssignmentById.ps1 index e17355c0..118a3f19 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceById.ps1 +++ b/source/Public/Domain/Add-FabricDomainWorkspaceAssignmentById.ps1 @@ -3,7 +3,7 @@ Assigns workspaces to a specified domain in Microsoft Fabric by their IDs. .DESCRIPTION -The `Assign-FabricDomainWorkspaceById` function sends a request to assign multiple workspaces to a specified domain using the provided domain ID and an array of workspace IDs. +The `Add-FabricDomainWorkspaceAssignmentById` function sends a request to assign multiple workspaces to a specified domain using the provided domain ID and an array of workspace IDs. .PARAMETER DomainId The ID of the domain to which workspaces will be assigned. This parameter is mandatory. @@ -12,7 +12,7 @@ The ID of the domain to which workspaces will be assigned. This parameter is man An array of workspace IDs to be assigned to the domain. This parameter is mandatory. .EXAMPLE -Assign-FabricDomainWorkspaceById -DomainId "12345" -WorkspaceIds @("ws1", "ws2", "ws3") +Add-FabricDomainWorkspaceAssignmentById -DomainId "12345" -WorkspaceIds @("ws1", "ws2", "ws3") Assigns the workspaces with IDs "ws1", "ws2", and "ws3" to the domain with ID "12345". @@ -23,8 +23,9 @@ Assigns the workspaces with IDs "ws1", "ws2", and "ws3" to the domain with ID "1 Author: Tiago Balabuch #> -function Assign-FabricDomainWorkspaceById { +function Add-FabricDomainWorkspaceAssignmentById { [CmdletBinding()] + [Alias("Assign-FabricDomainWorkspaceById")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -79,4 +80,4 @@ function Assign-FabricDomainWorkspaceById { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 b/source/Public/Domain/Add-FabricDomainWorkspaceAssignmentByPrincipal.ps1 similarity index 92% rename from source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 rename to source/Public/Domain/Add-FabricDomainWorkspaceAssignmentByPrincipal.ps1 index 5a52351f..58a40535 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceByPrincipal.ps1 +++ b/source/Public/Domain/Add-FabricDomainWorkspaceAssignmentByPrincipal.ps1 @@ -3,7 +3,7 @@ Assigns workspaces to a domain based on principal IDs in Microsoft Fabric. .DESCRIPTION -The `Assign-FabricDomainWorkspaceByPrincipal` function sends a request to assign workspaces to a specified domain using a JSON object of principal IDs and types. +The `Add-FabricDomainWorkspaceAssignmentByPrincipal` function sends a request to assign workspaces to a specified domain using a JSON object of principal IDs and types. .PARAMETER DomainId The ID of the domain to which workspaces will be assigned. This parameter is mandatory. @@ -17,7 +17,7 @@ $PrincipalIds = @( @{id = "b5b9495c-685a-447a-b4d3-2d8e963e6288"; type = "User"} ) -Assign-FabricDomainWorkspaceByPrincipal -DomainId "12345" -PrincipalIds $principals +Add-FabricDomainWorkspaceAssignmentByPrincipal -DomainId "12345" -PrincipalIds $principals Assigns the workspaces based on the provided principal IDs and types. @@ -28,8 +28,9 @@ Assigns the workspaces based on the provided principal IDs and types. Author: Tiago Balabuch #> -function Assign-FabricDomainWorkspaceByPrincipal { +function Add-FabricDomainWorkspaceAssignmentByPrincipal { [CmdletBinding()] + [Alias("Assign-FabricDomainWorkspaceByPrincipal")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -114,4 +115,4 @@ function Assign-FabricDomainWorkspaceByPrincipal { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign domain workspaces by principals. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 b/source/Public/Domain/Add-FabricDomainWorkspaceRoleAssignment.ps1 similarity index 97% rename from source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 rename to source/Public/Domain/Add-FabricDomainWorkspaceRoleAssignment.ps1 index 3cbcb1df..d39a642a 100644 --- a/source/Public/Domain/Assign-FabricDomainWorkspaceRoleAssignment.ps1 +++ b/source/Public/Domain/Add-FabricDomainWorkspaceRoleAssignment.ps1 @@ -30,8 +30,9 @@ Assigns the `Admins` role to the specified principals in the domain with ID "123 Author: Tiago Balabuch #> -function Assign-FabricDomainWorkspaceRoleAssignment { +function Add-FabricDomainWorkspaceRoleAssignment { [CmdletBinding()] + [Alias("Assign-FabricDomainWorkspaceRoleAssignment")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -98,4 +99,4 @@ function Assign-FabricDomainWorkspaceRoleAssignment { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Get-FabricDomain.ps1 b/source/Public/Domain/Get-FabricDomain.ps1 index 8ecf06cf..a3122983 100644 --- a/source/Public/Domain/Get-FabricDomain.ps1 +++ b/source/Public/Domain/Get-FabricDomain.ps1 @@ -33,6 +33,7 @@ Author: Tiago Balabuch #> function Get-FabricDomain { [CmdletBinding()] + [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] @@ -114,4 +115,4 @@ function Get-FabricDomain { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve environment. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/New-FabricDomain.ps1 b/source/Public/Domain/New-FabricDomain.ps1 index 20951ae2..f07affc8 100644 --- a/source/Public/Domain/New-FabricDomain.ps1 +++ b/source/Public/Domain/New-FabricDomain.ps1 @@ -27,8 +27,9 @@ Author: Tiago Balabuch #> -function New-FabricDomain { - [CmdletBinding()] +function New-FabricDomain +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -44,7 +45,8 @@ function New-FabricDomain { [string]$ParentDomainId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -59,11 +61,13 @@ function New-FabricDomain { displayName = $DomainName } - if ($DomainDescription) { + if ($DomainDescription) + { $body.description = $DomainDescription } - if ($ParentDomainId) { + if ($ParentDomainId) + { $body.parentDomainId = $ParentDomainId } @@ -71,25 +75,32 @@ function New-FabricDomain { $bodyJson = $body | ConvertTo-Json -Depth 2 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($DomainName, "Create Domain")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Domain '$DomainName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Domain '$DomainName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug @@ -98,7 +109,8 @@ function New-FabricDomain { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -106,21 +118,26 @@ function New-FabricDomain { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create domain. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Remove-FabricDomain.ps1 b/source/Public/Domain/Remove-FabricDomain.ps1 index 272ead51..27fcd905 100644 --- a/source/Public/Domain/Remove-FabricDomain.ps1 +++ b/source/Public/Domain/Remove-FabricDomain.ps1 @@ -21,15 +21,17 @@ Author: Tiago Balabuch #> -function Remove-FabricDomain { - [CmdletBinding()] +function Remove-FabricDomain +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DomainId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -38,19 +40,22 @@ function Remove-FabricDomain { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/admin/domains/{1}" -f $FabricConfig.BaseUrl, $DomainId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Domain")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -59,9 +64,11 @@ function Remove-FabricDomain { Write-Message -Message "Domain '$DomainId' deleted successfully!" -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete domain '$DomainId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 b/source/Public/Domain/Remove-FabricDomainWorkspaceAssignment.ps1 similarity index 67% rename from source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 rename to source/Public/Domain/Remove-FabricDomainWorkspaceAssignment.ps1 index 62b0b5fb..2dd1c856 100644 --- a/source/Public/Domain/Unassign-FabricDomainWorkspace.ps1 +++ b/source/Public/Domain/Remove-FabricDomainWorkspaceAssignment.ps1 @@ -14,12 +14,12 @@ The unique identifier of the Fabric domain. (Optional) An array of workspace IDs to unassign. If not provided, all workspaces will be unassigned. .EXAMPLE -Unassign-FabricDomainWorkspace -DomainId "12345" +Remove-FabricDomainWorkspaceAssignment -DomainId "12345" Unassigns all workspaces from the domain with ID "12345". .EXAMPLE -Unassign-FabricDomainWorkspace -DomainId "12345" -WorkspaceIds @("workspace1", "workspace2") +Remove-FabricDomainWorkspaceAssignment -DomainId "12345" -WorkspaceIds @("workspace1", "workspace2") Unassigns the specified workspaces from the domain with ID "12345". @@ -31,8 +31,10 @@ Unassigns the specified workspaces from the domain with ID "12345". Author: Tiago Balabuch #> -function Unassign-FabricDomainWorkspace { - [CmdletBinding()] +function Remove-FabricDomainWorkspaceAssignment +{ + [CmdletBinding(SupportsShouldProcess)] + [Alias("Unassign-FabricDomainWorkspace")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -43,7 +45,8 @@ function Unassign-FabricDomainWorkspace { [array]$WorkspaceIds ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -51,44 +54,60 @@ function Unassign-FabricDomainWorkspace { # Step 2: Construct the API URL # Determine the API endpoint URL based on the presence of WorkspaceIds - $endpointSuffix = if ($WorkspaceIds) { "unassignWorkspaces" } else { "unassignAllWorkspaces" } + $endpointSuffix = if ($WorkspaceIds) + { + "unassignWorkspaces" + } + else + { + "unassignAllWorkspaces" + } $apiEndpointUrl = "{0}/admin/domains/{1}/{2}" -f $FabricConfig.BaseUrl, $DomainId, $endpointSuffix Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug # Step 3: Construct the request body (if needed) - $bodyJson = if ($WorkspaceIds) { + $bodyJson = if ($WorkspaceIds) + { $body = @{ workspacesIds = $WorkspaceIds } $body | ConvertTo-Json -Depth 2 - } else { + } + else + { $null } Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request to unassign specific workspaces - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($DomainId, "Unassign Workspaces")) + { + # Step 4: Make the API request to unassign specific workspaces + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } Write-Message -Message "Successfully unassigned workspaces to the domain with ID '$DomainId'." -Level Info - } catch { + } + catch + { # Step 6: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to unassign workspaces to the domain with ID '$DomainId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 b/source/Public/Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 similarity index 94% rename from source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 rename to source/Public/Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 index d7bec206..a75c662b 100644 --- a/source/Public/Domain/Unassign-FabricDomainWorkspaceRoleAssignment.ps1 +++ b/source/Public/Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 @@ -31,8 +31,9 @@ Author: Tiago Balabuch #> -function Unassign-FabricDomainWorkspaceRoleAssignment { - [CmdletBinding()] +function Remove-FabricDomainWorkspaceRoleAssignment { + [CmdletBinding(SupportsShouldProcess)] + [Alias("Unassign-FabricDomainWorkspaceRoleAssignment")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -73,6 +74,7 @@ function Unassign-FabricDomainWorkspaceRoleAssignment { $bodyJson = $body | ConvertTo-Json -Depth 2 Write-Message -Message "Request Body: $bodyJson" -Level Debug + if($PSCmdlet.ShouldProcess($DomainId, "Unassign Roles")) { # Step 5: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -84,6 +86,7 @@ function Unassign-FabricDomainWorkspaceRoleAssignment { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" + } # Step 6: Validate the response code if ($statusCode -ne 200) { @@ -99,4 +102,4 @@ function Unassign-FabricDomainWorkspaceRoleAssignment { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to bulk assign roles in domain '$DomainId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Domain/Update-FabricDomain.ps1 b/source/Public/Domain/Update-FabricDomain.ps1 index 0ac66fe0..e3ed65d5 100644 --- a/source/Public/Domain/Update-FabricDomain.ps1 +++ b/source/Public/Domain/Update-FabricDomain.ps1 @@ -30,8 +30,9 @@ Author: Tiago Balabuch #> -function Update-FabricDomain { - [CmdletBinding()] +function Update-FabricDomain +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -52,7 +53,8 @@ function Update-FabricDomain { [string]$DomainContributorsScope ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -67,11 +69,13 @@ function Update-FabricDomain { displayName = $DomainName } - if ($DomainDescription) { + if ($DomainDescription) + { $body.description = $DomainDescription } - if ($DomainContributorsScope) { + if ($DomainContributorsScope) + { $body.contributorsScope = $DomainContributorsScope } @@ -79,20 +83,25 @@ function Update-FabricDomain { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($DomainName, "Update Domain")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -102,9 +111,11 @@ function Update-FabricDomain { # Step 6: Handle results Write-Message -Message "Domain '$DomainName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update domain '$DomainId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Import-FabricEnvironmentStagingLibrary.ps1 similarity index 94% rename from source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 rename to source/Public/Environment/Import-FabricEnvironmentStagingLibrary.ps1 index 1d3dfe02..1c33e533 100644 --- a/source/Public/Environment/Upload-FabricEnvironmentStagingLibrary.ps1 +++ b/source/Public/Environment/Import-FabricEnvironmentStagingLibrary.ps1 @@ -13,7 +13,7 @@ The unique identifier of the workspace where the environment exists. The unique identifier of the environment where the library will be uploaded. .EXAMPLE -Upload-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "env-67890" +Import-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -EnvironmentId "env-67890" .NOTES - This is not working code. It is a placeholder for future development. Fabric documentation is missing some important details on how to upload libraries. @@ -23,8 +23,9 @@ Upload-FabricEnvironmentStagingLibrary -WorkspaceId "workspace-12345" -Environme Author: Tiago Balabuch #> -function Upload-FabricEnvironmentStagingLibrary { +function Import-FabricEnvironmentStagingLibrary { [CmdletBinding()] + [Alias("Upload-FabricEnvironmentStagingLibrary")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -75,4 +76,4 @@ function Upload-FabricEnvironmentStagingLibrary { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to upload environment staging library. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Environment/New-FabricEnvironment.ps1 b/source/Public/Environment/New-FabricEnvironment.ps1 index 5b016513..e91fdc22 100644 --- a/source/Public/Environment/New-FabricEnvironment.ps1 +++ b/source/Public/Environment/New-FabricEnvironment.ps1 @@ -27,8 +27,9 @@ Author: Tiago Balabuch #> -function New-FabricEnvironment { - [CmdletBinding()] +function New-FabricEnvironment +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -44,7 +45,8 @@ function New-FabricEnvironment { [string]$EnvironmentDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -59,32 +61,38 @@ function New-FabricEnvironment { displayName = $EnvironmentName } - if ($EnvironmentDescription) { + if ($EnvironmentDescription) + { $body.description = $EnvironmentDescription } $bodyJson = $body | ConvertTo-Json -Depth 2 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($EnvironmentName, "Create Environment")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Environment '$EnvironmentName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Environment '$EnvironmentName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug @@ -93,7 +101,8 @@ function New-FabricEnvironment { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -101,21 +110,26 @@ function New-FabricEnvironment { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create environment. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Environment/Remove-FabricEnvironment.ps1 b/source/Public/Environment/Remove-FabricEnvironment.ps1 index ebf61f8e..96295bba 100644 --- a/source/Public/Environment/Remove-FabricEnvironment.ps1 +++ b/source/Public/Environment/Remove-FabricEnvironment.ps1 @@ -24,8 +24,9 @@ Author: Tiago Balabuch #> -function Remove-FabricEnvironment { - [CmdletBinding()] +function Remove-FabricEnvironment +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -36,7 +37,8 @@ function Remove-FabricEnvironment { [string]$EnvironmentId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -45,19 +47,22 @@ function Remove-FabricEnvironment { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Environment")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -65,9 +70,11 @@ function Remove-FabricEnvironment { } Write-Message -Message "Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 index 87639e8b..d814aff9 100644 --- a/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 +++ b/source/Public/Environment/Remove-FabricEnvironmentStagingLibrary.ps1 @@ -1,4 +1,5 @@ -function Remove-FabricEnvironmentStagingLibrary { +function Remove-FabricEnvironmentStagingLibrary +{ <# .SYNOPSIS Deletes a specified library from the staging environment in a Microsoft Fabric workspace. @@ -28,7 +29,7 @@ Deletes the specified library from the staging environment in the specified work Author: Tiago Balabuch #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -43,7 +44,8 @@ Author: Tiago Balabuch [string]$LibraryName ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -54,28 +56,33 @@ Author: Tiago Balabuch # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/libraries?libraryToDelete={3}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId, $LibraryName Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Staging Library")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error return $null } Write-Message -Message "Staging library $LibraryName for the Environment '$EnvironmentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 b/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 index f6ebd36b..eb198683 100644 --- a/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 +++ b/source/Public/Environment/Stop-FabricEnvironmentPublish.ps1 @@ -24,8 +24,9 @@ Cancels the publish operation for the specified environment. Author: Tiago Balabuch #> -function Stop-FabricEnvironmentPublish { - [CmdletBinding()] +function Stop-FabricEnvironmentPublish +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -36,7 +37,8 @@ function Stop-FabricEnvironmentPublish { [string]$EnvironmentId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -45,21 +47,24 @@ function Stop-FabricEnvironmentPublish { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/environments/{2}/staging/cancelPublish" -f $FabricConfig.BaseUrl, $WorkspaceId, $EnvironmentId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Cancel Publish")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -67,9 +72,11 @@ function Stop-FabricEnvironmentPublish { } Write-Message -Message "Publication for environment '$EnvironmentId' has been successfully canceled." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to cancel publication for environment '$EnvironmentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Environment/Update-FabricEnvironment.ps1 b/source/Public/Environment/Update-FabricEnvironment.ps1 index 7b530d96..a69f8962 100644 --- a/source/Public/Environment/Update-FabricEnvironment.ps1 +++ b/source/Public/Environment/Update-FabricEnvironment.ps1 @@ -35,8 +35,9 @@ Author: Tiago Balabuch #> -function Update-FabricEnvironment { - [CmdletBinding()] +function Update-FabricEnvironment +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,7 +57,8 @@ function Update-FabricEnvironment { [string]$EnvironmentDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,7 +73,8 @@ function Update-FabricEnvironment { displayName = $EnvironmentName } - if ($EnvironmentDescription) { + if ($EnvironmentDescription) + { $body.description = $EnvironmentDescription } @@ -79,20 +82,24 @@ function Update-FabricEnvironment { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($EnvironmentId, "Update Environment")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -102,9 +109,11 @@ function Update-FabricEnvironment { # Step 6: Handle results Write-Message -Message "Environment '$EnvironmentName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Environment. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 b/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 index e9b97672..f2e0cc02 100644 --- a/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 +++ b/source/Public/Environment/Update-FabricEnvironmentStagingSparkCompute.ps1 @@ -55,8 +55,9 @@ Update-FabricEnvironmentStagingSparkCompute -WorkspaceId "workspace-12345" -Envi Author: Tiago Balabuch #> -function Update-FabricEnvironmentStagingSparkCompute { - [CmdletBinding()] +function Update-FabricEnvironmentStagingSparkCompute +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -112,7 +113,8 @@ function Update-FabricEnvironmentStagingSparkCompute { [System.Object]$SparkProperties ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -145,6 +147,20 @@ function Update-FabricEnvironmentStagingSparkCompute { $bodyJson = $body | ConvertTo-Json -Depth 4 Write-Message -Message "Request Body: $bodyJson" -Level Debug + if ($PSCmdlet.ShouldProcess($EnvironmentId, "Update Environment Staging Spark Compute")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -158,7 +174,8 @@ function Update-FabricEnvironmentStagingSparkCompute { -StatusCodeVariable "statusCode" # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -168,9 +185,11 @@ function Update-FabricEnvironmentStagingSparkCompute { # Step 6: Handle results Write-Message -Message "Environment staging Spark compute updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update environment staging Spark compute. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventhouse/New-FabricEventhouse.ps1 b/source/Public/Eventhouse/New-FabricEventhouse.ps1 index 9ff47190..d2db2d3c 100644 --- a/source/Public/Eventhouse/New-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/New-FabricEventhouse.ps1 @@ -1,4 +1,5 @@ -function New-FabricEventhouse { +function New-FabricEventhouse +{ <# .SYNOPSIS Creates a new Eventhouse in a specified Microsoft Fabric workspace. @@ -34,7 +35,7 @@ function New-FabricEventhouse { #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -57,7 +58,8 @@ function New-FabricEventhouse { [ValidateNotNullOrEmpty()] [string]$EventhousePathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -72,15 +74,19 @@ function New-FabricEventhouse { displayName = $EventhouseName } - if ($EventhouseDescription) { + if ($EventhouseDescription) + { $body.description = $EventhouseDescription } - if ($EventhousePathDefinition) { + if ($EventhousePathDefinition) + { $eventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition - if (-not [string]::IsNullOrEmpty($eventhouseEncodedContent)) { + if (-not [string]::IsNullOrEmpty($eventhouseEncodedContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ parts = @() } @@ -92,18 +98,23 @@ function New-FabricEventhouse { payload = $eventhouseEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error return $null } } - if ($EventhousePathPlatformDefinition) { + if ($EventhousePathPlatformDefinition) + { $eventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($eventhouseEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($eventhouseEncodedPlatformContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ parts = @() } @@ -115,7 +126,9 @@ function New-FabricEventhouse { payload = $eventhouseEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -125,6 +138,20 @@ function New-FabricEventhouse { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug + if ($PSCmdlet.ShouldProcess($EventhouseName, "Create Eventhouse")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -140,12 +167,15 @@ function New-FabricEventhouse { Write-Message -Message "Response Code: $statusCode" -Level Debug # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Eventhouse '$EventhouseName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Eventhouse '$EventhouseName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -160,7 +190,8 @@ function New-FabricEventhouse { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -168,13 +199,16 @@ function New-FabricEventhouse { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -182,9 +216,11 @@ function New-FabricEventhouse { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Eventhouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 index 9f0a0481..6fe35621 100644 --- a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 @@ -1,4 +1,5 @@ -function Remove-FabricEventhouse { +function Remove-FabricEventhouse +{ <# .SYNOPSIS Removes an Eventhouse from a specified Microsoft Fabric workspace. @@ -37,7 +38,7 @@ function Remove-FabricEventhouse { #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -47,7 +48,8 @@ function Remove-FabricEventhouse { [ValidateNotNullOrEmpty()] [string]$EventhouseId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -56,19 +58,22 @@ function Remove-FabricEventhouse { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Eventhouse")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Handle response - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -77,9 +82,11 @@ function Remove-FabricEventhouse { } Write-Message -Message "Eventhouse '$EventhouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Eventhouse '$EventhouseId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventhouse/Update-FabricEventhouse.ps1 b/source/Public/Eventhouse/Update-FabricEventhouse.ps1 index 1de7e6c4..fa23a2a1 100644 --- a/source/Public/Eventhouse/Update-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Update-FabricEventhouse.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricEventhouse { - [CmdletBinding()] +function Update-FabricEventhouse +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -49,7 +50,8 @@ function Update-FabricEventhouse { [ValidateNotNullOrEmpty()] [string]$EventhouseDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -64,7 +66,8 @@ function Update-FabricEventhouse { displayName = $EventhouseName } - if ($EventhouseDescription) { + if ($EventhouseDescription) + { $body.description = $EventhouseDescription } @@ -72,20 +75,24 @@ function Update-FabricEventhouse { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess("Eventhouse", "Update")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -96,9 +103,11 @@ function Update-FabricEventhouse { # Step 6: Handle results Write-Message -Message "Eventhouse '$EventhouseName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 b/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 index 71e0152f..d5486e14 100644 --- a/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 +++ b/source/Public/Eventhouse/Update-FabricEventhouseDefinition.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricEventhouseDefinition { - [CmdletBinding()] +function Update-FabricEventhouseDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -48,7 +49,8 @@ function Update-FabricEventhouseDefinition { [ValidateNotNullOrEmpty()] [string]$EventhousePathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -58,7 +60,8 @@ function Update-FabricEventhouseDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/eventhouses/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventhouseId #if ($UpdateMetadata -eq $true) { - if ($EventhousePathPlatformDefinition) { + if ($EventhousePathPlatformDefinition) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -70,32 +73,40 @@ function Update-FabricEventhouseDefinition { } } - if ($EventhousePathDefinition) { + if ($EventhousePathDefinition) + { $EventhouseEncodedContent = Convert-ToBase64 -filePath $EventhousePathDefinition - if (-not [string]::IsNullOrEmpty($EventhouseEncodedContent)) { + if (-not [string]::IsNullOrEmpty($EventhouseEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "EventhouseProperties.json" payload = $EventhouseEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in Eventhouse definition." -Level Error return $null } } - if ($EventhousePathPlatformDefinition) { + if ($EventhousePathPlatformDefinition) + { $EventhouseEncodedPlatformContent = Convert-ToBase64 -filePath $EventhousePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($EventhouseEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($EventhouseEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $EventhouseEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -103,25 +114,30 @@ function Update-FabricEventhouseDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess("Eventhouse", "Update")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for Eventhouse '$EventhouseId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for Eventhouse '$EventhouseId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -136,7 +152,8 @@ function Update-FabricEventhouseDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -144,21 +161,26 @@ function Update-FabricEventhouseDefinition { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventhouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventstream/New-FabricEventstream.ps1 b/source/Public/Eventstream/New-FabricEventstream.ps1 index 481ec846..16b7d189 100644 --- a/source/Public/Eventstream/New-FabricEventstream.ps1 +++ b/source/Public/Eventstream/New-FabricEventstream.ps1 @@ -1,4 +1,5 @@ -function New-FabricEventstream { +function New-FabricEventstream +{ <# .SYNOPSIS Creates a new Eventstream in a specified Microsoft Fabric workspace. @@ -35,7 +36,7 @@ Author: Tiago Balabuch #> #TODO SupportsShouldProcess - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -59,7 +60,8 @@ Author: Tiago Balabuch [string]$EventstreamPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -74,16 +76,20 @@ Author: Tiago Balabuch displayName = $EventstreamName } - if ($EventstreamDescription) { + if ($EventstreamDescription) + { $body.description = $EventstreamDescription } - if ($EventstreamPathDefinition) { + if ($EventstreamPathDefinition) + { $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition - if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { + if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "eventstream" parts = @() @@ -96,18 +102,23 @@ Author: Tiago Balabuch payload = $EventstreamEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error return $null } } - if ($EventstreamPathPlatformDefinition) { + if ($EventstreamPathPlatformDefinition) + { $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "eventstream" parts = @() @@ -120,7 +131,9 @@ Author: Tiago Balabuch payload = $EventstreamEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -129,25 +142,31 @@ Author: Tiago Balabuch $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($EventstreamName, "Create Eventstream")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Eventstream '$EventstreamName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Eventstream '$EventstreamName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -157,7 +176,8 @@ Author: Tiago Balabuch $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -165,21 +185,26 @@ Author: Tiago Balabuch Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Eventstream. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventstream/Remove-FabricEventstream.ps1 b/source/Public/Eventstream/Remove-FabricEventstream.ps1 index 9e2470d5..0583485d 100644 --- a/source/Public/Eventstream/Remove-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Remove-FabricEventstream.ps1 @@ -1,4 +1,5 @@ -function Remove-FabricEventstream { +function Remove-FabricEventstream +{ <# .SYNOPSIS Deletes an Eventstream from a specified workspace in Microsoft Fabric. @@ -29,9 +30,7 @@ Deletes the Eventstream with ID "67890" from workspace "12345". Author: Tiago Balabuch #> - - #TODO SupportsShouldProcess - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -43,7 +42,8 @@ Author: Tiago Balabuch #TODO Add EventstreamName parameter to validate the name of the Eventstream to delete. ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -53,17 +53,21 @@ Author: Tiago Balabuch $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Eventstream")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -71,9 +75,11 @@ Author: Tiago Balabuch } Write-Message -Message "Eventstream '$EventstreamId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Eventstream '$EventstreamId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventstream/Update-FabricEventstream.ps1 b/source/Public/Eventstream/Update-FabricEventstream.ps1 index c5059ec3..9d2883d6 100644 --- a/source/Public/Eventstream/Update-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Update-FabricEventstream.ps1 @@ -35,8 +35,9 @@ Author: Tiago Balabuch #> -function Update-FabricEventstream { - [CmdletBinding()] +function Update-FabricEventstream +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,7 +57,8 @@ function Update-FabricEventstream { [string]$EventstreamDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,27 +73,30 @@ function Update-FabricEventstream { displayName = $EventstreamName } - if ($EventstreamDescription) { + if ($EventstreamDescription) + { $body.description = $EventstreamDescription } # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($EventstreamId, "Update Eventstream")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -101,9 +106,11 @@ function Update-FabricEventstream { # Step 6: Handle results Write-Message -Message "Eventstream '$EventstreamName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 b/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 index 3fb1b5ce..9ae03b65 100644 --- a/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 +++ b/source/Public/Eventstream/Update-FabricEventstreamDefinition.ps1 @@ -42,8 +42,9 @@ Author: Tiago Balabuch #> -function Update-FabricEventstreamDefinition { - [CmdletBinding()] +function Update-FabricEventstreamDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -62,7 +63,8 @@ function Update-FabricEventstreamDefinition { [string]$EventstreamPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,7 +73,8 @@ function Update-FabricEventstreamDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/eventstreams/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $EventstreamId - if ($EventstreamPathPlatformDefinition) { + if ($EventstreamPathPlatformDefinition) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -83,32 +86,40 @@ function Update-FabricEventstreamDefinition { } } - if ($EventstreamPathDefinition) { + if ($EventstreamPathDefinition) + { $EventstreamEncodedContent = Convert-ToBase64 -filePath $EventstreamPathDefinition - if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) { + if (-not [string]::IsNullOrEmpty($EventstreamEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "eventstream.json" payload = $EventstreamEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in Eventstream definition." -Level Error return $null } } - if ($EventstreamPathPlatformDefinition) { + if ($EventstreamPathPlatformDefinition) + { $EventstreamEncodedPlatformContent = Convert-ToBase64 -filePath $EventstreamPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($EventstreamEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $EventstreamEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -116,25 +127,30 @@ function Update-FabricEventstreamDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($EventstreamId, "Update Eventstream")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for Eventstream '$EventstreamId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for Eventstream '$EventstreamId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] @@ -148,7 +164,8 @@ function Update-FabricEventstreamDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -156,21 +173,26 @@ function Update-FabricEventstreamDefinition { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Eventstream. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Get-FabricAPIClusterURI.ps1 b/source/Public/Get-FabricAPIClusterURI.ps1 index 78414307..ced8ce60 100644 --- a/source/Public/Get-FabricAPIClusterURI.ps1 +++ b/source/Public/Get-FabricAPIClusterURI.ps1 @@ -19,6 +19,8 @@ function Get-FabricAPIclusterURI { #This function retrieves the cluster URI for the tenant. # Define aliases for the function for flexibility. [Alias("Get-FabAPIClusterURI")] + [CmdletBinding()] + [OutputType([string])] Param ( ) @@ -38,4 +40,4 @@ function Get-FabricAPIclusterURI { # Return the cluster URI. return $clusterURI -} \ No newline at end of file +} diff --git a/source/Public/Get-FabricDebugInfo.ps1 b/source/Public/Get-FabricDebugInfo.ps1 index d19e2177..428f4086 100644 --- a/source/Public/Get-FabricDebugInfo.ps1 +++ b/source/Public/Get-FabricDebugInfo.ps1 @@ -24,6 +24,7 @@ function Get-FabricDebugInfo { #> [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] param ( ) @@ -43,4 +44,4 @@ function Get-FabricDebugInfo { end { } -} \ No newline at end of file +} diff --git a/source/Public/Item/Import-FabricItem.ps1 b/source/Public/Item/Import-FabricItem.ps1 index 2ac35f34..1052201f 100644 --- a/source/Public/Item/Import-FabricItem.ps1 +++ b/source/Public/Item/Import-FabricItem.ps1 @@ -37,7 +37,7 @@ Function Import-FabricItem { .PARAMETER fileOverrides This parameter let's you override a PBIP file without altering the local file. #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [string] $path = '.\pbipOutput' @@ -166,7 +166,7 @@ Function Import-FabricItem { } $partPath = $filePath.Replace($itemPathAbs, "").TrimStart("\").Replace("\", "/") - Write-Host "Processing part: '$partPath'" + Write-Output "Processing part: '$partPath'" $fileEncodedContent = [Convert]::ToBase64String($fileContent) Write-Output @{ @@ -205,7 +205,9 @@ Function Import-FabricItem { } } | ConvertTo-Json -Depth 3 - $createItemResult = Invoke-FabricAPIRequest -uri "workspaces/$workspaceId/items" -method Post -body $itemRequest + if($PSCmdlet.ShouldProcess($itemPath, "Create Item")) { + $createItemResult = Invoke-FabricAPIRequest -uri "workspaces/$workspaceId/items" -method Post -body $itemRequest + } $itemId = $createItemResult.id @@ -220,7 +222,10 @@ Function Import-FabricItem { Parts = $parts } } | ConvertTo-Json -Depth 3 + if($PSCmdlet.ShouldProcess($itemPath, "Update Item")) { + Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/updateDefinition" -Method Post -Body $itemRequest + } Write-Output "Updated new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green @@ -234,4 +239,4 @@ Function Import-FabricItem { } } -} \ No newline at end of file +} diff --git a/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 index 18768780..1def48a3 100644 --- a/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/New-FabricKQLDashboard.ps1 @@ -33,8 +33,9 @@ Author: Tiago Balabuch #> -function New-FabricKQLDashboard { - [CmdletBinding()] +function New-FabricKQLDashboard +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -58,7 +59,8 @@ function New-FabricKQLDashboard { [string]$KQLDashboardPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -73,16 +75,20 @@ function New-FabricKQLDashboard { displayName = $KQLDashboardName } - if ($KQLDashboardDescription) { + if ($KQLDashboardDescription) + { $body.description = $KQLDashboardDescription } - if ($KQLDashboardPathDefinition) { + if ($KQLDashboardPathDefinition) + { $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "KQLDashboard" parts = @() @@ -95,18 +101,23 @@ function New-FabricKQLDashboard { payload = $KQLDashboardEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error return $null } } - if ($KQLDashboardPathPlatformDefinition) { + if ($KQLDashboardPathPlatformDefinition) + { $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = $null parts = @() @@ -119,7 +130,9 @@ function New-FabricKQLDashboard { payload = $KQLDashboardEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -127,26 +140,31 @@ function New-FabricKQLDashboard { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($KQLDashboardName, "Create KQLDashboard")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "KQLDashboard '$KQLDashboardName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "KQLDashboard '$KQLDashboardName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -156,7 +174,8 @@ function New-FabricKQLDashboard { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -164,21 +183,26 @@ function New-FabricKQLDashboard { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create KQLDashboard. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 index 275c07c5..f76af2fb 100644 --- a/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Remove-FabricKQLDashboard.ps1 @@ -24,8 +24,9 @@ Author: Tiago Balabuch #> -function Remove-FabricKQLDashboard { - [CmdletBinding()] +function Remove-FabricKQLDashboard +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -36,7 +37,8 @@ function Remove-FabricKQLDashboard { [string]$KQLDashboardId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -46,17 +48,21 @@ function Remove-FabricKQLDashboard { $apiEndpointUrl = "{0}/workspaces/{1}/kqlDashboards/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove KQLDashboard")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -64,9 +70,11 @@ function Remove-FabricKQLDashboard { } Write-Message -Message "KQLDashboard '$KQLDashboardId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete KQLDashboard '$KQLDashboardId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 index 81371165..85c6013d 100644 --- a/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboard.ps1 @@ -1,4 +1,5 @@ -function Update-FabricKQLDashboard { +function Update-FabricKQLDashboard +{ <# .SYNOPSIS Updates the properties of a Fabric KQLDashboard. @@ -35,7 +36,7 @@ Updates both the name and description of the KQLDashboard "KQLDashboard123". Author: Tiago Balabuch #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -55,7 +56,8 @@ Author: Tiago Balabuch [string]$KQLDashboardDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -70,7 +72,8 @@ Author: Tiago Balabuch displayName = $KQLDashboardName } - if ($KQLDashboardDescription) { + if ($KQLDashboardDescription) + { $body.description = $KQLDashboardDescription } @@ -78,19 +81,23 @@ Author: Tiago Balabuch $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($KQLDashboardId, "Update KQLDashboard")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -100,9 +107,11 @@ Author: Tiago Balabuch # Step 6: Handle results Write-Message -Message "KQLDashboard '$KQLDashboardName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 b/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 index be838874..3c8326f2 100644 --- a/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 +++ b/source/Public/KQL Dashboard/Update-FabricKQLDashboardDefinition.ps1 @@ -39,8 +39,9 @@ Author: Tiago Balabuch #> -function Update-FabricKQLDashboardDefinition { - [CmdletBinding()] +function Update-FabricKQLDashboardDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -59,7 +60,8 @@ function Update-FabricKQLDashboardDefinition { [string]$KQLDashboardPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -68,7 +70,8 @@ function Update-FabricKQLDashboardDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/KQLDashboards/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDashboardId - if ($KQLDashboardPathPlatformDefinition) { + if ($KQLDashboardPathPlatformDefinition) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -81,32 +84,40 @@ function Update-FabricKQLDashboardDefinition { } } - if ($KQLDashboardPathDefinition) { + if ($KQLDashboardPathDefinition) + { $KQLDashboardEncodedContent = Convert-ToBase64 -filePath $KQLDashboardPathDefinition - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) { + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "RealTimeDashboard.json" payload = $KQLDashboardEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in KQLDashboard definition." -Level Error return $null } } - if ($KQLDashboardPathPlatformDefinition) { + if ($KQLDashboardPathPlatformDefinition) + { $KQLDashboardEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDashboardPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($KQLDashboardEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $KQLDashboardEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -115,48 +126,60 @@ function Update-FabricKQLDashboardDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($KQLDashboardId, "Update KQLDashboard")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for KQLDashboard '$KQLDashboardId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for KQLDashboard '$KQLDashboardId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] $operationResult = Get-FabricLongRunningOperation -operationId $operationId # Handle operation result - if ($operationResult.status -eq "Succeeded") { + if ($operationResult.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug $result = Get-FabricLongRunningOperationResult -operationId $operationId return $result.definition.parts - } else { + } + else + { Write-Message -Message "Operation Failed" -Level Debug return $operationResult.definition.parts } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDashboard. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 index 9505147b..4b686982 100644 --- a/source/Public/KQL Database/New-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/New-FabricKQLDatabase.ps1 @@ -1,4 +1,5 @@ -function New-FabricKQLDatabase { +function New-FabricKQLDatabase +{ <# .SYNOPSIS Creates a new KQLDatabase in a specified Microsoft Fabric workspace. @@ -55,7 +56,7 @@ An optional path to the platform-specific definition (e.g., .platform file) to u Author: Tiago Balabuch #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -104,7 +105,8 @@ Author: Tiago Balabuch [string]$KQLDatabasePathSchemaDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -119,18 +121,21 @@ Author: Tiago Balabuch displayName = $KQLDatabaseName } - if ($KQLDatabaseDescription) { + if ($KQLDatabaseDescription) + { $body.description = $KQLDatabaseDescription } - if ($KQLDatabasePathDefinition) { + if ($KQLDatabasePathDefinition) + { $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition $body.definition = @{ parts = @() } - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) + { # Add new part to the parts array @@ -139,15 +144,19 @@ Author: Tiago Balabuch payload = $KQLDatabaseEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error return $null } - if ($KQLDatabasePathPlatformDefinition) { + if ($KQLDatabasePathPlatformDefinition) + { $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ @@ -155,16 +164,20 @@ Author: Tiago Balabuch payload = $KQLDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } } - if ($KQLDatabasePathSchemaDefinition) { + if ($KQLDatabasePathSchemaDefinition) + { $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) + { # Add new part to the parts array $body.definition.parts += @{ @@ -172,42 +185,54 @@ Author: Tiago Balabuch payload = $KQLDatabaseEncodedSchemaContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in schema definition." -Level Error return $null } } - } else { - if ($KQLDatabaseType -eq "Shortcut") { - if (-not $parentEventhouseId) { + } + else + { + if ($KQLDatabaseType -eq "Shortcut") + { + if (-not $parentEventhouseId) + { Write-Message -Message "Error: 'parentEventhouseId' is required for Shortcut type." -Level Error return $null } - if (-not ($KQLInvitationToken -or $KQLSourceClusterUri -or $KQLSourceDatabaseName)) { + if (-not ($KQLInvitationToken -or $KQLSourceClusterUri -or $KQLSourceDatabaseName)) + { Write-Message -Message "Error: Provide either 'KQLInvitationToken', 'KQLSourceClusterUri', or 'KQLSourceDatabaseName'." -Level Error return $null } - if ($KQLInvitationToken) { + if ($KQLInvitationToken) + { Write-Message -Message "Info: 'KQLInvitationToken' is provided." -Level Warning - if ($KQLSourceClusterUri) { + if ($KQLSourceClusterUri) + { Write-Message -Message "Warning: 'KQLSourceClusterUri' is ignored when 'KQLInvitationToken' is provided." -Level Warning #$KQLSourceClusterUri = $null } - if ($KQLSourceDatabaseName) { + if ($KQLSourceDatabaseName) + { Write-Message -Message "Warning: 'KQLSourceDatabaseName' is ignored when 'KQLInvitationToken' is provided." -Level Warning #$KQLSourceDatabaseName = $null } } - if ($KQLSourceClusterUri -and -not $KQLSourceDatabaseName) { + if ($KQLSourceClusterUri -and -not $KQLSourceDatabaseName) + { Write-Message -Message "Error: 'KQLSourceDatabaseName' is required when 'KQLSourceClusterUri' is provided." -Level Error return $null } } # Validate ReadWrite type database - if ($KQLDatabaseType -eq "ReadWrite" -and -not $parentEventhouseId) { + if ($KQLDatabaseType -eq "ReadWrite" -and -not $parentEventhouseId) + { Write-Message -Message "Error: 'parentEventhouseId' is required for ReadWrite type." -Level Error return $null } @@ -217,15 +242,19 @@ Author: Tiago Balabuch parentEventhouseItemId = $parentEventhouseId } - if ($KQLDatabaseType -eq "Shortcut") { - if ($KQLInvitationToken) { + if ($KQLDatabaseType -eq "Shortcut") + { + if ($KQLInvitationToken) + { $body.creationPayload.invitationToken = $KQLInvitationToken } - if ($KQLSourceClusterUri -and -not $KQLInvitationToken) { + if ($KQLSourceClusterUri -and -not $KQLInvitationToken) + { $body.creationPayload.sourceClusterUri = $KQLSourceClusterUri } - if ($KQLSourceDatabaseName -and -not $KQLInvitationToken) { + if ($KQLSourceDatabaseName -and -not $KQLInvitationToken) + { $body.creationPayload.sourceDatabaseName = $KQLSourceDatabaseName } } @@ -235,26 +264,31 @@ Author: Tiago Balabuch $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($KQLDatabaseName, "Create KQLDatabase")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "KQLDatabase '$KQLDatabaseName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "KQLDatabase '$KQLDatabaseName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -269,7 +303,8 @@ Author: Tiago Balabuch $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -277,13 +312,16 @@ Author: Tiago Balabuch Write-Message -Message "Long Running Operation result: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -291,9 +329,11 @@ Author: Tiago Balabuch throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create KQLDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 index 86c1d1a5..568e7f48 100644 --- a/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Remove-FabricKQLDatabase.ps1 @@ -24,8 +24,9 @@ Author: Tiago Balabuch #> -function Remove-FabricKQLDatabase { - [CmdletBinding()] +function Remove-FabricKQLDatabase +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -36,7 +37,8 @@ function Remove-FabricKQLDatabase { [string]$KQLDatabaseId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -45,18 +47,22 @@ function Remove-FabricKQLDatabase { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove KQLDatabase")) + { + # Step 3: Check if the API endpoint is valid + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -65,9 +71,11 @@ function Remove-FabricKQLDatabase { } Write-Message -Message "KQLDatabase '$KQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete KQLDatabase '$KQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 index 79e172b3..fe6b1548 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabase.ps1 @@ -35,7 +35,8 @@ Author: Tiago Balabuch #> -function Update-FabricKQLDatabase { +function Update-FabricKQLDatabase +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -56,7 +57,8 @@ function Update-FabricKQLDatabase { [string]$KQLDatabaseDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,27 +73,31 @@ function Update-FabricKQLDatabase { displayName = $KQLDatabaseName } - if ($KQLDatabaseDescription) { + if ($KQLDatabaseDescription) + { $body.description = $KQLDatabaseDescription } # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($KQLDatabaseId, "Update KQLDatabase")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -102,9 +108,11 @@ function Update-FabricKQLDatabase { # Step 6: Handle results Write-Message -Message "KQLDatabase '$KQLDatabaseName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 index b516aad2..4570ae4d 100644 --- a/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 +++ b/source/Public/KQL Database/Update-FabricKQLDatabaseDefinition.ps1 @@ -45,7 +45,8 @@ Author: Tiago Balabuch #> -function Update-FabricKQLDatabaseDefinition { +function Update-FabricKQLDatabaseDefinition +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -68,7 +69,8 @@ function Update-FabricKQLDatabaseDefinition { [ValidateNotNullOrEmpty()] [string]$KQLDatabasePathSchemaDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -77,7 +79,8 @@ function Update-FabricKQLDatabaseDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/kqlDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLDatabaseId - if ($KQLDatabasePathPlatformDefinition) { + if ($KQLDatabasePathPlatformDefinition) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -89,41 +92,51 @@ function Update-FabricKQLDatabaseDefinition { } } - if ($KQLDatabasePathDefinition) { + if ($KQLDatabasePathDefinition) + { $KQLDatabaseEncodedContent = Convert-ToBase64 -filePath $KQLDatabasePathDefinition - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) { + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "DatabaseProperties.json" payload = $KQLDatabaseEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in KQLDatabase definition." -Level Error return $null } } - if ($KQLDatabasePathPlatformDefinition) { + if ($KQLDatabasePathPlatformDefinition) + { $KQLDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $KQLDatabasePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $KQLDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } } - if ($KQLDatabasePathSchemaDefinition) { + if ($KQLDatabasePathSchemaDefinition) + { $KQLDatabaseEncodedSchemaContent = Convert-ToBase64 -filePath $KQLDatabasePathSchemaDefinition - if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) { + if (-not [string]::IsNullOrEmpty($KQLDatabaseEncodedSchemaContent)) + { # Add new part to the parts array $body.definition.parts += @{ @@ -131,7 +144,9 @@ function Update-FabricKQLDatabaseDefinition { payload = $KQLDatabaseEncodedSchemaContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in schema definition." -Level Error return $null } @@ -140,24 +155,30 @@ function Update-FabricKQLDatabaseDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($KQLDatabaseId, "Update KQLDatabase")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for KQLDatabase '$KQLDatabaseId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for KQLDatabase '$KQLDatabaseId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] @@ -171,20 +192,24 @@ function Update-FabricKQLDatabaseDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug Write-Message -Message "Operation completed successfully." -Level Info $operationResult = Get-FabricLongRunningOperationResult -operationId $operationId Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -192,9 +217,11 @@ function Update-FabricKQLDatabaseDefinition { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 index 92ca18a9..d465e65d 100644 --- a/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/New-FabricKQLQueryset.ps1 @@ -34,7 +34,7 @@ Author: Tiago Balabuch #> function New-FabricKQLQueryset { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -128,6 +128,7 @@ function New-FabricKQLQueryset { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug + if ($PSCmdlet.ShouldProcess($KQLQuerysetName, "Create KQLQueryset")) { # Step 4: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -139,6 +140,7 @@ function New-FabricKQLQueryset { -SkipHttpErrorCheck ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response switch ($statusCode) { @@ -181,4 +183,4 @@ function New-FabricKQLQueryset { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create KQLQueryset. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 index c36be042..c826735b 100644 --- a/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Remove-FabricKQLQueryset.ps1 @@ -24,7 +24,8 @@ Author: Tiago Balabuch #> -function Remove-FabricKQLQueryset { +function Remove-FabricKQLQueryset +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -36,7 +37,8 @@ function Remove-FabricKQLQueryset { [string]$KQLQuerysetId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -45,18 +47,20 @@ function Remove-FabricKQLQueryset { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/kqlQuerysets/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $KQLQuerysetId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove KQLQueryset")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -64,9 +68,11 @@ function Remove-FabricKQLQueryset { } Write-Message -Message "KQLQueryset '$KQLQuerysetId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete KQLQueryset '$KQLQuerysetId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 index 35a0fdc0..f7438a60 100644 --- a/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 +++ b/source/Public/KQL Queryset/Update-FabricKQLQueryset.ps1 @@ -35,8 +35,9 @@ Author: Tiago Balabuch #> -function Update-FabricKQLQueryset { - [CmdletBinding()] +function Update-FabricKQLQueryset +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,7 +57,8 @@ function Update-FabricKQLQueryset { [string]$KQLQuerysetDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,27 +73,31 @@ function Update-FabricKQLQueryset { displayName = $KQLQuerysetName } - if ($KQLQuerysetDescription) { + if ($KQLQuerysetDescription) + { $body.description = $KQLQuerysetDescription } # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($KQLQuerysetId, "Update KQLQueryset")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -101,9 +107,11 @@ function Update-FabricKQLQueryset { # Step 6: Handle results Write-Message -Message "KQLQueryset '$KQLQuerysetName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 b/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 index 91aea620..1e77c90b 100644 --- a/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 +++ b/source/Public/KQL Queryset/Update-FabricKQLQuerysetDefinition.ps1 @@ -40,7 +40,7 @@ Author: Tiago Balabuch #> function Update-FabricKQLQuerysetDefinition { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -115,6 +115,7 @@ function Update-FabricKQLQuerysetDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug + if ($PSCmdlet.ShouldProcess($KQLQuerysetId, "Update KQLQueryset")) { # Step 4: Make the API request $response = Invoke-RestMethod ` -Headers $FabricConfig.FabricHeaders ` @@ -125,6 +126,7 @@ function Update-FabricKQLQuerysetDefinition { -ErrorAction Stop ` -ResponseHeadersVariable "responseHeader" ` -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response switch ($statusCode) { @@ -159,4 +161,4 @@ function Update-FabricKQLQuerysetDefinition { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update KQLQueryset. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 index f608e63b..f4f4816d 100644 --- a/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 +++ b/source/Public/Lakehouse/Get-FabricLakehouseTable.ps1 @@ -15,6 +15,7 @@ This example retrieves all tables from the specified Lakehouse in the specified #> [CmdletBinding()] + [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -110,4 +111,4 @@ This example retrieves all tables from the specified Lakehouse in the specified Write-Message -Message "Failed to retrieve Lakehouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/New-FabricLakehouse.ps1 b/source/Public/Lakehouse/New-FabricLakehouse.ps1 index e88d7eb9..feda71b4 100644 --- a/source/Public/Lakehouse/New-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/New-FabricLakehouse.ps1 @@ -30,7 +30,8 @@ Author: Tiago Balabuch #> -function New-FabricLakehouse { +function New-FabricLakehouse +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -51,7 +52,8 @@ function New-FabricLakehouse { [bool]$LakehouseEnableSchemas = $false ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -66,37 +68,43 @@ function New-FabricLakehouse { displayName = $LakehouseName } - if ($LakehouseDescription) { + if ($LakehouseDescription) + { $body.description = $LakehouseDescription } - if ($true -eq $LakehouseEnableSchemas) { + if ($true -eq $LakehouseEnableSchemas) + { $body.creationPayload = @{ enableSchemas = $LakehouseEnableSchemas } } $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($LakehouseName, "Create Lakehouse")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Lakehouse '$LakehouseName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Lakehouse '$LakehouseName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -106,7 +114,8 @@ function New-FabricLakehouse { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -114,21 +123,26 @@ function New-FabricLakehouse { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Lakehouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 b/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 index d16b9f1b..4a2b8a8d 100644 --- a/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/Remove-FabricLakehouse.ps1 @@ -24,8 +24,9 @@ Author: Tiago Balabuch #> -function Remove-FabricLakehouse { - [CmdletBinding()] +function Remove-FabricLakehouse +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -36,7 +37,8 @@ function Remove-FabricLakehouse { [string]$LakehouseId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -45,18 +47,21 @@ function Remove-FabricLakehouse { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/lakehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $LakehouseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Lakehouse")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -64,9 +69,11 @@ function Remove-FabricLakehouse { } Write-Message -Message "Lakehouse '$LakehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Lakehouse '$LakehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 index a494df3a..a168801f 100644 --- a/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 +++ b/source/Public/Lakehouse/Start-FabricLakehouseTableMaintenance.ps1 @@ -1,4 +1,5 @@ -function Start-FabricLakehouseTableMaintenance { +function Start-FabricLakehouseTableMaintenance +{ <# .SYNOPSIS Initiates a table maintenance job for a specified Lakehouse in a Fabric workspace. @@ -43,7 +44,7 @@ function Start-FabricLakehouseTableMaintenance { #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -85,7 +86,8 @@ function Start-FabricLakehouseTableMaintenance { ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -93,7 +95,8 @@ function Start-FabricLakehouseTableMaintenance { $lakehouse = Get-FabricLakehouse -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId - if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and -not $SchemaName) { + if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and -not $SchemaName) + { Write-Error "The Lakehouse '$lakehouse.displayName' has schema enabled, but no schema name was provided. Please specify the 'SchemaName' parameter to proceed." return } @@ -109,17 +112,21 @@ function Start-FabricLakehouseTableMaintenance { optimizeSettings = @{ } } } - if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and $SchemaName) { + if ($lakehouse.properties.PSObject.Properties['defaultSchema'] -and $SchemaName) + { $body.executionData.schemaName = $SchemaName } - if ($IsVOrder) { + if ($IsVOrder) + { $body.executionData.optimizeSettings.vOrder = $IsVOrder } - if ($ColumnsZOrderBy) { + if ($ColumnsZOrderBy) + { # Ensure $ColumnsZOrderBy is an array - if (-not ($ColumnsZOrderBy -is [array])) { + if (-not ($ColumnsZOrderBy -is [array])) + { $ColumnsZOrderBy = $ColumnsZOrderBy -split "," } # Add it to the optimizeSettings in the request body @@ -128,9 +135,11 @@ function Start-FabricLakehouseTableMaintenance { - if ($retentionPeriod) { + if ($retentionPeriod) + { - if (-not $body.executionData.PSObject.Properties['vacuumSettings']) { + if (-not $body.executionData.PSObject.Properties['vacuumSettings']) + { $body.executionData.vacuumSettings = @{ retentionPeriod = @() } @@ -141,27 +150,32 @@ function Start-FabricLakehouseTableMaintenance { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Start Table Maintenance Job")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } Write-Message -Message "Response Code: $statusCode" -Level Debug # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Table maintenance job successfully initiated for Lakehouse '$lakehouse.displayName'." -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Table maintenance job accepted and is now running in the background. Job execution is in progress." -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] @@ -171,12 +185,15 @@ function Start-FabricLakehouseTableMaintenance { Write-Message -Message "Location: '$location'" -Level Debug Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - if ($waitForCompletion -eq $true) { + if ($waitForCompletion -eq $true) + { Write-Message -Message "Getting Long Running Operation status" -Level Debug $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug return $operationStatus - } else { + } + else + { Write-Message -Message "The operation is running asynchronously." -Level Info Write-Message -Message "Use the returned details to check the operation status." -Level Info Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info @@ -188,15 +205,18 @@ function Start-FabricLakehouseTableMaintenance { return $operationDetails } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to start table maintenance job. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/Update-FabricLakehouse.ps1 b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 index 5f95b744..37a439ea 100644 --- a/source/Public/Lakehouse/Update-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/Update-FabricLakehouse.ps1 @@ -35,8 +35,9 @@ Author: Tiago Balabuch #> -function Update-FabricLakehouse { - [CmdletBinding()] +function Update-FabricLakehouse +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,7 +57,8 @@ function Update-FabricLakehouse { [string]$LakehouseDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,27 +73,31 @@ function Update-FabricLakehouse { displayName = $LakehouseName } - if ($LakehouseDescription) { + if ($LakehouseDescription) + { $body.description = $LakehouseDescription } # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($LakehouseId, "Update Lakehouse")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -102,9 +108,11 @@ function Update-FabricLakehouse { # Step 6: Handle results Write-Message -Message "Lakehouse '$LakehouseName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 b/source/Public/Lakehouse/Write-FabricLakehouseTableData.ps1 similarity index 78% rename from source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 rename to source/Public/Lakehouse/Write-FabricLakehouseTableData.ps1 index 638cfc82..124f7e3e 100644 --- a/source/Public/Lakehouse/Load-FabricLakehouseTable.ps1 +++ b/source/Public/Lakehouse/Write-FabricLakehouseTableData.ps1 @@ -1,4 +1,5 @@ -function Load-FabricLakehouseTable { +function Write-FabricLakehouseTableData +{ <# .SYNOPSIS Loads data into a specified table in a Lakehouse within a Fabric workspace. @@ -25,13 +26,14 @@ The mode for loading data (append or overwrite). .PARAMETER Recursive Indicates whether to load data recursively from subfolders (default is false). .EXAMPLE -Load-FabricLakehouseTable -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" -TableName "your-table-name" -PathType "File" -RelativePath "path/to/your/file.csv" -FileFormat "CSV" -CsvDelimiter "," -CsvHeader $true -Mode "append" -Recursive $false +Import-FabricLakehouseTableData -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" -TableName "your-table-name" -PathType "File" -RelativePath "path/to/your/file.csv" -FileFormat "CSV" -CsvDelimiter "," -CsvHeader $true -Mode "append" -Recursive $false This example loads data from a CSV file into the specified table in the Lakehouse. .EXAMPLE -Load-FabricLakehouseTable -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" -TableName "your-table-name" -PathType "Folder" -RelativePath "path/to/your/folder" -FileFormat "Parquet" -Mode "overwrite" -Recursive $true +Import-FabricLakehouseTableData -WorkspaceId "your-workspace-id" -LakehouseId "your-lakehouse-id" -TableName "your-table-name" -PathType "Folder" -RelativePath "path/to/your/folder" -FileFormat "Parquet" -Mode "overwrite" -Recursive $true This example loads data from a folder into the specified table in the Lakehouse, overwriting any existing data. #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] + [Alias("Import-FabricLakehouseTableData")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -78,7 +80,8 @@ This example loads data from a folder into the specified table in the Lakehouse, [bool]$Recursive = $false ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -99,7 +102,8 @@ This example loads data from a folder into the specified table in the Lakehouse, } } - if ($FileFormat -eq "CSV") { + if ($FileFormat -eq "CSV") + { $body.formatOptions.delimiter = $CsvDelimiter $body.formatOptions.hasHeader = $CsvHeader } @@ -108,20 +112,24 @@ This example loads data from a folder into the specified table in the Lakehouse, $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Load Lakehouse Table Data")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 202) { + if ($statusCode -ne 202) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -130,8 +138,10 @@ This example loads data from a folder into the specified table in the Lakehouse, } # Step 5: Handle and log the response - switch ($statusCode) { - 202 { + switch ($statusCode) + { + 202 + { Write-Message -Message "Load table '$TableName' request accepted. Load table operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -141,17 +151,21 @@ This example loads data from a folder into the specified table in the Lakehouse, $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Load table '$TableName' operation complete successfully!" -Level Info return $operationStatus - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -161,9 +175,11 @@ This example loads data from a folder into the specified table in the Lakehouse, # Step 6: Handle results - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Lakehouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/ML Experiment/New-FabricMLExperiment.ps1 b/source/Public/ML Experiment/New-FabricMLExperiment.ps1 index f8f26544..2e561b7b 100644 --- a/source/Public/ML Experiment/New-FabricMLExperiment.ps1 +++ b/source/Public/ML Experiment/New-FabricMLExperiment.ps1 @@ -26,8 +26,9 @@ Author: Tiago Balabuch #> -function New-FabricMLExperiment { - [CmdletBinding()] +function New-FabricMLExperiment +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -43,7 +44,8 @@ function New-FabricMLExperiment { [string]$MLExperimentDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -58,32 +60,38 @@ function New-FabricMLExperiment { displayName = $MLExperimentName } - if ($MLExperimentDescription) { + if ($MLExperimentDescription) + { $body.description = $MLExperimentDescription } $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($MLExperimentName, "Create ML Experiment")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "ML Experiment '$MLExperimentName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "ML Experiment '$MLExperimentName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -98,7 +106,8 @@ function New-FabricMLExperiment { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -106,13 +115,16 @@ function New-FabricMLExperiment { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -120,9 +132,11 @@ function New-FabricMLExperiment { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create ML Experiment. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 index 2289f4f4..5a1e4652 100644 --- a/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 +++ b/source/Public/ML Experiment/Remove-FabricMLExperiment.ps1 @@ -23,8 +23,9 @@ Author: Tiago Balabuch #> -function Remove-FabricMLExperiment { - [CmdletBinding()] +function Remove-FabricMLExperiment +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -34,7 +35,8 @@ function Remove-FabricMLExperiment { [ValidateNotNullOrEmpty()] [string]$MLExperimentId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -43,18 +45,21 @@ function Remove-FabricMLExperiment { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/mlExperiments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLExperimentId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove ML Experiment")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -63,9 +68,11 @@ function Remove-FabricMLExperiment { } Write-Message -Message "ML Experiment '$MLExperimentId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete ML Experiment '$MLExperimentId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 b/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 index 16c4c922..2d26ae9e 100644 --- a/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 +++ b/source/Public/ML Experiment/Update-FabricMLExperiment.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricMLExperiment { - [CmdletBinding()] +function Update-FabricMLExperiment +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -50,7 +51,8 @@ function Update-FabricMLExperiment { [string]$MLExperimentDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -65,27 +67,30 @@ function Update-FabricMLExperiment { displayName = $MLExperimentName } - if ($MLExperimentDescription) { + if ($MLExperimentDescription) + { $body.description = $MLExperimentDescription } # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($MLExperimentName, "Update ML Experiment")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -96,9 +101,11 @@ function Update-FabricMLExperiment { # Step 6: Handle results Write-Message -Message "ML Experiment '$MLExperimentName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update ML Experiment. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/ML Model/New-FabricMLModel.ps1 b/source/Public/ML Model/New-FabricMLModel.ps1 index 757fe679..4281dba5 100644 --- a/source/Public/ML Model/New-FabricMLModel.ps1 +++ b/source/Public/ML Model/New-FabricMLModel.ps1 @@ -26,8 +26,9 @@ Author: Tiago Balabuch #> -function New-FabricMLModel { - [CmdletBinding()] +function New-FabricMLModel +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -43,7 +44,8 @@ function New-FabricMLModel { [string]$MLModelDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -58,32 +60,38 @@ function New-FabricMLModel { displayName = $MLModelName } - if ($MLModelDescription) { + if ($MLModelDescription) + { $body.description = $MLModelDescription } $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($MLModelName, "Create ML Model")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "ML Model '$MLModelName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "ML Model '$MLModelName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -98,7 +106,8 @@ function New-FabricMLModel { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -106,13 +115,16 @@ function New-FabricMLModel { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -120,9 +132,11 @@ function New-FabricMLModel { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create ML Model. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/ML Model/Remove-FabricMLModel.ps1 b/source/Public/ML Model/Remove-FabricMLModel.ps1 index 79c85218..dc7fedc9 100644 --- a/source/Public/ML Model/Remove-FabricMLModel.ps1 +++ b/source/Public/ML Model/Remove-FabricMLModel.ps1 @@ -23,8 +23,9 @@ Author: Tiago Balabuch #> -function Remove-FabricMLModel { - [CmdletBinding()] +function Remove-FabricMLModel +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -34,7 +35,8 @@ function Remove-FabricMLModel { [ValidateNotNullOrEmpty()] [string]$MLModelId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -43,18 +45,20 @@ function Remove-FabricMLModel { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/mlModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MLModelId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove ML Model")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -63,9 +67,11 @@ function Remove-FabricMLModel { } Write-Message -Message "ML Model '$MLModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete ML Model '$MLModelId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/ML Model/Update-FabricMLModel.ps1 b/source/Public/ML Model/Update-FabricMLModel.ps1 index eff3ecd1..2c78a81e 100644 --- a/source/Public/ML Model/Update-FabricMLModel.ps1 +++ b/source/Public/ML Model/Update-FabricMLModel.ps1 @@ -26,8 +26,9 @@ Author: Tiago Balabuch #> -function Update-FabricMLModel { - [CmdletBinding()] +function Update-FabricMLModel +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -42,7 +43,8 @@ function Update-FabricMLModel { [string]$MLModelDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -60,20 +62,22 @@ function Update-FabricMLModel { # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update ML Model")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -84,9 +88,11 @@ function Update-FabricMLModel { # Step 6: Handle results Write-Message -Message "ML Model '$MLModelId' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update ML Model. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 index 7e4efaa7..a8a8790f 100644 --- a/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 +++ b/source/Public/Mirrored Database/Get-FabricMirroredDatabaseTableStatus.ps1 @@ -16,6 +16,7 @@ The function retrieves the PowerBI access token and makes a POST request to the #> [CmdletBinding()] + [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -107,4 +108,4 @@ The function retrieves the PowerBI access token and makes a POST request to the Write-Message -Message "Failed to retrieve MirroredDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 index e603d050..98957dad 100644 --- a/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/New-FabricMirroredDatabase.ps1 @@ -33,8 +33,9 @@ Author: Tiago Balabuch #> -function New-FabricMirroredDatabase { - [CmdletBinding()] +function New-FabricMirroredDatabase +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -58,7 +59,8 @@ function New-FabricMirroredDatabase { [string]$MirroredDatabasePathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -73,16 +75,20 @@ function New-FabricMirroredDatabase { displayName = $MirroredDatabaseName } - if ($MirroredDatabaseDescription) { + if ($MirroredDatabaseDescription) + { $body.description = $MirroredDatabaseDescription } - if ($MirroredDatabasePathDefinition) { + if ($MirroredDatabasePathDefinition) + { $MirroredDatabaseEncodedContent = Convert-ToBase64 -filePath $MirroredDatabasePathDefinition - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) { + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ parts = @() } @@ -94,18 +100,23 @@ function New-FabricMirroredDatabase { payload = $MirroredDatabaseEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error return $null } } - if ($MirroredDatabasePathPlatformDefinition) { + if ($MirroredDatabasePathPlatformDefinition) + { $MirroredDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $MirroredDatabasePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "MirroredDatabase" parts = @() @@ -118,7 +129,9 @@ function New-FabricMirroredDatabase { payload = $MirroredDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -126,26 +139,31 @@ function New-FabricMirroredDatabase { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($MirroredDatabaseName, "Create MirroredDatabase")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -155,7 +173,8 @@ function New-FabricMirroredDatabase { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -163,20 +182,25 @@ function New-FabricMirroredDatabase { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation Failed" -Level Debug return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create MirroredDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 index 0a44a64b..440cc1c0 100644 --- a/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/Remove-FabricMirroredDatabase.ps1 @@ -23,8 +23,9 @@ Deletes the MirroredDatabase with ID "67890" from workspace "12345". Author: Tiago Balabuch #> -function Remove-FabricMirroredDatabase { - [CmdletBinding()] +function Remove-FabricMirroredDatabase +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -35,7 +36,8 @@ function Remove-FabricMirroredDatabase { [string]$MirroredDatabaseId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -44,18 +46,21 @@ function Remove-FabricMirroredDatabase { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove MirroredDatabase")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -63,9 +68,11 @@ function Remove-FabricMirroredDatabase { } Write-Message -Message "MirroredDatabase '$MirroredDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete MirroredDatabase '$MirroredDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 index d3936ffe..5525eb20 100644 --- a/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 +++ b/source/Public/Mirrored Database/Start-FabricMirroredDatabaseMirroring.ps1 @@ -1,4 +1,5 @@ -function Start-FabricMirroredDatabaseMirroring { +function Start-FabricMirroredDatabaseMirroring +{ <# .SYNOPSIS Starts the mirroring of a specified mirrored database in a given workspace. @@ -18,7 +19,7 @@ function Start-FabricMirroredDatabaseMirroring { - This function handles asynchronous operations and retrieves operation results if required. #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -29,7 +30,8 @@ function Start-FabricMirroredDatabaseMirroring { [string]$MirroredDatabaseId ) - try { + try + { # Step 2: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -37,19 +39,22 @@ function Start-FabricMirroredDatabaseMirroring { $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/startMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Start MirroredDatabase Mirroring")) + { + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 7: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -60,10 +65,12 @@ function Start-FabricMirroredDatabaseMirroring { # Step 9: Handle results Write-Message -Message "Database mirroring started successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info return - } catch { + } + catch + { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to start MirroredDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 index d5201c9d..eb2b686a 100644 --- a/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 +++ b/source/Public/Mirrored Database/Stop-FabricMirroredDatabaseMirroring.ps1 @@ -1,4 +1,5 @@ -function Stop-FabricMirroredDatabaseMirroring { +function Stop-FabricMirroredDatabaseMirroring +{ <# .SYNOPSIS Stops the mirroring of a specified mirrored database in a given workspace. @@ -22,7 +23,7 @@ function Stop-FabricMirroredDatabaseMirroring { - This function handles asynchronous operations and retrieves operation results if required. #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -33,7 +34,8 @@ function Stop-FabricMirroredDatabaseMirroring { [string]$MirroredDatabaseId ) - try { + try + { # Step 2: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -41,19 +43,22 @@ function Stop-FabricMirroredDatabaseMirroring { $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/stopMirroring" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 6: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Stop MirroredDatabase Mirroring")) + { + # Step 6: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 7: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -64,10 +69,12 @@ function Stop-FabricMirroredDatabaseMirroring { # Step 9: Handle results Write-Message -Message "Database mirroring stopped successfully for MirroredDatabaseId: $MirroredDatabaseId" -Level Info return - } catch { + } + catch + { # Step 10: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to stop MirroredDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 index 33f57b41..734bae10 100644 --- a/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 +++ b/source/Public/Mirrored Database/Update-FabricMirroredDatabase.ps1 @@ -35,8 +35,9 @@ Author: Tiago Balabuch #> -function Update-FabricMirroredDatabase { - [CmdletBinding()] +function Update-FabricMirroredDatabase +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,7 +57,8 @@ function Update-FabricMirroredDatabase { [string]$MirroredDatabaseDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,27 +73,31 @@ function Update-FabricMirroredDatabase { displayName = $MirroredDatabaseName } - if ($MirroredDatabaseDescription) { + if ($MirroredDatabaseDescription) + { $body.description = $MirroredDatabaseDescription } # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($MirroredDatabaseId, "Update MirroredDatabase")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -101,9 +107,11 @@ function Update-FabricMirroredDatabase { # Step 6: Handle results Write-Message -Message "MirroredDatabase '$MirroredDatabaseName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 b/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 index 1ef351e6..5e76ec63 100644 --- a/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 +++ b/source/Public/Mirrored Database/Update-FabricMirroredDatabaseDefinition.ps1 @@ -42,8 +42,9 @@ Author: Tiago Balabuch #> -function Update-FabricMirroredDatabaseDefinition { - [CmdletBinding()] +function Update-FabricMirroredDatabaseDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -62,7 +63,8 @@ function Update-FabricMirroredDatabaseDefinition { [string]$MirroredDatabasePathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,7 +73,8 @@ function Update-FabricMirroredDatabaseDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/mirroredDatabases/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $MirroredDatabaseId - if ($MirroredDatabasePathPlatformDefinition) { + if ($MirroredDatabasePathPlatformDefinition) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -83,32 +86,40 @@ function Update-FabricMirroredDatabaseDefinition { } } - if ($MirroredDatabasePathDefinition) { + if ($MirroredDatabasePathDefinition) + { $MirroredDatabaseEncodedContent = Convert-ToBase64 -filePath $MirroredDatabasePathDefinition - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) { + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "MirroredDatabase.json" payload = $MirroredDatabaseEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in MirroredDatabase definition." -Level Error return $null } } - if ($MirroredDatabasePathPlatformDefinition) { + if ($MirroredDatabasePathPlatformDefinition) + { $MirroredDatabaseEncodedPlatformContent = Convert-ToBase64 -filePath $MirroredDatabasePathPlatformDefinition - if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($MirroredDatabaseEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $MirroredDatabaseEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -116,49 +127,60 @@ function Update-FabricMirroredDatabaseDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($MirroredDatabaseId, "Update MirroredDatabase")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for MirroredDatabase '$MirroredDatabaseId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for MirroredDatabase '$MirroredDatabaseId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] $operationResult = Get-FabricLongRunningOperation -operationId $operationId # Handle operation result - if ($operationResult.status -eq "Succeeded") { + if ($operationResult.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug $result = Get-FabricLongRunningOperationResult -operationId $operationId return $result.definition.parts - } else { + } + else + { Write-Message -Message "Operation Failed" -Level Debug return $operationResult.definition.parts } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update MirroredDatabase. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Notebook/New-FabricNotebook.ps1 b/source/Public/Notebook/New-FabricNotebook.ps1 index 0b607990..96128aea 100644 --- a/source/Public/Notebook/New-FabricNotebook.ps1 +++ b/source/Public/Notebook/New-FabricNotebook.ps1 @@ -33,8 +33,9 @@ Author: Tiago Balabuch #> -function New-FabricNotebook { - [CmdletBinding()] +function New-FabricNotebook +{ + [CmdletBinding(supportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -58,7 +59,8 @@ function New-FabricNotebook { [string]$NotebookPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -73,16 +75,20 @@ function New-FabricNotebook { displayName = $NotebookName } - if ($NotebookDescription) { + if ($NotebookDescription) + { $body.description = $NotebookDescription } - if ($NotebookPathDefinition) { + if ($NotebookPathDefinition) + { $notebookEncodedContent = Convert-ToBase64 -filePath $NotebookPathDefinition - if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) { + if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "ipynb" parts = @() @@ -95,18 +101,23 @@ function New-FabricNotebook { payload = $notebookEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in notebook definition." -Level Error return $null } } - if ($NotebookPathPlatformDefinition) { + if ($NotebookPathPlatformDefinition) + { $notebookEncodedPlatformContent = Convert-ToBase64 -filePath $NotebookPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "ipynb" parts = @() @@ -119,7 +130,9 @@ function New-FabricNotebook { payload = $notebookEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -127,26 +140,30 @@ function New-FabricNotebook { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($NotebookName, "Create Notebook")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Notebook '$NotebookName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -161,7 +178,8 @@ function New-FabricNotebook { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -169,21 +187,26 @@ function New-FabricNotebook { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Notebook/New-FabricNotebookNEW.ps1 b/source/Public/Notebook/New-FabricNotebookNEW.ps1 index 2fb7356c..007ff7e6 100644 --- a/source/Public/Notebook/New-FabricNotebookNEW.ps1 +++ b/source/Public/Notebook/New-FabricNotebookNEW.ps1 @@ -33,8 +33,9 @@ Author: Tiago Balabuch #> -function New-FabricNotebookNEW { - [CmdletBinding()] +function New-FabricNotebookNEW +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -54,7 +55,8 @@ function New-FabricNotebookNEW { [string]$NotebookPathDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -69,12 +71,15 @@ function New-FabricNotebookNEW { displayName = $NotebookName } - if ($NotebookDescription) { + if ($NotebookDescription) + { $body.description = $NotebookDescription } - if ($NotebookPathDefinition) { - if (-not $body.definition) { + if ($NotebookPathDefinition) + { + if (-not $body.definition) + { $body.definition = @{ format = "ipynb" parts = @() @@ -85,8 +90,10 @@ function New-FabricNotebookNEW { $body.definition.parts = $jsonObjectParts.parts } # Check if any path is .platform - foreach ($part in $jsonObjectParts.parts) { - if ($part.path -eq ".platform") { + foreach ($part in $jsonObjectParts.parts) + { + if ($part.path -eq ".platform") + { $hasPlatformFile = $true Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug } @@ -94,26 +101,31 @@ function New-FabricNotebookNEW { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($NotebookName, "Create Notebook")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Notebook '$NotebookName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Notebook '$NotebookName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -128,7 +140,8 @@ function New-FabricNotebookNEW { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -136,21 +149,26 @@ function New-FabricNotebookNEW { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create notebook. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Notebook/Remove-FabricNotebook.ps1 b/source/Public/Notebook/Remove-FabricNotebook.ps1 index 8da9ae55..78f03b98 100644 --- a/source/Public/Notebook/Remove-FabricNotebook.ps1 +++ b/source/Public/Notebook/Remove-FabricNotebook.ps1 @@ -24,8 +24,9 @@ Author: Tiago Balabuch #> -function Remove-FabricNotebook { - [CmdletBinding()] +function Remove-FabricNotebook +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -36,7 +37,8 @@ function Remove-FabricNotebook { [string]$NotebookId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -45,18 +47,21 @@ function Remove-FabricNotebook { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Notebook")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -64,9 +69,11 @@ function Remove-FabricNotebook { } Write-Message -Message "Notebook '$NotebookId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete notebook '$NotebookId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Notebook/Update-FabricNotebook.ps1 b/source/Public/Notebook/Update-FabricNotebook.ps1 index 8d967ce1..bdf39e5e 100644 --- a/source/Public/Notebook/Update-FabricNotebook.ps1 +++ b/source/Public/Notebook/Update-FabricNotebook.ps1 @@ -35,8 +35,9 @@ Author: Tiago Balabuch #> -function Update-FabricNotebook { - [CmdletBinding()] +function Update-FabricNotebook +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,7 +57,8 @@ function Update-FabricNotebook { [string]$NotebookDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,27 +73,30 @@ function Update-FabricNotebook { displayName = $NotebookName } - if ($NotebookDescription) { + if ($NotebookDescription) + { $body.description = $NotebookDescription } # Convert the body to JSON $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update Notebook")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -101,9 +106,11 @@ function Update-FabricNotebook { # Step 6: Handle results Write-Message -Message "Notebook '$NotebookName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 b/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 index b1f765d5..d867e626 100644 --- a/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 +++ b/source/Public/Notebook/Update-FabricNotebookDefinition.ps1 @@ -38,8 +38,9 @@ Author: Tiago Balabuch #> -function Update-FabricNotebookDefinition { - [CmdletBinding()] +function Update-FabricNotebookDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -58,7 +59,8 @@ function Update-FabricNotebookDefinition { [string]$NotebookPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -67,7 +69,8 @@ function Update-FabricNotebookDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/notebooks/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $NotebookId - if ($NotebookPathPlatformDefinition) { + if ($NotebookPathPlatformDefinition) + { $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -80,32 +83,40 @@ function Update-FabricNotebookDefinition { } } - if ($NotebookPathDefinition) { + if ($NotebookPathDefinition) + { $notebookEncodedContent = Convert-ToBase64 -filePath $NotebookPathDefinition - if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) { + if (-not [string]::IsNullOrEmpty($notebookEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "notebook-content.py" payload = $notebookEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in notebook definition." -Level Error return $null } } - if ($NotebookPathPlatformDefinition) { + if ($NotebookPathPlatformDefinition) + { $notebookEncodedPlatformContent = Convert-ToBase64 -filePath $NotebookPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($notebookEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $notebookEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -113,25 +124,30 @@ function Update-FabricNotebookDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($NotebookId, "Update Notebook Definition")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for notebook '$NotebookId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for notebook '$NotebookId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] [string]$location = $responseHeader["Location"] @@ -145,7 +161,8 @@ function Update-FabricNotebookDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -153,13 +170,16 @@ function Update-FabricNotebookDefinition { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -167,9 +187,11 @@ function Update-FabricNotebookDefinition { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update notebook. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 b/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 index 4a8bfa31..e8d17474 100644 --- a/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 +++ b/source/Public/Paginated Reports/Update-FabricPaginatedReport.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricPaginatedReport { - [CmdletBinding()] +function Update-FabricPaginatedReport +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -50,7 +51,8 @@ function Update-FabricPaginatedReport { [string]$PaginatedReportDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -65,7 +67,8 @@ function Update-FabricPaginatedReport { displayName = $PaginatedReportName } - if ($PaginatedReportDescription) { + if ($PaginatedReportDescription) + { $body.description = $PaginatedReportDescription } @@ -73,19 +76,23 @@ function Update-FabricPaginatedReport { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($PaginatedReportName, "Update Paginated Report")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -96,9 +103,11 @@ function Update-FabricPaginatedReport { # Step 6: Handle results Write-Message -Message "Paginated Report '$PaginatedReportName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Paginated Report. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Reflex/New-FabricReflex.ps1 b/source/Public/Reflex/New-FabricReflex.ps1 index ec792de0..f19cc0ad 100644 --- a/source/Public/Reflex/New-FabricReflex.ps1 +++ b/source/Public/Reflex/New-FabricReflex.ps1 @@ -32,8 +32,9 @@ Author: Tiago Balabuch #> -function New-FabricReflex { - [CmdletBinding()] +function New-FabricReflex +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,7 +57,8 @@ function New-FabricReflex { [ValidateNotNullOrEmpty()] [string]$ReflexPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,15 +73,19 @@ function New-FabricReflex { displayName = $ReflexName } - if ($ReflexDescription) { + if ($ReflexDescription) + { $body.description = $ReflexDescription } - if ($ReflexPathDefinition) { + if ($ReflexPathDefinition) + { $ReflexEncodedContent = Convert-ToBase64 -filePath $ReflexPathDefinition - if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) { + if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ parts = @() } @@ -91,18 +97,23 @@ function New-FabricReflex { payload = $ReflexEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error return $null } } - if ($ReflexPathPlatformDefinition) { + if ($ReflexPathPlatformDefinition) + { $ReflexEncodedPlatformContent = Convert-ToBase64 -filePath $ReflexPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ parts = @() } @@ -114,7 +125,9 @@ function New-FabricReflex { payload = $ReflexEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -124,27 +137,33 @@ function New-FabricReflex { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($ReflexName, "Create Reflex")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } Write-Message -Message "Response Code: $statusCode" -Level Debug # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Reflex '$ReflexName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Reflex '$ReflexName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -159,7 +178,8 @@ function New-FabricReflex { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -167,13 +187,16 @@ function New-FabricReflex { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -181,9 +204,11 @@ function New-FabricReflex { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Reflex. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Reflex/Remove-FabricReflex.ps1 b/source/Public/Reflex/Remove-FabricReflex.ps1 index ff259aab..affbcea9 100644 --- a/source/Public/Reflex/Remove-FabricReflex.ps1 +++ b/source/Public/Reflex/Remove-FabricReflex.ps1 @@ -23,8 +23,9 @@ Author: Tiago Balabuch #> -function Remove-FabricReflex { - [CmdletBinding()] +function Remove-FabricReflex +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -34,7 +35,8 @@ function Remove-FabricReflex { [ValidateNotNullOrEmpty()] [string]$ReflexId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -44,18 +46,22 @@ function Remove-FabricReflex { $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Reflex")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Handle response - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -64,9 +70,11 @@ function Remove-FabricReflex { } Write-Message -Message "Reflex '$ReflexId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Reflex '$ReflexId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Reflex/Update-FabricReflex.ps1 b/source/Public/Reflex/Update-FabricReflex.ps1 index 2b3ab89c..da7b2252 100644 --- a/source/Public/Reflex/Update-FabricReflex.ps1 +++ b/source/Public/Reflex/Update-FabricReflex.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricReflex { - [CmdletBinding()] +function Update-FabricReflex +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -49,7 +50,8 @@ function Update-FabricReflex { [ValidateNotNullOrEmpty()] [string]$ReflexDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -64,7 +66,8 @@ function Update-FabricReflex { displayName = $ReflexName } - if ($ReflexDescription) { + if ($ReflexDescription) + { $body.description = $ReflexDescription } @@ -72,20 +75,24 @@ function Update-FabricReflex { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess("Reflex", "Update")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -96,9 +103,11 @@ function Update-FabricReflex { # Step 6: Handle results Write-Message -Message "Reflex '$ReflexName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Reflex/Update-FabricReflexDefinition.ps1 b/source/Public/Reflex/Update-FabricReflexDefinition.ps1 index 24730ac1..8eb8862d 100644 --- a/source/Public/Reflex/Update-FabricReflexDefinition.ps1 +++ b/source/Public/Reflex/Update-FabricReflexDefinition.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricReflexDefinition { - [CmdletBinding()] +function Update-FabricReflexDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -48,7 +49,8 @@ function Update-FabricReflexDefinition { [ValidateNotNullOrEmpty()] [string]$ReflexPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -58,7 +60,8 @@ function Update-FabricReflexDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/reflexes/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReflexId #if ($UpdateMetadata -eq $true) { - if ($ReflexPathPlatformDefinition) { + if ($ReflexPathPlatformDefinition) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -70,32 +73,40 @@ function Update-FabricReflexDefinition { } } - if ($ReflexPathDefinition) { + if ($ReflexPathDefinition) + { $ReflexEncodedContent = Convert-ToBase64 -filePath $ReflexPathDefinition - if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) { + if (-not [string]::IsNullOrEmpty($ReflexEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "ReflexEntities.json" payload = $ReflexEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in Reflex definition." -Level Error return $null } } - if ($ReflexPathPlatformDefinition) { + if ($ReflexPathPlatformDefinition) + { $ReflexEncodedPlatformContent = Convert-ToBase64 -filePath $ReflexPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($ReflexEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $ReflexEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -104,24 +115,30 @@ function Update-FabricReflexDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update Reflex Definition")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for Reflex '$ReflexId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for Reflex '$ReflexId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -136,7 +153,8 @@ function Update-FabricReflexDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -144,21 +162,26 @@ function Update-FabricReflexDefinition { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Reflex. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Report/New-FabricReport.ps1 b/source/Public/Report/New-FabricReport.ps1 index 151b9e9d..c80e4a60 100644 --- a/source/Public/Report/New-FabricReport.ps1 +++ b/source/Public/Report/New-FabricReport.ps1 @@ -30,8 +30,9 @@ Author: Tiago Balabuch #> -function New-FabricReport { - [CmdletBinding()] +function New-FabricReport +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -50,7 +51,8 @@ function New-FabricReport { [ValidateNotNullOrEmpty()] [string]$ReportPathDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -65,11 +67,14 @@ function New-FabricReport { displayName = $ReportName } - if ($ReportDescription) { + if ($ReportDescription) + { $body.description = $ReportDescription } - if ($ReportPathDefinition) { - if (-not $body.definition) { + if ($ReportPathDefinition) + { + if (-not $body.definition) + { $body.definition = @{ parts = @() } @@ -83,27 +88,32 @@ function New-FabricReport { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($ReportName, "Create Report")){ + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } Write-Message -Message "Response Code: $statusCode" -Level Debug # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Report '$ReportName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Report '$ReportName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -118,7 +128,8 @@ function New-FabricReport { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -126,13 +137,16 @@ function New-FabricReport { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -140,9 +154,11 @@ function New-FabricReport { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Report. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Report/Remove-FabricReport.ps1 b/source/Public/Report/Remove-FabricReport.ps1 index 3a3ef692..130a1762 100644 --- a/source/Public/Report/Remove-FabricReport.ps1 +++ b/source/Public/Report/Remove-FabricReport.ps1 @@ -23,8 +23,9 @@ Author: Tiago Balabuch #> -function Remove-FabricReport { - [CmdletBinding()] +function Remove-FabricReport +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -34,7 +35,8 @@ function Remove-FabricReport { [ValidateNotNullOrEmpty()] [string]$ReportId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -44,18 +46,22 @@ function Remove-FabricReport { $apiEndpointUrl = "{0}/workspaces/{1}/reports/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $ReportId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Report")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Handle response - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -64,9 +70,11 @@ function Remove-FabricReport { } Write-Message -Message "Report '$ReportId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Report '$ReportId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Report/Update-FabricReport.ps1 b/source/Public/Report/Update-FabricReport.ps1 index 0d5a9536..c28725b0 100644 --- a/source/Public/Report/Update-FabricReport.ps1 +++ b/source/Public/Report/Update-FabricReport.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricReport { - [CmdletBinding()] +function Update-FabricReport +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -49,7 +50,8 @@ function Update-FabricReport { [ValidateNotNullOrEmpty()] [string]$ReportDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -64,7 +66,8 @@ function Update-FabricReport { displayName = $ReportName } - if ($ReportDescription) { + if ($ReportDescription) + { $body.description = $ReportDescription } @@ -72,20 +75,24 @@ function Update-FabricReport { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($ReportName, "Update Report")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -96,9 +103,11 @@ function Update-FabricReport { # Step 6: Handle results Write-Message -Message "Report '$ReportName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Report/Update-FabricReportDefinition.ps1 b/source/Public/Report/Update-FabricReportDefinition.ps1 index db0f06f0..2cb998c1 100644 --- a/source/Public/Report/Update-FabricReportDefinition.ps1 +++ b/source/Public/Report/Update-FabricReportDefinition.ps1 @@ -26,8 +26,9 @@ Author: Tiago Balabuch #> -function Update-FabricReportDefinition { - [CmdletBinding()] +function Update-FabricReportDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -41,7 +42,8 @@ function Update-FabricReportDefinition { [ValidateNotNullOrEmpty()] [string]$ReportPathDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -60,8 +62,10 @@ function Update-FabricReportDefinition { } } - if ($ReportPathDefinition) { - if (-not $body.definition) { + if ($ReportPathDefinition) + { + if (-not $body.definition) + { $body.definition = @{ parts = @() } @@ -71,14 +75,17 @@ function Update-FabricReportDefinition { $body.definition.parts = $jsonObjectParts.parts } # Check if any path is .platform - foreach ($part in $jsonObjectParts.parts) { - if ($part.path -eq ".platform") { + foreach ($part in $jsonObjectParts.parts) + { + if ($part.path -eq ".platform") + { $hasPlatformFile = $true Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug } } - if ($hasPlatformFile -eq $true) { + if ($hasPlatformFile -eq $true) + { $apiEndpointUrl += "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -87,24 +94,30 @@ function Update-FabricReportDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update Report Definition")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for Report '$ReportId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for Report '$ReportId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -119,25 +132,31 @@ function Update-FabricReportDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Update definition operation for Report '$ReportId' succeeded!" -Level Info return $operationStatus - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Report. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/SQL Database/New-FabricSQLDatabase.ps1 b/source/Public/SQL Database/New-FabricSQLDatabase.ps1 index ce9f1332..f6fb22db 100644 --- a/source/Public/SQL Database/New-FabricSQLDatabase.ps1 +++ b/source/Public/SQL Database/New-FabricSQLDatabase.ps1 @@ -28,8 +28,9 @@ Author: Kamil Nowinski #> -function New-FabricSQLDatabase { - [CmdletBinding()] +function New-FabricSQLDatabase +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -45,7 +46,8 @@ function New-FabricSQLDatabase { [string]$Description ) - try { + try + { # Step 1: Ensure token validity Test-TokenExpired @@ -58,31 +60,37 @@ function New-FabricSQLDatabase { displayName = $Name } - if ($Description) { + if ($Description) + { $body.description = $Description } $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Create SQL Database")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response Write-Message "RESPONSE: $response" -Level Debug Test-FabricApiResponse -response $response -responseHeader $responseHeader -statusCode $statusCode -Name $Name -TypeName 'SQL Database' - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create SQL Database. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 index 4f72cf67..a780ea12 100644 --- a/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 +++ b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 @@ -24,8 +24,9 @@ Author: Kamil Nowinski #> -function Remove-FabricSQLDatabase { - [CmdletBinding()] +function Remove-FabricSQLDatabase +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -36,7 +37,8 @@ function Remove-FabricSQLDatabase { [string]$SQLDatabaseId ) - try { + try + { # Step 1: Ensure token validity Confirm-FabricAuthToken | Out-Null Test-TokenExpired @@ -45,17 +47,22 @@ function Remove-FabricSQLDatabase { $apiEndpointUrl = "{0}/workspaces/{1}/sqldatabases/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SQLDatabaseId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Delete SQL Database")) + { + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -63,9 +70,11 @@ function Remove-FabricSQLDatabase { } Write-Message -Message "SQL Database '$SQLDatabaseId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SQL Database '$SQLDatabaseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Semantic Model/New-FabricSemanticModel.ps1 b/source/Public/Semantic Model/New-FabricSemanticModel.ps1 index 0a0cd70d..fd7fb974 100644 --- a/source/Public/Semantic Model/New-FabricSemanticModel.ps1 +++ b/source/Public/Semantic Model/New-FabricSemanticModel.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function New-FabricSemanticModel { - [CmdletBinding()] +function New-FabricSemanticModel +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -49,7 +50,8 @@ function New-FabricSemanticModel { [ValidateNotNullOrEmpty()] [string]$SemanticModelPathDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -71,7 +73,8 @@ function New-FabricSemanticModel { # Add new part to the parts array $body.definition.parts = $jsonObjectParts.parts - if ($SemanticModelDescription) { + if ($SemanticModelDescription) + { $body.description = $SemanticModelDescription } @@ -79,27 +82,33 @@ function New-FabricSemanticModel { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess("Create SemanticModel", "Creating the SemanticModel '$SemanticModelName' in workspace '$WorkspaceId'.")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } Write-Message -Message "Response Code: $statusCode" -Level Debug # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "SemanticModel '$SemanticModelName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "SemanticModel '$SemanticModelName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -114,7 +123,8 @@ function New-FabricSemanticModel { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -122,13 +132,16 @@ function New-FabricSemanticModel { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -136,9 +149,11 @@ function New-FabricSemanticModel { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create SemanticModel. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 index b1025a38..2f165eee 100644 --- a/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 +++ b/source/Public/Semantic Model/Remove-FabricSemanticModel.ps1 @@ -23,8 +23,9 @@ Author: Tiago Balabuch #> -function Remove-FabricSemanticModel { - [CmdletBinding()] +function Remove-FabricSemanticModel +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -34,7 +35,8 @@ function Remove-FabricSemanticModel { [ValidateNotNullOrEmpty()] [string]$SemanticModelId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -44,18 +46,22 @@ function Remove-FabricSemanticModel { $apiEndpointUrl = "{0}/workspaces/{1}/semanticModels/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SemanticModelId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove SemanticModel")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Handle response - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -64,9 +70,11 @@ function Remove-FabricSemanticModel { } Write-Message -Message "SemanticModel '$SemanticModelId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SemanticModel '$SemanticModelId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 b/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 index 0082d596..5fa9ceaa 100644 --- a/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 +++ b/source/Public/Semantic Model/Update-FabricSemanticModel.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricSemanticModel { - [CmdletBinding()] +function Update-FabricSemanticModel +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -49,7 +50,8 @@ function Update-FabricSemanticModel { [ValidateNotNullOrEmpty()] [string]$SemanticModelDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -64,7 +66,8 @@ function Update-FabricSemanticModel { displayName = $SemanticModelName } - if ($SemanticModelDescription) { + if ($SemanticModelDescription) + { $body.description = $SemanticModelDescription } @@ -72,20 +75,24 @@ function Update-FabricSemanticModel { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess("Update SemanticModel", "Updating the SemanticModel with ID '$SemanticModelId' in workspace '$WorkspaceId'.")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -96,9 +103,11 @@ function Update-FabricSemanticModel { # Step 6: Handle results Write-Message -Message "SemanticModel '$SemanticModelName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 b/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 index 2c98e150..08c0b59b 100644 --- a/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 +++ b/source/Public/Semantic Model/Update-FabricSemanticModelDefinition.ps1 @@ -26,8 +26,9 @@ Author: Tiago Balabuch #> -function Update-FabricSemanticModelDefinition { - [CmdletBinding()] +function Update-FabricSemanticModelDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -41,7 +42,8 @@ function Update-FabricSemanticModelDefinition { [ValidateNotNullOrEmpty()] [string]$SemanticModelPathDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -61,14 +63,17 @@ function Update-FabricSemanticModelDefinition { # Add new part to the parts array $body.definition.parts = $jsonObjectParts.parts # Check if any path is .platform - foreach ($part in $jsonObjectParts.parts) { - if ($part.path -eq ".platform") { + foreach ($part in $jsonObjectParts.parts) + { + if ($part.path -eq ".platform") + { $hasPlatformFile = $true Write-Message -Message "Platform File: $hasPlatformFile" -Level Debug } } - if ($hasPlatformFile -eq $true) { + if ($hasPlatformFile -eq $true) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -77,24 +82,30 @@ function Update-FabricSemanticModelDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update SemanticModel Definition")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for SemanticModel '$SemanticModelId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for SemanticModel '$SemanticModelId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -109,25 +120,31 @@ function Update-FabricSemanticModelDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Update definition operation for Semantic Model '$SemanticModelId' succeeded!" -Level Info return $operationStatus - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SemanticModel. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Set-FabricAuthToken.ps1 b/source/Public/Set-FabricAuthToken.ps1 index ac5f5dd1..d9eedda3 100644 --- a/source/Public/Set-FabricAuthToken.ps1 +++ b/source/Public/Set-FabricAuthToken.ps1 @@ -1,5 +1,6 @@ -function Set-FabricAuthToken { - <# +function Set-FabricAuthToken +{ + <# .SYNOPSIS Sets the Fabric authentication token. @@ -42,63 +43,74 @@ function Set-FabricAuthToken { This function was originally written by Rui Romano. https://github.com/RuiRomano/fabricps-pbip #> - - [CmdletBinding(SupportsShouldProcess)] - param - ( - [string] $servicePrincipalId - , [string] $servicePrincipalSecret - , [PSCredential] $credential - , [string] $tenantId - , [switch] $reset - , [string] $apiUrl - ) - - if (!$reset) { - $azContext = Get-AzContext - } - - if ($apiUrl) { - $FabricSession.BaseApiUrl = $apiUrl - } - - if (!$azContext) { - Write-Output "Getting authentication token" - if ($servicePrincipalId) { - $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $servicePrincipalId, ($servicePrincipalSecret | ConvertTo-SecureString -AsPlainText -Force) - - Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential | Out-Null - - Set-AzContext -Tenant $tenantId | Out-Null - } elseif ($null -ne $credential) { - Connect-AzAccount -Credential $credential -Tenant $tenantId | Out-Null - } else { - Connect-AzAccount | Out-Null - } - $azContext = Get-AzContext - } - if ($PSCmdlet.ShouldProcess("Setting Fabric authentication token for $($azContext.Account)")) { - Write-Output "Connnected: $($azContext.Account)" - Write-Output "Fabric ResourceUrl: $($FabricSession.ResourceUrl)" - - $FabricSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $FabricSession.ResourceUrl) - $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($FabricSession.AccessToken.Token) - $FabricSession.FabricToken = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) - Write-Verbose "Setup headers for API calls" - $FabricSession.HeaderParams = @{ Authorization = $FabricSession.AccessToken.Type + ' ' + $FabricSession.FabricToken } - - Write-Output "Azure BaseApiUrl: $($FabricSession.ResourceUrl)" - $script:AzureSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $AzureSession.BaseApiUrl) - $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzureSession.AccessToken.Token) - $script:AzureSession.Token = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) - $AzureSession.HeaderParams = @{ Authorization = $AzureSession.AccessToken.Type + ' ' + $AzureSession.Token } - - # Copy session values to exposed $FabricConfig - $FabricConfig.TenantIdGlobal = $FabricSession.AccessToken.TenantId - $FabricConfig.TokenExpiresOn = $FabricSession.AccessToken.ExpiresOn - $FabricConfig.FabricHeaders = $FabricSession.HeaderParams - - return $($FabricSession.FabricToken) - - } -} \ No newline at end of file + [OutputType([System.Collections.Hashtable])] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification = "To pass current unit tests")] + [CmdletBinding(SupportsShouldProcess)] + param + ( + [string] $servicePrincipalId + , [string] $servicePrincipalSecret + , [PSCredential] $credential + , [string] $tenantId + , [switch] $reset + , [string] $apiUrl + ) + + + if (!$reset) + { + $azContext = Get-AzContext + } + + if ($apiUrl) + { + $FabricSession.BaseApiUrl = $apiUrl + } + + if (!$azContext) + { + Write-Output "Getting authentication token" + if ($servicePrincipalId) + { + $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $servicePrincipalId, ($servicePrincipalSecret | ConvertTo-SecureString -AsPlainText -Force) + + Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential | Out-Null + + Set-AzContext -Tenant $tenantId | Out-Null + } + elseif ($null -ne $credential) + { + Connect-AzAccount -Credential $credential -Tenant $tenantId | Out-Null + } + else + { + Connect-AzAccount | Out-Null + } + $azContext = Get-AzContext + } + if ($PSCmdlet.ShouldProcess("Setting Fabric authentication token for $($azContext.Account)")) + { + Write-Output "Connnected: $($azContext.Account)" + Write-Output "Fabric ResourceUrl: $($FabricSession.ResourceUrl)" + + $FabricSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $FabricSession.ResourceUrl) + $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($FabricSession.AccessToken.Token) + $FabricSession.FabricToken = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) + Write-Verbose "Setup headers for API calls" + $FabricSession.HeaderParams = @{ Authorization = $FabricSession.AccessToken.Type + ' ' + $FabricSession.FabricToken } + + Write-Output "Azure BaseApiUrl: $($FabricSession.ResourceUrl)" + $script:AzureSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $AzureSession.BaseApiUrl) + $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzureSession.AccessToken.Token) + $script:AzureSession.Token = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)) + $AzureSession.HeaderParams = @{ Authorization = $AzureSession.AccessToken.Type + ' ' + $AzureSession.Token } + + # Copy session values to exposed $FabricConfig + $FabricConfig.TenantIdGlobal = $FabricSession.AccessToken.TenantId + $FabricConfig.TokenExpiresOn = $FabricSession.AccessToken.ExpiresOn + $FabricConfig.FabricHeaders = $FabricSession.HeaderParams + + return $($FabricSession.FabricToken) + + } +} diff --git a/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 index 2c9e779d..deb64c00 100644 --- a/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/New-FabricSparkJobDefinition.ps1 @@ -31,8 +31,9 @@ Author: Tiago Balabuch #> -function New-FabricSparkJobDefinition { - [CmdletBinding()] +function New-FabricSparkJobDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -55,7 +56,8 @@ function New-FabricSparkJobDefinition { [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -70,15 +72,19 @@ function New-FabricSparkJobDefinition { displayName = $SparkJobDefinitionName } - if ($SparkJobDefinitionDescription) { + if ($SparkJobDefinitionDescription) + { $body.description = $SparkJobDefinitionDescription } - if ($SparkJobDefinitionPathDefinition) { + if ($SparkJobDefinitionPathDefinition) + { $SparkJobDefinitionEncodedContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathDefinition - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) { + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "SparkJobDefinitionV1" parts = @() @@ -91,18 +97,23 @@ function New-FabricSparkJobDefinition { payload = $SparkJobDefinitionEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error return $null } } - if ($SparkJobDefinitionPathPlatformDefinition) { + if ($SparkJobDefinitionPathPlatformDefinition) + { $SparkJobDefinitionEncodedPlatformContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) + { # Initialize definition if it doesn't exist - if (-not $body.definition) { + if (-not $body.definition) + { $body.definition = @{ format = "SparkJobDefinitionV1" parts = @() @@ -115,7 +126,9 @@ function New-FabricSparkJobDefinition { payload = $SparkJobDefinitionEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -125,27 +138,32 @@ function New-FabricSparkJobDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Create Spark Job Definition")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + + Write-Message -Message "Response Code: $statusCode" -Level Debug + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -160,7 +178,8 @@ function New-FabricSparkJobDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -168,13 +187,16 @@ function New-FabricSparkJobDefinition { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -182,9 +204,11 @@ function New-FabricSparkJobDefinition { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Spark Job Definition. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 index 17a14a0f..3554e88c 100644 --- a/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 @@ -22,8 +22,9 @@ Author: Tiago Balabuch #> -function Remove-FabricSparkJobDefinition { - [CmdletBinding()] +function Remove-FabricSparkJobDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -33,7 +34,8 @@ function Remove-FabricSparkJobDefinition { [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -42,19 +44,21 @@ function Remove-FabricSparkJobDefinition { # Step 2: Construct the API URL $apiEndpointUrl = "{0}/workspaces/{1}/sparkJobDefinitions/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove SparkJobDefinition")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Handle response - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -63,9 +67,11 @@ function Remove-FabricSparkJobDefinition { } Write-Message -Message "Spark Job Definition '$SparkJobDefinitionId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SparkJobDefinition '$SparkJobDefinitionId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 b/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 index 83683a1c..5cdd5f61 100644 --- a/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 +++ b/source/Public/Spark Job Definition/Start-FabricSparkJobDefinitionOnDemand.ps1 @@ -26,8 +26,9 @@ Ensure that the necessary authentication tokens are valid before running this function. The function logs detailed messages for debugging and informational purposes. #> -function Start-FabricSparkJobDefinitionOnDemand { - [CmdletBinding()] +function Start-FabricSparkJobDefinitionOnDemand +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -47,7 +48,8 @@ function Start-FabricSparkJobDefinitionOnDemand { [bool]$waitForCompletion = $false ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -57,60 +59,69 @@ function Start-FabricSparkJobDefinitionOnDemand { $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/jobs/instances?jobType={3}" -f $FabricConfig.BaseUrl, $WorkspaceId , $SparkJobDefinitionId, $JobType Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - Write-Message -Message "Response Code: $statusCode" -Level Debug - # Step 5: Handle and log the response - switch ($statusCode) { - 201 { - Write-Message -Message "Spark Job Definition on demand successfully initiated for SparkJobDefinition '$SparkJobDefinition.displayName'." -Level Info - return $response + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Start Spark Job Definition on demand")){ + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" } - 202 { - Write-Message -Message "Spark Job Definition on demand accepted and is now running in the background. Job execution is in progress." -Level Info - [string]$operationId = $responseHeader["x-ms-operation-id"] - [string]$location = $responseHeader["Location"] - [string]$retryAfter = $responseHeader["Retry-After"] - - Write-Message -Message "Operation ID: '$operationId'" -Level Debug - Write-Message -Message "Location: '$location'" -Level Debug - Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug - - if ($waitForCompletion -eq $true) { - Write-Message -Message "Getting Long Running Operation status" -Level Debug - $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter - Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug - return $operationStatus - } else { - Write-Message -Message "The operation is running asynchronously." -Level Info - Write-Message -Message "Use the returned details to check the operation status." -Level Info - Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info - $operationDetails = [PSCustomObject]@{ - OperationId = $operationId - Location = $location - RetryAfter = $retryAfter + Write-Message -Message "Response Code: $statusCode" -Level Debug + # Step 5: Handle and log the response + switch ($statusCode) + { + 201 + { + Write-Message -Message "Spark Job Definition on demand successfully initiated for SparkJobDefinition '$SparkJobDefinition.displayName'." -Level Info + return $response + } + 202 + { + Write-Message -Message "Spark Job Definition on demand accepted and is now running in the background. Job execution is in progress." -Level Info + [string]$operationId = $responseHeader["x-ms-operation-id"] + [string]$location = $responseHeader["Location"] + [string]$retryAfter = $responseHeader["Retry-After"] + + Write-Message -Message "Operation ID: '$operationId'" -Level Debug + Write-Message -Message "Location: '$location'" -Level Debug + Write-Message -Message "Retry-After: '$retryAfter'" -Level Debug + + if ($waitForCompletion -eq $true) + { + Write-Message -Message "Getting Long Running Operation status" -Level Debug + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location -retryAfter $retryAfter + Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug + return $operationStatus + } + else + { + Write-Message -Message "The operation is running asynchronously." -Level Info + Write-Message -Message "Use the returned details to check the operation status." -Level Info + Write-Message -Message "To wait for the operation to complete, set the 'waitForCompletion' parameter to true." -Level Info + $operationDetails = [PSCustomObject]@{ + OperationId = $operationId + Location = $location + RetryAfter = $retryAfter + } + return $operationDetails } - return $operationDetails + } + default + { + Write-Message -Message "Unexpected response code: $statusCode" -Level Error + Write-Message -Message "Error details: $($response.message)" -Level Error + throw "API request failed with status code $statusCode." } } - default { - Write-Message -Message "Unexpected response code: $statusCode" -Level Error - Write-Message -Message "Error details: $($response.message)" -Level Error - throw "API request failed with status code $statusCode." - } } - } catch { - # Step 6: Handle and log errors - $errorDetails = $_.Exception.Message - Write-Message -Message "Failed to start Spark Job Definition on demand. Error: $errorDetails" -Level Error + catch { + # Step 6: Handle and log errors + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to start Spark Job Definition on demand. Error: $errorDetails" -Level Error + } } -} \ No newline at end of file diff --git a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 index e6f8e476..f841d006 100644 --- a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinition.ps1 @@ -28,8 +28,9 @@ Author: Tiago Balabuch #> -function Update-FabricSparkJobDefinition { - [CmdletBinding()] +function Update-FabricSparkJobDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -48,7 +49,8 @@ function Update-FabricSparkJobDefinition { [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -63,7 +65,8 @@ function Update-FabricSparkJobDefinition { displayName = $SparkJobDefinitionName } - if ($SparkJobDefinitionDescription) { + if ($SparkJobDefinitionDescription) + { $body.description = $SparkJobDefinitionDescription } @@ -71,20 +74,24 @@ function Update-FabricSparkJobDefinition { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update SparkJobDefinition")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -95,9 +102,11 @@ function Update-FabricSparkJobDefinition { # Step 6: Handle results Write-Message -Message "Spark Job Definition '$SparkJobDefinitionName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SparkJobDefinition. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 index ef7c2651..1c680b99 100644 --- a/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 +++ b/source/Public/Spark Job Definition/Update-FabricSparkJobDefinitionDefinition.ps1 @@ -28,8 +28,9 @@ Author: Tiago Balabuch #> -function Update-FabricSparkJobDefinitionDefinition { - [CmdletBinding()] +function Update-FabricSparkJobDefinitionDefinition +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -47,7 +48,8 @@ function Update-FabricSparkJobDefinitionDefinition { [ValidateNotNullOrEmpty()] [string]$SparkJobDefinitionPathPlatformDefinition ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -57,7 +59,8 @@ function Update-FabricSparkJobDefinitionDefinition { $apiEndpointUrl = "{0}/workspaces/{1}/SparkJobDefinitions/{2}/updateDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkJobDefinitionId #if ($UpdateMetadata -eq $true) { - if ($SparkJobDefinitionPathPlatformDefinition) { + if ($SparkJobDefinitionPathPlatformDefinition) + { $apiEndpointUrl = "?updateMetadata=true" -f $apiEndpointUrl } Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug @@ -70,32 +73,40 @@ function Update-FabricSparkJobDefinitionDefinition { } } - if ($SparkJobDefinitionPathDefinition) { + if ($SparkJobDefinitionPathDefinition) + { $SparkJobDefinitionEncodedContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathDefinition - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) { + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = "SparkJobDefinitionV1.json" payload = $SparkJobDefinitionEncodedContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in SparkJobDefinition definition." -Level Error return $null } } - if ($SparkJobDefinitionPathPlatformDefinition) { + if ($SparkJobDefinitionPathPlatformDefinition) + { $SparkJobDefinitionEncodedPlatformContent = Convert-ToBase64 -filePath $SparkJobDefinitionPathPlatformDefinition - if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) { + if (-not [string]::IsNullOrEmpty($SparkJobDefinitionEncodedPlatformContent)) + { # Add new part to the parts array $body.definition.parts += @{ path = ".platform" payload = $SparkJobDefinitionEncodedPlatformContent payloadType = "InlineBase64" } - } else { + } + else + { Write-Message -Message "Invalid or empty content in platform definition." -Level Error return $null } @@ -104,24 +115,31 @@ function Update-FabricSparkJobDefinitionDefinition { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update Spark Job Definition")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Update definition for Spark Job Definition '$SparkJobDefinitionId' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Update definition for Spark Job Definition '$SparkJobDefinitionId' accepted. Operation in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -136,7 +154,8 @@ function Update-FabricSparkJobDefinitionDefinition { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $location Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -144,21 +163,26 @@ function Update-FabricSparkJobDefinitionDefinition { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Spark Job Definition. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark/Get-FabricSparkSettings.ps1 b/source/Public/Spark/Get-FabricSparkSettings.ps1 index 6b094ec0..e111847c 100644 --- a/source/Public/Spark/Get-FabricSparkSettings.ps1 +++ b/source/Public/Spark/Get-FabricSparkSettings.ps1 @@ -22,6 +22,7 @@ #> function Get-FabricSparkSettings { [CmdletBinding()] + [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -112,4 +113,4 @@ function Get-FabricSparkSettings { Write-Message -Message "Failed to retrieve SparkSettings. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark/New-FabricSparkCustomPool.ps1 b/source/Public/Spark/New-FabricSparkCustomPool.ps1 index 75365fd0..750c977f 100644 --- a/source/Public/Spark/New-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/New-FabricSparkCustomPool.ps1 @@ -48,8 +48,9 @@ #> -function New-FabricSparkCustomPool { - [CmdletBinding()] +function New-FabricSparkCustomPool +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -95,7 +96,8 @@ function New-FabricSparkCustomPool { [int]$DynamicExecutorAllocationMaxExecutors ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -125,25 +127,31 @@ function New-FabricSparkCustomPool { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Create Spark Custom Pool")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "SparkCustomPool '$SparkCustomPoolName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "SparkCustomPool '$SparkCustomPoolName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] @@ -158,7 +166,8 @@ function New-FabricSparkCustomPool { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -166,13 +175,16 @@ function New-FabricSparkCustomPool { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -180,9 +192,11 @@ function New-FabricSparkCustomPool { throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create SparkCustomPool. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 index b1e546d3..0fbc7087 100644 --- a/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 @@ -23,8 +23,9 @@ Author: Tiago Balabuch #> -function Remove-FabricSparkCustomPool { - [CmdletBinding()] +function Remove-FabricSparkCustomPool +{ + [CmdletBinding(supportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -34,7 +35,8 @@ function Remove-FabricSparkCustomPool { [ValidateNotNullOrEmpty()] [string]$SparkCustomPoolId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -44,17 +46,21 @@ function Remove-FabricSparkCustomPool { $apiEndpointUrl = "{0}/workspaces/{1}/spark/pools/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $SparkCustomPoolId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Remove Spark Custom Pool")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -63,9 +69,11 @@ function Remove-FabricSparkCustomPool { } Write-Message -Message "Spark Custom Pool '$SparkCustomPoolId' deleted successfully from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete SparkCustomPool '$SparkCustomPoolId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark/Update-FabricSparkCustomPool.ps1 b/source/Public/Spark/Update-FabricSparkCustomPool.ps1 index d42419f5..f67013ad 100644 --- a/source/Public/Spark/Update-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/Update-FabricSparkCustomPool.ps1 @@ -50,8 +50,9 @@ Author: Tiago Balabuch #> -function Update-FabricSparkCustomPool { - [CmdletBinding()] +function Update-FabricSparkCustomPool +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -101,7 +102,8 @@ function Update-FabricSparkCustomPool { [int]$DynamicExecutorAllocationMaxExecutors ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -132,19 +134,24 @@ function Update-FabricSparkCustomPool { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update SparkCustomPool")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -155,9 +162,11 @@ function Update-FabricSparkCustomPool { # Step 6: Handle results Write-Message -Message "Spark Custom Pool '$SparkCustomPoolName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SparkCustomPool. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Spark/Update-FabricSparkSettings.ps1 b/source/Public/Spark/Update-FabricSparkSettings.ps1 index a00c4806..749a2e2b 100644 --- a/source/Public/Spark/Update-FabricSparkSettings.ps1 +++ b/source/Public/Spark/Update-FabricSparkSettings.ps1 @@ -77,8 +77,9 @@ Author: Tiago Balabuch #> -function Update-FabricSparkSettings { - [CmdletBinding()] +function Update-FabricSparkSettings +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -123,7 +124,8 @@ function Update-FabricSparkSettings { [string]$EnvironmentRuntimeVersion ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -138,43 +140,52 @@ function Update-FabricSparkSettings { $body = @{ } - if ($PSBoundParameters.ContainsKey('automaticLogEnabled')) { + if ($PSBoundParameters.ContainsKey('automaticLogEnabled')) + { $body.automaticLog = @{ enabled = $automaticLogEnabled } } - if ($PSBoundParameters.ContainsKey('notebookInteractiveRunEnabled')) { + if ($PSBoundParameters.ContainsKey('notebookInteractiveRunEnabled')) + { $body.highConcurrency = @{ notebookInteractiveRunEnabled = $notebookInteractiveRunEnabled } } - if ($PSBoundParameters.ContainsKey('customizeComputeEnabled') ) { + if ($PSBoundParameters.ContainsKey('customizeComputeEnabled') ) + { $body.pool = @{ customizeComputeEnabled = $customizeComputeEnabled } } - if ($PSBoundParameters.ContainsKey('defaultPoolName') -or $PSBoundParameters.ContainsKey('defaultPoolType')) { - if ($PSBoundParameters.ContainsKey('defaultPoolName') -and $PSBoundParameters.ContainsKey('defaultPoolType')) { + if ($PSBoundParameters.ContainsKey('defaultPoolName') -or $PSBoundParameters.ContainsKey('defaultPoolType')) + { + if ($PSBoundParameters.ContainsKey('defaultPoolName') -and $PSBoundParameters.ContainsKey('defaultPoolType')) + { $body.pool = @{ defaultPool = @{ name = $defaultPoolName type = $defaultPoolType } } - } else { + } + else + { Write-Message -Message "Both 'defaultPoolName' and 'defaultPoolType' must be provided together." -Level Error throw } } - if ($PSBoundParameters.ContainsKey('EnvironmentName') -or $PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) { + if ($PSBoundParameters.ContainsKey('EnvironmentName') -or $PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) + { $body.environment = @{ name = $EnvironmentName } } - if ($PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) { + if ($PSBoundParameters.ContainsKey('EnvironmentRuntimeVersion')) + { $body.environment = @{ runtimeVersion = $EnvironmentRuntimeVersion } @@ -184,19 +195,24 @@ function Update-FabricSparkSettings { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update SparkSettings")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message -Message "Error Details: $($response.moreDetails)" -Level Error @@ -207,9 +223,11 @@ function Update-FabricSparkSettings { # Step 6: Handle results Write-Message -Message "Spark Custom Pool '$SparkSettingsName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update SparkSettings. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Tenant/Get-FabricTenantSettings.ps1 b/source/Public/Tenant/Get-FabricTenantSettings.ps1 deleted file mode 100644 index b3372aad..00000000 --- a/source/Public/Tenant/Get-FabricTenantSettings.ps1 +++ /dev/null @@ -1,27 +0,0 @@ - -<# -.SYNOPSIS -Retrieves the tenant settings from the Fabric API. - -.DESCRIPTION -The Get-FabricTenantSettings function makes a GET request to the Fabric API to retrieve the tenant settings. It returns the 'tenantSettings' property of the first item in the response. - -.PARAMETER None -This function does not have any parameters. - -.EXAMPLE -Get-FabricTenantSettings -Retrieves the tenant settings from the Fabric API. - -#> - -function Get-FabricTenantSettings { - [Alias("Get-FabTenantSettings")] - Param () - - Confirm-FabricAuthToken | Out-Null - - $result = Invoke-FabricAPIRequest -uri "admin/tenantsettings" -Method GET - - return $result.tenantSettings -} \ No newline at end of file diff --git a/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 index 8fcf7d3e..5c6f6fe4 100644 --- a/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Update-FabricCapacityTenantSettingOverrides.ps1 @@ -41,8 +41,9 @@ Author: Tiago Balabuch #> -function Update-FabricCapacityTenantSettingOverrides { - [CmdletBinding()] +function Update-FabricCapacityTenantSettingOverrides +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -69,24 +70,31 @@ function Update-FabricCapacityTenantSettingOverrides { [System.Object]$ExcludedSecurityGroups ) - try { + try + { # Validate authentication token Write-Message -Message "Validating authentication token..." -Level Debug Test-TokenExpired Write-Message -Message "Authentication token is valid." -Level Debug # Validate Security Groups if provided - if ($EnabledSecurityGroups) { - foreach ($enabledGroup in $EnabledSecurityGroups) { - if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) { + if ($EnabledSecurityGroups) + { + foreach ($enabledGroup in $EnabledSecurityGroups) + { + if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) + { throw "Each enabled security group must contain 'graphId' and 'name' properties." } } } - if ($ExcludedSecurityGroups) { - foreach ($excludedGroup in $ExcludedSecurityGroups) { - if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) { + if ($ExcludedSecurityGroups) + { + foreach ($excludedGroup in $ExcludedSecurityGroups) + { + if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) + { throw "Each excluded security group must contain 'graphId' and 'name' properties." } } @@ -102,33 +110,39 @@ function Update-FabricCapacityTenantSettingOverrides { SettingTitle = $SettingTitle } - if ($DelegateToWorkspace) { + if ($DelegateToWorkspace) + { $body.delegateToWorkspace = $DelegateToWorkspace } - if ($EnabledSecurityGroups) { + if ($EnabledSecurityGroups) + { $body.enabledSecurityGroups = $EnabledSecurityGroups } - if ($ExcludedSecurityGroups) { + if ($ExcludedSecurityGroups) + { $body.excludedSecurityGroups = $ExcludedSecurityGroups } # Convert body to JSON $bodyJson = $body | ConvertTo-Json -Depth 4 Write-Message -Message "Request Body: $bodyJson" -Level Debug - - # Invoke Fabric API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Update Tenant Setting Overrides")){ + # Invoke Fabric API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -method Post ` + -body $bodyJson + } Write-Message -Message "Successfully updated capacity tenant setting overrides for CapacityId: $CapacityId and SettingTitle: $SettingTitle." -Level Info return $response - } catch { + } + catch + { $errorDetails = $_.Exception.Message Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Tenant/Update-FabricTenantSetting.ps1 b/source/Public/Tenant/Update-FabricTenantSetting.ps1 index 4e086ba8..88fd6845 100644 --- a/source/Public/Tenant/Update-FabricTenantSetting.ps1 +++ b/source/Public/Tenant/Update-FabricTenantSetting.ps1 @@ -41,8 +41,9 @@ Author: Tiago Balabuch #> -function Update-FabricCapacityTenantSettingOverrides { - [CmdletBinding()] +function Update-FabricCapacityTenantSettingOverrides +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -77,33 +78,43 @@ function Update-FabricCapacityTenantSettingOverrides { [System.Object]$Properties ) - try { + try + { # Validate authentication token Write-Message -Message "Validating authentication token..." -Level Debug Test-TokenExpired Write-Message -Message "Authentication token is valid." -Level Debug # Validate Security Groups if provided - if ($EnabledSecurityGroups) { - foreach ($enabledGroup in $EnabledSecurityGroups) { - if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) { + if ($EnabledSecurityGroups) + { + foreach ($enabledGroup in $EnabledSecurityGroups) + { + if (-not ($enabledGroup.PSObject.Properties.Name -contains 'graphId' -and $enabledGroup.PSObject.Properties.Name -contains 'name')) + { throw "Each enabled security group must contain 'graphId' and 'name' properties." } } } - if ($ExcludedSecurityGroups) { - foreach ($excludedGroup in $ExcludedSecurityGroups) { - if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) { + if ($ExcludedSecurityGroups) + { + foreach ($excludedGroup in $ExcludedSecurityGroups) + { + if (-not ($excludedGroup.PSObject.Properties.Name -contains 'graphId' -and $excludedGroup.PSObject.Properties.Name -contains 'name')) + { throw "Each excluded security group must contain 'graphId' and 'name' properties." } } } # Validate Security Groups if provided - if ($Properties) { - foreach ($property in $Properties) { - if (-not ($property.PSObject.Properties.Name -contains 'name' -and $property.PSObject.Properties.Name -contains 'type' -and $property.PSObject.Properties.Name -contains 'value')) { + if ($Properties) + { + foreach ($property in $Properties) + { + if (-not ($property.PSObject.Properties.Name -contains 'name' -and $property.PSObject.Properties.Name -contains 'type' -and $property.PSObject.Properties.Name -contains 'value')) + { throw "Each property object must include 'name', 'type', and 'value' properties to be valid." } } @@ -118,27 +129,33 @@ function Update-FabricCapacityTenantSettingOverrides { EnableTenantSetting = $EnableTenantSetting } - if ($DelegateToCapacity) { + if ($DelegateToCapacity) + { $body.delegateToCapacity = $DelegateToCapacity } - if ($DelegateToDomain) { + if ($DelegateToDomain) + { $body.delegateToDomain = $DelegateToDomain } - if ($DelegateToWorkspace) { + if ($DelegateToWorkspace) + { $body.delegateToWorkspace = $DelegateToWorkspace } - if ($EnabledSecurityGroups) { + if ($EnabledSecurityGroups) + { $body.enabledSecurityGroups = $EnabledSecurityGroups } - if ($ExcludedSecurityGroups) { + if ($ExcludedSecurityGroups) + { $body.excludedSecurityGroups = $ExcludedSecurityGroups } - if ($Properties) { + if ($Properties) + { $body.properties = $Properties } @@ -146,17 +163,23 @@ function Update-FabricCapacityTenantSettingOverrides { $bodyJson = $body | ConvertTo-Json -Depth 5 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Invoke Fabric API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Update Tenant Setting")) + { + + # Invoke Fabric API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -method Post ` + -body $bodyJson + } Write-Message -Message "Successfully updated tenant setting." -Level Info return $response - } catch { + } + catch + { $errorDetails = $_.Exception.Message Write-Message -Message "Error updating tenant settings: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Utils/Convert-ToBase64.ps1 b/source/Public/Utils/Convert-ToBase64.ps1 index 5118aa91..78a17453 100644 --- a/source/Public/Utils/Convert-ToBase64.ps1 +++ b/source/Public/Utils/Convert-ToBase64.ps1 @@ -31,6 +31,7 @@ function Convert-ToBase64 { Tiago Balabuch #> [CmdletBinding()] + [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,4 +57,4 @@ function Convert-ToBase64 { Write-Message -Message "An error occurred while encoding to Base64: $errorDetails" -Level Error throw "An error occurred while encoding to Base64: $_" } -} \ No newline at end of file +} diff --git a/source/Public/Utils/Invoke-FabricAPIRequest.ps1 b/source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 similarity index 99% rename from source/Public/Utils/Invoke-FabricAPIRequest.ps1 rename to source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 index 1f855ebc..e3fa987c 100644 --- a/source/Public/Utils/Invoke-FabricAPIRequest.ps1 +++ b/source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 @@ -173,4 +173,4 @@ function Invoke-FabricAPIRequest_duplicate { } while ($null -ne $continuationToken) return $results -} \ No newline at end of file +} diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 index f9e19d8f..37ccfd24 100644 --- a/source/Public/Utils/Set-FabricApiHeaders.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -1,4 +1,5 @@ -function Set-FabricApiHeaders { +function Set-FabricApiHeaders +{ <# .SYNOPSIS Sets the Fabric API headers with a valid token for the specified Azure tenant. @@ -38,7 +39,7 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the Tiago Balabuch #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -51,23 +52,27 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the [System.Security.SecureString]$AppSecret ) - try { + try + { # Step 1: Connect to the Azure account Write-Message -Message "Logging in to Azure tenant: $TenantId" -Level Info # Step 2: Performing validation checks on the parameters passed to a function or script. # Checks if 'AppId' is provided without 'AppSecret' and vice versa. - if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) { + if ($PSBoundParameters.ContainsKey('AppId') -and -not $PSBoundParameters.ContainsKey('AppSecret')) + { Write-Message -Message "AppSecret is required when using AppId: $AppId" -Level Error throw "AppSecret is required when using AppId." } - if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) { + if ($PSBoundParameters.ContainsKey('AppSecret') -and -not $PSBoundParameters.ContainsKey('AppId')) + { Write-Message -Message "AppId is required when using AppSecret." -Level Error throw "AppId is required when using AppId." } # Step 3: Connect to the Azure account # Using AppId and AppSecret - if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) { + if ($PSBoundParameters.ContainsKey('AppId') -and $PSBoundParameters.ContainsKey('AppSecret')) + { Write-Message -Message "Logging in using the AppId: $AppId" -Level Debug Write-Message -Message "Logging in using the AppId: $AppId" -Level Info @@ -76,7 +81,8 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the } # Using the current user - else { + else + { Write-Message -Message "Logging in using the current user" -Level Debug Write-Message -Message "Logging in using the current user" -Level Info @@ -92,24 +98,29 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the $plainToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($fabricToken.Token) ) - - ## Step 6: Set the headers in the global configuration - Write-Message -Message "Set the headers in the global configuration" -Level Debug - $FabricConfig.FabricHeaders = @{ - 'Content-Type' = 'application/json' - 'Authorization' = "Bearer $plainToken" + if ($PSCmdlet.ShouldProcess("Set the headers in the global configuration $($TenantId)")) + { + ## Step 6: Set the headers in the global configuration + $FabricConfig.FabricHeaders = @{ + 'Content-Type' = 'application/json' + 'Authorization' = "Bearer $plainToken" + } } - ## Step 7: Update token metadata in the global configuration Write-Message -Message "Update token metadata in the global configuration" -Level Debug - $FabricConfig.TokenExpiresOn = $fabricToken.ExpiresOn - $FabricConfig.TenantIdGlobal = $TenantId + if ($PSCmdlet.ShouldProcess("Update token metadata in the global configuration")) + { + $FabricConfig.TokenExpiresOn = $fabricToken.ExpiresOn + $FabricConfig.TenantIdGlobal = $TenantId + } Write-Message -Message "Fabric token successfully configured." -Level Info - } catch { + } + catch + { # Step 8: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to set Fabric token: $errorDetails" -Level Error throw "Unable to configure Fabric token. Ensure tenant and API configurations are correct." } -} \ No newline at end of file +} diff --git a/source/Public/Warehouse/New-FabricWarehouse.ps1 b/source/Public/Warehouse/New-FabricWarehouse.ps1 index ab1d0462..32dcb89b 100644 --- a/source/Public/Warehouse/New-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/New-FabricWarehouse.ps1 @@ -25,8 +25,9 @@ Author: Tiago Balabuch #> -function New-FabricWarehouse { - [CmdletBinding()] +function New-FabricWarehouse +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -42,7 +43,8 @@ function New-FabricWarehouse { [string]$WarehouseDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -57,26 +59,32 @@ function New-FabricWarehouse { displayName = $WarehouseName } - if ($WarehouseDescription) { + if ($WarehouseDescription) + { $body.description = $WarehouseDescription } $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Create Warehouse")) + { + # Step 4: Make the API request + $response = Invoke-FabricAPIRequest ` + -BaseURI $apiEndpointURI ` + -Headers $FabricConfig.FabricHeaders ` + -method Post ` + -body $bodyJson + } Write-Message -Message "Data Warehouse created successfully!" -Level Info return $response - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create Warehouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Warehouse/Remove-FabricWarehouse.ps1 b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 index c9f2b191..6d95c07e 100644 --- a/source/Public/Warehouse/Remove-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 @@ -22,8 +22,9 @@ Author: Tiago Balabuch #> -function Remove-FabricWarehouse { - [CmdletBinding()] +function Remove-FabricWarehouse +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -33,7 +34,8 @@ function Remove-FabricWarehouse { [ValidateNotNullOrEmpty()] [string]$WarehouseId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -43,18 +45,24 @@ function Remove-FabricWarehouse { $apiEndpointURI = "{0}/workspaces/{1}/warehouses/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WarehouseId Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug - # Step 3: Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Delete ` + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Delete Warehouse")) + { + # Step 3: Make the API request + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -method Delete ` + + } Write-Message -Message "Warehouse '$WarehouseId' deleted successfully from workspace '$WorkspaceId'." -Level Info return $response - } catch { + } + catch + { # Step 5: Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Warehouse '$WarehouseId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Warehouse/Update-FabricWarehouse.ps1 b/source/Public/Warehouse/Update-FabricWarehouse.ps1 index 18e8554b..02c10f3c 100644 --- a/source/Public/Warehouse/Update-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/Update-FabricWarehouse.ps1 @@ -29,8 +29,9 @@ Author: Tiago Balabuch #> -function Update-FabricWarehouse { - [CmdletBinding()] +function Update-FabricWarehouse +{ + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -50,7 +51,8 @@ function Update-FabricWarehouse { [string]$WarehouseDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -65,7 +67,8 @@ function Update-FabricWarehouse { displayName = $WarehouseName } - if ($WarehouseDescription) { + if ($WarehouseDescription) + { $body.description = $WarehouseDescription } @@ -73,18 +76,24 @@ function Update-FabricWarehouse { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Patch ` - -Body $bodyJson + if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Update Warehouse")) + { + + $response = Invoke-FabricAPIRequest ` + -Headers $FabricConfig.FabricHeaders ` + -BaseURI $apiEndpointURI ` + -method Patch ` + -body $bodyJson + } # Step 6: Handle results Write-Message -Message "Warehouse '$WarehouseName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update Warehouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace/Add-FabricWorkspaceCapacityAssignment.ps1 similarity index 89% rename from source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 rename to source/Public/Workspace/Add-FabricWorkspaceCapacityAssignment.ps1 index fe657b62..11e52bf6 100644 --- a/source/Public/Workspace/Assign-FabricWorkspaceCapacity.ps1 +++ b/source/Public/Workspace/Add-FabricWorkspaceCapacityAssignment.ps1 @@ -3,7 +3,7 @@ Assigns a Fabric workspace to a specified capacity. .DESCRIPTION -The `Assign-FabricWorkspaceCapacity` function sends a POST request to assign a workspace to a specific capacity. +The `Add-FabricWorkspaceCapacityAssignment` function sends a POST request to assign a workspace to a specific capacity. .PARAMETER WorkspaceId The unique identifier of the workspace to be assigned. @@ -12,7 +12,7 @@ The unique identifier of the workspace to be assigned. The unique identifier of the capacity to which the workspace should be assigned. .EXAMPLE -Assign-FabricWorkspaceCapacity -WorkspaceId "workspace123" -CapacityId "capacity456" +Add-FabricWorkspaceCapacityAssignment -WorkspaceId "workspace123" -CapacityId "capacity456" Assigns the workspace with ID "workspace123" to the capacity "capacity456". @@ -23,8 +23,9 @@ Assigns the workspace with ID "workspace123" to the capacity "capacity456". Author: Tiago Balabuch #> -function Assign-FabricWorkspaceCapacity { +function Add-FabricWorkspaceCapacityAssignment { [CmdletBinding()] + [Alias("Assign-FabricWorkspaceCapacity")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -79,4 +80,4 @@ function Assign-FabricWorkspaceCapacity { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign workspace with ID '$WorkspaceId' to capacity with ID '$CapacityId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 index ec08ae69..82464a4f 100644 --- a/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Add-FabricWorkspaceRoleAssignment.ps1 @@ -31,6 +31,7 @@ Author: Tiago Balabuch function Add-FabricWorkspaceRoleAssignment { [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -108,4 +109,4 @@ function Add-FabricWorkspaceRoleAssignment { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to assign role. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 index bc139908..dd393868 100644 --- a/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspaceRoleAssignment.ps1 @@ -30,6 +30,7 @@ Author: Tiago Balabuch function Get-FabricWorkspaceRoleAssignment { [CmdletBinding()] + [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -144,4 +145,4 @@ function Get-FabricWorkspaceRoleAssignment { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/New-FabricWorkspace.ps1 b/source/Public/Workspace/New-FabricWorkspace.ps1 index 77e5462d..efaa8813 100644 --- a/source/Public/Workspace/New-FabricWorkspace.ps1 +++ b/source/Public/Workspace/New-FabricWorkspace.ps1 @@ -1,4 +1,5 @@ -function New-FabricWorkspace { +function New-FabricWorkspace +{ <# .SYNOPSIS Creates a new Fabric workspace with the specified display name. @@ -26,7 +27,7 @@ Creates a workspace named "NewWorkspace". Author: Tiago Balabuch #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -42,7 +43,8 @@ Author: Tiago Balabuch [string]$CapacityId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -57,11 +59,13 @@ Author: Tiago Balabuch displayName = $WorkspaceName } - if ($WorkspaceDescription) { + if ($WorkspaceDescription) + { $body.description = $WorkspaceDescription } - if ($CapacityId) { + if ($CapacityId) + { $body.capacityId = $CapacityId } @@ -69,25 +73,32 @@ Author: Tiago Balabuch $bodyJson = $body | ConvertTo-Json -Depth 2 Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Create Workspace")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Handle and log the response - switch ($statusCode) { - 201 { + switch ($statusCode) + { + 201 + { Write-Message -Message "Workspace '$WorkspaceName' created successfully!" -Level Info return $response } - 202 { + 202 + { Write-Message -Message "Workspace '$WorkspaceName' creation accepted. Provisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug @@ -96,7 +107,8 @@ Author: Tiago Balabuch $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -104,22 +116,27 @@ Author: Tiago Balabuch Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 6: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to create workspace. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Remove-FabricWorkspace.ps1 b/source/Public/Workspace/Remove-FabricWorkspace.ps1 index 2057b995..5cbfa4d4 100644 --- a/source/Public/Workspace/Remove-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspace.ps1 @@ -27,7 +27,8 @@ function Remove-FabricWorkspace { [string]$WorkspaceId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -37,18 +38,22 @@ function Remove-FabricWorkspace { $apiEndpointUrl = "{0}/workspaces/{1}" -f $FabricConfig.BaseUrl, $WorkspaceId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Delete Workspace")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -58,10 +63,12 @@ function Remove-FabricWorkspace { Write-Message -Message "Workspace '$WorkspaceId' deleted successfully!" -Level Info return $null - } catch { + } + catch + { # Step 5: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error return $null } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 b/source/Public/Workspace/Remove-FabricWorkspaceCapacityAssignment.ps1 similarity index 65% rename from source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 rename to source/Public/Workspace/Remove-FabricWorkspaceCapacityAssignment.ps1 index decd5247..85e89633 100644 --- a/source/Public/Workspace/Unassign-FabricWorkspaceCapacity.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspaceCapacityAssignment.ps1 @@ -3,13 +3,13 @@ Unassigns a Fabric workspace from its capacity. .DESCRIPTION -The `Unassign-FabricWorkspaceCapacity` function sends a POST request to unassign a workspace from its assigned capacity. +The `Remove-FabricWorkspaceCapacityAssignment` function sends a POST request to unassign a workspace from its assigned capacity. .PARAMETER WorkspaceId The unique identifier of the workspace to be unassigned from its capacity. .EXAMPLE -Unassign-FabricWorkspaceCapacity -WorkspaceId "workspace123" +Remove-FabricWorkspaceCapacityAssignment -WorkspaceId "workspace123" Unassigns the workspace with ID "workspace123" from its capacity. @@ -20,14 +20,17 @@ Unassigns the workspace with ID "workspace123" from its capacity. Author: Tiago Balabuch #> -function Unassign-FabricWorkspaceCapacity { +function Remove-FabricWorkspaceCapacityAssignment +{ [CmdletBinding(SupportsShouldProcess)] + [Alias("Unassign-FabricWorkspaceCapacity")] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$WorkspaceId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -37,20 +40,22 @@ function Unassign-FabricWorkspaceCapacity { $apiEndpointUrl = "{0}/workspaces/{1}/unassignFromCapacity" -f $FabricConfig.BaseUrl, $WorkspaceId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Message - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Unassign Workspace from Capacity")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 202) { + if ($statusCode -ne 202) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -58,9 +63,11 @@ function Unassign-FabricWorkspaceCapacity { } Write-Message -Message "Workspace capacity has been successfully unassigned from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to unassign workspace from capacity. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 b/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 index 52137138..9fe5914c 100644 --- a/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspaceIdentity.ps1 @@ -20,7 +20,8 @@ Deprovisions the Managed Identity for the workspace with ID "workspace123". Author: Tiago Balabuch #> -function Remove-FabricWorkspaceIdentity { +function Remove-FabricWorkspaceIdentity +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -28,7 +29,8 @@ function Remove-FabricWorkspaceIdentity { [string]$WorkspaceId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -38,24 +40,31 @@ function Remove-FabricWorkspaceIdentity { $apiEndpointUrl = "{0}/workspaces/{1}/deprovisionIdentity" -f $FabricConfig.BaseUrl, $WorkspaceId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Post ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Deprovision Identity")) + { + + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Post ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Handle and log the response - switch ($statusCode) { - 200 { + switch ($statusCode) + { + 200 + { Write-Message -Message "Workspace identity was successfully deprovisioned for workspace '$WorkspaceId'." -Level Info return $response.value } - 202 { + 202 + { Write-Message -Message "Workspace identity deprovisioning accepted for workspace '$WorkspaceId'. Deprovisioning in progress!" -Level Info [string]$operationId = $responseHeader["x-ms-operation-id"] Write-Message -Message "Operation ID: '$operationId'" -Level Debug @@ -64,7 +73,8 @@ function Remove-FabricWorkspaceIdentity { $operationStatus = Get-FabricLongRunningOperation -operationId $operationId Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result - if ($operationStatus.status -eq "Succeeded") { + if ($operationStatus.status -eq "Succeeded") + { Write-Message -Message "Operation Succeeded" -Level Debug Write-Message -Message "Getting Long Running Operation result" -Level Debug @@ -72,21 +82,26 @@ function Remove-FabricWorkspaceIdentity { Write-Message -Message "Long Running Operation status: $operationResult" -Level Debug return $operationResult - } else { + } + else + { Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Debug Write-Message -Message "Operation failed. Status: $($operationStatus)" -Level Error return $operationStatus } } - default { + default + { Write-Message -Message "Unexpected response code: $statusCode" -Level Error Write-Message -Message "Error details: $($response.message)" -Level Error throw "API request failed with status code $statusCode." } } - } catch { + } + catch + { # Step 5: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to deprovision workspace identity. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 index 95f26caf..dd96b1a2 100644 --- a/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Remove-FabricWorkspaceRoleAssignment.ps1 @@ -24,7 +24,8 @@ Author: Tiago Balabuch #> -function Remove-FabricWorkspaceRoleAssignment { +function Remove-FabricWorkspaceRoleAssignment +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -36,7 +37,8 @@ function Remove-FabricWorkspaceRoleAssignment { [string]$WorkspaceRoleAssignmentId ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -46,18 +48,21 @@ function Remove-FabricWorkspaceRoleAssignment { $apiEndpointUrl = "{0}/workspaces/{1}/roleAssignments/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $WorkspaceRoleAssignmentId Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug - # Step 3: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Delete ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Delete Role Assignment")) + { + # Step 3: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Delete ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 4: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -65,9 +70,11 @@ function Remove-FabricWorkspaceRoleAssignment { } Write-Message -Message "Role assignment '$WorkspaceRoleAssignmentId' successfully removed from workspace '$WorkspaceId'." -Level Info - } catch { + } + catch + { # Step 5: Capture and log error details $errorDetails = $_.Exception.Message Write-Message -Message "Failed to remove role assignments for WorkspaceId '$WorkspaceId'. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Update-FabricWorkspace.ps1 b/source/Public/Workspace/Update-FabricWorkspace.ps1 index ab2dbd9e..3dae3ed0 100644 --- a/source/Public/Workspace/Update-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Update-FabricWorkspace.ps1 @@ -31,7 +31,8 @@ Updates both the name and description of the workspace "workspace123". Author: Tiago Balabuch #> -function Update-FabricWorkspace { +function Update-FabricWorkspace +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -48,7 +49,8 @@ function Update-FabricWorkspace { [string]$WorkspaceDescription ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -63,7 +65,8 @@ function Update-FabricWorkspace { displayName = $WorkspaceName } - if ($WorkspaceDescription) { + if ($WorkspaceDescription) + { $body.description = $WorkspaceDescription } @@ -71,20 +74,24 @@ function Update-FabricWorkspace { $bodyJson = $body | ConvertTo-Json Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update Workspace")) + { + + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -94,9 +101,11 @@ function Update-FabricWorkspace { # Step 6: Handle results Write-Message -Message "Workspace '$WorkspaceName' updated successfully!" -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update workspace. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 b/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 index 8c14afe3..3c2994fa 100644 --- a/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 +++ b/source/Public/Workspace/Update-FabricWorkspaceRoleAssignment.ps1 @@ -30,7 +30,8 @@ Updates the role assignment to "Admin" for the specified workspace and role assi Author: Tiago Balabuch #> -function Update-FabricWorkspaceRoleAssignment { +function Update-FabricWorkspaceRoleAssignment +{ [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -47,7 +48,8 @@ function Update-FabricWorkspaceRoleAssignment { [string]$WorkspaceRole ) - try { + try + { # Step 1: Ensure token validity Write-Message -Message "Validating token..." -Level Debug Test-TokenExpired @@ -66,20 +68,23 @@ function Update-FabricWorkspaceRoleAssignment { $bodyJson = $body | ConvertTo-Json -Depth 4 -Compress Write-Message -Message "Request Body: $bodyJson" -Level Debug - # Step 4: Make the API request - $response = Invoke-RestMethod ` - -Headers $FabricConfig.FabricHeaders ` - -Uri $apiEndpointUrl ` - -Method Patch ` - -Body $bodyJson ` - -ContentType "application/json" ` - -ErrorAction Stop ` - -SkipHttpErrorCheck ` - -ResponseHeadersVariable "responseHeader" ` - -StatusCodeVariable "statusCode" - + if ($PSCmdlet.ShouldProcess($apiEndpointUrl, "Update Role Assignment")) + { + # Step 4: Make the API request + $response = Invoke-RestMethod ` + -Headers $FabricConfig.FabricHeaders ` + -Uri $apiEndpointUrl ` + -Method Patch ` + -Body $bodyJson ` + -ContentType "application/json" ` + -ErrorAction Stop ` + -SkipHttpErrorCheck ` + -ResponseHeadersVariable "responseHeader" ` + -StatusCodeVariable "statusCode" + } # Step 5: Validate the response code - if ($statusCode -ne 200) { + if ($statusCode -ne 200) + { Write-Message -Message "Unexpected response code: $statusCode from the API." -Level Error Write-Message -Message "Error: $($response.message)" -Level Error Write-Message "Error Code: $($response.errorCode)" -Level Error @@ -87,7 +92,8 @@ function Update-FabricWorkspaceRoleAssignment { } # Step 6: Handle empty response - if (-not $response) { + if (-not $response) + { Write-Message -Message "No data returned from the API." -Level Warning return $null } @@ -95,9 +101,11 @@ function Update-FabricWorkspaceRoleAssignment { Write-Message -Message "Role assignment $WorkspaceRoleAssignmentId updated successfully in workspace '$WorkspaceId'." -Level Info return $response - } catch { + } + catch + { # Step 7: Handle and log errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to update role assignment. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/tests/QA/module.tests.ps1 b/tests/QA/module.tests.ps1 index 8fcb660d..e1f3847c 100644 --- a/tests/QA/module.tests.ps1 +++ b/tests/QA/module.tests.ps1 @@ -86,7 +86,21 @@ BeforeDiscovery { # Build test cases. $testCases = @() - foreach ($function in $allModuleFunctions) + foreach ($function in $allModuleFunctions | Where-Object -FilterScript { + $_.Name -notin ( + 'Test-TokenExpired', + 'Get-FabricUri', + 'Get-FileDefinitionParts', + 'Set-FabConfig', + 'Write-Message', + 'Invoke-FabricAPIRequest_duplicate' + ) + }) + { + $testCases += @{ + Name = $function.Name + } + } { $testCases += @{ Name = $function.Name @@ -117,7 +131,7 @@ Describe 'Quality for module' -Tags 'TestQuality' { Get-ChildItem -Path 'tests\' -Recurse -Include "$Name.Tests.ps1" | Should -Not -BeNullOrEmpty } - It 'Should pass Script Analyzer for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) -Skip:(-not $scriptAnalyzerRules) { + It 'Should pass Script Analyzer for ' -ForEach ($testCases | Where-Object { $_.Name -in $mut.ExportedCommands.Values.Name }) -Skip:(-not $scriptAnalyzerRules) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $pssaResult = (Invoke-ScriptAnalyzer -Path $functionFile.FullName) @@ -128,7 +142,7 @@ Describe 'Quality for module' -Tags 'TestQuality' { } Describe 'Help for module' -Tags 'helpQuality' { - It 'Should have .SYNOPSIS for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { + It 'Should have .SYNOPSIS for ' -ForEach ($testCases | Where-Object { $_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName @@ -147,7 +161,7 @@ Describe 'Help for module' -Tags 'helpQuality' { $functionHelp.Synopsis | Should -Not -BeNullOrEmpty } - It 'Should have a .DESCRIPTION with length greater than 40 characters for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { + It 'Should have a .DESCRIPTION with length greater than 40 characters for ' -ForEach ($testCases | Where-Object { $_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName @@ -166,7 +180,7 @@ Describe 'Help for module' -Tags 'helpQuality' { $functionHelp.Description.Length | Should -BeGreaterThan 40 } - It 'Should have at least one (1) example for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { + It 'Should have at least one (1) example for ' -ForEach ($testCases | Where-Object { $_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName @@ -188,7 +202,7 @@ Describe 'Help for module' -Tags 'helpQuality' { } - It 'Should have described all parameters for ' -ForEach ($testCases | Where-Object {$_.Name -in $mut.ExportedCommands.Values.Name }) { + It 'Should have described all parameters for ' -ForEach ($testCases | Where-Object { $_.Name -in $mut.ExportedCommands.Values.Name }) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName diff --git a/tests/Unit/Add-FabricDomainWorkspaceAssignmentByCapacity.Tests.ps1 b/tests/Unit/Add-FabricDomainWorkspaceAssignmentByCapacity.Tests.ps1 new file mode 100644 index 00000000..c630e23c --- /dev/null +++ b/tests/Unit/Add-FabricDomainWorkspaceAssignmentByCapacity.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "CapacitiesIds" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Add-FabricDomainWorkspaceAssignmentByCapacity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Add-FabricDomainWorkspaceAssignmentByCapacity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Add-FabricDomainWorkspaceAssignmentByCapacity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Add-FabricDomainWorkspaceAssignmentById.Tests.ps1 b/tests/Unit/Add-FabricDomainWorkspaceAssignmentById.Tests.ps1 new file mode 100644 index 00000000..519c954f --- /dev/null +++ b/tests/Unit/Add-FabricDomainWorkspaceAssignmentById.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "WorkspaceIds" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Add-FabricDomainWorkspaceAssignmentById" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Add-FabricDomainWorkspaceAssignmentById + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Add-FabricDomainWorkspaceAssignmentById + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Add-FabricDomainWorkspaceAssignmentByPrincipal.Tests.ps1 b/tests/Unit/Add-FabricDomainWorkspaceAssignmentByPrincipal.Tests.ps1 new file mode 100644 index 00000000..93c481c9 --- /dev/null +++ b/tests/Unit/Add-FabricDomainWorkspaceAssignmentByPrincipal.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "PrincipalIds" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Add-FabricDomainWorkspaceAssignmentByPrincipal" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Add-FabricDomainWorkspaceAssignmentByPrincipal + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Add-FabricDomainWorkspaceAssignmentByPrincipal + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Add-FabricDomainWorkspaceRoleAssignment.Tests.ps1 b/tests/Unit/Add-FabricDomainWorkspaceRoleAssignment.Tests.ps1 new file mode 100644 index 00000000..1ff6107a --- /dev/null +++ b/tests/Unit/Add-FabricDomainWorkspaceRoleAssignment.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "DomainRole" + "PrincipalIds" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Assign-FabricDomainWorkspaceRoleAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Assign-FabricDomainWorkspaceRoleAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Assign-FabricDomainWorkspaceRoleAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Add-FabricWorkspaceCapacityAssignment.Tests.ps1 b/tests/Unit/Add-FabricWorkspaceCapacityAssignment.Tests.ps1 new file mode 100644 index 00000000..1780283a --- /dev/null +++ b/tests/Unit/Add-FabricWorkspaceCapacityAssignment.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "CapacityId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Add-FabricWorkspaceCapacityAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Add-FabricWorkspaceCapacityAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Add-FabricWorkspaceCapacityAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Add-FabricWorkspaceIdentity.Tests.ps1 b/tests/Unit/Add-FabricWorkspaceIdentity.Tests.ps1 new file mode 100644 index 00000000..2563787f --- /dev/null +++ b/tests/Unit/Add-FabricWorkspaceIdentity.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Add-FabricWorkspaceIdentity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Add-FabricWorkspaceIdentity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Add-FabricWorkspaceIdentity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Add-FabricWorkspaceRoleAssignment.Tests.ps1 b/tests/Unit/Add-FabricWorkspaceRoleAssignment.Tests.ps1 new file mode 100644 index 00000000..bd4a541b --- /dev/null +++ b/tests/Unit/Add-FabricWorkspaceRoleAssignment.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "PrincipalId" + "PrincipalType" + "WorkspaceRole" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Add-FabricWorkspaceRoleAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Add-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Add-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Confirm-FabricAuthToken.Tests.ps1 b/tests/Unit/Confirm-FabricAuthToken.Tests.ps1 new file mode 100644 index 00000000..ac94caf3 --- /dev/null +++ b/tests/Unit/Confirm-FabricAuthToken.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Confirm-FabricAuthToken" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Confirm-FabricAuthToken + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Confirm-FabricAuthToken + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Connect-FabricAccount.Tests.ps1 b/tests/Unit/Connect-FabricAccount.Tests.ps1 new file mode 100644 index 00000000..8167126a --- /dev/null +++ b/tests/Unit/Connect-FabricAccount.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "TenantId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Connect-FabricAccount" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Connect-FabricAccount + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Connect-FabricAccount + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Convert-FromBase64.Tests.ps1 b/tests/Unit/Convert-FromBase64.Tests.ps1 new file mode 100644 index 00000000..adf4f0d1 --- /dev/null +++ b/tests/Unit/Convert-FromBase64.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "Base64String" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Convert-FromBase64" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Convert-FromBase64 + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Convert-FromBase64 + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Convert-ToBase64.Tests.ps1 b/tests/Unit/Convert-ToBase64.Tests.ps1 new file mode 100644 index 00000000..3180dece --- /dev/null +++ b/tests/Unit/Convert-ToBase64.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "filePath" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Convert-ToBase64" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Convert-ToBase64 + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Convert-ToBase64 + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Export-FabricItem.Tests.ps1 b/tests/Unit/Export-FabricItem.Tests.ps1 new file mode 100644 index 00000000..85b76288 --- /dev/null +++ b/tests/Unit/Export-FabricItem.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "path" + "workspaceId" + "filter" + "itemID" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Export-FabricItem" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Export-FabricItem + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Export-FabricItem + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricAPIclusterURI.Tests.ps1 b/tests/Unit/Get-FabricAPIclusterURI.Tests.ps1 new file mode 100644 index 00000000..adcdcfa1 --- /dev/null +++ b/tests/Unit/Get-FabricAPIclusterURI.Tests.ps1 @@ -0,0 +1,31 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + ) +) + +Describe "Get-FabricAPIclusterURI" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricAPIclusterURI + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricAPIclusterURI + $expected = $expectedParams + } + + # It "Has parameter: <_>" -ForEach $expected { + # $command | Should -HaveParameter $PSItem + # } +# + # It "Should have exactly the number of expected parameters $($expected.Count)# " { + # $hasparms = $command.Parameters.Values.Name + # #$hasparms.Count | Should -BeExactly $expected.Count + # Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | # Should -BeNullOrEmpty + # } + } +} diff --git a/tests/Unit/Get-FabricAuthToken.Tests.ps1 b/tests/Unit/Get-FabricAuthToken.Tests.ps1 new file mode 100644 index 00000000..5aeecc03 --- /dev/null +++ b/tests/Unit/Get-FabricAuthToken.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricAuthToken" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricAuthToken + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricAuthToken + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCapacities.Tests.ps1 b/tests/Unit/Get-FabricCapacities.Tests.ps1 new file mode 100644 index 00000000..4e4334d9 --- /dev/null +++ b/tests/Unit/Get-FabricCapacities.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "subscriptionID" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCapacities" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacities + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacities + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCapacity.Tests.ps1 b/tests/Unit/Get-FabricCapacity.Tests.ps1 new file mode 100644 index 00000000..668ec459 --- /dev/null +++ b/tests/Unit/Get-FabricCapacity.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "capacityId" + "capacityName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCapacity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCapacityRefreshables.Tests.ps1 b/tests/Unit/Get-FabricCapacityRefreshables.Tests.ps1 new file mode 100644 index 00000000..9e045f74 --- /dev/null +++ b/tests/Unit/Get-FabricCapacityRefreshables.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "top" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCapacityRefreshables" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacityRefreshables + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacityRefreshables + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCapacitySkus.Tests.ps1 b/tests/Unit/Get-FabricCapacitySkus.Tests.ps1 new file mode 100644 index 00000000..8547af11 --- /dev/null +++ b/tests/Unit/Get-FabricCapacitySkus.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "subscriptionID" + "ResourceGroupName" + "capacity" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCapacitySkus" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacitySkus + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacitySkus + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCapacityState.Tests.ps1 b/tests/Unit/Get-FabricCapacityState.Tests.ps1 new file mode 100644 index 00000000..a6880ca6 --- /dev/null +++ b/tests/Unit/Get-FabricCapacityState.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "subscriptionID" + "resourcegroup" + "capacity" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCapacityState" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacityState + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacityState + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCapacityTenantOverrides.Tests.ps1 b/tests/Unit/Get-FabricCapacityTenantOverrides.Tests.ps1 new file mode 100644 index 00000000..5b0bd23d --- /dev/null +++ b/tests/Unit/Get-FabricCapacityTenantOverrides.Tests.ps1 @@ -0,0 +1,33 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + + + ) +) + +Describe "Get-FabricCapacityTenantOverrides" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacityTenantOverrides + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacityTenantOverrides + $expected = $expectedParams + } + + # It "Has parameter: <_>" -ForEach $expected { + # $command | Should -HaveParameter $PSItem + # } +# + # It "Should have exactly the number of expected parameters $($expected.Count)#" { + # $hasparms = $command.Parameters.Values.Name + # #$hasparms.Count | Should -BeExactly $expected.Count + # Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | #Should -BeNullOrEmpty + # } + } +} diff --git a/tests/Unit/Get-FabricCapacityTenantSettingOverrides.Tests.ps1 b/tests/Unit/Get-FabricCapacityTenantSettingOverrides.Tests.ps1 new file mode 100644 index 00000000..b2d2b2c2 --- /dev/null +++ b/tests/Unit/Get-FabricCapacityTenantSettingOverrides.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "capacityId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCapacityTenantSettingOverrides" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacityTenantSettingOverrides + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacityTenantSettingOverrides + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCapacityWorkload.Tests.ps1 b/tests/Unit/Get-FabricCapacityWorkload.Tests.ps1 new file mode 100644 index 00000000..6f612312 --- /dev/null +++ b/tests/Unit/Get-FabricCapacityWorkload.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "capacityID" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCapacityWorkload" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCapacityWorkload + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCapacityWorkload + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricConnection.Tests.ps1 b/tests/Unit/Get-FabricConnection.Tests.ps1 new file mode 100644 index 00000000..e6ed6ba4 --- /dev/null +++ b/tests/Unit/Get-FabricConnection.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "connectionId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricConnection" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricConnection + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricConnection + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCopyJob.Tests.ps1 b/tests/Unit/Get-FabricCopyJob.Tests.ps1 new file mode 100644 index 00000000..0e49492a --- /dev/null +++ b/tests/Unit/Get-FabricCopyJob.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "CopyJobId" + "CopyJob" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCopyJob" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCopyJob + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCopyJob + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricCopyJobDefinition.Tests.ps1 b/tests/Unit/Get-FabricCopyJobDefinition.Tests.ps1 new file mode 100644 index 00000000..fcdd430e --- /dev/null +++ b/tests/Unit/Get-FabricCopyJobDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "CopyJobId" + "CopyJobFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricCopyJobDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricCopyJobDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricCopyJobDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDashboard.Tests.ps1 b/tests/Unit/Get-FabricDashboard.Tests.ps1 new file mode 100644 index 00000000..e6062a4c --- /dev/null +++ b/tests/Unit/Get-FabricDashboard.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDashboard" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDashboard + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDashboard + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDataPipeline.Tests.ps1 b/tests/Unit/Get-FabricDataPipeline.Tests.ps1 new file mode 100644 index 00000000..e2475f30 --- /dev/null +++ b/tests/Unit/Get-FabricDataPipeline.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "DataPipelineId" + "DataPipelineName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDataPipeline" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDataPipeline + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDataPipeline + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDatamart.Tests.ps1 b/tests/Unit/Get-FabricDatamart.Tests.ps1 new file mode 100644 index 00000000..e2192416 --- /dev/null +++ b/tests/Unit/Get-FabricDatamart.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "datamartId" + "datamartName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDatamart" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDatamart + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDatamart + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDatasetRefreshes.Tests.ps1 b/tests/Unit/Get-FabricDatasetRefreshes.Tests.ps1 new file mode 100644 index 00000000..9f94eb23 --- /dev/null +++ b/tests/Unit/Get-FabricDatasetRefreshes.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DatasetID" + "workspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDatasetRefreshes" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDatasetRefreshes + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDatasetRefreshes + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDebugInfo.Tests.ps1 b/tests/Unit/Get-FabricDebugInfo.Tests.ps1 new file mode 100644 index 00000000..4894a880 --- /dev/null +++ b/tests/Unit/Get-FabricDebugInfo.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDebugInfo" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDebugInfo + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDebugInfo + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDomain.Tests.ps1 b/tests/Unit/Get-FabricDomain.Tests.ps1 new file mode 100644 index 00000000..24fce27b --- /dev/null +++ b/tests/Unit/Get-FabricDomain.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "DomainName" + "NonEmptyDomainsOnly" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDomain" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDomain + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDomain + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDomainTenantSettingOverrides.Tests.ps1 b/tests/Unit/Get-FabricDomainTenantSettingOverrides.Tests.ps1 new file mode 100644 index 00000000..916f1dd8 --- /dev/null +++ b/tests/Unit/Get-FabricDomainTenantSettingOverrides.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDomainTenantSettingOverrides" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDomainTenantSettingOverrides + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDomainTenantSettingOverrides + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricDomainWorkspace.Tests.ps1 b/tests/Unit/Get-FabricDomainWorkspace.Tests.ps1 new file mode 100644 index 00000000..646c0a4c --- /dev/null +++ b/tests/Unit/Get-FabricDomainWorkspace.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricDomainWorkspace" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricDomainWorkspace + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricDomainWorkspace + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEnvironment.Tests.ps1 b/tests/Unit/Get-FabricEnvironment.Tests.ps1 new file mode 100644 index 00000000..c43f2f95 --- /dev/null +++ b/tests/Unit/Get-FabricEnvironment.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "EnvironmentName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEnvironment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEnvironment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEnvironment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEnvironmentLibrary.Tests.ps1 b/tests/Unit/Get-FabricEnvironmentLibrary.Tests.ps1 new file mode 100644 index 00000000..e31bb133 --- /dev/null +++ b/tests/Unit/Get-FabricEnvironmentLibrary.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEnvironmentLibrary" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEnvironmentLibrary + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEnvironmentLibrary + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEnvironmentSparkCompute.Tests.ps1 b/tests/Unit/Get-FabricEnvironmentSparkCompute.Tests.ps1 new file mode 100644 index 00000000..ae3e24a5 --- /dev/null +++ b/tests/Unit/Get-FabricEnvironmentSparkCompute.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEnvironmentSparkCompute" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEnvironmentSparkCompute + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEnvironmentSparkCompute + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEnvironmentStagingLibrary.Tests.ps1 b/tests/Unit/Get-FabricEnvironmentStagingLibrary.Tests.ps1 new file mode 100644 index 00000000..601f5485 --- /dev/null +++ b/tests/Unit/Get-FabricEnvironmentStagingLibrary.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEnvironmentStagingLibrary" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEnvironmentStagingLibrary + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEnvironmentStagingLibrary + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEnvironmentStagingSparkCompute.Tests.ps1 b/tests/Unit/Get-FabricEnvironmentStagingSparkCompute.Tests.ps1 new file mode 100644 index 00000000..43d27225 --- /dev/null +++ b/tests/Unit/Get-FabricEnvironmentStagingSparkCompute.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEnvironmentStagingSparkCompute" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEnvironmentStagingSparkCompute + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEnvironmentStagingSparkCompute + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEventhouse.Tests.ps1 b/tests/Unit/Get-FabricEventhouse.Tests.ps1 new file mode 100644 index 00000000..07c5635b --- /dev/null +++ b/tests/Unit/Get-FabricEventhouse.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventhouseId" + "EventhouseName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEventhouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEventhouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEventhouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEventhouseDefinition.Tests.ps1 b/tests/Unit/Get-FabricEventhouseDefinition.Tests.ps1 new file mode 100644 index 00000000..b9848cf0 --- /dev/null +++ b/tests/Unit/Get-FabricEventhouseDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventhouseId" + "EventhouseFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEventhouseDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEventhouseDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEventhouseDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEventstream.Tests.ps1 b/tests/Unit/Get-FabricEventstream.Tests.ps1 new file mode 100644 index 00000000..c79a2ca4 --- /dev/null +++ b/tests/Unit/Get-FabricEventstream.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventstreamId" + "EventstreamName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEventstream" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEventstream + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEventstream + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricEventstreamDefinition.Tests.ps1 b/tests/Unit/Get-FabricEventstreamDefinition.Tests.ps1 new file mode 100644 index 00000000..553a7731 --- /dev/null +++ b/tests/Unit/Get-FabricEventstreamDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventstreamId" + "EventstreamFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricEventstreamDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricEventstreamDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricEventstreamDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricExternalDataShares.Tests.ps1 b/tests/Unit/Get-FabricExternalDataShares.Tests.ps1 new file mode 100644 index 00000000..b7a2d106 --- /dev/null +++ b/tests/Unit/Get-FabricExternalDataShares.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricExternalDataShares" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricExternalDataShares + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricExternalDataShares + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricItem.Tests.ps1 b/tests/Unit/Get-FabricItem.Tests.ps1 new file mode 100644 index 00000000..a633e704 --- /dev/null +++ b/tests/Unit/Get-FabricItem.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "workspaceId" + "Workspace" + "type" + "itemID" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricItem" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricItem + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricItem + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricKQLDashboard.Tests.ps1 b/tests/Unit/Get-FabricKQLDashboard.Tests.ps1 new file mode 100644 index 00000000..535f1f39 --- /dev/null +++ b/tests/Unit/Get-FabricKQLDashboard.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDashboardId" + "KQLDashboardName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricKQLDashboard" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricKQLDashboard + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricKQLDashboard + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricKQLDashboardDefinition.Tests.ps1 b/tests/Unit/Get-FabricKQLDashboardDefinition.Tests.ps1 new file mode 100644 index 00000000..794f8cdd --- /dev/null +++ b/tests/Unit/Get-FabricKQLDashboardDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDashboardId" + "KQLDashboardFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricKQLDashboardDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricKQLDashboardDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricKQLDashboardDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricKQLDatabase.Tests.ps1 b/tests/Unit/Get-FabricKQLDatabase.Tests.ps1 new file mode 100644 index 00000000..7ad7f295 --- /dev/null +++ b/tests/Unit/Get-FabricKQLDatabase.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDatabaseId" + "KQLDatabaseName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricKQLDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricKQLDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricKQLDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricKQLDatabaseDefinition.Tests.ps1 b/tests/Unit/Get-FabricKQLDatabaseDefinition.Tests.ps1 new file mode 100644 index 00000000..d877a181 --- /dev/null +++ b/tests/Unit/Get-FabricKQLDatabaseDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDatabaseId" + "KQLDatabaseFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricKQLDatabaseDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricKQLDatabaseDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricKQLDatabaseDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricKQLQueryset.Tests.ps1 b/tests/Unit/Get-FabricKQLQueryset.Tests.ps1 new file mode 100644 index 00000000..71fd2d8c --- /dev/null +++ b/tests/Unit/Get-FabricKQLQueryset.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLQuerysetId" + "KQLQuerysetName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricKQLQueryset" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricKQLQueryset + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricKQLQueryset + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricKQLQuerysetDefinition.Tests.ps1 b/tests/Unit/Get-FabricKQLQuerysetDefinition.Tests.ps1 new file mode 100644 index 00000000..bde67925 --- /dev/null +++ b/tests/Unit/Get-FabricKQLQuerysetDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLQuerysetId" + "KQLQuerysetFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricKQLQuerysetDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricKQLQuerysetDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricKQLQuerysetDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricLakehouse.Tests.ps1 b/tests/Unit/Get-FabricLakehouse.Tests.ps1 new file mode 100644 index 00000000..9a2714b0 --- /dev/null +++ b/tests/Unit/Get-FabricLakehouse.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "LakehouseId" + "LakehouseName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricLakehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricLakehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricLakehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricLakehouseTable.Tests.ps1 b/tests/Unit/Get-FabricLakehouseTable.Tests.ps1 new file mode 100644 index 00000000..c8656337 --- /dev/null +++ b/tests/Unit/Get-FabricLakehouseTable.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "LakehouseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricLakehouseTable" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricLakehouseTable + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricLakehouseTable + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 b/tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 new file mode 100644 index 00000000..a6fa16ea --- /dev/null +++ b/tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "operationId" + "location" + "retryAfter" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricLongRunningOperation" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricLongRunningOperation + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricLongRunningOperation + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricLongRunningOperationResult.Tests.ps1 b/tests/Unit/Get-FabricLongRunningOperationResult.Tests.ps1 new file mode 100644 index 00000000..c1a231d9 --- /dev/null +++ b/tests/Unit/Get-FabricLongRunningOperationResult.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "operationId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricLongRunningOperationResult" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricLongRunningOperationResult + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricLongRunningOperationResult + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricMLExperiment.Tests.ps1 b/tests/Unit/Get-FabricMLExperiment.Tests.ps1 new file mode 100644 index 00000000..cf09ee38 --- /dev/null +++ b/tests/Unit/Get-FabricMLExperiment.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLExperimentId" + "MLExperimentName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricMLExperiment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricMLExperiment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricMLExperiment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricMLModel.Tests.ps1 b/tests/Unit/Get-FabricMLModel.Tests.ps1 new file mode 100644 index 00000000..a41397da --- /dev/null +++ b/tests/Unit/Get-FabricMLModel.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLModelId" + "MLModelName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricMLModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricMLModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricMLModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricMirroredDatabase.Tests.ps1 b/tests/Unit/Get-FabricMirroredDatabase.Tests.ps1 new file mode 100644 index 00000000..cd394f2d --- /dev/null +++ b/tests/Unit/Get-FabricMirroredDatabase.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "MirroredDatabaseName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricMirroredDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricMirroredDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricMirroredDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricMirroredDatabaseDefinition.Tests.ps1 b/tests/Unit/Get-FabricMirroredDatabaseDefinition.Tests.ps1 new file mode 100644 index 00000000..e3d0390e --- /dev/null +++ b/tests/Unit/Get-FabricMirroredDatabaseDefinition.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricMirroredDatabaseDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricMirroredDatabaseDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricMirroredDatabaseDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricMirroredDatabaseStatus.Tests.ps1 b/tests/Unit/Get-FabricMirroredDatabaseStatus.Tests.ps1 new file mode 100644 index 00000000..90625c2c --- /dev/null +++ b/tests/Unit/Get-FabricMirroredDatabaseStatus.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricMirroredDatabaseStatus" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricMirroredDatabaseStatus + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricMirroredDatabaseStatus + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricMirroredDatabaseTableStatus.Tests.ps1 b/tests/Unit/Get-FabricMirroredDatabaseTableStatus.Tests.ps1 new file mode 100644 index 00000000..e7c9ba05 --- /dev/null +++ b/tests/Unit/Get-FabricMirroredDatabaseTableStatus.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricMirroredDatabaseTableStatus" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricMirroredDatabaseTableStatus + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricMirroredDatabaseTableStatus + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricMirroredWarehouse.Tests.ps1 b/tests/Unit/Get-FabricMirroredWarehouse.Tests.ps1 new file mode 100644 index 00000000..dd62ac74 --- /dev/null +++ b/tests/Unit/Get-FabricMirroredWarehouse.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredWarehouseId" + "MirroredWarehouseName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricMirroredWarehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricMirroredWarehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricMirroredWarehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricNotebook.Tests.ps1 b/tests/Unit/Get-FabricNotebook.Tests.ps1 new file mode 100644 index 00000000..8cc0a2fc --- /dev/null +++ b/tests/Unit/Get-FabricNotebook.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "NotebookId" + "NotebookName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricNotebook" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricNotebook + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricNotebook + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricNotebookDefinition.Tests.ps1 b/tests/Unit/Get-FabricNotebookDefinition.Tests.ps1 new file mode 100644 index 00000000..035f2ce7 --- /dev/null +++ b/tests/Unit/Get-FabricNotebookDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "NotebookId" + "NotebookFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricNotebookDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricNotebookDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricNotebookDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricPaginatedReport.Tests.ps1 b/tests/Unit/Get-FabricPaginatedReport.Tests.ps1 new file mode 100644 index 00000000..f95868a2 --- /dev/null +++ b/tests/Unit/Get-FabricPaginatedReport.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "PaginatedReportId" + "PaginatedReportName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricPaginatedReport" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricPaginatedReport + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricPaginatedReport + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricReflex.Tests.ps1 b/tests/Unit/Get-FabricReflex.Tests.ps1 new file mode 100644 index 00000000..4c82f7b1 --- /dev/null +++ b/tests/Unit/Get-FabricReflex.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReflexId" + "ReflexName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricReflex" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricReflex + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricReflex + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricReflexDefinition.Tests.ps1 b/tests/Unit/Get-FabricReflexDefinition.Tests.ps1 new file mode 100644 index 00000000..fce6662d --- /dev/null +++ b/tests/Unit/Get-FabricReflexDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReflexId" + "ReflexFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricReflexDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricReflexDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricReflexDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricReport.Tests.ps1 b/tests/Unit/Get-FabricReport.Tests.ps1 new file mode 100644 index 00000000..ed8538ee --- /dev/null +++ b/tests/Unit/Get-FabricReport.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReportId" + "ReportName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricReport" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricReport + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricReport + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricReportDefinition.Tests.ps1 b/tests/Unit/Get-FabricReportDefinition.Tests.ps1 new file mode 100644 index 00000000..3418fbf7 --- /dev/null +++ b/tests/Unit/Get-FabricReportDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReportId" + "ReportFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricReportDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricReportDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricReportDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSQLDatabase.Tests.ps1 b/tests/Unit/Get-FabricSQLDatabase.Tests.ps1 new file mode 100644 index 00000000..d363b3e2 --- /dev/null +++ b/tests/Unit/Get-FabricSQLDatabase.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SQLDatabaseName" + "SQLDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSQLDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSQLDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSQLDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSQLEndpoint.Tests.ps1 b/tests/Unit/Get-FabricSQLEndpoint.Tests.ps1 new file mode 100644 index 00000000..05c03b16 --- /dev/null +++ b/tests/Unit/Get-FabricSQLEndpoint.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SQLEndpointId" + "SQLEndpointName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSQLEndpoint" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSQLEndpoint + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSQLEndpoint + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSemanticModel.Tests.ps1 b/tests/Unit/Get-FabricSemanticModel.Tests.ps1 new file mode 100644 index 00000000..89049c2d --- /dev/null +++ b/tests/Unit/Get-FabricSemanticModel.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SemanticModelId" + "SemanticModelName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSemanticModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSemanticModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSemanticModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSemanticModelDefinition.Tests.ps1 b/tests/Unit/Get-FabricSemanticModelDefinition.Tests.ps1 new file mode 100644 index 00000000..020d281f --- /dev/null +++ b/tests/Unit/Get-FabricSemanticModelDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SemanticModelId" + "SemanticModelFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSemanticModelDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSemanticModelDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSemanticModelDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSparkCustomPool.Tests.ps1 b/tests/Unit/Get-FabricSparkCustomPool.Tests.ps1 new file mode 100644 index 00000000..ecba6b7d --- /dev/null +++ b/tests/Unit/Get-FabricSparkCustomPool.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkCustomPoolId" + "SparkCustomPoolName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSparkCustomPool" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSparkCustomPool + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSparkCustomPool + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSparkJobDefinition.Tests.ps1 b/tests/Unit/Get-FabricSparkJobDefinition.Tests.ps1 new file mode 100644 index 00000000..fa2f7d75 --- /dev/null +++ b/tests/Unit/Get-FabricSparkJobDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkJobDefinitionId" + "SparkJobDefinitionName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSparkJobDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSparkJobDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSparkJobDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSparkJobDefinitionDefinition.Tests.ps1 b/tests/Unit/Get-FabricSparkJobDefinitionDefinition.Tests.ps1 new file mode 100644 index 00000000..e946f4aa --- /dev/null +++ b/tests/Unit/Get-FabricSparkJobDefinitionDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkJobDefinitionId" + "SparkJobDefinitionFormat" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSparkJobDefinitionDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSparkJobDefinitionDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSparkJobDefinitionDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricSparkSettings.Tests.ps1 b/tests/Unit/Get-FabricSparkSettings.Tests.ps1 new file mode 100644 index 00000000..5daa621d --- /dev/null +++ b/tests/Unit/Get-FabricSparkSettings.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricSparkSettings" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricSparkSettings + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricSparkSettings + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricTenantSetting.Tests.ps1 b/tests/Unit/Get-FabricTenantSetting.Tests.ps1 new file mode 100644 index 00000000..d742a66f --- /dev/null +++ b/tests/Unit/Get-FabricTenantSetting.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "SettingTitle" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricTenantSetting" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricTenantSetting + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricTenantSetting + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Get-FabricUsageMetricsQuery.Tests.ps1 b/tests/Unit/Get-FabricUsageMetricsQuery.Tests.ps1 new file mode 100644 index 00000000..9f3f055c --- /dev/null +++ b/tests/Unit/Get-FabricUsageMetricsQuery.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DatasetID" + "groupId" + "reportname" + "ImpersonatedUser" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricUsageMetricsQuery" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricUsageMetricsQuery + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricUsageMetricsQuery + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricUserListAccessEntities.Tests.ps1 b/tests/Unit/Get-FabricUserListAccessEntities.Tests.ps1 new file mode 100644 index 00000000..64c85b2d --- /dev/null +++ b/tests/Unit/Get-FabricUserListAccessEntities.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "UserId" + "Type" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricUserListAccessEntities" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricUserListAccessEntities + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricUserListAccessEntities + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricWarehouse.Tests.ps1 b/tests/Unit/Get-FabricWarehouse.Tests.ps1 new file mode 100644 index 00000000..5de72f9f --- /dev/null +++ b/tests/Unit/Get-FabricWarehouse.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WarehouseId" + "WarehouseName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricWarehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricWarehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricWarehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricWorkspace.Tests.ps1 b/tests/Unit/Get-FabricWorkspace.Tests.ps1 new file mode 100644 index 00000000..9af3e68c --- /dev/null +++ b/tests/Unit/Get-FabricWorkspace.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WorkspaceName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricWorkspace" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricWorkspace + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricWorkspace + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricWorkspaceDatasetRefreshes.Tests.ps1 b/tests/Unit/Get-FabricWorkspaceDatasetRefreshes.Tests.ps1 new file mode 100644 index 00000000..24ea5c10 --- /dev/null +++ b/tests/Unit/Get-FabricWorkspaceDatasetRefreshes.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceID" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricWorkspaceDatasetRefreshes" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricWorkspaceDatasetRefreshes + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricWorkspaceDatasetRefreshes + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricWorkspaceRoleAssignment.Tests.ps1 b/tests/Unit/Get-FabricWorkspaceRoleAssignment.Tests.ps1 new file mode 100644 index 00000000..9f62e7ef --- /dev/null +++ b/tests/Unit/Get-FabricWorkspaceRoleAssignment.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WorkspaceRoleAssignmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricWorkspaceRoleAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricWorkspaceTenantSettingOverrides.Tests.ps1 b/tests/Unit/Get-FabricWorkspaceTenantSettingOverrides.Tests.ps1 new file mode 100644 index 00000000..41b1ea99 --- /dev/null +++ b/tests/Unit/Get-FabricWorkspaceTenantSettingOverrides.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricWorkspaceTenantSettingOverrides" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricWorkspaceTenantSettingOverrides + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricWorkspaceTenantSettingOverrides + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricWorkspaceUsageMetricsData.Tests.ps1 b/tests/Unit/Get-FabricWorkspaceUsageMetricsData.Tests.ps1 new file mode 100644 index 00000000..896262be --- /dev/null +++ b/tests/Unit/Get-FabricWorkspaceUsageMetricsData.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "workspaceId" + "username" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricWorkspaceUsageMetricsData" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricWorkspaceUsageMetricsData + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricWorkspaceUsageMetricsData + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-FabricWorkspaceUsers.Tests.ps1 b/tests/Unit/Get-FabricWorkspaceUsers.Tests.ps1 new file mode 100644 index 00000000..93e0cec2 --- /dev/null +++ b/tests/Unit/Get-FabricWorkspaceUsers.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Workspace" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Get-FabricWorkspaceUsers" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-FabricWorkspaceUsers + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-FabricWorkspaceUsers + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Get-Sha256.Tests.ps1 b/tests/Unit/Get-Sha256.Tests.ps1 new file mode 100644 index 00000000..50003a1e --- /dev/null +++ b/tests/Unit/Get-Sha256.Tests.ps1 @@ -0,0 +1,34 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "string" + + ) +) + +Describe "Get-Sha256" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Get-Sha256 + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Get-Sha256 + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Import-FabricEnvironmentStagingLibrary.Tests.ps1 b/tests/Unit/Import-FabricEnvironmentStagingLibrary.Tests.ps1 new file mode 100644 index 00000000..bc07327a --- /dev/null +++ b/tests/Unit/Import-FabricEnvironmentStagingLibrary.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + ) +) + +Describe "Import-FabricEnvironmentStagingLibrary" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Import-FabricEnvironmentStagingLibrary + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Import-FabricEnvironmentStagingLibrary + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Import-FabricItem.Tests.ps1 b/tests/Unit/Import-FabricItem.Tests.ps1 new file mode 100644 index 00000000..0a068db8 --- /dev/null +++ b/tests/Unit/Import-FabricItem.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "path" + "workspaceId" + "filter" + "fileOverrides" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Import-FabricItem" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Import-FabricItem + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Import-FabricItem + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Invoke-FabricAPIRequest.Tests.ps1 b/tests/Unit/Invoke-FabricAPIRequest.Tests.ps1 new file mode 100644 index 00000000..96baaede --- /dev/null +++ b/tests/Unit/Invoke-FabricAPIRequest.Tests.ps1 @@ -0,0 +1,52 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "authToken" + "uri" + "method" + "body" + "contentType" + "timeoutSec" + "retryCount" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Invoke-FabricAPIRequest" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Invoke-FabricAPIRequest + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Invoke-FabricAPIRequest + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Invoke-FabricDatasetRefresh.Tests.ps1 b/tests/Unit/Invoke-FabricDatasetRefresh.Tests.ps1 new file mode 100644 index 00000000..fdced642 --- /dev/null +++ b/tests/Unit/Invoke-FabricDatasetRefresh.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DatasetID" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Invoke-FabricDatasetRefresh" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Invoke-FabricDatasetRefresh + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Invoke-FabricDatasetRefresh + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Invoke-FabricKQLCommand.Tests.ps1 b/tests/Unit/Invoke-FabricKQLCommand.Tests.ps1 new file mode 100644 index 00000000..07cba83f --- /dev/null +++ b/tests/Unit/Invoke-FabricKQLCommand.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDatabaseName" + "KQLDatabaseId" + "KQLCommand" + "ReturnRawResult" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Invoke-FabricKQLCommand" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Invoke-FabricKQLCommand + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Invoke-FabricKQLCommand + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/New-FabricCopyJob.Tests.ps1 b/tests/Unit/New-FabricCopyJob.Tests.ps1 new file mode 100644 index 00000000..1cd7681c --- /dev/null +++ b/tests/Unit/New-FabricCopyJob.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "CopyJobName" + "CopyJobDescription" + "CopyJobPathDefinition" + "CopyJobPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "New-FabricCopyJob" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricCopyJob + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricCopyJob + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricDataPipeline.Tests.ps1 b/tests/Unit/New-FabricDataPipeline.Tests.ps1 new file mode 100644 index 00000000..fffa4021 --- /dev/null +++ b/tests/Unit/New-FabricDataPipeline.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "DataPipelineName" + "DataPipelineDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "New-FabricDataPipeline" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricDataPipeline + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricDataPipeline + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricDomain.Tests.ps1 b/tests/Unit/New-FabricDomain.Tests.ps1 new file mode 100644 index 00000000..18b89db9 --- /dev/null +++ b/tests/Unit/New-FabricDomain.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainName" + "DomainDescription" + "ParentDomainId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "New-FabricDomain" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricDomain + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricDomain + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricEnvironment.Tests.ps1 b/tests/Unit/New-FabricEnvironment.Tests.ps1 new file mode 100644 index 00000000..cb663b4c --- /dev/null +++ b/tests/Unit/New-FabricEnvironment.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentName" + "EnvironmentDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricEnvironment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricEnvironment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricEnvironment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricEventhouse.Tests.ps1 b/tests/Unit/New-FabricEventhouse.Tests.ps1 new file mode 100644 index 00000000..5e93e30c --- /dev/null +++ b/tests/Unit/New-FabricEventhouse.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventhouseName" + "EventhouseDescription" + "EventhousePathDefinition" + "EventhousePathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricEventhouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricEventhouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricEventhouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricEventstream.Tests.ps1 b/tests/Unit/New-FabricEventstream.Tests.ps1 new file mode 100644 index 00000000..4a841489 --- /dev/null +++ b/tests/Unit/New-FabricEventstream.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventstreamName" + "EventstreamDescription" + "EventstreamPathDefinition" + "EventstreamPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricEventstream" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricEventstream + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricEventstream + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricKQLDashboard.Tests.ps1 b/tests/Unit/New-FabricKQLDashboard.Tests.ps1 new file mode 100644 index 00000000..f60dac55 --- /dev/null +++ b/tests/Unit/New-FabricKQLDashboard.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDashboardName" + "KQLDashboardDescription" + "KQLDashboardPathDefinition" + "KQLDashboardPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricKQLDashboard" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricKQLDashboard + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricKQLDashboard + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricKQLDatabase.Tests.ps1 b/tests/Unit/New-FabricKQLDatabase.Tests.ps1 new file mode 100644 index 00000000..de31137f --- /dev/null +++ b/tests/Unit/New-FabricKQLDatabase.Tests.ps1 @@ -0,0 +1,56 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDatabaseName" + "KQLDatabaseDescription" + "parentEventhouseId" + "KQLDatabaseType" + "KQLInvitationToken" + "KQLSourceClusterUri" + "KQLSourceDatabaseName" + "KQLDatabasePathDefinition" + "KQLDatabasePathPlatformDefinition" + "KQLDatabasePathSchemaDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricKQLDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricKQLDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricKQLDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricKQLQueryset.Tests.ps1 b/tests/Unit/New-FabricKQLQueryset.Tests.ps1 new file mode 100644 index 00000000..48fe0a8c --- /dev/null +++ b/tests/Unit/New-FabricKQLQueryset.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLQuerysetName" + "KQLQuerysetDescription" + "KQLQuerysetPathDefinition" + "KQLQuerysetPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricKQLQueryset" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricKQLQueryset + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricKQLQueryset + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricLakehouse.Tests.ps1 b/tests/Unit/New-FabricLakehouse.Tests.ps1 new file mode 100644 index 00000000..168fc1c8 --- /dev/null +++ b/tests/Unit/New-FabricLakehouse.Tests.ps1 @@ -0,0 +1,51 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "LakehouseName" + "LakehouseDescription" + "LakehouseEnableSchemas" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "New-FabricLakehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricLakehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricLakehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/New-FabricMLExperiment.Tests.ps1 b/tests/Unit/New-FabricMLExperiment.Tests.ps1 new file mode 100644 index 00000000..88f2f199 --- /dev/null +++ b/tests/Unit/New-FabricMLExperiment.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLExperimentName" + "MLExperimentDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricMLExperiment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricMLExperiment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricMLExperiment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricMLModel.Tests.ps1 b/tests/Unit/New-FabricMLModel.Tests.ps1 new file mode 100644 index 00000000..375775ba --- /dev/null +++ b/tests/Unit/New-FabricMLModel.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLModelName" + "MLModelDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricMLModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricMLModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricMLModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricMirroredDatabase.Tests.ps1 b/tests/Unit/New-FabricMirroredDatabase.Tests.ps1 new file mode 100644 index 00000000..65bca204 --- /dev/null +++ b/tests/Unit/New-FabricMirroredDatabase.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseName" + "MirroredDatabaseDescription" + "MirroredDatabasePathDefinition" + "MirroredDatabasePathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricMirroredDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricMirroredDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricMirroredDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricNotebook.Tests.ps1 b/tests/Unit/New-FabricNotebook.Tests.ps1 new file mode 100644 index 00000000..bc1d57e0 --- /dev/null +++ b/tests/Unit/New-FabricNotebook.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "NotebookName" + "NotebookDescription" + "NotebookPathDefinition" + "NotebookPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricNotebook" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricNotebook + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricNotebook + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricNotebookNEW.Tests.ps1 b/tests/Unit/New-FabricNotebookNEW.Tests.ps1 new file mode 100644 index 00000000..e25dfc60 --- /dev/null +++ b/tests/Unit/New-FabricNotebookNEW.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "NotebookName" + "NotebookDescription" + "NotebookPathDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricNotebookNEW" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricNotebookNEW + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricNotebookNEW + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricReflex.Tests.ps1 b/tests/Unit/New-FabricReflex.Tests.ps1 new file mode 100644 index 00000000..fc59c976 --- /dev/null +++ b/tests/Unit/New-FabricReflex.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReflexName" + "ReflexDescription" + "ReflexPathDefinition" + "ReflexPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricReflex" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricReflex + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricReflex + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricReport.Tests.ps1 b/tests/Unit/New-FabricReport.Tests.ps1 new file mode 100644 index 00000000..411926f1 --- /dev/null +++ b/tests/Unit/New-FabricReport.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReportName" + "ReportDescription" + "ReportPathDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricReport" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricReport + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricReport + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricSQLDatabase.Tests.ps1 b/tests/Unit/New-FabricSQLDatabase.Tests.ps1 new file mode 100644 index 00000000..a445ae29 --- /dev/null +++ b/tests/Unit/New-FabricSQLDatabase.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Name" + "Description" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricSQLDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricSQLDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricSQLDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricSemanticModel.Tests.ps1 b/tests/Unit/New-FabricSemanticModel.Tests.ps1 new file mode 100644 index 00000000..315cef1f --- /dev/null +++ b/tests/Unit/New-FabricSemanticModel.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SemanticModelName" + "SemanticModelDescription" + "SemanticModelPathDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricSemanticModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricSemanticModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricSemanticModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricSparkCustomPool.Tests.ps1 b/tests/Unit/New-FabricSparkCustomPool.Tests.ps1 new file mode 100644 index 00000000..a8b0da00 --- /dev/null +++ b/tests/Unit/New-FabricSparkCustomPool.Tests.ps1 @@ -0,0 +1,55 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkCustomPoolName" + "NodeFamily" + "NodeSize" + "AutoScaleEnabled" + "AutoScaleMinNodeCount" + "AutoScaleMaxNodeCount" + "DynamicExecutorAllocationEnabled" + "DynamicExecutorAllocationMinExecutors" + "DynamicExecutorAllocationMaxExecutors" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricSparkCustomPool" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricSparkCustomPool + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricSparkCustomPool + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricSparkJobDefinition.Tests.ps1 b/tests/Unit/New-FabricSparkJobDefinition.Tests.ps1 new file mode 100644 index 00000000..45e63800 --- /dev/null +++ b/tests/Unit/New-FabricSparkJobDefinition.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkJobDefinitionName" + "SparkJobDefinitionDescription" + "SparkJobDefinitionPathDefinition" + "SparkJobDefinitionPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricSparkJobDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricSparkJobDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricSparkJobDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricWarehouse.Tests.ps1 b/tests/Unit/New-FabricWarehouse.Tests.ps1 new file mode 100644 index 00000000..7f5faf03 --- /dev/null +++ b/tests/Unit/New-FabricWarehouse.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WarehouseName" + "WarehouseDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricWarehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricWarehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricWarehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricWorkspace.Tests.ps1 b/tests/Unit/New-FabricWorkspace.Tests.ps1 new file mode 100644 index 00000000..d2f77bba --- /dev/null +++ b/tests/Unit/New-FabricWorkspace.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceName" + "WorkspaceDescription" + "CapacityId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "New-FabricWorkspace" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricWorkspace + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricWorkspace + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/New-FabricWorkspaceUsageMetricsReport.Tests.ps1 b/tests/Unit/New-FabricWorkspaceUsageMetricsReport.Tests.ps1 new file mode 100644 index 00000000..ee9419f4 --- /dev/null +++ b/tests/Unit/New-FabricWorkspaceUsageMetricsReport.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "workspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "New-FabricWorkspaceUsageMetricsReport" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name New-FabricWorkspaceUsageMetricsReport + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name New-FabricWorkspaceUsageMetricsReport + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Publish-FabricEnvironment.Tests.ps1 b/tests/Unit/Publish-FabricEnvironment.Tests.ps1 new file mode 100644 index 00000000..1ed304fd --- /dev/null +++ b/tests/Unit/Publish-FabricEnvironment.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Publish-FabricEnvironment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Publish-FabricEnvironment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Publish-FabricEnvironment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Register-FabricWorkspaceToCapacity.Tests.ps1 b/tests/Unit/Register-FabricWorkspaceToCapacity.Tests.ps1 new file mode 100644 index 00000000..6e37cd47 --- /dev/null +++ b/tests/Unit/Register-FabricWorkspaceToCapacity.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Workspace" + "CapacityId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Register-FabricWorkspaceToCapacity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Register-FabricWorkspaceToCapacity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Register-FabricWorkspaceToCapacity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Remove-FabricCopyJob.Tests.ps1 b/tests/Unit/Remove-FabricCopyJob.Tests.ps1 new file mode 100644 index 00000000..abe5fb86 --- /dev/null +++ b/tests/Unit/Remove-FabricCopyJob.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "CopyJobId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricCopyJob" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricCopyJob + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricCopyJob + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricDataPipeline.Tests.ps1 b/tests/Unit/Remove-FabricDataPipeline.Tests.ps1 new file mode 100644 index 00000000..1c611b9e --- /dev/null +++ b/tests/Unit/Remove-FabricDataPipeline.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "DataPipelineId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricDataPipeline" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricDataPipeline + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricDataPipeline + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricDomain.Tests.ps1 b/tests/Unit/Remove-FabricDomain.Tests.ps1 new file mode 100644 index 00000000..b6851f5b --- /dev/null +++ b/tests/Unit/Remove-FabricDomain.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricDomain" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricDomain + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricDomain + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricDomainWorkspaceAssignment.Tests.ps1 b/tests/Unit/Remove-FabricDomainWorkspaceAssignment.Tests.ps1 new file mode 100644 index 00000000..c3fd918d --- /dev/null +++ b/tests/Unit/Remove-FabricDomainWorkspaceAssignment.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "WorkspaceIds" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + + ) +) + +Describe "Remove-FabricDomainWorkspaceAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricDomainWorkspaceAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricDomainWorkspaceAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricDomainWorkspaceRoleAssignment.Tests.ps1 b/tests/Unit/Remove-FabricDomainWorkspaceRoleAssignment.Tests.ps1 new file mode 100644 index 00000000..8ed251b0 --- /dev/null +++ b/tests/Unit/Remove-FabricDomainWorkspaceRoleAssignment.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "DomainRole" + "PrincipalIds" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + + ) +) + +Describe "Remove-FabricDomainWorkspaceRoleAssignment " -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricDomainWorkspaceRoleAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricDomainWorkspaceRoleAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricEnvironment.Tests.ps1 b/tests/Unit/Remove-FabricEnvironment.Tests.ps1 new file mode 100644 index 00000000..66ede1ac --- /dev/null +++ b/tests/Unit/Remove-FabricEnvironment.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricEnvironment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricEnvironment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricEnvironment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricEnvironmentStagingLibrary.Tests.ps1 b/tests/Unit/Remove-FabricEnvironmentStagingLibrary.Tests.ps1 new file mode 100644 index 00000000..585efb87 --- /dev/null +++ b/tests/Unit/Remove-FabricEnvironmentStagingLibrary.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "LibraryName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricEnvironmentStagingLibrary" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricEnvironmentStagingLibrary + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricEnvironmentStagingLibrary + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricEventhouse.Tests.ps1 b/tests/Unit/Remove-FabricEventhouse.Tests.ps1 new file mode 100644 index 00000000..73315278 --- /dev/null +++ b/tests/Unit/Remove-FabricEventhouse.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventhouseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricEventhouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricEventhouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricEventhouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricEventstream.Tests.ps1 b/tests/Unit/Remove-FabricEventstream.Tests.ps1 new file mode 100644 index 00000000..9308b0ee --- /dev/null +++ b/tests/Unit/Remove-FabricEventstream.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventstreamId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricEventstream" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricEventstream + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricEventstream + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricItem.Tests.ps1 b/tests/Unit/Remove-FabricItem.Tests.ps1 new file mode 100644 index 00000000..df850903 --- /dev/null +++ b/tests/Unit/Remove-FabricItem.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "workspaceId" + "filter" + "itemID" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Remove-FabricItem" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricItem + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricItem + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Remove-FabricKQLDashboard.Tests.ps1 b/tests/Unit/Remove-FabricKQLDashboard.Tests.ps1 new file mode 100644 index 00000000..47bc34b0 --- /dev/null +++ b/tests/Unit/Remove-FabricKQLDashboard.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDashboardId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricKQLDashboard" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricKQLDashboard + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricKQLDashboard + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricKQLDatabase.Tests.ps1 b/tests/Unit/Remove-FabricKQLDatabase.Tests.ps1 new file mode 100644 index 00000000..18f27cda --- /dev/null +++ b/tests/Unit/Remove-FabricKQLDatabase.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricKQLDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricKQLDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricKQLDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricKQLQueryset.Tests.ps1 b/tests/Unit/Remove-FabricKQLQueryset.Tests.ps1 new file mode 100644 index 00000000..99377eba --- /dev/null +++ b/tests/Unit/Remove-FabricKQLQueryset.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLQuerysetId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Remove-FabricKQLQueryset" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricKQLQueryset + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricKQLQueryset + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Remove-FabricLakehouse.Tests.ps1 b/tests/Unit/Remove-FabricLakehouse.Tests.ps1 new file mode 100644 index 00000000..75bab867 --- /dev/null +++ b/tests/Unit/Remove-FabricLakehouse.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "LakehouseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + + ) +) + +Describe "Remove-FabricLakehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricLakehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricLakehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricMLExperiment.Tests.ps1 b/tests/Unit/Remove-FabricMLExperiment.Tests.ps1 new file mode 100644 index 00000000..9b6ddbde --- /dev/null +++ b/tests/Unit/Remove-FabricMLExperiment.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLExperimentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Remove-FabricMLExperiment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricMLExperiment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricMLExperiment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricMLModel.Tests.ps1 b/tests/Unit/Remove-FabricMLModel.Tests.ps1 new file mode 100644 index 00000000..748144a7 --- /dev/null +++ b/tests/Unit/Remove-FabricMLModel.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLModelId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricMLModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricMLModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricMLModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricMirroredDatabase.Tests.ps1 b/tests/Unit/Remove-FabricMirroredDatabase.Tests.ps1 new file mode 100644 index 00000000..205e8b15 --- /dev/null +++ b/tests/Unit/Remove-FabricMirroredDatabase.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + + ) +) + +Describe "Remove-FabricMirroredDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricMirroredDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricMirroredDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricNotebook.Tests.ps1 b/tests/Unit/Remove-FabricNotebook.Tests.ps1 new file mode 100644 index 00000000..a0c69463 --- /dev/null +++ b/tests/Unit/Remove-FabricNotebook.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "NotebookId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Remove-FabricNotebook" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricNotebook + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricNotebook + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricReflex.Tests.ps1 b/tests/Unit/Remove-FabricReflex.Tests.ps1 new file mode 100644 index 00000000..848fa2e2 --- /dev/null +++ b/tests/Unit/Remove-FabricReflex.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReflexId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricReflex" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricReflex + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricReflex + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricReport.Tests.ps1 b/tests/Unit/Remove-FabricReport.Tests.ps1 new file mode 100644 index 00000000..d09a5a73 --- /dev/null +++ b/tests/Unit/Remove-FabricReport.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReportId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricReport" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricReport + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricReport + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricSQLDatabase.Tests.ps1 b/tests/Unit/Remove-FabricSQLDatabase.Tests.ps1 new file mode 100644 index 00000000..930c21d1 --- /dev/null +++ b/tests/Unit/Remove-FabricSQLDatabase.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SQLDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricSQLDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricSQLDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricSQLDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricSemanticModel.Tests.ps1 b/tests/Unit/Remove-FabricSemanticModel.Tests.ps1 new file mode 100644 index 00000000..daae5f47 --- /dev/null +++ b/tests/Unit/Remove-FabricSemanticModel.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SemanticModelId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricSemanticModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricSemanticModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricSemanticModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricSparkCustomPool.Tests.ps1 b/tests/Unit/Remove-FabricSparkCustomPool.Tests.ps1 new file mode 100644 index 00000000..b5747b3c --- /dev/null +++ b/tests/Unit/Remove-FabricSparkCustomPool.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkCustomPoolId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricSparkCustomPool" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricSparkCustomPool + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricSparkCustomPool + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricSparkJobDefinition.Tests.ps1 b/tests/Unit/Remove-FabricSparkJobDefinition.Tests.ps1 new file mode 100644 index 00000000..fb7620f8 --- /dev/null +++ b/tests/Unit/Remove-FabricSparkJobDefinition.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkJobDefinitionId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricSparkJobDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricSparkJobDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricSparkJobDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricWarehouse.Tests.ps1 b/tests/Unit/Remove-FabricWarehouse.Tests.ps1 new file mode 100644 index 00000000..b4eb9b62 --- /dev/null +++ b/tests/Unit/Remove-FabricWarehouse.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WarehouseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Remove-FabricWarehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricWarehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricWarehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Remove-FabricWorkspace.Tests.ps1 b/tests/Unit/Remove-FabricWorkspace.Tests.ps1 new file mode 100644 index 00000000..2ab7580f --- /dev/null +++ b/tests/Unit/Remove-FabricWorkspace.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Remove-FabricWorkspace" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricWorkspace + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricWorkspace + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Remove-FabricWorkspaceCapacityAssignment.Tests.ps1 b/tests/Unit/Remove-FabricWorkspaceCapacityAssignment.Tests.ps1 new file mode 100644 index 00000000..0e3b413c --- /dev/null +++ b/tests/Unit/Remove-FabricWorkspaceCapacityAssignment.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Remove-FabricWorkspaceCapacityAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricWorkspaceCapacityAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricWorkspaceCapacityAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } + +} diff --git a/tests/Unit/Remove-FabricWorkspaceIdentity.Tests.ps1 b/tests/Unit/Remove-FabricWorkspaceIdentity.Tests.ps1 new file mode 100644 index 00000000..324256f3 --- /dev/null +++ b/tests/Unit/Remove-FabricWorkspaceIdentity.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Remove-FabricWorkspaceIdentity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricWorkspaceIdentity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricWorkspaceIdentity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Remove-FabricWorkspaceRoleAssignment.Tests.ps1 b/tests/Unit/Remove-FabricWorkspaceRoleAssignment.Tests.ps1 new file mode 100644 index 00000000..afbb7566 --- /dev/null +++ b/tests/Unit/Remove-FabricWorkspaceRoleAssignment.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WorkspaceRoleAssignmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Remove-FabricWorkspaceRoleAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Remove-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Remove-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Resume-FabricCapacity.Tests.ps1 b/tests/Unit/Resume-FabricCapacity.Tests.ps1 new file mode 100644 index 00000000..7c6156fa --- /dev/null +++ b/tests/Unit/Resume-FabricCapacity.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "subscriptionID" + "resourcegroup" + "capacity" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Resume-FabricCapacity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Resume-FabricCapacity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Resume-FabricCapacity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 b/tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 new file mode 100644 index 00000000..bc4216eb --- /dev/null +++ b/tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "capacityId" + "tenantSettingName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Revoke-FabricCapacityTenantSettingOverrides" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Revoke-FabricCapacityTenantSettingOverrides + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Revoke-FabricCapacityTenantSettingOverrides + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 b/tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 new file mode 100644 index 00000000..9a51be26 --- /dev/null +++ b/tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ItemId" + "ExternalDataShareId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Revoke-FabricExternalDataShares" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Revoke-FabricExternalDataShares + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Revoke-FabricExternalDataShares + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Set-FabricApiHeaders.Tests.ps1 b/tests/Unit/Set-FabricApiHeaders.Tests.ps1 new file mode 100644 index 00000000..310ecbc8 --- /dev/null +++ b/tests/Unit/Set-FabricApiHeaders.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "TenantId" + "AppId" + "AppSecret" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Set-FabricApiHeaders" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Set-FabricApiHeaders + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Set-FabricApiHeaders + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Set-FabricAuthToken.Tests.ps1 b/tests/Unit/Set-FabricAuthToken.Tests.ps1 new file mode 100644 index 00000000..f185f65a --- /dev/null +++ b/tests/Unit/Set-FabricAuthToken.Tests.ps1 @@ -0,0 +1,53 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "servicePrincipalId" + "servicePrincipalSecret" + "credential" + "tenantId" + "reset" + "apiUrl" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Set-FabricAuthToken" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Set-FabricAuthToken + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Set-FabricAuthToken + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Start-FabricLakehouseTableMaintenance.Tests.ps1 b/tests/Unit/Start-FabricLakehouseTableMaintenance.Tests.ps1 new file mode 100644 index 00000000..8e47c628 --- /dev/null +++ b/tests/Unit/Start-FabricLakehouseTableMaintenance.Tests.ps1 @@ -0,0 +1,54 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "LakehouseId" + "JobType" + "SchemaName" + "TableName" + "IsVOrder" + "ColumnsZOrderBy" + "retentionPeriod" + "waitForCompletion" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Start-FabricLakehouseTableMaintenance" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Start-FabricLakehouseTableMaintenance + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Start-FabricLakehouseTableMaintenance + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Start-FabricMirroredDatabaseMirroring.Tests.ps1 b/tests/Unit/Start-FabricMirroredDatabaseMirroring.Tests.ps1 new file mode 100644 index 00000000..7b95ca42 --- /dev/null +++ b/tests/Unit/Start-FabricMirroredDatabaseMirroring.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Start-FabricMirroredDatabaseMirroring" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Start-FabricMirroredDatabaseMirroring + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Start-FabricMirroredDatabaseMirroring + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Start-FabricSparkJobDefinitionOnDemand.Tests.ps1 b/tests/Unit/Start-FabricSparkJobDefinitionOnDemand.Tests.ps1 new file mode 100644 index 00000000..48bef72a --- /dev/null +++ b/tests/Unit/Start-FabricSparkJobDefinitionOnDemand.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkJobDefinitionId" + "JobType" + "waitForCompletion" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Start-FabricSparkJobDefinitionOnDemand" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Start-FabricSparkJobDefinitionOnDemand + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Start-FabricSparkJobDefinitionOnDemand + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Stop-FabricEnvironmentPublish.Tests.ps1 b/tests/Unit/Stop-FabricEnvironmentPublish.Tests.ps1 new file mode 100644 index 00000000..2c5f2b30 --- /dev/null +++ b/tests/Unit/Stop-FabricEnvironmentPublish.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Stop-FabricEnvironmentPublish" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Stop-FabricEnvironmentPublish + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Stop-FabricEnvironmentPublish + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Stop-FabricMirroredDatabaseMirroring.Tests.ps1 b/tests/Unit/Stop-FabricMirroredDatabaseMirroring.Tests.ps1 new file mode 100644 index 00000000..c3617ce2 --- /dev/null +++ b/tests/Unit/Stop-FabricMirroredDatabaseMirroring.Tests.ps1 @@ -0,0 +1,47 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Stop-FabricMirroredDatabaseMirroring" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Stop-FabricMirroredDatabaseMirroring + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Stop-FabricMirroredDatabaseMirroring + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Suspend-FabricCapacity.Tests.ps1 b/tests/Unit/Suspend-FabricCapacity.Tests.ps1 new file mode 100644 index 00000000..68beff0f --- /dev/null +++ b/tests/Unit/Suspend-FabricCapacity.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "subscriptionID" + "resourcegroup" + "capacity" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Suspend-FabricCapacity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Suspend-FabricCapacity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Suspend-FabricCapacity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Test-FabricApiResponse.Tests.ps1 b/tests/Unit/Test-FabricApiResponse.Tests.ps1 new file mode 100644 index 00000000..ea83fd26 --- /dev/null +++ b/tests/Unit/Test-FabricApiResponse.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "statusCode" + "response" + "responseHeader" + "Name" + "typeName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + + ) +) + +Describe "Test-FabricApiResponse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Test-FabricApiResponse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Test-FabricApiResponse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Unregister-FabricWorkspaceToCapacity.Tests.ps1 b/tests/Unit/Unregister-FabricWorkspaceToCapacity.Tests.ps1 new file mode 100644 index 00000000..d9c0a0cd --- /dev/null +++ b/tests/Unit/Unregister-FabricWorkspaceToCapacity.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "Workspace" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Unregister-FabricWorkspaceToCapacity" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Unregister-FabricWorkspaceToCapacity + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Unregister-FabricWorkspaceToCapacity + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} + diff --git a/tests/Unit/Update-FabricCapacityTenantSettingOverrides.Tests.ps1 b/tests/Unit/Update-FabricCapacityTenantSettingOverrides.Tests.ps1 new file mode 100644 index 00000000..c4ab4e7a --- /dev/null +++ b/tests/Unit/Update-FabricCapacityTenantSettingOverrides.Tests.ps1 @@ -0,0 +1,53 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "TenantSettingName" + "EnableTenantSetting" + "DelegateToCapacity" + "DelegateToDomain" + "DelegateToWorkspace" + "EnabledSecurityGroups" + "ExcludedSecurityGroups" + "Properties" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricCapacityTenantSettingOverrides" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricCapacityTenantSettingOverrides + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricCapacityTenantSettingOverrides + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricCopyJob.Tests.ps1 b/tests/Unit/Update-FabricCopyJob.Tests.ps1 new file mode 100644 index 00000000..f58b2a01 --- /dev/null +++ b/tests/Unit/Update-FabricCopyJob.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "CopyJobId" + "CopyJobName" + "CopyJobDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricCopyJob" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricCopyJob + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricCopyJob + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricCopyJobDefinition.Tests.ps1 b/tests/Unit/Update-FabricCopyJobDefinition.Tests.ps1 new file mode 100644 index 00000000..9caa670f --- /dev/null +++ b/tests/Unit/Update-FabricCopyJobDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "CopyJobId" + "CopyJobPathDefinition" + "CopyJobPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricCopyJobDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricCopyJobDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricCopyJobDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricDataPipeline.Tests.ps1 b/tests/Unit/Update-FabricDataPipeline.Tests.ps1 new file mode 100644 index 00000000..db2de7f7 --- /dev/null +++ b/tests/Unit/Update-FabricDataPipeline.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "DataPipelineId" + "DataPipelineName" + "DataPipelineDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricDataPipeline" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricDataPipeline + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricDataPipeline + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricDomain.Tests.ps1 b/tests/Unit/Update-FabricDomain.Tests.ps1 new file mode 100644 index 00000000..dc040311 --- /dev/null +++ b/tests/Unit/Update-FabricDomain.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "DomainId" + "DomainName" + "DomainDescription" + "DomainContributorsScope" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricDomain" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricDomain + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricDomain + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricEnvironment.Tests.ps1 b/tests/Unit/Update-FabricEnvironment.Tests.ps1 new file mode 100644 index 00000000..d500788e --- /dev/null +++ b/tests/Unit/Update-FabricEnvironment.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "EnvironmentName" + "EnvironmentDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricEnvironment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricEnvironment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricEnvironment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricEnvironmentStagingSparkCompute.Tests.ps1 b/tests/Unit/Update-FabricEnvironmentStagingSparkCompute.Tests.ps1 new file mode 100644 index 00000000..d2e9b335 --- /dev/null +++ b/tests/Unit/Update-FabricEnvironmentStagingSparkCompute.Tests.ps1 @@ -0,0 +1,58 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EnvironmentId" + "InstancePoolName" + "InstancePoolType" + "DriverCores" + "DriverMemory" + "ExecutorCores" + "ExecutorMemory" + "DynamicExecutorAllocationEnabled" + "DynamicExecutorAllocationMinExecutors" + "DynamicExecutorAllocationMaxExecutors" + "RuntimeVersion" + "SparkProperties" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricEnvironmentStagingSparkCompute" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricEnvironmentStagingSparkCompute + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricEnvironmentStagingSparkCompute + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricEventhouse.Tests.ps1 b/tests/Unit/Update-FabricEventhouse.Tests.ps1 new file mode 100644 index 00000000..5673634b --- /dev/null +++ b/tests/Unit/Update-FabricEventhouse.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventhouseId" + "EventhouseName" + "EventhouseDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricEventhouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricEventhouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricEventhouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricEventhouseDefinition.Tests.ps1 b/tests/Unit/Update-FabricEventhouseDefinition.Tests.ps1 new file mode 100644 index 00000000..bd880261 --- /dev/null +++ b/tests/Unit/Update-FabricEventhouseDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventhouseId" + "EventhousePathDefinition" + "EventhousePathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricEventhouseDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricEventhouseDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricEventhouseDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricEventstream.Tests.ps1 b/tests/Unit/Update-FabricEventstream.Tests.ps1 new file mode 100644 index 00000000..69ba7fa2 --- /dev/null +++ b/tests/Unit/Update-FabricEventstream.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventstreamId" + "EventstreamName" + "EventstreamDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricEventstream" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricEventstream + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricEventstream + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricEventstreamDefinition.Tests.ps1 b/tests/Unit/Update-FabricEventstreamDefinition.Tests.ps1 new file mode 100644 index 00000000..fd441624 --- /dev/null +++ b/tests/Unit/Update-FabricEventstreamDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "EventstreamId" + "EventstreamPathDefinition" + "EventstreamPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricEventstreamDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricEventstreamDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricEventstreamDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricKQLDashboard.Tests.ps1 b/tests/Unit/Update-FabricKQLDashboard.Tests.ps1 new file mode 100644 index 00000000..d5158500 --- /dev/null +++ b/tests/Unit/Update-FabricKQLDashboard.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDashboardId" + "KQLDashboardName" + "KQLDashboardDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricKQLDashboard" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricKQLDashboard + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricKQLDashboard + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricKQLDashboardDefinition.Tests.ps1 b/tests/Unit/Update-FabricKQLDashboardDefinition.Tests.ps1 new file mode 100644 index 00000000..06830127 --- /dev/null +++ b/tests/Unit/Update-FabricKQLDashboardDefinition.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDashboardId" + "KQLDashboardPathDefinition" + "KQLDashboardPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + + ) +) + +Describe "Update-FabricKQLDashboardDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricKQLDashboardDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricKQLDashboardDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricKQLDatabase.Tests.ps1 b/tests/Unit/Update-FabricKQLDatabase.Tests.ps1 new file mode 100644 index 00000000..6b446cb2 --- /dev/null +++ b/tests/Unit/Update-FabricKQLDatabase.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDatabaseId" + "KQLDatabaseName" + "KQLDatabaseDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Update-FabricKQLDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricKQLDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricKQLDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricKQLDatabaseDefinition.Tests.ps1 b/tests/Unit/Update-FabricKQLDatabaseDefinition.Tests.ps1 new file mode 100644 index 00000000..ffc56b6e --- /dev/null +++ b/tests/Unit/Update-FabricKQLDatabaseDefinition.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLDatabaseId" + "KQLDatabasePathDefinition" + "KQLDatabasePathPlatformDefinition" + "KQLDatabasePathSchemaDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricKQLDatabaseDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricKQLDatabaseDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricKQLDatabaseDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricKQLQueryset.Tests.ps1 b/tests/Unit/Update-FabricKQLQueryset.Tests.ps1 new file mode 100644 index 00000000..eaf5ae6e --- /dev/null +++ b/tests/Unit/Update-FabricKQLQueryset.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLQuerysetId" + "KQLQuerysetName" + "KQLQuerysetDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricKQLQueryset" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricKQLQueryset + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricKQLQueryset + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricKQLQuerysetDefinition.Tests.ps1 b/tests/Unit/Update-FabricKQLQuerysetDefinition.Tests.ps1 new file mode 100644 index 00000000..b966774b --- /dev/null +++ b/tests/Unit/Update-FabricKQLQuerysetDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "KQLQuerysetId" + "KQLQuerysetPathDefinition" + "KQLQuerysetPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricKQLQuerysetDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricKQLQuerysetDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricKQLQuerysetDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricLakehouse.Tests.ps1 b/tests/Unit/Update-FabricLakehouse.Tests.ps1 new file mode 100644 index 00000000..81faa4e4 --- /dev/null +++ b/tests/Unit/Update-FabricLakehouse.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "LakehouseId" + "LakehouseName" + "LakehouseDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricLakehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricLakehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricLakehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricMLExperiment.Tests.ps1 b/tests/Unit/Update-FabricMLExperiment.Tests.ps1 new file mode 100644 index 00000000..42cad877 --- /dev/null +++ b/tests/Unit/Update-FabricMLExperiment.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLExperimentId" + "MLExperimentName" + "MLExperimentDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricMLExperiment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricMLExperiment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricMLExperiment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricMLModel.Tests.ps1 b/tests/Unit/Update-FabricMLModel.Tests.ps1 new file mode 100644 index 00000000..8763ff35 --- /dev/null +++ b/tests/Unit/Update-FabricMLModel.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MLModelId" + "MLModelDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricMLModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricMLModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricMLModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricMirroredDatabase.Tests.ps1 b/tests/Unit/Update-FabricMirroredDatabase.Tests.ps1 new file mode 100644 index 00000000..58389317 --- /dev/null +++ b/tests/Unit/Update-FabricMirroredDatabase.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "MirroredDatabaseName" + "MirroredDatabaseDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricMirroredDatabase" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricMirroredDatabase + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricMirroredDatabase + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricMirroredDatabaseDefinition.Tests.ps1 b/tests/Unit/Update-FabricMirroredDatabaseDefinition.Tests.ps1 new file mode 100644 index 00000000..4234a76c --- /dev/null +++ b/tests/Unit/Update-FabricMirroredDatabaseDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "MirroredDatabaseId" + "MirroredDatabasePathDefinition" + "MirroredDatabasePathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricMirroredDatabaseDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricMirroredDatabaseDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricMirroredDatabaseDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricNotebook.Tests.ps1 b/tests/Unit/Update-FabricNotebook.Tests.ps1 new file mode 100644 index 00000000..fd929b4a --- /dev/null +++ b/tests/Unit/Update-FabricNotebook.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "NotebookId" + "NotebookName" + "NotebookDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricNotebook" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricNotebook + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricNotebook + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricNotebookDefinition.Tests.ps1 b/tests/Unit/Update-FabricNotebookDefinition.Tests.ps1 new file mode 100644 index 00000000..481c8c02 --- /dev/null +++ b/tests/Unit/Update-FabricNotebookDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "NotebookId" + "NotebookPathDefinition" + "NotebookPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricNotebookDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricNotebookDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricNotebookDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricPaginatedReport.Tests.ps1 b/tests/Unit/Update-FabricPaginatedReport.Tests.ps1 new file mode 100644 index 00000000..863cfbbf --- /dev/null +++ b/tests/Unit/Update-FabricPaginatedReport.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "PaginatedReportId" + "PaginatedReportName" + "PaginatedReportDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" + ) +) + +Describe "Update-FabricPaginatedReport" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricPaginatedReport + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricPaginatedReport + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricReflex.Tests.ps1 b/tests/Unit/Update-FabricReflex.Tests.ps1 new file mode 100644 index 00000000..1bc5d514 --- /dev/null +++ b/tests/Unit/Update-FabricReflex.Tests.ps1 @@ -0,0 +1,50 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReflexId" + "ReflexName" + "ReflexDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + + ) +) + +Describe "Update-FabricReflex" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricReflex + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricReflex + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricReflexDefinition.Tests.ps1 b/tests/Unit/Update-FabricReflexDefinition.Tests.ps1 new file mode 100644 index 00000000..99fd3083 --- /dev/null +++ b/tests/Unit/Update-FabricReflexDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReflexId" + "ReflexPathDefinition" + "ReflexPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricReflexDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricReflexDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricReflexDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricReport.Tests.ps1 b/tests/Unit/Update-FabricReport.Tests.ps1 new file mode 100644 index 00000000..342ebff8 --- /dev/null +++ b/tests/Unit/Update-FabricReport.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReportId" + "ReportName" + "ReportDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricReport" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricReport + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricReport + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricReportDefinition.Tests.ps1 b/tests/Unit/Update-FabricReportDefinition.Tests.ps1 new file mode 100644 index 00000000..52862848 --- /dev/null +++ b/tests/Unit/Update-FabricReportDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "ReportId" + "ReportPathDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricReportDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricReportDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricReportDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricSemanticModel.Tests.ps1 b/tests/Unit/Update-FabricSemanticModel.Tests.ps1 new file mode 100644 index 00000000..d9084fe5 --- /dev/null +++ b/tests/Unit/Update-FabricSemanticModel.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SemanticModelId" + "SemanticModelName" + "SemanticModelDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricSemanticModel" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricSemanticModel + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricSemanticModel + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricSemanticModelDefinition.Tests.ps1 b/tests/Unit/Update-FabricSemanticModelDefinition.Tests.ps1 new file mode 100644 index 00000000..6186124b --- /dev/null +++ b/tests/Unit/Update-FabricSemanticModelDefinition.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SemanticModelId" + "SemanticModelPathDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricSemanticModelDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricSemanticModelDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricSemanticModelDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricSparkCustomPool.Tests.ps1 b/tests/Unit/Update-FabricSparkCustomPool.Tests.ps1 new file mode 100644 index 00000000..bd8815ab --- /dev/null +++ b/tests/Unit/Update-FabricSparkCustomPool.Tests.ps1 @@ -0,0 +1,56 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkCustomPoolId" + "InstancePoolName" + "NodeFamily" + "NodeSize" + "AutoScaleEnabled" + "AutoScaleMinNodeCount" + "AutoScaleMaxNodeCount" + "DynamicExecutorAllocationEnabled" + "DynamicExecutorAllocationMinExecutors" + "DynamicExecutorAllocationMaxExecutors" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricSparkCustomPool" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricSparkCustomPool + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricSparkCustomPool + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricSparkJobDefinition.Tests.ps1 b/tests/Unit/Update-FabricSparkJobDefinition.Tests.ps1 new file mode 100644 index 00000000..68ff79ab --- /dev/null +++ b/tests/Unit/Update-FabricSparkJobDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkJobDefinitionId" + "SparkJobDefinitionName" + "SparkJobDefinitionDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricSparkJobDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricSparkJobDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricSparkJobDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricSparkJobDefinitionDefinition.Tests.ps1 b/tests/Unit/Update-FabricSparkJobDefinitionDefinition.Tests.ps1 new file mode 100644 index 00000000..1269fa0d --- /dev/null +++ b/tests/Unit/Update-FabricSparkJobDefinitionDefinition.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "SparkJobDefinitionId" + "SparkJobDefinitionPathDefinition" + "SparkJobDefinitionPathPlatformDefinition" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricSparkJobDefinitionDefinition" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricSparkJobDefinitionDefinition + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricSparkJobDefinitionDefinition + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricSparkSettings.Tests.ps1 b/tests/Unit/Update-FabricSparkSettings.Tests.ps1 new file mode 100644 index 00000000..eadda2cd --- /dev/null +++ b/tests/Unit/Update-FabricSparkSettings.Tests.ps1 @@ -0,0 +1,55 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "automaticLogEnabled" + "notebookInteractiveRunEnabled" + "customizeComputeEnabled" + "defaultPoolName" + "defaultPoolType" + "starterPoolMaxNode" + "starterPoolMaxExecutors" + "EnvironmentName" + "EnvironmentRuntimeVersion" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricSparkSettings" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricSparkSettings + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricSparkSettings + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricWarehouse.Tests.ps1 b/tests/Unit/Update-FabricWarehouse.Tests.ps1 new file mode 100644 index 00000000..14eddb62 --- /dev/null +++ b/tests/Unit/Update-FabricWarehouse.Tests.ps1 @@ -0,0 +1,49 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WarehouseId" + "WarehouseName" + "WarehouseDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricWarehouse" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricWarehouse + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricWarehouse + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricWorkspace.Tests.ps1 b/tests/Unit/Update-FabricWorkspace.Tests.ps1 new file mode 100644 index 00000000..14cd0ac6 --- /dev/null +++ b/tests/Unit/Update-FabricWorkspace.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WorkspaceName" + "WorkspaceDescription" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricWorkspace" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricWorkspace + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricWorkspace + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Update-FabricWorkspaceRoleAssignment.Tests.ps1 b/tests/Unit/Update-FabricWorkspaceRoleAssignment.Tests.ps1 new file mode 100644 index 00000000..eb37ecb9 --- /dev/null +++ b/tests/Unit/Update-FabricWorkspaceRoleAssignment.Tests.ps1 @@ -0,0 +1,48 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "WorkspaceRoleAssignmentId" + "WorkspaceRole" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Update-FabricWorkspaceRoleAssignment" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Update-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Update-FabricWorkspaceRoleAssignment + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Write-FabricLakehouseTableData.Tests.ps1 b/tests/Unit/Write-FabricLakehouseTableData.Tests.ps1 new file mode 100644 index 00000000..cd915691 --- /dev/null +++ b/tests/Unit/Write-FabricLakehouseTableData.Tests.ps1 @@ -0,0 +1,55 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param( + $ModuleName = "FabricTools", + $expectedParams = @( + "WorkspaceId" + "LakehouseId" + "TableName" + "PathType" + "RelativePath" + "FileFormat" + "CsvDelimiter" + "CsvHeader" + "Mode" + "Recursive" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "WhatIf" + "Confirm" + ) +) + +Describe "Write-FabricLakehouseTableData" -Tag "UnitTests" { + + BeforeDiscovery { + $command = Get-Command -Name Write-FabricLakehouseTableData + $expected = $expectedParams + } + + Context "Parameter validation" { + BeforeAll { + $command = Get-Command -Name Write-FabricLakehouseTableData + $expected = $expectedParams + } + + It "Has parameter: <_>" -ForEach $expected { + $command | Should -HaveParameter $PSItem + } + + It "Should have exactly the number of expected parameters $($expected.Count)" { + $hasparms = $command.Parameters.Values.Name + #$hasparms.Count | Should -BeExactly $expected.Count + Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty + } + } +} From d88808a64520c5498bd1e91fec4ff7de9e566a90 Mon Sep 17 00:00:00 2001 From: jpomfret Date: Fri, 16 May 2025 18:02:19 +0100 Subject: [PATCH 69/76] update issue template for new function --- .../ISSUE_TEMPLATE/Problem_with_module.yml | 3 +- .../ISSUE_TEMPLATE/Problem_with_resource.yml | 56 +++---------------- .github/ISSUE_TEMPLATE/Resource_proposal.yml | 28 ++++++---- .github/ISSUE_TEMPLATE/config.yml | 7 +-- 4 files changed, 28 insertions(+), 66 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Problem_with_module.yml b/.github/ISSUE_TEMPLATE/Problem_with_module.yml index d699a261..8b8cce18 100644 --- a/.github/ISSUE_TEMPLATE/Problem_with_module.yml +++ b/.github/ISSUE_TEMPLATE/Problem_with_module.yml @@ -61,7 +61,7 @@ body: - type: textarea id: targetNodeOS attributes: - label: Operating system the target node is running + label: Operating system you are running FabricTools on description: | Please provide as much as possible about the node running FabricTools. _Will be automatically formatted as plain text._ @@ -100,4 +100,3 @@ body: render: text validations: required: true - diff --git a/.github/ISSUE_TEMPLATE/Problem_with_resource.yml b/.github/ISSUE_TEMPLATE/Problem_with_resource.yml index a74a61de..eaff08a5 100644 --- a/.github/ISSUE_TEMPLATE/Problem_with_resource.yml +++ b/.github/ISSUE_TEMPLATE/Problem_with_resource.yml @@ -1,12 +1,12 @@ -name: Problem with a resource -description: If you have a problem, bug, or enhancement with a resource in this resource module. +name: Problem with a function +description: If you have a problem, bug, or enhancement with a function in this module. labels: [] assignees: [] body: - type: markdown attributes: value: | - Please prefix the issue title (above) with the resource name, e.g. 'ResourceName: Short description of my issue'! + Please prefix the issue title (above) with the function name, e.g. 'FunctionName: Short description of my issue'! Your feedback and support is greatly appreciated, thanks for contributing! - type: textarea @@ -28,14 +28,14 @@ body: validations: required: true - type: textarea - id: configuration + id: version attributes: - label: DSC configuration + label: Module Version description: | - The DSC configuration that is used to reproduce the issue (as detailed as possible). **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as PowerShell code._ + The version of the FabricTools module you are using. _Will be automatically formatted as plain text._ + To help with this information, please run this command: `Get-Module -Name 'FabricTools' | ft Name,Version,Path` placeholder: | - Paste DSC configuration here - render: powershell + Paste output here validations: required: true - type: textarea @@ -45,43 +45,3 @@ body: description: Do you have any suggestions how to solve the issue? validations: required: true - - type: textarea - id: targetNodeOS - attributes: - label: Operating system the target node is running - description: | - Please provide as much as possible about the target node, for example edition, version, build, and language. _Will be automatically formatted as plain text._ - - On OS with WMF 5.1 the following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` - placeholder: | - Add operating system information here - render: text - validations: - required: true - - type: textarea - id: targetNodePS - attributes: - label: PowerShell version and build the target node is running - description: | - Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ - - To help with this information, please run this command: `$PSVersionTable` - placeholder: | - Add PowerShell information here - render: text - validations: - required: true - - type: textarea - id: moduleVersion - attributes: - label: FabricTools version - description: | - Please provide the version of the FabricTools module that was used. _Will be automatically formatted as plain text._ - - To help with this information, please run this command: `Get-Module -Name 'FabricTools' -ListAvailable | ft Name,Version,Path` - placeholder: | - Add module information here - render: text - validations: - required: true - diff --git a/.github/ISSUE_TEMPLATE/Resource_proposal.yml b/.github/ISSUE_TEMPLATE/Resource_proposal.yml index 2ddd0986..075099d9 100644 --- a/.github/ISSUE_TEMPLATE/Resource_proposal.yml +++ b/.github/ISSUE_TEMPLATE/Resource_proposal.yml @@ -1,20 +1,20 @@ -name: New resource proposal -description: If you have a new resource proposal that you think should be added to this resource module. -title: "NewResourceName: New resource proposal" +name: New function proposal +description: If you have a new function proposal that you think should be added to this module. +title: "NewFunctionName: New resource proposal" labels: [] assignees: [] body: - type: markdown attributes: value: | - Please replace `NewResourceName` in the issue title (above) with your proposed resource name. + Please replace `NewFunctionName` in the issue title (above) with your proposed function name. - Thank you for contributing and making this resource module better! + Thank you for contributing and making FabricTools better! - type: textarea id: description attributes: - label: Resource proposal - description: Provide information how this resource will/should work and how it will help users. + label: Function proposal + description: Provide information how this function will/should work and how it will help users. validations: required: true - type: textarea @@ -22,11 +22,15 @@ body: attributes: label: Proposed properties description: | - List all the proposed properties that the resource should have (key, required, write, and/or read). For each property provide a detailed description, the data type, if a default value should be used, and if the property is limited to a set of values. + List all the proposed properties and parameters that the function should have. value: | - Property | Type qualifier | Data type | Description | Default value | Allowed values - --- | --- | --- | --- | --- | --- - PropertyName | Key | String | Detailed description | None | None + Parameter | Description | Data type | Default value | Allowed values + --- | --- | --- | --- | --- + ParameterName | Detailed description | String | None | Any + + Property | Description + --- | --- + PropertyName | Detailed description validations: required: true - type: textarea @@ -34,6 +38,6 @@ body: attributes: label: Special considerations or limitations description: | - Provide any considerations or limitations you can think of that a contributor should take in account when coding the proposed resource, and or what limitations a user will encounter or should consider when using the proposed resource. + Provide any considerations or limitations you can think of that a contributor should take in account when coding the proposed function, and or what limitations a user will encounter or should consider when using the proposed function. validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9917040e..e0b172d8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,6 +1,5 @@ blank_issues_enabled: false contact_links: - - name: "Virtual PowerShell User Group #DSC channel" - url: https://dsccommunity.org/community/contact/ - about: "To talk to the community and maintainers of DSC Community, please visit the #DSC channel." - + - name: "FabricTools Community" + url: https://github.com/dataplat/FabricTools/discussions + about: "To talk to the community and maintainers of FabricTools, please head to GitHub discussions." From f2f63f644ee56c60711aa0050137802d9c97f7ce Mon Sep 17 00:00:00 2001 From: Kamil Nowinski Date: Fri, 16 May 2025 21:02:48 +0200 Subject: [PATCH 70/76] The requirement for PS7 has been abolished #4 --- .gitignore | 5 ++--- source/Public/Connect-FabricAccount.ps1 | 3 +-- source/Public/Get-FabricDebugInfo.ps1 | 1 - source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 | 5 ++++- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 52d2b265..a3eb64e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ -output/ - **.bak *.local.* !**/README.md .kitchen/ +~*.* *.nupkg *.suo *.user @@ -12,7 +11,7 @@ output/ *.sln.docstates # Build results -Output/ +[Oo]utput/ # MSTest test Results [Tt]est[Rr]esult*/ diff --git a/source/Public/Connect-FabricAccount.ps1 b/source/Public/Connect-FabricAccount.ps1 index c840ff62..253150d3 100644 --- a/source/Public/Connect-FabricAccount.ps1 +++ b/source/Public/Connect-FabricAccount.ps1 @@ -1,5 +1,4 @@ function Connect-FabricAccount { - #Requires -Version 7.1 <# .SYNOPSIS @@ -53,4 +52,4 @@ function Connect-FabricAccount { end { } -} \ No newline at end of file +} diff --git a/source/Public/Get-FabricDebugInfo.ps1 b/source/Public/Get-FabricDebugInfo.ps1 index 428f4086..f51809ef 100644 --- a/source/Public/Get-FabricDebugInfo.ps1 +++ b/source/Public/Get-FabricDebugInfo.ps1 @@ -1,5 +1,4 @@ function Get-FabricDebugInfo { - #Requires -Version 7.1 <# .SYNOPSIS diff --git a/source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 b/source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 index e3fa987c..25632b9d 100644 --- a/source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 +++ b/source/Public/Utils/Invoke-FabricAPIRequest_duplicate.ps1 @@ -119,7 +119,10 @@ function Invoke-FabricAPIRequest_duplicate { } else { $results += $response } - $continuationToken = $response.PSObject.Properties.Match("continuationToken") ? $response.continuationToken : $null + $continuationToken = $null + if ($response.PSObject.Properties.Match("continuationToken")) { + $continuationToken = $response.continuationToken + } } else { Write-Message -Message "No data in response" -Level Debug $continuationToken = $null From d7462e3def362e591fedee5840ff55f9fa46d273 Mon Sep 17 00:00:00 2001 From: Kamil Nowinski Date: Fri, 16 May 2025 22:55:39 +0200 Subject: [PATCH 71/76] Added unit test & fixed validation for WorkspaceName in Get-FabricWorkspace cmdlet #28 --- source/Public/Workspace/Get-FabricWorkspace.ps1 | 4 ++-- tests/Unit/Get-FabricWorkspace.Tests.ps1 | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/source/Public/Workspace/Get-FabricWorkspace.ps1 b/source/Public/Workspace/Get-FabricWorkspace.ps1 index 1f0b91cc..6e43bbc3 100644 --- a/source/Public/Workspace/Get-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspace.ps1 @@ -38,7 +38,7 @@ function Get-FabricWorkspace { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [ValidatePattern('^[a-zA-Z0-9_- ]*$')] [string]$WorkspaceName ) @@ -140,4 +140,4 @@ function Get-FabricWorkspace { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve workspace. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/tests/Unit/Get-FabricWorkspace.Tests.ps1 b/tests/Unit/Get-FabricWorkspace.Tests.ps1 index 9af3e68c..19ecc9bc 100644 --- a/tests/Unit/Get-FabricWorkspace.Tests.ps1 +++ b/tests/Unit/Get-FabricWorkspace.Tests.ps1 @@ -16,7 +16,7 @@ param( "OutVariable" "OutBuffer" "PipelineVariable" - + ) ) @@ -43,5 +43,17 @@ Describe "Get-FabricWorkspace" -Tag "UnitTests" { Compare-Object -ReferenceObject $expected -DifferenceObject $hasparms | Should -BeNullOrEmpty } } -} + Context "WorkspaceName parameter validation" { + It "Throws error when WorkspaceName does not match ValidatePattern" { + # Assuming the ValidatePattern allows only alphanumeric, underscore, space and hyphen + { Get-FabricWorkspace -WorkspaceName "InvalidName!" } | Should -Throw + { Get-FabricWorkspace -WorkspaceName "Another@Invalid" } | Should -Throw + } + + It "Does not throw when WorkspaceName matches ValidatePattern" { + { Get-FabricWorkspace -WorkspaceName "Valid_Name-123" } | Should -Not -Throw + { Get-FabricWorkspace -WorkspaceName "Another Valid Name" } | Should -Not -Throw + } + } +} From 1b6035a00a6e164d76c9dffc1d33af11993764c7 Mon Sep 17 00:00:00 2001 From: Jess Pomfret Date: Sat, 17 May 2025 14:58:47 +0100 Subject: [PATCH 72/76] Apply suggestions from code review the `-` needs to be escaped within the regular expression (good test, that caught this issue!) Signed-off-by: Jess Pomfret --- source/Public/Workspace/Get-FabricWorkspace.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Public/Workspace/Get-FabricWorkspace.ps1 b/source/Public/Workspace/Get-FabricWorkspace.ps1 index 6e43bbc3..c64078e2 100644 --- a/source/Public/Workspace/Get-FabricWorkspace.ps1 +++ b/source/Public/Workspace/Get-FabricWorkspace.ps1 @@ -38,7 +38,7 @@ function Get-FabricWorkspace { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_- ]*$')] + [ValidatePattern('^[a-zA-Z0-9_\- ]*$')] [string]$WorkspaceName ) From 2161f3d93acbd5be1b2efcba96822db1f7dfafcc Mon Sep 17 00:00:00 2001 From: Jess Pomfret Date: Sun, 18 May 2025 08:45:29 +0100 Subject: [PATCH 73/76] Validation correction fixes #28 fixes #41 --- source/Public/Workspace/New-FabricWorkspace.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Public/Workspace/New-FabricWorkspace.ps1 b/source/Public/Workspace/New-FabricWorkspace.ps1 index efaa8813..a6c020ed 100644 --- a/source/Public/Workspace/New-FabricWorkspace.ps1 +++ b/source/Public/Workspace/New-FabricWorkspace.ps1 @@ -31,7 +31,7 @@ Author: Tiago Balabuch param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [ValidatePattern('^[a-zA-Z0-9_ ]*$')] + [ValidatePattern('^[a-zA-Z0-9_\- ]*$')] [string]$WorkspaceName, [Parameter(Mandatory = $false)] From a34d18e92b7115aa6b475fe597c09323ae8e0179 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Mon, 19 May 2025 18:16:12 +0000 Subject: [PATCH 74/76] Update CmdletBinding to include ConfirmImpact - closes ConfirmImpact of destructive functions should be high #46 This change enhances the safety of the following functions by setting the ConfirmImpact to 'High': - Remove-FabricDomainWorkspaceRoleAssignment - Remove-FabricEnvironment - Remove-FabricEventhouse - Remove-FabricEventstream - Revoke-FabricExternalDataShares - Remove-FabricItem - Remove-FabricMLModel - Remove-FabricReflex - Remove-FabricSQLDatabase - Remove-FabricSparkJobDefinition - Remove-FabricSparkCustomPool - Revoke-FabricCapacityTenantSettingOverrides - Remove-FabricWarehouse This ensures that users are prompted for confirmation before executing potentially destructive actions. Thank you! --- .../Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 | 2 +- source/Public/Environment/Remove-FabricEnvironment.ps1 | 2 +- source/Public/Eventhouse/Remove-FabricEventhouse.ps1 | 2 +- source/Public/Eventstream/Remove-FabricEventstream.ps1 | 2 +- .../External Data Share/Revoke-FabricExternalDataShares.ps1 | 4 ++-- source/Public/Item/Remove-FabricItem.ps1 | 4 ++-- source/Public/ML Model/Remove-FabricMLModel.ps1 | 2 +- source/Public/Reflex/Remove-FabricReflex.ps1 | 2 +- source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 | 2 +- .../Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 | 2 +- source/Public/Spark/Remove-FabricSparkCustomPool.ps1 | 2 +- .../Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 | 4 ++-- source/Public/Warehouse/Remove-FabricWarehouse.ps1 | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/source/Public/Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 b/source/Public/Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 index a75c662b..9d61e3c5 100644 --- a/source/Public/Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 +++ b/source/Public/Domain/Remove-FabricDomainWorkspaceRoleAssignment.ps1 @@ -32,7 +32,7 @@ Author: Tiago Balabuch #> function Remove-FabricDomainWorkspaceRoleAssignment { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [Alias("Unassign-FabricDomainWorkspaceRoleAssignment")] param ( [Parameter(Mandatory = $true)] diff --git a/source/Public/Environment/Remove-FabricEnvironment.ps1 b/source/Public/Environment/Remove-FabricEnvironment.ps1 index 96295bba..01719b20 100644 --- a/source/Public/Environment/Remove-FabricEnvironment.ps1 +++ b/source/Public/Environment/Remove-FabricEnvironment.ps1 @@ -26,7 +26,7 @@ Author: Tiago Balabuch function Remove-FabricEnvironment { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 index 6fe35621..2a2756e4 100644 --- a/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 +++ b/source/Public/Eventhouse/Remove-FabricEventhouse.ps1 @@ -38,7 +38,7 @@ function Remove-FabricEventhouse #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/Eventstream/Remove-FabricEventstream.ps1 b/source/Public/Eventstream/Remove-FabricEventstream.ps1 index 0583485d..0b9da7e3 100644 --- a/source/Public/Eventstream/Remove-FabricEventstream.ps1 +++ b/source/Public/Eventstream/Remove-FabricEventstream.ps1 @@ -30,7 +30,7 @@ Deletes the Eventstream with ID "67890" from workspace "12345". Author: Tiago Balabuch #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 index 7e811a20..018f1d54 100644 --- a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 +++ b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 @@ -27,7 +27,7 @@ function Revoke-FabricExternalDataShares { Author: Tiago Balabuch #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -67,4 +67,4 @@ function Revoke-FabricExternalDataShares { Write-Message -Message "Failed to retrieve External Data Shares. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Item/Remove-FabricItem.ps1 b/source/Public/Item/Remove-FabricItem.ps1 index 60b50b11..219d358d 100644 --- a/source/Public/Item/Remove-FabricItem.ps1 +++ b/source/Public/Item/Remove-FabricItem.ps1 @@ -31,7 +31,7 @@ #> Function Remove-FabricItem { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] @@ -67,4 +67,4 @@ Function Remove-FabricItem { } } } -} \ No newline at end of file +} diff --git a/source/Public/ML Model/Remove-FabricMLModel.ps1 b/source/Public/ML Model/Remove-FabricMLModel.ps1 index dc7fedc9..6542378a 100644 --- a/source/Public/ML Model/Remove-FabricMLModel.ps1 +++ b/source/Public/ML Model/Remove-FabricMLModel.ps1 @@ -25,7 +25,7 @@ #> function Remove-FabricMLModel { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/Reflex/Remove-FabricReflex.ps1 b/source/Public/Reflex/Remove-FabricReflex.ps1 index affbcea9..d57a3188 100644 --- a/source/Public/Reflex/Remove-FabricReflex.ps1 +++ b/source/Public/Reflex/Remove-FabricReflex.ps1 @@ -25,7 +25,7 @@ #> function Remove-FabricReflex { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 index a780ea12..382d4c98 100644 --- a/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 +++ b/source/Public/SQL Database/Remove-FabricSQLDatabase.ps1 @@ -26,7 +26,7 @@ Author: Kamil Nowinski function Remove-FabricSQLDatabase { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 index 3554e88c..ea9146db 100644 --- a/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 +++ b/source/Public/Spark Job Definition/Remove-FabricSparkJobDefinition.ps1 @@ -24,7 +24,7 @@ #> function Remove-FabricSparkJobDefinition { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 index 0fbc7087..cf44ffc1 100644 --- a/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 +++ b/source/Public/Spark/Remove-FabricSparkCustomPool.ps1 @@ -25,7 +25,7 @@ #> function Remove-FabricSparkCustomPool { - [CmdletBinding(supportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 index 8c41dfc7..c903afdf 100644 --- a/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 @@ -23,7 +23,7 @@ Removes the tenant setting override named "ExampleSetting" from the capacity wit Author: Tiago Balabuch #> function Revoke-FabricCapacityTenantSettingOverrides { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -56,4 +56,4 @@ function Revoke-FabricCapacityTenantSettingOverrides { $errorDetails = $_.Exception.Message Write-Message -Message "Error retrieving capacity tenant setting overrides: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Warehouse/Remove-FabricWarehouse.ps1 b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 index 6d95c07e..67e7cd97 100644 --- a/source/Public/Warehouse/Remove-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/Remove-FabricWarehouse.ps1 @@ -24,7 +24,7 @@ #> function Remove-FabricWarehouse { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] From a0ea5b8bb08913a50a71fc4e31731eec53d7fcaa Mon Sep 17 00:00:00 2001 From: Jess Pomfret Date: Tue, 20 May 2025 08:38:58 +0100 Subject: [PATCH 75/76] Fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 (#45) * fix for #39 * contenttype not used so post commands were failign * correct comment wording * update api call to fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 * fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 * fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 * fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 * formatting & spelling * whitespace changes * lets have a copyJob when we ask for one not a warehouse * fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 * fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 * change confirmImpact to high so confirm works cc. @SQLDBAWithABeard * fix for New-FabricDataPipeline - issue with call to Invoke-FabricAPIRequest #39 * fix for #39 * Removed leading slash from $apiEndpointURI #39 * Removed leading slash #39 --------- --- source/Public/Capacity/Get-FabricCapacity.ps1 | 18 +++---- source/Public/Confirm-FabricAuthToken.ps1 | 14 ++--- source/Public/Copy Job/Get-FabricCopyJob.ps1 | 11 ++-- .../Copy Job/Get-FabricCopyJobDefinition.ps1 | 13 ++--- source/Public/Copy Job/New-FabricCopyJob.ps1 | 17 +++--- .../Public/Copy Job/Remove-FabricCopyJob.ps1 | 21 ++++---- .../Public/Dashboard/Get-FabricDashboard.ps1 | 13 ++--- .../Data Pipeline/Get-FabricDataPipeline.ps1 | 15 +++--- .../Data Pipeline/New-FabricDataPipeline.ps1 | 16 +++--- source/Public/Datamart/Get-FabricDatamart.ps1 | 12 +++-- source/Public/Invoke-FabricAPIRequest.ps1 | 32 +++++++---- .../Public/Lakehouse/New-FabricLakehouse.ps1 | 4 +- source/Public/Set-FabricAuthToken.ps1 | 54 +++++++++---------- source/Public/Utils/Set-FabricApiHeaders.ps1 | 2 +- .../Public/Warehouse/Get-FabricWarehouse.ps1 | 14 ++--- .../Public/Warehouse/New-FabricWarehouse.ps1 | 13 ++--- 16 files changed, 142 insertions(+), 127 deletions(-) diff --git a/source/Public/Capacity/Get-FabricCapacity.ps1 b/source/Public/Capacity/Get-FabricCapacity.ps1 index 40380787..8f563083 100644 --- a/source/Public/Capacity/Get-FabricCapacity.ps1 +++ b/source/Public/Capacity/Get-FabricCapacity.ps1 @@ -14,11 +14,11 @@ The name of the capacity to retrieve. This parameter is optional. .EXAMPLE - Get-FabricCapacity -capacityId "capacity-12345" + Get-FabricCapacity -capacityId "capacity-12345" This example retrieves the capacity details for the capacity with ID "capacity-12345". .EXAMPLE - Get-FabricCapacity -capacityName "MyCapacity" + Get-FabricCapacity -capacityName "MyCapacity" This example retrieves the capacity details for the capacity named "MyCapacity". .NOTES @@ -51,14 +51,14 @@ function Get-FabricCapacity { Write-Message -Message "Token validation completed." -Level Debug # Construct the API endpoint URL - $apiEndpointURI = "{0}/capacities" -f $FabricConfig.BaseUrl + $apiEndpointURI = "capacities" # Invoke the Fabric API to retrieve capacity details - $capacities = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get - + $apiParams = @{ + Uri = $apiEndpointURI + Method = 'Get' + } + $capacities = (Invoke-FabricAPIRequest @apiParams).Value # Filter results based on provided parameters $response = if ($capacityId) { @@ -85,4 +85,4 @@ function Get-FabricCapacity { Write-Message -Message "Failed to retrieve capacity. Error: $errorDetails" -Level Error return $null } -} \ No newline at end of file +} diff --git a/source/Public/Confirm-FabricAuthToken.ps1 b/source/Public/Confirm-FabricAuthToken.ps1 index e31b28ec..7400251c 100644 --- a/source/Public/Confirm-FabricAuthToken.ps1 +++ b/source/Public/Confirm-FabricAuthToken.ps1 @@ -1,20 +1,20 @@ <# .SYNOPSIS - Check whether the Fabric API authentication token is set and not expired and reset it if necessary. + Check whether the Fabric API authentication token is set and not expired and reset it if necessary. .DESCRIPTION - The Confirm-FabricAuthToken function retrieves the Fabric API authentication token. If the token is not already set, it calls the Set-FabricAuthToken function to set it. It then outputs the token. + The Confirm-FabricAuthToken function retrieves the Fabric API authentication token. If the token is not already set, it calls the Set-FabricAuthToken function to set it. It then outputs the token. .EXAMPLE - Confirm-FabricAuthToken + Confirm-FabricAuthToken - This command retrieves the Fabric API authentication token. + This command retrieves the Fabric API authentication token. .INPUTS - None. You cannot pipe inputs to this function. + None. You cannot pipe inputs to this function. .OUTPUTS - Returns object as Get-FabricDebugInfo function + Returns object as Get-FabricDebugInfo function .NOTES @@ -47,4 +47,4 @@ function Confirm-FabricAuthToken { $s = Get-FabricDebugInfo return $s -} \ No newline at end of file +} diff --git a/source/Public/Copy Job/Get-FabricCopyJob.ps1 b/source/Public/Copy Job/Get-FabricCopyJob.ps1 index d96cd709..8f616ae7 100644 --- a/source/Public/Copy Job/Get-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJob.ps1 @@ -60,13 +60,14 @@ function Get-FabricCopyJob { # Construct the API endpoint URL - $apiEndpointURI = "{0}/workspaces/{1}/copyJobs" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = "workspaces/{0}/copyJobs" -f $WorkspaceId # Invoke the Fabric API to retrieve capacity details - $copyJobs = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get + $apiParams = @{ + Uri = $apiEndpointURI + Method = 'Get' + } + $copyJobs = Invoke-FabricAPIRequest @apiParams # Filter results based on provided parameters $response = if ($CopyJobId) { diff --git a/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 b/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 index 4a2ce844..96bdbb56 100644 --- a/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 +++ b/source/Public/Copy Job/Get-FabricCopyJobDefinition.ps1 @@ -50,7 +50,7 @@ function Get-FabricCopyJobDefinition { Write-Message -Message "Authentication token is valid." -Level Debug # Step 2: Construct the API endpoint URL for retrieving the Copy Job definition. - $apiEndpointUrl = "{0}/workspaces/{1}/copyJobs/{2}/getDefinition" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId + $apiEndpointUrl = "workspaces/{0}/copyJobs/{1}/getDefinition" -f $WorkspaceId, $CopyJobId # Step 3: Append the format query parameter if specified by the user. if ($CopyJobFormat) { @@ -59,10 +59,11 @@ function Get-FabricCopyJobDefinition { Write-Message -Message "Constructed API Endpoint URL: $apiEndpointUrl" -Level Debug # Step 4: Execute the API request to retrieve the Copy Job definition. - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointUrl ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post + $apiParams = @{ + Uri = $apiEndpointUrl + Method = 'POST' + } + $response = Invoke-FabricAPIRequest @apiParams # Step 5: Return the API response containing the Copy Job definition. return $response @@ -71,4 +72,4 @@ function Get-FabricCopyJobDefinition { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Copy Job definition. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Copy Job/New-FabricCopyJob.ps1 b/source/Public/Copy Job/New-FabricCopyJob.ps1 index 88e76a8f..6675f6fb 100644 --- a/source/Public/Copy Job/New-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/New-FabricCopyJob.ps1 @@ -62,7 +62,7 @@ function New-FabricCopyJob { Write-Message -Message "Token validation completed." -Level Debug # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = "workspaces/{0}/copyJobs" -f $WorkspaceId Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug # Step 3: Construct the request body @@ -127,15 +127,16 @@ function New-FabricCopyJob { if($PSCmdlet.ShouldProcess($apiEndpointURI, "Create Copy Job")) { # Step 6: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Post ` - -Body $bodyJson + $apiParams = @{ + Uri = $apiEndpointURI + Method = 'Post' + Body = $bodyJson } + $response = Invoke-FabricAPIRequest @apiParams + } - Write-Message -Message "Copy Job created successfully!" -Level Info - return $response + Write-Message -Message "Copy Job created successfully!" -Level Info + return $response } catch { # Step 7: Handle and log errors diff --git a/source/Public/Copy Job/Remove-FabricCopyJob.ps1 b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 index 0db6e321..f9a30175 100644 --- a/source/Public/Copy Job/Remove-FabricCopyJob.ps1 +++ b/source/Public/Copy Job/Remove-FabricCopyJob.ps1 @@ -23,7 +23,7 @@ Author: Tiago Balabuch #> function Remove-FabricCopyJob { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -40,22 +40,23 @@ function Remove-FabricCopyJob { Write-Message -Message "Token validation completed." -Level Debug # Construct the API endpoint URI - $apiEndpointURI = "{0}/workspaces/{1}/copyJobs/{2}" -f $FabricConfig.BaseUrl, $WorkspaceId, $CopyJobId + $apiEndpointURI = "workspaces/{0}/copyJobs/{1}" -f $WorkspaceId, $CopyJobId Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug if($PSCmdlet.ShouldProcess($apiEndpointURI, "Delete Copy Job")) { # Make the API request - $response = Invoke-FabricAPIRequest ` - -Headers $FabricConfig.FabricHeaders ` - -BaseURI $apiEndpointURI ` - -Method Delete + $apiParams = @{ + Uri = $apiEndpointURI + Method = 'DELETE' } - Write-Message -Message "Copy Job '$CopyJobId' deleted successfully from workspace '$WorkspaceId'." -Level Info - return $response + $response = Invoke-FabricAPIRequest @apiParams + } + Write-Message -Message "Copy Job '$CopyJobId' deleted successfully from workspace '$WorkspaceId'." -Level Info + return $response - } catch { - # Log and handle errors +} catch { + # Log and handle errors $errorDetails = $_.Exception.Message Write-Message -Message "Failed to delete Copy Job '$CopyJobId' from workspace '$WorkspaceId'. Error: $errorDetails" -Level Error } diff --git a/source/Public/Dashboard/Get-FabricDashboard.ps1 b/source/Public/Dashboard/Get-FabricDashboard.ps1 index b8335ff6..b938d89d 100644 --- a/source/Public/Dashboard/Get-FabricDashboard.ps1 +++ b/source/Public/Dashboard/Get-FabricDashboard.ps1 @@ -35,13 +35,14 @@ function Get-FabricDashboard { Write-Message -Message "Token validation completed." -Level Debug # Construct the API endpoint URL - $apiEndpointURI = "{0}/workspaces/{1}/dashboards" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = "workspaces/{0}/dashboards" -f $WorkspaceId # Invoke the Fabric API to retrieve capacity details - $Dashboards = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get + $apiParams = @{ + Uri = $apiEndpointURI + Method = 'Get' + } + $Dashboards = Invoke-FabricAPIRequest @apiParams return $Dashboards @@ -50,4 +51,4 @@ function Get-FabricDashboard { $errorDetails = $_.Exception.Message Write-Message -Message "Failed to retrieve Dashboard. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 index c1510999..060e2e0d 100644 --- a/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/Get-FabricDataPipeline.ps1 @@ -17,11 +17,11 @@ function Get-FabricDataPipeline { The name of the Data Pipeline to retrieve. This parameter is optional. .EXAMPLE - Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineId "Data Pipeline-67890" + Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineId "Data Pipeline-67890" This example retrieves the Data Pipeline details for the Data Pipeline with ID "Data Pipeline-67890" in the workspace with ID "workspace-12345". .EXAMPLE - Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineName "My Data Pipeline" + Get-FabricData Pipeline -WorkspaceId "workspace-12345" -Data PipelineName "My Data Pipeline" This example retrieves the Data Pipeline details for the Data Pipeline named "My Data Pipeline" in the workspace with ID "workspace-12345". .NOTES @@ -59,13 +59,10 @@ function Get-FabricDataPipeline { Write-Message -Message "Token validation completed." -Level Debug # Construct the API endpoint URL - $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = ("workspaces/{0}/dataPipelines" -f $WorkspaceId) - # Invoke the Fabric API to retrieve capacity details - $DataPipelines = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get + # Invoke the Fabric API to retrieve data pipeline details + $DataPipelines = (Invoke-FabricAPIRequest -uri $apiEndpointURI -Method Get).Value # Filter results based on provided parameters $response = if ($DataPipelineId) { @@ -92,4 +89,4 @@ function Get-FabricDataPipeline { Write-Message -Message "Failed to retrieve DataPipeline. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 index 2de33a13..221de75b 100644 --- a/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 +++ b/source/Public/Data Pipeline/New-FabricDataPipeline.ps1 @@ -17,7 +17,7 @@ An optional description for the DataPipeline. .EXAMPLE - New-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineName "New DataPipeline" + New-FabricDataPipeline -WorkspaceId "workspace-12345" -DataPipelineName "New DataPipeline" This example creates a new DataPipeline named "New DataPipeline" in the workspace with ID "workspace-12345" and uploads the definition file from the specified path. .NOTES @@ -53,7 +53,7 @@ function New-FabricDataPipeline Write-Message -Message "Token validation completed." -Level Debug # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/dataPipelines" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = ("workspaces/{0}/dataPipelines" -f $WorkspaceId) Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug # Step 3: Construct the request body @@ -67,16 +67,18 @@ function New-FabricDataPipeline } $bodyJson = $body | ConvertTo-Json -Depth 10 + Write-Message -Message "Request Body: $bodyJson" -Level Debug if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Create DataPipeline")) { # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -method Post ` - -body $bodyJson + $apiParams = @{ + Uri = $apiEndpointURI + method = 'Post' + body = $bodyJson + } + $response = Invoke-FabricAPIRequest @apiParams } Write-Message -Message "Data Pipeline created successfully!" -Level Info return $response diff --git a/source/Public/Datamart/Get-FabricDatamart.ps1 b/source/Public/Datamart/Get-FabricDatamart.ps1 index b9a764e1..4dde6c90 100644 --- a/source/Public/Datamart/Get-FabricDatamart.ps1 +++ b/source/Public/Datamart/Get-FabricDatamart.ps1 @@ -48,12 +48,14 @@ function Get-FabricDatamart { Write-Message -Message "Token validation completed." -Level Debug # Step 3: Initialize variables - $apiEndpointURI = "{0}/workspaces/{1}/Datamarts" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = "workspaces/{0}/Datamarts" -f $WorkspaceId + + $apiParams = @{ + Uri = $apiEndpointURI + method = 'Get' + } + $Datamarts = Invoke-FabricAPIRequest @apiParams - $Datamarts = = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get # Step 9: Filter results based on provided parameters $response = if ($datamartId) { diff --git a/source/Public/Invoke-FabricAPIRequest.ps1 b/source/Public/Invoke-FabricAPIRequest.ps1 index 387506bb..5ff3ad80 100644 --- a/source/Public/Invoke-FabricAPIRequest.ps1 +++ b/source/Public/Invoke-FabricAPIRequest.ps1 @@ -49,13 +49,27 @@ Function Invoke-FabricAPIRequest { #> [CmdletBinding()] param( - [Parameter(Mandatory = $false)] [string] $authToken, - [Parameter(Mandatory = $true)] [string] $uri, - [Parameter(Mandatory = $false)] [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] [string] $method = "Get", - [Parameter(Mandatory = $false)] $body, - [Parameter(Mandatory = $false)] [string] $contentType = "application/json; charset=utf-8", - [Parameter(Mandatory = $false)] [int] $timeoutSec = 240, - [Parameter(Mandatory = $false)] [int] $retryCount = 0 + [Parameter(Mandatory = $false)] + [string] $authToken, + + [Parameter(Mandatory = $true)] + [string] $uri, + + [Parameter(Mandatory = $false)] + [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch')] + [string] $method = "Get", + + [Parameter(Mandatory = $false)] + $body, + + [Parameter(Mandatory = $false)] + [string] $contentType = "application/json; charset=utf-8", + + [Parameter(Mandatory = $false)] + [int] $timeoutSec = 240, + + [Parameter(Mandatory = $false)] + [int] $retryCount = 0 ) Confirm-FabricAuthToken | Out-Null @@ -65,7 +79,7 @@ Function Invoke-FabricAPIRequest { $requestUrl = "$($FabricSession.BaseApiUrl)/$uri" Write-Verbose "Calling $requestUrl" - $response = Invoke-WebRequest -Headers $fabricHeaders -Method $method -Uri $requestUrl -Body $body -TimeoutSec $timeoutSec + $response = Invoke-WebRequest -Headers $fabricHeaders -ContentType $contentType -Method $method -Uri $requestUrl -Body $body -TimeoutSec $timeoutSec if ($response.StatusCode -eq 202) { if ($uri -match "jobType=Pipeline") { @@ -156,4 +170,4 @@ Function Invoke-FabricAPIRequest { throw $message } } -} \ No newline at end of file +} diff --git a/source/Public/Lakehouse/New-FabricLakehouse.ps1 b/source/Public/Lakehouse/New-FabricLakehouse.ps1 index feda71b4..70393c3a 100644 --- a/source/Public/Lakehouse/New-FabricLakehouse.ps1 +++ b/source/Public/Lakehouse/New-FabricLakehouse.ps1 @@ -20,9 +20,9 @@ An optional description for the Lakehouse. An optional path to enable schemas in the Lakehouse .EXAMPLE - Add-FabricLakehouse -WorkspaceId "workspace-12345" -LakehouseName "New Lakehouse" -LakehouseEnableSchemas $true +Add-FabricLakehouse -WorkspaceId "workspace-12345" -LakehouseName "New Lakehouse" -LakehouseEnableSchemas $true - .NOTES +.NOTES - Requires `$FabricConfig` global configuration, including `BaseUrl` and `FabricHeaders`. - Calls `Test-TokenExpired` to ensure token validity before making the API request. diff --git a/source/Public/Set-FabricAuthToken.ps1 b/source/Public/Set-FabricAuthToken.ps1 index d9eedda3..0781c372 100644 --- a/source/Public/Set-FabricAuthToken.ps1 +++ b/source/Public/Set-FabricAuthToken.ps1 @@ -1,48 +1,47 @@ -function Set-FabricAuthToken -{ - <# +<# .SYNOPSIS - Sets the Fabric authentication token. +Sets the Fabric authentication token. .DESCRIPTION - The Set-FabricAuthToken function sets the Fabric authentication token. It checks if an Azure context is already available. If not, it connects to the Azure account using either a service principal ID and secret, a provided credential, or interactive login. It then gets the Azure context and sets the Fabric authentication token. +The Set-FabricAuthToken function sets the Fabric authentication token. It checks if an Azure context is already available. If not, it connects to the Azure account using either a service principal ID and secret, a provided credential, or interactive login. It then gets the Azure context and sets the Fabric authentication token. .PARAMETER azContext - The Azure context. If not provided, the function connects to the Azure account and gets the context. +The Azure context. If not provided, the function connects to the Azure account and gets the context. .PARAMETER servicePrincipalId - The service principal ID. If provided, the function uses this ID and the service principal secret to connect to the Azure account. +The service principal ID. If provided, the function uses this ID and the service principal secret to connect to the Azure account. .PARAMETER servicePrincipalSecret - The service principal secret. Used with the service principal ID to connect to the Azure account. +The service principal secret. Used with the service principal ID to connect to the Azure account. .PARAMETER tenantId - The tenant ID. Used with the service principal ID and secret or the credential to connect to the Azure account. +The tenant ID. Used with the service principal ID and secret or the credential to connect to the Azure account. .PARAMETER credential - The credential. If provided, the function uses this credential to connect to the Azure account. +The credential. If provided, the function uses this credential to connect to the Azure account. .PARAMETER reset - A switch parameter. If provided, the function resets the Fabric authentication token. +A switch parameter. If provided, the function resets the Fabric authentication token. .PARAMETER apiUrl - The API URL. If provided, the function sets the Fabric API URL to this value. +The API URL. If provided, the function sets the Fabric API URL to this value. .EXAMPLE - Set-FabricAuthToken -servicePrincipalId "12345678-90ab-cdef-1234-567890abcdef" -servicePrincipalSecret "secret" -tenantId "12345678-90ab-cdef-1234-567890abcdef" +Set-FabricAuthToken -servicePrincipalId "12345678-90ab-cdef-1234-567890abcdef" -servicePrincipalSecret "secret" -tenantId "12345678-90ab-cdef-1234-567890abcdef" - This command sets the Fabric authentication token using the provided service principal ID, secret, and tenant ID. +This command sets the Fabric authentication token using the provided service principal ID, secret, and tenant ID. .INPUTS - String, SecureString, PSCredential. You can pipe a string that contains the service principal ID, a secure string that contains the service principal secret, and a PSCredential object that contains the credential to Set-FabricAuthToken. +String, SecureString, PSCredential. You can pipe a string that contains the service principal ID, a secure string that contains the service principal secret, and a PSCredential object that contains the credential to Set-FabricAuthToken. .OUTPUTS - None. This function does not return any output. +None. This function does not return any output. .NOTES - This function was originally written by Rui Romano. - https://github.com/RuiRomano/fabricps-pbip - #> +This function was originally written by Rui Romano. +https://github.com/RuiRomano/fabricps-pbip +#> +function Set-FabricAuthToken { [OutputType([System.Collections.Hashtable])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification = "To pass current unit tests")] [CmdletBinding(SupportsShouldProcess)] @@ -62,35 +61,30 @@ function Set-FabricAuthToken $azContext = Get-AzContext } - if ($apiUrl) - { + if ($apiUrl) { $FabricSession.BaseApiUrl = $apiUrl } - if (!$azContext) - { + if (!$azContext) { Write-Output "Getting authentication token" - if ($servicePrincipalId) - { + if ($servicePrincipalId) { $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $servicePrincipalId, ($servicePrincipalSecret | ConvertTo-SecureString -AsPlainText -Force) Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential | Out-Null Set-AzContext -Tenant $tenantId | Out-Null } - elseif ($null -ne $credential) - { + elseif ($null -ne $credential) { Connect-AzAccount -Credential $credential -Tenant $tenantId | Out-Null } - else - { + else { Connect-AzAccount | Out-Null } $azContext = Get-AzContext } if ($PSCmdlet.ShouldProcess("Setting Fabric authentication token for $($azContext.Account)")) { - Write-Output "Connnected: $($azContext.Account)" + Write-Output "Connected: $($azContext.Account)" Write-Output "Fabric ResourceUrl: $($FabricSession.ResourceUrl)" $FabricSession.AccessToken = (Get-AzAccessToken -AsSecureString -ResourceUrl $FabricSession.ResourceUrl) diff --git a/source/Public/Utils/Set-FabricApiHeaders.ps1 b/source/Public/Utils/Set-FabricApiHeaders.ps1 index 37ccfd24..ef46e676 100644 --- a/source/Public/Utils/Set-FabricApiHeaders.ps1 +++ b/source/Public/Utils/Set-FabricApiHeaders.ps1 @@ -35,7 +35,7 @@ Logs in to Azure with the specified tenant ID, retrieves an access token for the - Ensure the `Connect-AzAccount` and `Get-AzAccessToken` commands are available (Azure PowerShell module required). - Relies on a global `$FabricConfig` object for storing headers and token metadata. - AUTHOR + AUTHOR Tiago Balabuch #> diff --git a/source/Public/Warehouse/Get-FabricWarehouse.ps1 b/source/Public/Warehouse/Get-FabricWarehouse.ps1 index 48447f37..f3f08178 100644 --- a/source/Public/Warehouse/Get-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/Get-FabricWarehouse.ps1 @@ -61,13 +61,13 @@ function Get-FabricWarehouse { # Step 4: Loop to retrieve all capacities with continuation token - $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = "workspaces/{0}/warehouses" -f $WorkspaceId - $Warehouses = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -Method Get ` - -Body $null + $apiParams = @{ + Uri = $apiEndpointURI + Method = 'Get' + } + $Warehouses = (Invoke-FabricAPIRequest @apiParams).Value # Step 8: Filter results based on provided parameters $Warehouse = if ($WarehouseId) { @@ -94,4 +94,4 @@ function Get-FabricWarehouse { Write-Message -Message "Failed to retrieve Warehouse. Error: $errorDetails" -Level Error } -} \ No newline at end of file +} diff --git a/source/Public/Warehouse/New-FabricWarehouse.ps1 b/source/Public/Warehouse/New-FabricWarehouse.ps1 index 32dcb89b..0b9c464c 100644 --- a/source/Public/Warehouse/New-FabricWarehouse.ps1 +++ b/source/Public/Warehouse/New-FabricWarehouse.ps1 @@ -51,7 +51,7 @@ function New-FabricWarehouse Write-Message -Message "Token validation completed." -Level Debug # Step 2: Construct the API URL - $apiEndpointURI = "{0}/workspaces/{1}/warehouses" -f $FabricConfig.BaseUrl, $WorkspaceId + $apiEndpointURI = "workspaces/{0}/warehouses" -f $WorkspaceId Write-Message -Message "API Endpoint: $apiEndpointURI" -Level Debug # Step 3: Construct the request body @@ -70,11 +70,12 @@ function New-FabricWarehouse if ($PSCmdlet.ShouldProcess($apiEndpointURI, "Create Warehouse")) { # Step 4: Make the API request - $response = Invoke-FabricAPIRequest ` - -BaseURI $apiEndpointURI ` - -Headers $FabricConfig.FabricHeaders ` - -method Post ` - -body $bodyJson + $apiParams = @{ + Uri = $apiEndpointURI + Method = 'Post' + Body = $bodyJson + } + $response = Invoke-FabricAPIRequest @apiParams } Write-Message -Message "Data Warehouse created successfully!" -Level Info From 4f6a3958607140e922871f0cfebbdf6495a11335 Mon Sep 17 00:00:00 2001 From: Rob Sewell Date: Tue, 20 May 2025 08:47:21 +0100 Subject: [PATCH 76/76] Add ShouldProcess support for revoke functions Enhance the Revoke-FabricExternalDataShares and Revoke-FabricCapacityTenantSettingOverrides functions to include ShouldProcess support, allowing for confirmation prompts before executing the revoke actions. This improves safety and user control over destructive operations. Also, update the corresponding test files to include new parameters for Confirm and WhatIf, ensuring comprehensive testing for these changes. Thank you! --- .../Revoke-FabricExternalDataShares.ps1 | 3 ++ ...e-FabricCapacityTenantSettingOverrides.ps1 | 3 +- ...icCapacityTenantSettingOverrides.Tests.ps1 | 30 ++++++++--------- .../Revoke-FabricExternalDataShares.Tests.ps1 | 32 +++++++++---------- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 index 018f1d54..373000df 100644 --- a/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 +++ b/source/Public/External Data Share/Revoke-FabricExternalDataShares.ps1 @@ -53,10 +53,13 @@ function Revoke-FabricExternalDataShares { Write-Message -Message "Constructing API endpoint URI..." -Level Debug $apiEndpointURI = "{0}/admin/workspaces/{1}/items/{2}/externalDataShares/{3}/revoke" -f $FabricConfig.BaseUrl, $WorkspaceId, $ItemId, $ExternalDataShareId + if ($PSCmdlet.ShouldProcess("$ExternalDataShareId", "revoke")) { + $externalDataShares = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Post + } # Step 4: Return retrieved data Write-Message -Message "Successfully revoked external data shares." -Level Info diff --git a/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 b/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 index c903afdf..e47c7d14 100644 --- a/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 +++ b/source/Public/Tenant/Revoke-FabricCapacityTenantSettingOverrides.ps1 @@ -43,12 +43,13 @@ function Revoke-FabricCapacityTenantSettingOverrides { $apiEndpointURI = "{0}/admin/capacities/{1}/delegatedTenantSettingOverrides/{2}" -f $FabricConfig.BaseUrl, $capacityId, $tenantSettingName Write-Message -Message "Constructed API Endpoint: $apiEndpointURI" -Level Debug + if ($PSCmdlet.ShouldProcess("$tenantSettingName" , "Revoke")) { # Step 3: Invoke the Fabric API to retrieve capacity tenant setting overrides $response = Invoke-FabricAPIRequest ` -BaseURI $apiEndpointURI ` -Headers $FabricConfig.FabricHeaders ` -Method Delete - + } Write-Message -Message "Successfully removed the tenant setting override '$tenantSettingName' from the capacity with ID '$capacityId'." -Level Info return $response } catch { diff --git a/tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 b/tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 index bc4216eb..1e6822d4 100644 --- a/tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 +++ b/tests/Unit/Revoke-FabricCapacityTenantSettingOverrides.Tests.ps1 @@ -3,20 +3,21 @@ param( $ModuleName = "FabricTools", $expectedParams = @( "capacityId" - "tenantSettingName" - "Verbose" - "Debug" - "ErrorAction" - "WarningAction" - "InformationAction" - "ProgressAction" - "ErrorVariable" - "WarningVariable" - "InformationVariable" - "OutVariable" - "OutBuffer" - "PipelineVariable" - + "tenantSettingName" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" ) ) @@ -44,4 +45,3 @@ Describe "Revoke-FabricCapacityTenantSettingOverrides" -Tag "UnitTests" { } } } - diff --git a/tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 b/tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 index 9a51be26..ab5122be 100644 --- a/tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 +++ b/tests/Unit/Revoke-FabricExternalDataShares.Tests.ps1 @@ -3,21 +3,22 @@ param( $ModuleName = "FabricTools", $expectedParams = @( "WorkspaceId" - "ItemId" - "ExternalDataShareId" - "Verbose" - "Debug" - "ErrorAction" - "WarningAction" - "InformationAction" - "ProgressAction" - "ErrorVariable" - "WarningVariable" - "InformationVariable" - "OutVariable" - "OutBuffer" - "PipelineVariable" - + "ItemId" + "ExternalDataShareId" + "Verbose" + "Debug" + "ErrorAction" + "WarningAction" + "InformationAction" + "ProgressAction" + "ErrorVariable" + "WarningVariable" + "InformationVariable" + "OutVariable" + "OutBuffer" + "PipelineVariable" + "Confirm" + "WhatIf" ) ) @@ -45,4 +46,3 @@ Describe "Revoke-FabricExternalDataShares" -Tag "UnitTests" { } } } -

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 C5-e5# diff --git a/images/Fabtools.png b/images/Fabtools.png deleted file mode 100644 index 887aeb3d007992bd75c0e541b7cd1164264d18cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 543892 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≧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 b4H_qh{oYy+@AO+3_}sXrp`5RH|Ji>5pX=db diff --git a/Test/SamplePBIP/Sales.Report/StaticResources/SharedResources/BaseThemes/CY23SU04.json b/Test/SamplePBIP/Sales.Report/StaticResources/SharedResources/BaseThemes/CY23SU04.json deleted file mode 100644 index 7d092cbd..00000000 --- a/Test/SamplePBIP/Sales.Report/StaticResources/SharedResources/BaseThemes/CY23SU04.json +++ /dev/null @@ -1,674 +0,0 @@ -{ - "name": "CY23SU04", - "dataColors": [ - "#118DFF", - "#12239E", - "#E66C37", - "#6B007B", - "#E044A7", - "#744EC2", - "#D9B300", - "#D64550", - "#197278", - "#1AAB40", - "#15C6F4", - "#4092FF", - "#FFA058", - "#BE5DC9", - "#F472D0", - "#B5A1FF", - "#C4A200", - "#FF8080", - "#00DBBC", - "#5BD667", - "#0091D5", - "#4668C5", - "#FF6300", - "#99008A", - "#EC008C", - "#533285", - "#99700A", - "#FF4141", - "#1F9A85", - "#25891C", - "#0057A2", - "#002050", - "#C94F0F", - "#450F54", - "#B60064", - "#34124F", - "#6A5A29", - "#1AAB40", - "#BA141A", - "#0C3D37", - "#0B511F" - ], - "foreground": "#252423", - "foregroundNeutralSecondary": "#605E5C", - "foregroundNeutralTertiary": "#B3B0AD", - "background": "#FFFFFF", - "backgroundLight": "#F3F2F1", - "backgroundNeutral": "#C8C6C4", - "tableAccent": "#118DFF", - "good": "#1AAB40", - "neutral": "#D9B300", - "bad": "#D64554", - "maximum": "#118DFF", - "center": "#D9B300", - "minimum": "#DEEFFF", - "null": "#FF7F48", - "hyperlink": "#0078d4", - "visitedHyperlink": "#0078d4", - "textClasses": { - "callout": { - "fontSize": 45, - "fontFace": "DIN", - "color": "#252423" - }, - "title": { - "fontSize": 12, - "fontFace": "DIN", - "color": "#252423" - }, - "header": { - "fontSize": 12, - "fontFace": "Segoe UI Semibold", - "color": "#252423" - }, - "label": { - "fontSize": 10, - "fontFace": "Segoe UI", - "color": "#252423" - } - }, - "visualStyles": { - "*": { - "*": { - "*": [ - { - "wordWrap": true - } - ], - "line": [ - { - "transparency": 0 - } - ], - "outline": [ - { - "transparency": 0 - } - ], - "plotArea": [ - { - "transparency": 0 - } - ], - "categoryAxis": [ - { - "showAxisTitle": true, - "gridlineStyle": "dotted", - "concatenateLabels": false - } - ], - "valueAxis": [ - { - "showAxisTitle": true, - "gridlineStyle": "dotted" - } - ], - "y2Axis": [ - { - "show": true - } - ], - "title": [ - { - "titleWrap": true - } - ], - "lineStyles": [ - { - "strokeWidth": 3 - } - ], - "wordWrap": [ - { - "show": true - } - ], - "background": [ - { - "show": true, - "transparency": 0 - } - ], - "outspacePane": [ - { - "backgroundColor": { - "solid": { - "color": "#ffffff" - } - }, - "transparency": 0, - "border": true, - "borderColor": { - "solid": { - "color": "#B3B0AD" - } - } - } - ], - "filterCard": [ - { - "$id": "Applied", - "transparency": 0, - "foregroundColor": { - "solid": { - "color": "#252423" - } - }, - "border": true - }, - { - "$id": "Available", - "transparency": 0, - "foregroundColor": { - "solid": { - "color": "#252423" - } - }, - "border": true - } - ] - } - }, - "scatterChart": { - "*": { - "bubbles": [ - { - "bubbleSize": -10 - } - ], - "general": [ - { - "responsive": true - } - ], - "fillPoint": [ - { - "show": true - } - ], - "legend": [ - { - "showGradientLegend": true - } - ] - } - }, - "lineChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "map": { - "*": { - "bubbles": [ - { - "bubbleSize": -10 - } - ] - } - }, - "azureMap": { - "*": { - "bubbleLayer": [ - { - "bubbleRadius": 8, - "minBubbleRadius": 8, - "maxRadius": 40 - } - ], - "barChart": [ - { - "barHeight": 3, - "thickness": 3 - } - ] - } - }, - "pieChart": { - "*": { - "legend": [ - { - "show": true, - "position": "RightCenter" - } - ], - "labels": [ - { - "labelStyle": "Data value, percent of total" - } - ] - } - }, - "donutChart": { - "*": { - "legend": [ - { - "show": true, - "position": "RightCenter" - } - ], - "labels": [ - { - "labelStyle": "Data value, percent of total" - } - ] - } - }, - "pivotTable": { - "*": { - "*": [ - { - "showExpandCollapseButtons": true - } - ] - } - }, - "multiRowCard": { - "*": { - "card": [ - { - "outlineWeight": 2, - "barShow": true, - "barWeight": 2 - } - ] - } - }, - "kpi": { - "*": { - "trendline": [ - { - "transparency": 20 - } - ] - } - }, - "slicer": { - "*": { - "general": [ - { - "responsive": true - } - ], - "date": [ - { - "hideDatePickerButton": false - } - ], - "items": [ - { - "padding": 4, - "accessibilityContrastProperties": true - } - ] - } - }, - "waterfallChart": { - "*": { - "general": [ - { - "responsive": true - } - ] - } - }, - "columnChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "legend": [ - { - "showGradientLegend": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "clusteredColumnChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "legend": [ - { - "showGradientLegend": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "hundredPercentStackedColumnChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "legend": [ - { - "showGradientLegend": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "barChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "legend": [ - { - "showGradientLegend": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "clusteredBarChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "legend": [ - { - "showGradientLegend": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "hundredPercentStackedBarChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "legend": [ - { - "showGradientLegend": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "areaChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "stackedAreaChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "lineClusteredColumnComboChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "lineStackedColumnComboChart": { - "*": { - "general": [ - { - "responsive": true - } - ], - "smallMultiplesLayout": [ - { - "backgroundTransparency": 0, - "gridLineType": "inner" - } - ] - } - }, - "ribbonChart": { - "*": { - "general": [ - { - "responsive": true - } - ] - } - }, - "group": { - "*": { - "background": [ - { - "show": false - } - ] - } - }, - "basicShape": { - "*": { - "background": [ - { - "show": false - } - ], - "general": [ - { - "keepLayerOrder": true - } - ], - "visualHeader": [ - { - "show": false - } - ] - } - }, - "shape": { - "*": { - "background": [ - { - "show": false - } - ], - "general": [ - { - "keepLayerOrder": true - } - ], - "visualHeader": [ - { - "show": false - } - ] - } - }, - "image": { - "*": { - "background": [ - { - "show": false - } - ], - "general": [ - { - "keepLayerOrder": true - } - ], - "visualHeader": [ - { - "show": false - } - ], - "lockAspect": [ - { - "show": true - } - ] - } - }, - "actionButton": { - "*": { - "background": [ - { - "show": false - } - ], - "visualHeader": [ - { - "show": false - } - ] - } - }, - "pageNavigator": { - "*": { - "background": [ - { - "show": false - } - ], - "visualHeader": [ - { - "show": false - } - ] - } - }, - "bookmarkNavigator": { - "*": { - "background": [ - { - "show": false - } - ], - "visualHeader": [ - { - "show": false - } - ] - } - }, - "textbox": { - "*": { - "general": [ - { - "keepLayerOrder": true - } - ], - "visualHeader": [ - { - "show": false - } - ] - } - }, - "page": { - "*": { - "outspace": [ - { - "color": { - "solid": { - "color": "#FFFFFF" - } - } - } - ], - "background": [ - { - "transparency": 100 - } - ] - } - } - } -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Report/definition.pbir b/Test/SamplePBIP/Sales.Report/definition.pbir deleted file mode 100644 index 8889dcf4..00000000 --- a/Test/SamplePBIP/Sales.Report/definition.pbir +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": "1.0", - "datasetReference": { - "byPath": { - "path": "../Sales.Dataset" - }, - "byConnection": null - } -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Report/item.config.json b/Test/SamplePBIP/Sales.Report/item.config.json deleted file mode 100644 index 172a9ff4..00000000 --- a/Test/SamplePBIP/Sales.Report/item.config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "version": "1.0", - "logicalId": "e5e0eb61-bd59-44f6-8408-35e72b54012c" -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Report/item.metadata.json b/Test/SamplePBIP/Sales.Report/item.metadata.json deleted file mode 100644 index eef95209..00000000 --- a/Test/SamplePBIP/Sales.Report/item.metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "report", - "displayName": "Sales" -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.Report/report.json b/Test/SamplePBIP/Sales.Report/report.json deleted file mode 100644 index 1c883cee..00000000 --- a/Test/SamplePBIP/Sales.Report/report.json +++ /dev/null @@ -1,517 +0,0 @@ -{ - "config": "{\"version\":\"5.43\",\"themeCollection\":{\"baseTheme\":{\"name\":\"CY23SU04\",\"version\":\"5.43\",\"type\":2},\"customTheme\":{\"name\":\"Light4437032645752863.json\",\"version\":\"5.48\",\"type\":1}},\"activeSectionIndex\":0,\"bookmarks\":[{\"displayName\":\"Bookmark 1\",\"name\":\"Bookmark7c19b7211ada7de10c30\",\"explorationState\":{\"version\":\"1.3\",\"activeSection\":\"ReportSection89a9619c7025093ade1c\",\"sections\":{\"ReportSection89a9619c7025093ade1c\":{\"filters\":{\"byExpr\":[{\"name\":\"Filterf99428c5489cc4e6747a\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"howCreated\":1},{\"name\":\"Filter78bda1908a0bd749d86a\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"howCreated\":1},{\"name\":\"Filter9ce4dd28c25b24ab1603\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Subcategory\"}},\"howCreated\":1},{\"name\":\"Filterfa59dace34a0d2b060c1\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}},\"howCreated\":1}]},\"visualContainers\":{\"c258740674a8a53d2c4c\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter7a63ba4725d907434991\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"Function\":2}}}]}},\"0f9bcdc145fc9e6fdc33\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter2a1ae8311dd5bd80e06c\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Margin\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Margin\"}}}]}},\"9472f90b5973fdfeb1a9\":{\"filters\":{\"byExpr\":[{\"name\":\"Filterac4e7a5cd0c9731a470e\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}}}]}},\"bf50a0a6082015e4bcfe\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter2bc8e5e0b6ab2126005e\",\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Qty\"}},\"howCreated\":1},{\"name\":\"Filter0f21badd084a049e0663\",\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}},\"howCreated\":1},{\"name\":\"Filterf321968e3dc8aa5d2791\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"# Customers (with Sales)\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"# Customers (with Sales)\"}}}]}},\"8b8727ff328bdc49692c\":{\"filters\":{\"byExpr\":[{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Date\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"slicer\",\"objects\":{},\"orderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Date\"}}}],\"activeProjections\":{\"Values\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Date\"}}]}}},\"32b08d7f32cb5f9816cc\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Product\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Brand\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Customer\"}},\"Property\":\"Gender\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"barChart\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}}}],\"activeProjections\":{\"Category\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Brand\"}}]}}},\"484fbdd73143c5bf71fa\":{\"filters\":{\"byExpr\":[{\"type\":\"Advanced\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount (LY)\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount (12M average)\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Margin\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"lineClusteredColumnComboChart\",\"objects\":{},\"orderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}}},{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}}}],\"activeProjections\":{\"Category\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}}]}}},\"4b18758374093c2e23fb\":{\"filters\":{\"byExpr\":[{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"# Customers (with Sales)\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"scatterChart\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}}}],\"activeProjections\":{\"X\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}}]}}},\"5acb1caf298449a8acb4\":{\"filters\":{\"byExpr\":[{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount (LY)\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Subcategory\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Product\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"clusteredBarChart\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}}}],\"activeProjections\":{\"Category\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}}]}},\"highlight\":{\"selection\":[{\"dataMap\":{\"Product.Category\":[{\"scopeId\":{\"Comparison\":{\"ComparisonKind\":0,\"Left\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"Right\":{\"Literal\":{\"Value\":\"'Computers'\"}}}}}]},\"metadata\":[\"Measure Table.Sales Amount\"]}],\"filterExpressionMetadata\":{\"expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}}],\"cachedValueItems\":[{\"identities\":[{\"scopeId\":{\"Comparison\":{\"ComparisonKind\":0,\"Left\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"Right\":{\"Literal\":{\"Value\":\"'Computers'\"}}}}}],\"valueMap\":{\"0\":\"Computers\"}}]}}},\"eb5c360e357e8b54eb88\":{\"singleVisual\":{\"visualType\":\"textbox\",\"objects\":{}}},\"3641d5ce63678e76a370\":{\"filters\":{\"byExpr\":[{\"name\":\"Filterf1ec25bb722d30be4755\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"a\",\"Entity\":\"About\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"a\"}},\"Property\":\"Key\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Last Refresh'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"About\"}},\"Property\":\"Key\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"About\"}},\"Property\":\"Value\"}},\"Function\":3}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{}}},\"624827fbb3e3bd2b52a0\":{\"filters\":{\"byExpr\":[{\"type\":\"Advanced\",\"expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Environment\"}},\"Function\":3}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{}}}}}},\"objects\":{\"merge\":{\"outspacePane\":[{\"properties\":{\"expanded\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}},\"visible\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]}}},\"options\":{\"targetVisualNames\":[\"5acb1caf298449a8acb4\"]}},{\"displayName\":\"Bookmark 2\",\"name\":\"Bookmarkd2402dab4df672d2c479\",\"explorationState\":{\"version\":\"1.3\",\"activeSection\":\"ReportSection89a9619c7025093ade1c\",\"sections\":{\"ReportSection89a9619c7025093ade1c\":{\"filters\":{\"byExpr\":[{\"name\":\"Filterf99428c5489cc4e6747a\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"howCreated\":1},{\"name\":\"Filter78bda1908a0bd749d86a\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Product\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Category\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Audio'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"howCreated\":1},{\"name\":\"Filter9ce4dd28c25b24ab1603\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Subcategory\"}},\"howCreated\":1},{\"name\":\"Filterfa59dace34a0d2b060c1\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}},\"howCreated\":1}]},\"visualContainers\":{\"c258740674a8a53d2c4c\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter7a63ba4725d907434991\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"Function\":2}}}]}},\"0f9bcdc145fc9e6fdc33\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter2a1ae8311dd5bd80e06c\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Margin\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Margin\"}}}]}},\"9472f90b5973fdfeb1a9\":{\"filters\":{\"byExpr\":[{\"name\":\"Filterac4e7a5cd0c9731a470e\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}}}]}},\"bf50a0a6082015e4bcfe\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter2bc8e5e0b6ab2126005e\",\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Qty\"}},\"howCreated\":1},{\"name\":\"Filter0f21badd084a049e0663\",\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}},\"howCreated\":1},{\"name\":\"Filterf321968e3dc8aa5d2791\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"# Customers (with Sales)\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"# Customers (with Sales)\"}}}]}},\"8b8727ff328bdc49692c\":{\"filters\":{\"byExpr\":[{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Date\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"slicer\",\"objects\":{},\"orderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Date\"}}}],\"activeProjections\":{\"Values\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Date\"}}]}}},\"32b08d7f32cb5f9816cc\":{\"filters\":{\"byExpr\":[{\"name\":\"Filter\",\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Product\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Brand\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Customer\"}},\"Property\":\"Gender\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"barChart\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}}}],\"activeProjections\":{\"Category\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Brand\"}}]}}},\"484fbdd73143c5bf71fa\":{\"filters\":{\"byExpr\":[{\"type\":\"Advanced\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount (LY)\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount (12M average)\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Margin\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"lineClusteredColumnComboChart\",\"objects\":{},\"orderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}}},{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}}}],\"activeProjections\":{\"Category\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}}]}}},\"4b18758374093c2e23fb\":{\"filters\":{\"byExpr\":[{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"# Customers (with Sales)\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"scatterChart\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}}}],\"activeProjections\":{\"X\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Month\"}}]}}},\"5acb1caf298449a8acb4\":{\"filters\":{\"byExpr\":[{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}},\"howCreated\":0},{\"type\":\"Advanced\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount (LY)\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Subcategory\"}},\"howCreated\":0},{\"type\":\"Categorical\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Product\"}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"clusteredBarChart\",\"objects\":{},\"orderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount\"}}}],\"activeProjections\":{\"Category\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}}]}}},\"eb5c360e357e8b54eb88\":{\"singleVisual\":{\"visualType\":\"textbox\",\"objects\":{}}},\"3641d5ce63678e76a370\":{\"filters\":{\"byExpr\":[{\"name\":\"Filterf1ec25bb722d30be4755\",\"type\":\"Categorical\",\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"a\",\"Entity\":\"About\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"a\"}},\"Property\":\"Key\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Last Refresh'\"}}]]}}}]},\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"About\"}},\"Property\":\"Key\"}},\"howCreated\":1},{\"type\":\"Advanced\",\"expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"About\"}},\"Property\":\"Value\"}},\"Function\":3}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{}}},\"624827fbb3e3bd2b52a0\":{\"filters\":{\"byExpr\":[{\"type\":\"Advanced\",\"expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Environment\"}},\"Function\":3}},\"howCreated\":0}]},\"singleVisual\":{\"visualType\":\"card\",\"objects\":{}}}}}},\"objects\":{\"merge\":{\"outspacePane\":[{\"properties\":{\"expanded\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"visible\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]}}},\"options\":{\"targetVisualNames\":[\"5acb1caf298449a8acb4\"]}}],\"defaultDrillFilterOtherVisuals\":true,\"slowDataSourceSettings\":{\"isCrossHighlightingDisabled\":false,\"isSlicerSelectionsButtonEnabled\":false,\"isFilterSelectionsButtonEnabled\":false,\"isFieldWellButtonEnabled\":false,\"isApplyAllButtonEnabled\":false},\"linguisticSchemaSyncVersion\":2,\"settings\":{\"isPersistentUserStateDisabled\":false,\"hideVisualContainerHeader\":false,\"useStylableVisualContainerHeader\":true,\"exportDataMode\":0,\"useNewFilterPaneExperience\":true,\"optOutNewFilterPaneExperience\":false,\"defaultFilterActionIsDataFilter\":true,\"useCrossReportDrillthrough\":false,\"allowChangeFilterTypes\":true,\"allowInlineExploration\":true,\"disableFilterPaneSearch\":false,\"allowDataPointLassoSelect\":true},\"objects\":{\"outspacePane\":[{\"properties\":{\"expanded\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"visible\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]}}", - "filters": "[]", - "layoutOptimization": 1, - "pods": [ - { - "boundSection": "ReportSectiond90903559771e58905a1", - "config": "{}", - "name": "Pod", - "parameters": "[{\"name\":\"Param_Filterf99428c5489cc4e6747a\",\"boundFilter\":\"Filterf99428c5489cc4e6747a\",\"fieldExpr\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"isLegacySingleSelection\":true}]", - "type": 1 - }, - { - "boundSection": "ReportSection89a9619c7025093ade1c", - "config": "{}", - "name": "Pod6", - "parameters": "[{\"name\":\"Param_Filterf99428c5489cc4e6747a\",\"boundFilter\":\"Filterf99428c5489cc4e6747a\",\"fieldExpr\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"isLegacySingleSelection\":true}]", - "type": 1 - } - ], - "publicCustomVisuals": [ - "FilterByList507A2DBEC31244C8AAABDE9BA541F723" - ], - "resourcePackages": [ - { - "resourcePackage": { - "disabled": false, - "items": [ - { - "name": "CY23SU04", - "path": "BaseThemes/CY23SU04.json", - "type": 202 - } - ], - "name": "SharedResources", - "type": 2 - } - }, - { - "resourcePackage": { - "disabled": false, - "items": [ - { - "name": "Light4437032645752863.json", - "path": "Light4437032645752863.json", - "type": 201 - }, - { - "name": "_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg", - "path": "_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg", - "type": 100 - } - ], - "name": "RegisteredResources", - "type": 1 - } - } - ], - "sections": [ - { - "config": "{\"visibility\":1}", - "displayName": "Sales Detail", - "displayOption": 1, - "filters": "[]", - "height": 720.00, - "name": "ReportSection61481e08c8c340011ce0", - "ordinal": 3, - "visualContainers": [ - { - "config": "{\"name\":\"3852e5607b224b8ebd1a\",\"layouts\":[{\"id\":0,\"position\":{\"x\":292.52156694070004,\"y\":90.4157570543982,\"z\":4000,\"width\":958.0081317307926,\"height\":611.6360036032819,\"tabOrder\":4000}}],\"singleVisual\":{\"visualType\":\"tableEx\",\"projections\":{\"Values\":[{\"queryRef\":\"Calendar.Year\"},{\"queryRef\":\"Product.Product\"},{\"queryRef\":\"Customer.Customer\"},{\"queryRef\":\"Sales.Margin\"},{\"queryRef\":\"Sales.Sales Qty\"},{\"queryRef\":\"Product.Product Code\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0},{\"Name\":\"p\",\"Entity\":\"Parameter - Dimension\",\"Type\":0},{\"Name\":\"p1\",\"Entity\":\"Product\",\"Type\":0},{\"Name\":\"c1\",\"Entity\":\"Customer\",\"Type\":0},{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Year\"},\"Name\":\"Calendar.Year\",\"NativeReferenceName\":\"Year\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p1\"}},\"Property\":\"Product\"},\"Name\":\"Product.Product\",\"NativeReferenceName\":\"Product\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c1\"}},\"Property\":\"Customer\"},\"Name\":\"Customer.Customer\",\"NativeReferenceName\":\"Customer\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Margin\"},\"Name\":\"Sales.Margin\",\"NativeReferenceName\":\"Margin\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Qty\"},\"Name\":\"Sales.Sales Qty\",\"NativeReferenceName\":\"Sales Qty\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p1\"}},\"Property\":\"Product Code\"},\"Name\":\"Product.Product Code\",\"NativeReferenceName\":\"Product Code\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Parameter - Dimension\"}}}]},\"queryFieldParametersByRole\":{\"Values\":[{\"index\":1,\"length\":2,\"expr\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Parameter - Dimension\"}},\"Property\":\"Parameter - Dimension\"}}},{\"index\":2,\"length\":2,\"expr\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Parameter - Measure\"}},\"Property\":\"Parameter - Measure\"}}}]},\"columnProperties\":{\"Product.Product\":{\"displayName\":\"Product\"},\"Customer.Customer\":{\"displayName\":\"Customer\"},\"Sales.Margin\":{\"displayName\":\"Margin\"},\"Sales.Sales Qty\":{\"displayName\":\"Sales Qty\"}},\"drillFilterOtherVisuals\":true}}", - "filters": "[]", - "height": 611.64, - "width": 958.01, - "x": 292.52, - "y": 90.42, - "z": 4000.00 - }, - { - "config": "{\"name\":\"5019b5b630a0b1096507\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.137339055793994,\"y\":6.180257510729613,\"z\":1000,\"width\":65.5107296137339,\"height\":65.5107296137339,\"tabOrder\":1000}}],\"singleVisual\":{\"visualType\":\"image\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"imageUrl\":{\"expr\":{\"ResourcePackageItem\":{\"PackageName\":\"RegisteredResources\",\"PackageType\":1,\"ItemName\":\"_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg\"}}}}}]}}}", - "filters": "[]", - "height": 65.51, - "width": 65.51, - "x": 32.14, - "y": 6.18, - "z": 1000.00 - }, - { - "config": "{\"name\":\"7df3763f63115a096029\",\"layouts\":[{\"id\":0,\"position\":{\"x\":106.30042918454936,\"y\":8.034334763948499,\"z\":0,\"width\":585.2703862660944,\"height\":62.4206008583691,\"tabOrder\":0}}],\"singleVisual\":{\"visualType\":\"textbox\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"paragraphs\":[{\"textRuns\":[{\"value\":\"MyCompany - \",\"textStyle\":{\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}},{\"value\":\"Sales Detail\",\"textStyle\":{\"fontWeight\":\"bold\",\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}}]}]}}]},\"vcObjects\":{}}}", - "filters": "[]", - "height": 62.42, - "width": 585.27, - "x": 106.30, - "y": 8.03, - "z": 0.00 - }, - { - "config": "{\"name\":\"8c05ec5365e4a086b40d\",\"layouts\":[{\"id\":0,\"position\":{\"x\":19.776824034334762,\"y\":254.0085836909871,\"z\":3000,\"width\":216.30901287553647,\"height\":235.46781115879827,\"tabOrder\":2000}}],\"singleVisual\":{\"visualType\":\"slicer\",\"projections\":{\"Values\":[{\"queryRef\":\"Parameter - Measure.Parameter - Measure\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Parameter - Measure\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Parameter - Measure\"},\"Name\":\"Parameter - Measure.Parameter - Measure\",\"NativeReferenceName\":\"Measure\"}]},\"columnProperties\":{\"Parameter - Measure.Parameter - Measure\":{\"displayName\":\"Measure\"}},\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"filter\":{\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Parameter - Measure\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Parameter - Measure Fields\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'''Sales''[Margin]'\"}}],[{\"Literal\":{\"Value\":\"'''Sales''[Sales Qty]'\"}}]]}}}]}}}}],\"selection\":[{\"properties\":{\"singleSelect\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]},\"cachedFilterDisplayItems\":[{\"id\":{\"scopeId\":{\"Comparison\":{\"ComparisonKind\":0,\"Left\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Parameter - Measure\"}},\"Property\":\"Parameter - Measure Fields\"}},\"Right\":{\"Literal\":{\"Value\":\"'''Sales''[Margin]'\"}}}}},\"displayName\":\"Margin\"},{\"id\":{\"scopeId\":{\"Comparison\":{\"ComparisonKind\":0,\"Left\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Parameter - Measure\"}},\"Property\":\"Parameter - Measure Fields\"}},\"Right\":{\"Literal\":{\"Value\":\"'''Sales''[Sales Qty]'\"}}}}},\"displayName\":\"Sales Qty\"}]}}", - "filters": "[]", - "height": 235.47, - "width": 216.31, - "x": 19.78, - "y": 254.01, - "z": 3000.00 - }, - { - "config": "{\"name\":\"e04b35ccb498a1d0a1ba\",\"layouts\":[{\"id\":0,\"position\":{\"x\":20,\"y\":489,\"width\":216,\"height\":213,\"z\":5000,\"tabOrder\":5000}}],\"singleVisual\":{\"visualType\":\"FilterByList507A2DBEC31244C8AAABDE9BA541F723\",\"projections\":{\"category\":[{\"queryRef\":\"Product.Product Code\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Product\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Product Code\"},\"Name\":\"Product.Product Code\",\"NativeReferenceName\":\"Product Code\"}]},\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{}}]}}}", - "filters": "[]", - "height": 213.00, - "width": 216.00, - "x": 20.00, - "y": 489.00, - "z": 5000.00 - }, - { - "config": "{\"name\":\"e1795a417ee6a1b4bbb6\",\"layouts\":[{\"id\":0,\"position\":{\"x\":20.394849785407725,\"y\":90.23175965665236,\"z\":2000,\"width\":216.30901287553647,\"height\":147.09012875536482,\"tabOrder\":3000}}],\"singleVisual\":{\"visualType\":\"slicer\",\"projections\":{\"Values\":[{\"queryRef\":\"Parameter - Dimension.Parameter - Dimension\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Parameter - Dimension\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Parameter - Dimension\"},\"Name\":\"Parameter - Dimension.Parameter - Dimension\",\"NativeReferenceName\":\"Dimension\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Parameter - Dimension\"}}}]},\"columnProperties\":{\"Parameter - Dimension.Parameter - Dimension\":{\"displayName\":\"Dimension\"}},\"drillFilterOtherVisuals\":true,\"objects\":{\"data\":[{\"properties\":{\"mode\":{\"expr\":{\"Literal\":{\"Value\":\"'Basic'\"}}}}}],\"general\":[{\"properties\":{\"orientation\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}},\"filter\":{\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Parameter - Dimension\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Parameter - Dimension Fields\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'''Product''[Product]'\"}}],[{\"Literal\":{\"Value\":\"'''Customer''[Customer]'\"}}]]}}}]}}}}],\"selection\":[{\"properties\":{\"singleSelect\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]},\"cachedFilterDisplayItems\":[{\"id\":{\"scopeId\":{\"Comparison\":{\"ComparisonKind\":0,\"Left\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Parameter - Dimension\"}},\"Property\":\"Parameter - Dimension Fields\"}},\"Right\":{\"Literal\":{\"Value\":\"'''Product''[Product]'\"}}}}},\"displayName\":\"Product\"},{\"id\":{\"scopeId\":{\"Comparison\":{\"ComparisonKind\":0,\"Left\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Parameter - Dimension\"}},\"Property\":\"Parameter - Dimension Fields\"}},\"Right\":{\"Literal\":{\"Value\":\"'''Customer''[Customer]'\"}}}}},\"displayName\":\"Customer\"}]}}", - "filters": "[]", - "height": 147.09, - "width": 216.31, - "x": 20.39, - "y": 90.23, - "z": 2000.00 - } - ], - "width": 1280.00 - }, - { - "config": "{}", - "displayName": "KPI", - "displayOption": 1, - "filters": "[]", - "height": 720.00, - "name": "ReportSection6687c48ea8a9b7740002", - "ordinal": 4, - "visualContainers": [ - { - "config": "{\"name\":\"4ebf2d5595cb5ae43396\",\"layouts\":[{\"id\":0,\"position\":{\"x\":38.80781089414183,\"y\":98.6639260020555,\"z\":0,\"width\":1205.6731757451182,\"height\":279.54779033915725,\"tabOrder\":0}}],\"singleVisual\":{\"visualType\":\"pivotTable\",\"projections\":{\"Rows\":[{\"queryRef\":\"Product.Category\",\"active\":true}],\"Values\":[{\"queryRef\":\"Sales.Margin %\"},{\"queryRef\":\"Sales._Margin Status\"}],\"Columns\":[{\"queryRef\":\"Calendar.Year\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Product\",\"Type\":0},{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0},{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Category\"},\"Name\":\"Product.Category\",\"NativeReferenceName\":\"Category\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Margin %\"},\"Name\":\"Sales.Margin %\",\"NativeReferenceName\":\"Margin %\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Year\"},\"Name\":\"Calendar.Year\",\"NativeReferenceName\":\"Year\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"_Margin Status\"},\"Name\":\"Sales._Margin Status\",\"NativeReferenceName\":\"Margin Status\"}]},\"autoSelectVisualType\":false,\"drillFilterOtherVisuals\":true,\"objects\":{\"values\":[{\"properties\":{\"icon\":{\"kind\":\"Icon\",\"layout\":{\"expr\":{\"Literal\":{\"Value\":\"'IconOnly'\"}}},\"verticalAlignment\":{\"expr\":{\"Literal\":{\"Value\":\"'Top'\"}}},\"value\":{\"expr\":{\"Conditional\":{\"Cases\":[{\"Condition\":{\"Comparison\":{\"ComparisonKind\":2,\"Left\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Right\":{\"RangePercent\":{\"Min\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":3}},\"Scope\":[]}},\"Max\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":4}},\"Scope\":[]}},\"Percent\":0.67}}}},\"Value\":{\"Literal\":{\"Value\":\"'CircleHigh'\"}}},{\"Condition\":{\"And\":{\"Left\":{\"Comparison\":{\"ComparisonKind\":2,\"Left\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Right\":{\"RangePercent\":{\"Min\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":3}},\"Scope\":[]}},\"Max\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":4}},\"Scope\":[]}},\"Percent\":0.33}}}},\"Right\":{\"Comparison\":{\"ComparisonKind\":3,\"Left\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Right\":{\"RangePercent\":{\"Min\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":3}},\"Scope\":[]}},\"Max\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":4}},\"Scope\":[]}},\"Percent\":0.67}}}}}},\"Value\":{\"Literal\":{\"Value\":\"'SignMedium'\"}}},{\"Condition\":{\"Comparison\":{\"ComparisonKind\":3,\"Left\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Right\":{\"RangePercent\":{\"Min\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":3}},\"Scope\":[]}},\"Max\":{\"ScopedEval\":{\"Expression\":{\"Aggregation\":{\"Expression\":{\"ScopedEval\":{\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"_Margin Status\"}},\"Scope\":[{\"AllRolesRef\":{}}]}},\"Function\":4}},\"Scope\":[]}},\"Percent\":0.33}}}},\"Value\":{\"Literal\":{\"Value\":\"'SignLow'\"}}}]}}}}},\"selector\":{\"data\":[{\"dataViewWildcard\":{\"matchingOption\":1}}],\"metadata\":\"Sales._Margin Status\"}}]}}}", - "filters": "[]", - "height": 279.55, - "width": 1205.67, - "x": 38.81, - "y": 98.66, - "z": 0.00 - }, - { - "config": "{\"name\":\"54d5a6fb015310d05ab1\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.137339055793994,\"y\":6.180257510729613,\"z\":2000,\"width\":65.5107296137339,\"height\":65.5107296137339,\"tabOrder\":2000}}],\"singleVisual\":{\"visualType\":\"image\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"imageUrl\":{\"expr\":{\"ResourcePackageItem\":{\"PackageName\":\"RegisteredResources\",\"PackageType\":1,\"ItemName\":\"_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg\"}}}}}]}}}", - "filters": "[]", - "height": 65.51, - "width": 65.51, - "x": 32.14, - "y": 6.18, - "z": 2000.00 - }, - { - "config": "{\"name\":\"92658440a6c902ba3d82\",\"layouts\":[{\"id\":0,\"position\":{\"x\":106.30042918454936,\"y\":8.034334763948499,\"z\":1000,\"width\":585.2703862660944,\"height\":62.4206008583691,\"tabOrder\":1000}}],\"singleVisual\":{\"visualType\":\"textbox\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"paragraphs\":[{\"textRuns\":[{\"value\":\"MyCompany - \",\"textStyle\":{\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}},{\"value\":\"Margin KPI\",\"textStyle\":{\"fontWeight\":\"bold\",\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}}]}]}}]},\"vcObjects\":{}}}", - "filters": "[]", - "height": 62.42, - "width": 585.27, - "x": 106.30, - "y": 8.03, - "z": 1000.00 - } - ], - "width": 1280.00 - }, - { - "config": "{\"objects\":{\"outspacePane\":[{\"properties\":{\"width\":{\"expr\":{\"Literal\":{\"Value\":\"303L\"}}}}}]},\"filterSortOrder\":3}", - "displayName": "Sales", - "displayOption": 1, - "filters": "[{\"name\":\"Filterf99428c5489cc4e6747a\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{\"general\":[{\"properties\":{\"requireSingleSelect\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]},\"ordinal\":0},{\"name\":\"Filter78bda1908a0bd749d86a\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{},\"ordinal\":1},{\"name\":\"Filter9ce4dd28c25b24ab1603\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Subcategory\"}},\"type\":\"Categorical\",\"howCreated\":1,\"ordinal\":2},{\"name\":\"Filterfa59dace34a0d2b060c1\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}},\"type\":\"Categorical\",\"howCreated\":1,\"ordinal\":3}]", - "height": 720.00, - "name": "ReportSection89a9619c7025093ade1c", - "visualContainers": [ - { - "config": "{\"name\":\"0f9bcdc145fc9e6fdc33\",\"layouts\":[{\"id\":0,\"position\":{\"x\":256.1310133060389,\"y\":80.57318321392016,\"z\":1000,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":1000}},{\"id\":1,\"position\":{\"x\":0,\"y\":240,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Sales.Margin\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Margin\"},\"Name\":\"Sales.Margin\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Margin\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":3,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filter2a1ae8311dd5bd80e06c\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 111.36, - "width": 224.03, - "x": 256.13, - "y": 80.57, - "z": 1000.00 - }, - { - "config": "{\"name\":\"223d9300700274da67c8\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.137339055793994,\"y\":6.180257510729613,\"z\":13000,\"width\":65.5107296137339,\"height\":65.5107296137339,\"tabOrder\":13000}}],\"singleVisual\":{\"visualType\":\"image\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"imageUrl\":{\"expr\":{\"ResourcePackageItem\":{\"PackageName\":\"RegisteredResources\",\"PackageType\":1,\"ItemName\":\"_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg\"}}}}}]}}}", - "filters": "[]", - "height": 65.51, - "width": 65.51, - "x": 32.14, - "y": 6.18, - "z": 13000.00 - }, - { - "config": "{\"name\":\"32b08d7f32cb5f9816cc\",\"layouts\":[{\"id\":0,\"position\":{\"x\":490.7838242636046,\"y\":207.6884672990514,\"z\":5000,\"width\":313.1303045431852,\"height\":272.23165252121817,\"tabOrder\":5000}},{\"id\":1,\"position\":{\"x\":0,\"y\":1860,\"width\":324,\"height\":420}}],\"singleVisual\":{\"visualType\":\"barChart\",\"projections\":{\"Y\":[{\"queryRef\":\"Measure Table.Sales Amount\"}],\"Category\":[{\"queryRef\":\"Product.Brand\",\"active\":true},{\"queryRef\":\"Product.Product\"}],\"Series\":[{\"queryRef\":\"Customer.Gender\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0},{\"Name\":\"p\",\"Entity\":\"Product\",\"Type\":0},{\"Name\":\"c\",\"Entity\":\"Customer\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"},\"Name\":\"Measure Table.Sales Amount\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Product\"},\"Name\":\"Product.Product\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Brand\"},\"Name\":\"Product.Brand\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Gender\"},\"Name\":\"Customer.Gender\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"}}}]},\"drillFilterOtherVisuals\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[{\"name\":\"Filter\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"type\":\"Categorical\",\"howCreated\":1}]", - "height": 272.23, - "width": 313.13, - "x": 490.78, - "y": 207.69, - "z": 5000.00 - }, - { - "config": "{\"name\":\"3641d5ce63678e76a370\",\"layouts\":[{\"id\":0,\"position\":{\"x\":980.6184987787653,\"y\":3.8355351973093823,\"z\":11000,\"width\":299.17174539013183,\"height\":70.31814528400534,\"tabOrder\":11000}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Min(About.Value)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"a\",\"Entity\":\"About\",\"Type\":0}],\"Select\":[{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"a\"}},\"Property\":\"Value\"}},\"Function\":3},\"Name\":\"Min(About.Value)\"}]},\"drillFilterOtherVisuals\":true,\"objects\":{\"labels\":[{\"properties\":{\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]},\"vcObjects\":{}}}", - "filters": "[{\"name\":\"Filterf1ec25bb722d30be4755\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"About\"}},\"Property\":\"Key\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"a\",\"Entity\":\"About\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"a\"}},\"Property\":\"Key\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Last Refresh'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 70.32, - "width": 299.17, - "x": 980.62, - "y": 3.84, - "z": 11000.00 - }, - { - "config": "{\"name\":\"484fbdd73143c5bf71fa\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.31031733102402,\"y\":495.1907330080855,\"z\":6000,\"width\":1230.601651390306,\"height\":213.52905366589786,\"tabOrder\":6000}},{\"id\":1,\"position\":{\"x\":0,\"y\":1200,\"width\":324,\"height\":240}}],\"singleVisual\":{\"visualType\":\"lineClusteredColumnComboChart\",\"projections\":{\"Y\":[{\"queryRef\":\"Sales.Sales Amount\"},{\"queryRef\":\"Sales.Sales Amount (LY)\"},{\"queryRef\":\"Sales.Margin\"}],\"Y2\":[{\"queryRef\":\"Sales.Sales Amount (12M average)\"}],\"Category\":[{\"queryRef\":\"Calendar.Date (Year-Month)\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0},{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"},\"Name\":\"Sales.Sales Amount\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount (LY)\"},\"Name\":\"Sales.Sales Amount (LY)\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount (12M average)\"},\"Name\":\"Sales.Sales Amount (12M average)\",\"NativeReferenceName\":\"Sales Amount (12M average)\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Margin\"},\"Name\":\"Sales.Margin\",\"NativeReferenceName\":\"Margin\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date (Year-Month)\"},\"Name\":\"Calendar.Date (Year-Month)\",\"NativeReferenceName\":\"Date (Year-Month)\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date (Year-Month)\"}}}]},\"queryOptions\":{\"keepProjectionOrder\":true},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 213.53, - "width": 1230.60, - "x": 32.31, - "y": 495.19, - "z": 6000.00 - }, - { - "config": "{\"name\":\"4b18758374093c2e23fb\",\"layouts\":[{\"id\":0,\"position\":{\"x\":834.4490649838376,\"y\":207.90986804311106,\"z\":7000,\"width\":428.4629037374924,\"height\":272.5305027051591,\"tabOrder\":7000}}],\"singleVisual\":{\"visualType\":\"scatterChart\",\"projections\":{\"X\":[{\"queryRef\":\"Calendar.Month\",\"active\":true}],\"Series\":[{\"queryRef\":\"Store.Store\"}],\"Y\":[{\"queryRef\":\"Sales.Sales Amount\"}],\"Size\":[{\"queryRef\":\"Sales.# Customers (with Sales)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0},{\"Name\":\"s1\",\"Entity\":\"Store\",\"Type\":0},{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Month\"},\"Name\":\"Calendar.Month\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s1\"}},\"Property\":\"Store\"},\"Name\":\"Store.Store\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"},\"Name\":\"Sales.Sales Amount\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"# Customers (with Sales)\"},\"Name\":\"Sales.# Customers (with Sales)\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s1\"}},\"Property\":\"Store\"}}}]},\"queryOptions\":{\"keepProjectionOrder\":true},\"drillFilterOtherVisuals\":true,\"objects\":{},\"vcObjects\":{\"title\":[{\"properties\":{\"text\":{\"expr\":{\"Literal\":{\"Value\":\"'Sales Amount vs # Customers'\"}}}}}]}}}", - "filters": "[]", - "height": 272.53, - "width": 428.46, - "x": 834.45, - "y": 207.91, - "z": 7000.00 - }, - { - "config": "{\"name\":\"5acb1caf298449a8acb4\",\"layouts\":[{\"id\":0,\"position\":{\"x\":31.952071892161754,\"y\":207.6884672990514,\"z\":8000,\"width\":428.1577633549675,\"height\":272.23165252121817,\"tabOrder\":8000}},{\"id\":1,\"position\":{\"x\":0,\"y\":1500,\"width\":324,\"height\":300}}],\"singleVisual\":{\"visualType\":\"clusteredBarChart\",\"projections\":{\"Y\":[{\"queryRef\":\"Measure Table.Sales Amount\"},{\"queryRef\":\"Measure Table.Sales Amount (ly)\"}],\"Category\":[{\"queryRef\":\"Product.Category\",\"active\":true},{\"queryRef\":\"Product.Subcategory\"},{\"queryRef\":\"Product.Product\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0},{\"Name\":\"p\",\"Entity\":\"Product\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"},\"Name\":\"Measure Table.Sales Amount\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount (LY)\"},\"Name\":\"Measure Table.Sales Amount (ly)\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Category\"},\"Name\":\"Product.Category\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Subcategory\"},\"Name\":\"Product.Subcategory\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Product\"},\"Name\":\"Product.Product\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"}}}]},\"drillFilterOtherVisuals\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 272.23, - "width": 428.16, - "x": 31.95, - "y": 207.69, - "z": 8000.00 - }, - { - "config": "{\"name\":\"624827fbb3e3bd2b52a0\",\"layouts\":[{\"id\":0,\"position\":{\"x\":765.803224723949,\"y\":5.2362613656338395,\"z\":12000,\"width\":223.85017338084663,\"height\":68.0713977532399,\"tabOrder\":12000}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Min(Sales.Environment)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Environment\"}},\"Function\":3},\"Name\":\"Min(Sales.Environment)\"}]},\"drillFilterOtherVisuals\":true,\"objects\":{\"labels\":[{\"properties\":{\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[]", - "height": 68.07, - "width": 223.85, - "x": 765.80, - "y": 5.24, - "z": 12000.00 - }, - { - "config": "{\"name\":\"8b8727ff328bdc49692c\",\"layouts\":[{\"id\":0,\"position\":{\"x\":942.7956989247311,\"y\":79.82795698924731,\"z\":4000,\"width\":304.1720430107527,\"height\":112.86021505376344,\"tabOrder\":4000}},{\"id\":1,\"position\":{\"x\":0,\"y\":900,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"slicer\",\"projections\":{\"Values\":[{\"queryRef\":\"Calendar.Date\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date\"},\"Name\":\"Calendar.Date\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 112.86, - "width": 304.17, - "x": 942.80, - "y": 79.83, - "z": 4000.00 - }, - { - "config": "{\"name\":\"9472f90b5973fdfeb1a9\",\"layouts\":[{\"id\":0,\"position\":{\"x\":480.16376663254863,\"y\":80.57318321392016,\"z\":2000,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":2000}},{\"id\":1,\"position\":{\"x\":0,\"y\":360,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Sales.Sales Amount Avg per Day\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount Avg per Day\"},\"Name\":\"Sales.Sales Amount Avg per Day\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount Avg per Day\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":4,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filterac4e7a5cd0c9731a470e\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 111.36, - "width": 224.03, - "x": 480.16, - "y": 80.57, - "z": 2000.00 - }, - { - "config": "{\"name\":\"bf50a0a6082015e4bcfe\",\"layouts\":[{\"id\":0,\"position\":{\"x\":702.8863868986693,\"y\":80.57318321392016,\"z\":3000,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":3000}},{\"id\":1,\"position\":{\"x\":0,\"y\":480,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Sales.# Customers (with Sales)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"# Customers (with Sales)\"},\"Name\":\"Sales.# Customers (with Sales)\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"# Customers (with Sales)\"}}}]},\"columnProperties\":{\"Sales.# Customers (with Sales)\":{\"displayName\":\"# Customers\"}},\"drillFilterOtherVisuals\":true,\"filterSortOrder\":3,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":5,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filter2bc8e5e0b6ab2126005e\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Qty\"}},\"type\":\"Advanced\",\"howCreated\":1,\"isHiddenInViewMode\":false,\"ordinal\":0},{\"name\":\"Filter0f21badd084a049e0663\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}},\"type\":\"Advanced\",\"howCreated\":1,\"isHiddenInViewMode\":false,\"ordinal\":1},{\"name\":\"Filterf321968e3dc8aa5d2791\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{\"general\":[{\"properties\":{}}]},\"ordinal\":2}]", - "height": 111.36, - "width": 224.03, - "x": 702.89, - "y": 80.57, - "z": 3000.00 - }, - { - "config": "{\"name\":\"c258740674a8a53d2c4c\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.09825997952917,\"y\":80.57318321392016,\"z\":0,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":0}},{\"id\":1,\"position\":{\"x\":0,\"y\":120,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Measure Table.Sales Amount\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0},{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"},\"Name\":\"Measure Table.Sales Amount\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Year\"}},\"Function\":2}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}],\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":2,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filter7a63ba4725d907434991\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 111.36, - "width": 224.03, - "x": 32.10, - "y": 80.57, - "z": 0.00 - }, - { - "config": "{\"name\":\"eb5c360e357e8b54eb88\",\"layouts\":[{\"id\":0,\"position\":{\"x\":106.30042918454936,\"y\":8.034334763948499,\"z\":10000,\"width\":585.2703862660944,\"height\":62.4206008583691,\"tabOrder\":10000}},{\"id\":1,\"position\":{\"x\":0,\"y\":0,\"width\":324,\"height\":60}}],\"singleVisual\":{\"visualType\":\"textbox\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"paragraphs\":[{\"textRuns\":[{\"value\":\"MyCompany - \",\"textStyle\":{\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}},{\"value\":\"Sales Analysis\",\"textStyle\":{\"fontWeight\":\"bold\",\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}}]}]}}]},\"vcObjects\":{}}}", - "filters": "[]", - "height": 62.42, - "width": 585.27, - "x": 106.30, - "y": 8.03, - "z": 10000.00 - } - ], - "width": 1280.00 - }, - { - "config": "{\"filterSortOrder\":3}", - "displayName": "Dynamic Measure", - "displayOption": 1, - "filters": "[{\"name\":\"Filter5c809dc57d8789cbe6b2\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Dynamic Measure - Apply Format'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{},\"isHiddenInViewMode\":true,\"isLockedInViewMode\":true,\"ordinal\":0}]", - "height": 720.00, - "name": "ReportSection8a2d1e93c8442763827c", - "ordinal": 2, - "visualContainers": [ - { - "config": "{\"name\":\"0d3e7e800d157447d967\",\"layouts\":[{\"id\":0,\"position\":{\"x\":128.54935622317598,\"y\":8.034334763948499,\"z\":10000,\"width\":534.5922746781116,\"height\":62.4206008583691,\"tabOrder\":10000}}],\"singleVisual\":{\"visualType\":\"textbox\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"paragraphs\":[{\"textRuns\":[{\"value\":\"MyCompany - Dynamic Measure\",\"textStyle\":{\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}}]}]}}]},\"vcObjects\":{}}}", - "filters": "[]", - "height": 62.42, - "width": 534.59, - "x": 128.55, - "y": 8.03, - "z": 10000.00 - }, - { - "config": "{\"name\":\"469bc55e70ada4022984\",\"layouts\":[{\"id\":0,\"position\":{\"x\":702.3728813559321,\"y\":80,\"z\":3000,\"width\":225.08474576271186,\"height\":112.54237288135593,\"tabOrder\":3000}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Dynamic Measure.Value % (Δ ly)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value % (Δ ly)\"},\"Name\":\"Dynamic Measure.Value % (Δ ly)\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value % (Δ ly)\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":5,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}],\"title\":[{\"properties\":{\"titleWrap\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]}}}", - "filters": "[]", - "height": 112.54, - "width": 225.08, - "x": 702.37, - "y": 80.00, - "z": 3000.00 - }, - { - "config": "{\"name\":\"6f9803040a3195450ea1\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.2508398656215,\"y\":416.39417693169094,\"z\":6000,\"width\":672.2508398656215,\"height\":289.54087346024636,\"tabOrder\":6000}}],\"singleVisual\":{\"visualType\":\"barChart\",\"projections\":{\"Category\":[{\"queryRef\":\"Product.Category\"},{\"queryRef\":\"Product.Product\",\"active\":true}],\"Y\":[{\"queryRef\":\"Dynamic Measure.Value\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"p\",\"Entity\":\"Product\",\"Type\":0},{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Product\"},\"Name\":\"Product.Product\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value\"},\"Name\":\"Dynamic Measure.Value\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"p\"}},\"Property\":\"Category\"},\"Name\":\"Product.Category\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 289.54, - "width": 672.25, - "x": 32.25, - "y": 416.39, - "z": 6000.00 - }, - { - "config": "{\"name\":\"836c673550db49514e6b\",\"layouts\":[{\"id\":0,\"position\":{\"x\":479.99999999999994,\"y\":80,\"z\":2000,\"width\":223.72881355932202,\"height\":112.54237288135593,\"tabOrder\":2000}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Dynamic Measure.Value Avg per Month\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value Avg per Month\"},\"Name\":\"Dynamic Measure.Value Avg per Month\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value Avg per Month\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}],\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":4,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}],\"title\":[{\"properties\":{\"titleWrap\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]}}}", - "filters": "[]", - "height": 112.54, - "width": 223.73, - "x": 480.00, - "y": 80.00, - "z": 2000.00 - }, - { - "config": "{\"name\":\"8e729be52dab6a0bd956\",\"layouts\":[{\"id\":0,\"position\":{\"x\":256.2711864406779,\"y\":80,\"z\":1000,\"width\":223.72881355932202,\"height\":112.54237288135593,\"tabOrder\":1000}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Dynamic Measure.Value (ly)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value (ly)\"},\"Name\":\"Dynamic Measure.Value (ly)\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value (ly)\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":3,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}],\"title\":[{\"properties\":{\"titleWrap\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]}}}", - "filters": "[]", - "height": 112.54, - "width": 223.73, - "x": 256.27, - "y": 80.00, - "z": 1000.00 - }, - { - "config": "{\"name\":\"b1acdc3d6014876e1960\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.54237288135593,\"y\":80,\"z\":0,\"width\":223.72881355932202,\"height\":112.54237288135593,\"tabOrder\":0}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Dynamic Measure.Value\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value\"},\"Name\":\"Dynamic Measure.Value\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}],\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":2,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[]", - "height": 112.54, - "width": 223.73, - "x": 32.54, - "y": 80.00, - "z": 0.00 - }, - { - "config": "{\"name\":\"c199c1700c0b520ba80a\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.137339055793994,\"y\":6.180257510729613,\"z\":11000,\"width\":65.5107296137339,\"height\":65.5107296137339,\"tabOrder\":11000}}],\"singleVisual\":{\"visualType\":\"image\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"imageUrl\":{\"expr\":{\"ResourcePackageItem\":{\"PackageName\":\"RegisteredResources\",\"PackageType\":1,\"ItemName\":\"_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg\"}}}}}]}}}", - "filters": "[]", - "height": 65.51, - "width": 65.51, - "x": 32.14, - "y": 6.18, - "z": 11000.00 - }, - { - "config": "{\"name\":\"cedd897ce35c9089630c\",\"layouts\":[{\"id\":0,\"position\":{\"x\":942.441209406495,\"y\":80.26875699888018,\"z\":4000,\"width\":312.474804031355,\"height\":112.51959686450168,\"tabOrder\":4000}}],\"singleVisual\":{\"visualType\":\"slicer\",\"projections\":{\"Values\":[{\"queryRef\":\"Calendar.Date\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date\"},\"Name\":\"Calendar.Date\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 112.52, - "width": 312.47, - "x": 942.44, - "y": 80.27, - "z": 4000.00 - }, - { - "config": "{\"name\":\"e50725a866c432900b35\",\"layouts\":[{\"id\":0,\"position\":{\"x\":728.7591240875912,\"y\":416.4337851929093,\"z\":8000,\"width\":525.881126173097,\"height\":289.6350364963503,\"tabOrder\":8000}}],\"singleVisual\":{\"visualType\":\"lineChart\",\"projections\":{\"Category\":[{\"queryRef\":\"Calendar.Date\",\"active\":true}],\"Y\":[{\"queryRef\":\"Dynamic Measure.Value\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c1\",\"Entity\":\"Calendar\",\"Type\":0},{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value\"},\"Name\":\"Dynamic Measure.Value\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c1\"}},\"Property\":\"Date\"},\"Name\":\"Calendar.Date\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c1\"}},\"Property\":\"Date\"}}}]},\"queryOptions\":{\"keepProjectionOrder\":true},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 289.64, - "width": 525.88, - "x": 728.76, - "y": 416.43, - "z": 8000.00 - }, - { - "config": "{\"name\":\"e859ee4d7c5b10e836d6\",\"layouts\":[{\"id\":0,\"position\":{\"x\":31.419423692636073,\"y\":207.64140875133404,\"z\":5000,\"width\":672.102454642476,\"height\":191.24866595517608,\"tabOrder\":5000}}],\"singleVisual\":{\"visualType\":\"lineChart\",\"projections\":{\"Category\":[{\"queryRef\":\"Calendar.Month Start Date\",\"active\":true}],\"Y\":[{\"queryRef\":\"Dynamic Measure.Value\"},{\"queryRef\":\"Dynamic Measure.Value (ly)\"}],\"Y2\":[{\"queryRef\":\"Dynamic Measure.Value Daily Max\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c1\",\"Entity\":\"Calendar\",\"Type\":0},{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value\"},\"Name\":\"Dynamic Measure.Value\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value (ly)\"},\"Name\":\"Dynamic Measure.Value (ly)\"},{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value Daily Max\"},\"Name\":\"Dynamic Measure.Value Daily Max\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c1\"}},\"Property\":\"Month Start Date\"},\"Name\":\"Calendar.Month Start Date\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c1\"}},\"Property\":\"Month Start Date\"}}}]},\"queryOptions\":{\"keepProjectionOrder\":true},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 191.25, - "width": 672.10, - "x": 31.42, - "y": 207.64, - "z": 5000.00 - }, - { - "config": "{\"name\":\"f0143b84500069035233\",\"layouts\":[{\"id\":0,\"position\":{\"x\":728.1522956326988,\"y\":207.8387458006719,\"z\":9000,\"width\":526.7637178051511,\"height\":191.35498320268758,\"tabOrder\":9000}}],\"singleVisual\":{\"visualType\":\"areaChart\",\"projections\":{\"Category\":[{\"queryRef\":\"Calendar.Month Start Date\",\"active\":true}],\"Y\":[{\"queryRef\":\"Dynamic Measure.Value (ytd)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c1\",\"Entity\":\"Calendar\",\"Type\":0},{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Value (ytd)\"},\"Name\":\"Dynamic Measure.Value (ytd)\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c1\"}},\"Property\":\"Month Start Date\"},\"Name\":\"Calendar.Month Start Date\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c1\"}},\"Property\":\"Month Start Date\"}}}]},\"queryOptions\":{\"keepProjectionOrder\":true},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 191.35, - "width": 526.76, - "x": 728.15, - "y": 207.84, - "z": 9000.00 - }, - { - "config": "{\"name\":\"faf65e012ba31569ae03\",\"layouts\":[{\"id\":0,\"position\":{\"x\":800.3433476394849,\"y\":8.65236051502146,\"z\":7000,\"width\":454.86695278969955,\"height\":61.18454935622317,\"tabOrder\":7000}}],\"singleVisual\":{\"visualType\":\"slicer\",\"projections\":{\"Values\":[{\"queryRef\":\"Dynamic Measure.Measure\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"d\",\"Entity\":\"Dynamic Measure\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"d\"}},\"Property\":\"Measure\"},\"Name\":\"Dynamic Measure.Measure\"}]},\"drillFilterOtherVisuals\":true,\"objects\":{\"header\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}],\"data\":[{\"properties\":{\"mode\":{\"expr\":{\"Literal\":{\"Value\":\"'Dropdown'\"}}}}}]},\"vcObjects\":{}}}", - "filters": "[]", - "height": 61.18, - "width": 454.87, - "x": 800.34, - "y": 8.65, - "z": 7000.00 - } - ], - "width": 1280.00 - }, - { - "config": "{\"objects\":{\"outspacePane\":[{\"properties\":{\"width\":{\"expr\":{\"Literal\":{\"Value\":\"303L\"}}}}}]},\"filterSortOrder\":3}", - "displayName": "Sales Geo", - "displayOption": 1, - "filters": "[{\"name\":\"Filterf99428c5489cc4e6747a\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Calendar\"}},\"Property\":\"Year\"}},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{\"general\":[{\"properties\":{\"requireSingleSelect\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}}}}]},\"ordinal\":0},{\"name\":\"Filter78bda1908a0bd749d86a\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Category\"}},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{},\"ordinal\":1},{\"name\":\"Filter9ce4dd28c25b24ab1603\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Product\"}},\"Property\":\"Subcategory\"}},\"type\":\"Categorical\",\"howCreated\":1,\"ordinal\":2},{\"name\":\"Filterfa59dace34a0d2b060c1\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Store\"}},\"Property\":\"Store\"}},\"type\":\"Categorical\",\"howCreated\":1,\"ordinal\":3}]", - "height": 720.00, - "name": "ReportSectiond90903559771e58905a1", - "ordinal": 1, - "visualContainers": [ - { - "config": "{\"name\":\"07ec3a800d9302761545\",\"layouts\":[{\"id\":0,\"position\":{\"x\":480.16376663254863,\"y\":80.57318321392016,\"z\":2000,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":2000}},{\"id\":1,\"position\":{\"x\":0,\"y\":360,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Sales.Sales Amount Avg per Day\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount Avg per Day\"},\"Name\":\"Sales.Sales Amount Avg per Day\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount Avg per Day\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":4,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filterac4e7a5cd0c9731a470e\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 111.36, - "width": 224.03, - "x": 480.16, - "y": 80.57, - "z": 2000.00 - }, - { - "config": "{\"name\":\"3580cccd5431ed37d799\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.137339055793994,\"y\":6.180257510729613,\"z\":13000,\"width\":65.5107296137339,\"height\":65.5107296137339,\"tabOrder\":13000}}],\"singleVisual\":{\"visualType\":\"image\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"imageUrl\":{\"expr\":{\"ResourcePackageItem\":{\"PackageName\":\"RegisteredResources\",\"PackageType\":1,\"ItemName\":\"_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg\"}}}}}]}}}", - "filters": "[]", - "height": 65.51, - "width": 65.51, - "x": 32.14, - "y": 6.18, - "z": 13000.00 - }, - { - "config": "{\"name\":\"40792541a9e9a0e80ce0\",\"layouts\":[{\"id\":0,\"position\":{\"x\":980.6184987787653,\"y\":3.8355351973093823,\"z\":11000,\"width\":299.17174539013183,\"height\":70.31814528400534,\"tabOrder\":11000}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Min(About.Value)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"a\",\"Entity\":\"About\",\"Type\":0}],\"Select\":[{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"a\"}},\"Property\":\"Value\"}},\"Function\":3},\"Name\":\"Min(About.Value)\"}]},\"drillFilterOtherVisuals\":true,\"objects\":{\"labels\":[{\"properties\":{\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]},\"vcObjects\":{}}}", - "filters": "[{\"name\":\"Filterf1ec25bb722d30be4755\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"About\"}},\"Property\":\"Key\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"a\",\"Entity\":\"About\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"a\"}},\"Property\":\"Key\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Last Refresh'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 70.32, - "width": 299.17, - "x": 980.62, - "y": 3.84, - "z": 11000.00 - }, - { - "config": "{\"name\":\"477e4ffc7bd2ba217c85\",\"layouts\":[{\"id\":0,\"position\":{\"x\":106.30042918454936,\"y\":8.034334763948499,\"z\":10000,\"width\":585.2703862660944,\"height\":62.4206008583691,\"tabOrder\":10000}},{\"id\":1,\"position\":{\"x\":0,\"y\":0,\"width\":324,\"height\":60}}],\"singleVisual\":{\"visualType\":\"textbox\",\"drillFilterOtherVisuals\":true,\"objects\":{\"general\":[{\"properties\":{\"paragraphs\":[{\"textRuns\":[{\"value\":\"MyCompany - \",\"textStyle\":{\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}},{\"value\":\"Geo Analysis\",\"textStyle\":{\"fontWeight\":\"bold\",\"fontFamily\":\"Segoe (Bold)\",\"fontSize\":\"24pt\",\"color\":\"#000000\"}}]}]}}]},\"vcObjects\":{}}}", - "filters": "[]", - "height": 62.42, - "width": 585.27, - "x": 106.30, - "y": 8.03, - "z": 10000.00 - }, - { - "config": "{\"name\":\"4a9f9e8013c4b3d2ed84\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.09825997952917,\"y\":80.57318321392016,\"z\":0,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":0}},{\"id\":1,\"position\":{\"x\":0,\"y\":120,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Measure Table.Sales Amount\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0},{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"},\"Name\":\"Measure Table.Sales Amount\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Year\"}},\"Function\":2}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}],\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":2,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filter7a63ba4725d907434991\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 111.36, - "width": 224.03, - "x": 32.10, - "y": 80.57, - "z": 0.00 - }, - { - "config": "{\"name\":\"836caf85daad17452d48\",\"layouts\":[{\"id\":0,\"position\":{\"x\":942.7956989247311,\"y\":79.82795698924731,\"z\":4000,\"width\":304.1720430107527,\"height\":112.86021505376344,\"tabOrder\":4000}},{\"id\":1,\"position\":{\"x\":0,\"y\":900,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"slicer\",\"projections\":{\"Values\":[{\"queryRef\":\"Calendar.Date\",\"active\":true}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"c\",\"Entity\":\"Calendar\",\"Type\":0}],\"Select\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date\"},\"Name\":\"Calendar.Date\"}],\"OrderBy\":[{\"Direction\":1,\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Date\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{},\"vcObjects\":{}}}", - "filters": "[]", - "height": 112.86, - "width": 304.17, - "x": 942.80, - "y": 79.83, - "z": 4000.00 - }, - { - "config": "{\"name\":\"a02ce779cd248c240002\",\"layouts\":[{\"id\":0,\"position\":{\"x\":765.803224723949,\"y\":5.2362613656338395,\"z\":12000,\"width\":223.85017338084663,\"height\":68.0713977532399,\"tabOrder\":12000}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Min(Sales.Environment)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Aggregation\":{\"Expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Environment\"}},\"Function\":3},\"Name\":\"Min(Sales.Environment)\"}]},\"drillFilterOtherVisuals\":true,\"objects\":{\"labels\":[{\"properties\":{\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"20D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[]", - "height": 68.07, - "width": 223.85, - "x": 765.80, - "y": 5.24, - "z": 12000.00 - }, - { - "config": "{\"name\":\"a64038428c095bd71739\",\"layouts\":[{\"id\":0,\"position\":{\"x\":32.137339055793994,\"y\":207.656652360515,\"z\":9000,\"width\":1215.038626609442,\"height\":494.4206008583691,\"tabOrder\":9000}},{\"id\":1,\"position\":{\"x\":0,\"y\":660,\"width\":324,\"height\":180}}],\"singleVisual\":{\"visualType\":\"azureMap\",\"projections\":{\"Size\":[{\"queryRef\":\"Sales.Sales Amount\"}],\"Category\":[{\"queryRef\":\"Customer.Country\",\"active\":true},{\"queryRef\":\"Customer.State\"},{\"queryRef\":\"Customer.City\"}],\"Series\":[{\"queryRef\":\"Customer.Gender\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0},{\"Name\":\"c\",\"Entity\":\"Customer\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"},\"Name\":\"Sales.Sales Amount\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Country\"},\"Name\":\"Customer.Country\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"State\"},\"Name\":\"Customer.State\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"City\"},\"Name\":\"Customer.City\"},{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"c\"}},\"Property\":\"Gender\"},\"Name\":\"Customer.Gender\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Sales Amount\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"mapControls\":[{\"properties\":{\"defaultStyle\":{\"expr\":{\"Literal\":{\"Value\":\"'road'\"}}},\"showStylePicker\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}},\"showNavigationControls\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}},\"showSelectionControl\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}],\"bubbleLayer\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"bubbleRadius\":{\"expr\":{\"Literal\":{\"Value\":\"6L\"}}},\"minBubbleRadius\":{\"expr\":{\"Literal\":{\"Value\":\"6L\"}}},\"maxRadius\":{\"expr\":{\"Literal\":{\"Value\":\"21L\"}}},\"bubbleStrokeWidth\":{\"expr\":{\"Literal\":{\"Value\":\"1L\"}}},\"autoStrokeColor\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"layerPosition\":{\"expr\":{\"Literal\":{\"Value\":\"''\"}}},\"markerRangeType\":{\"expr\":{\"Literal\":{\"Value\":\"'scatterDeprecated'\"}}}}}]},\"vcObjects\":{}}}", - "filters": "[]", - "height": 494.42, - "width": 1215.04, - "x": 32.14, - "y": 207.66, - "z": 9000.00 - }, - { - "config": "{\"name\":\"b5f2ce8ee65990353917\",\"layouts\":[{\"id\":0,\"position\":{\"x\":702.8863868986693,\"y\":80.57318321392016,\"z\":3000,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":3000}},{\"id\":1,\"position\":{\"x\":0,\"y\":480,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Sales.# Customers (with Sales)\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"# Customers (with Sales)\"},\"Name\":\"Sales.# Customers (with Sales)\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"# Customers (with Sales)\"}}}]},\"columnProperties\":{\"Sales.# Customers (with Sales)\":{\"displayName\":\"# Customers\"}},\"drillFilterOtherVisuals\":true,\"filterSortOrder\":3,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":5,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filter2bc8e5e0b6ab2126005e\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Qty\"}},\"type\":\"Advanced\",\"howCreated\":1,\"isHiddenInViewMode\":false,\"ordinal\":0},{\"name\":\"Filter0f21badd084a049e0663\",\"expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Sales\"}},\"Property\":\"Sales Amount Avg per Day\"}},\"type\":\"Advanced\",\"howCreated\":1,\"isHiddenInViewMode\":false,\"ordinal\":1},{\"name\":\"Filterf321968e3dc8aa5d2791\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{\"general\":[{\"properties\":{}}]},\"ordinal\":2}]", - "height": 111.36, - "width": 224.03, - "x": 702.89, - "y": 80.57, - "z": 3000.00 - }, - { - "config": "{\"name\":\"c541b1fe7656a8301322\",\"layouts\":[{\"id\":0,\"position\":{\"x\":256.1310133060389,\"y\":80.57318321392016,\"z\":1000,\"width\":224.03275332650972,\"height\":111.36131013306039,\"tabOrder\":1000}},{\"id\":1,\"position\":{\"x\":0,\"y\":240,\"width\":324,\"height\":120}}],\"singleVisual\":{\"visualType\":\"card\",\"projections\":{\"Values\":[{\"queryRef\":\"Sales.Margin\"}]},\"prototypeQuery\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Sales\",\"Type\":0}],\"Select\":[{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Margin\"},\"Name\":\"Sales.Margin\"}],\"OrderBy\":[{\"Direction\":2,\"Expression\":{\"Measure\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Margin\"}}}]},\"drillFilterOtherVisuals\":true,\"hasDefaultSort\":true,\"objects\":{\"labels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}},\"fontSize\":{\"expr\":{\"Literal\":{\"Value\":\"16D\"}}}}}],\"categoryLabels\":[{\"properties\":{\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":0,\"Percent\":0}}}}}}}]},\"vcObjects\":{\"background\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"true\"}}},\"color\":{\"solid\":{\"color\":{\"expr\":{\"ThemeDataColor\":{\"ColorId\":3,\"Percent\":0}}}}},\"transparency\":{\"expr\":{\"Literal\":{\"Value\":\"0D\"}}}}}],\"border\":[{\"properties\":{\"show\":{\"expr\":{\"Literal\":{\"Value\":\"false\"}}}}}]}}}", - "filters": "[{\"name\":\"Filter2a1ae8311dd5bd80e06c\",\"expression\":{\"Column\":{\"Expression\":{\"SourceRef\":{\"Entity\":\"Smart Calcs\"}},\"Property\":\"Smart Calc\"}},\"filter\":{\"Version\":2,\"From\":[{\"Name\":\"s\",\"Entity\":\"Smart Calcs\",\"Type\":0}],\"Where\":[{\"Condition\":{\"In\":{\"Expressions\":[{\"Column\":{\"Expression\":{\"SourceRef\":{\"Source\":\"s\"}},\"Property\":\"Smart Calc\"}}],\"Values\":[[{\"Literal\":{\"Value\":\"'Label - â–² LY'\"}}]]}}}]},\"type\":\"Categorical\",\"howCreated\":1,\"objects\":{}}]", - "height": 111.36, - "width": 224.03, - "x": 256.13, - "y": 80.57, - "z": 1000.00 - } - ], - "width": 1280.00 - } - ], - "theme": "Light4437032645752863.json" -} \ No newline at end of file diff --git a/Test/SamplePBIP/Sales.pbip b/Test/SamplePBIP/Sales.pbip deleted file mode 100644 index 285e545a..00000000 --- a/Test/SamplePBIP/Sales.pbip +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "1.0", - "artifacts": [ - { - "report": { - "path": "Sales.Report" - } - } - ], - "settings": { - "enableAutoRecovery": true - } -} \ No newline at end of file diff --git a/Test/Test-DeployPBIP-Overrides.ps1 b/Test/Test-DeployPBIP-Overrides.ps1 deleted file mode 100644 index 1790acb6..00000000 --- a/Test/Test-DeployPBIP-Overrides.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -$workspaceName = "RR - FabricAPIs - Deploy 2" -$datasetName = "Dataset A" -$reportName = "Report A" -$pbipDatasetPath = "$currentPath\SamplePBIP\Sales.Dataset" -$pbipReportPath = "$currentPath\SamplePBIP\Sales.Report" - -# Ensure workspace exists - -$workspaceId = New-FabricWorkspace -name $workspaceName -skipErrorIfExists - -if (!$workspaceId) { throw "WorkspaceId cannot be null"} - -# Deploy Dataset - -$fileDatasetOverrides = @{ - "*item.metadata.json" = @{ - "type" = "dataset" - "displayName" = $datasetName - } | ConvertTo-Json -} - -$datasetId = Import-FabricItems -workspaceId $workspaceId -path $pbipDatasetPath -fileOverrides $fileDatasetOverrides - -# Deploy Report - -$fileReportOverrides = @{ - - # Change the connected dataset - - "*definition.pbir" = @{ - "version" = "1.0" - "datasetReference" = @{ - "byConnection" = @{ - "connectionString" = $null - "pbiServiceModelId" = $null - "pbiModelVirtualServerName" = "sobe_wowvirtualserver" - "pbiModelDatabaseName" = "$datasetId" - "name" = "EntityDataSource" - "connectionType" = "pbiServiceXmlaStyleLive" - } - } - } | ConvertTo-Json - - # Change logo - - "*_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg" = [System.IO.File]::ReadAllBytes("$currentPath\sample-resources\logo2.jpg") - - # Change theme - - "*Light4437032645752863.json" = [System.IO.File]::ReadAllBytes("$currentPath\sample-resources\theme_dark.json") - - # Report Name - - "*item.metadata.json" = @{ - "type" = "report" - "displayName" = $reportName - } | ConvertTo-Json -} - -$reportId = Import-FabricItems -workspaceId $workspaceId -path $pbipReportPath -fileOverrides $fileReportOverrides - - - - - - diff --git a/Test/Test-DeployPBIP.ps1 b/Test/Test-DeployPBIP.ps1 deleted file mode 100644 index 8e8e8156..00000000 --- a/Test/Test-DeployPBIP.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -$workspaceName = "RR - FabricAPIs - Deploy 6" -$datasetName = "Dataset A" -$reportName = "Report A" -$pbipPath = "$currentPath\SamplePBIP" - -# Ensure workspace exists - -$workspaceId = New-FabricWorkspace -name $workspaceName -skipErrorIfExists - -$fileOverrides = @{ - - "*.Dataset\item.metadata.json" = @{ - "type" = "dataset" - "displayName" = $datasetName - } | ConvertTo-Json - - "*.Report\item.metadata.json" = @{ - "type" = "report" - "displayName" = $reportName - } | ConvertTo-Json - - # Change logo - - "*_7abfc6c7-1a23-4b5f-bd8b-8dc472366284171093267.jpg" = [System.IO.File]::ReadAllBytes("$currentPath\sample-resources\logo2.jpg") - - # # Change theme - "*Light4437032645752863.json" = [System.IO.File]::ReadAllBytes("$currentPath\sample-resources\theme_dark.json") - -} - -# Import the PBIP to service - -Import-FabricItems -workspaceId $workspaceId -path $pbipPath -fileOverrides $fileOverrides - - - - - - diff --git a/Test/Test-Export.ps1 b/Test/Test-Export.ps1 deleted file mode 100644 index f871d922..00000000 --- a/Test/Test-Export.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -# Set-FabricAuthToken -reset - -$workspaceId = "9e34f19a-dec9-4405-88a9-f09b8c99310f" - -Export-FabricItems -workspaceId $workspaceId -path '.\export' -filter {$_.id -eq "74ab67cc-1cfc-4275-8a96-1d03ef4e186b"} diff --git a/Test/Test-ExportForBPA.ps1 b/Test/Test-ExportForBPA.ps1 deleted file mode 100644 index 43b04f74..00000000 --- a/Test/Test-ExportForBPA.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -#$workspaces = @("d036b3f6-049e-4757-b1cd-80ea88dfbac5", "409a6698-e92d-432c-9901-179107aecf03") -$workspaces = @("9e34f19a-dec9-4405-88a9-f09b8c99310f") -$exportLocation = "$currentPath\export\rules" - -# Download Tabular Editor - -$tabularEditorPath = "$currentPath\_tools\TE\TabularEditor.exe" -$tabularEditorRulesPath = "$currentPath\_tools\TE\rules.json" -if (!(Test-Path $tabularEditorPath)) -{ - $toolPath = "$currentPath\_tools\TE" - New-Item -ItemType Directory -Path $toolPath -ErrorAction SilentlyContinue | Out-Null - - Write-Host "Downloading Tabular Editor binaries" - $downloadUrl = "https://github.com/TabularEditor/TabularEditor/releases/latest/download/TabularEditor.Portable.zip" - $zipFile = "$toolPath\TabularEditor.zip" - Invoke-WebRequest -Uri $downloadUrl -OutFile $zipFile - Expand-Archive -Path $zipFile -DestinationPath $toolPath -Force - - Write-Host "Downloading Dataset default rules" - $downloadUrl = "https://raw.githubusercontent.com/microsoft/Analysis-Services/master/BestPracticeRules/BPARules.json" - Invoke-WebRequest -Uri $downloadUrl -OutFile $tabularEditorRulesPath -} - -# Download PBI Inspector - -$pbiInspectorPath = "$currentPath\_tools\PBIInspector\win-x64\CLI\PBIXInspectorCLI.exe" -$pbiInspectorRulesPath = "$currentPath\_tools\PBIInspector\rules.json" - -if (!(Test-Path $pbiInspectorPath)) -{ - $toolPath = "$currentPath\_Tools\PBIInspector" - New-Item -ItemType Directory -Path $toolPath -ErrorAction SilentlyContinue | Out-Null - - Write-Host "##[debug]Downloading PBI Inspector" - $downloadUrl = "https://github.com/NatVanG/PBI-Inspector/releases/latest/download/win-x64-CLI.zip" - $zipFile = "$toolPath\PBIXInspector.zip" - Invoke-WebRequest -Uri $downloadUrl -OutFile $zipFile - Expand-Archive -Path $zipFile -DestinationPath $toolPath -Force - - Write-Host "##[debug]Downloading Report default rules" - $downloadUrl = "https://raw.githubusercontent.com/NatVanG/PBI-Inspector/main/Rules/Base-rules.json" - Invoke-WebRequest -Uri $downloadUrl -OutFile $pbiInspectorRulesPath -} - -# Export Fabric content - -foreach($workspaceId in $workspaces) -{ - Export-FabricItems -workspaceId $workspaceId -path $exportLocation -} - -# Run Rules for each dataset and report and persist output in file - -$itemsFolders = Get-ChildItem -Path $exportLocation -recurse -include *.pbir, *.pbidataset - -$itemsFolders = $itemsFolders | Select-Object @{n="Order";e={ if ($_.Name -like "*.pbidataset") {1} else {2} }}, * | sort-object Order - -foreach ($itemFolder in $itemsFolders) { - - # Get the parent folder - - $itemFolderPath = $itemFolder.Directory.FullName - $workspaceId = $itemFolder.Directory.Parent.Name - - $itemMetadata = Get-Content "$itemFolderPath\item.metadata.json" | ConvertFrom-Json - - $itemName = $itemMetadata.displayName - $itemType = $itemMetadata.type - - Write-Host "Running rules for '$itemFolderPath' ($itemName - $itemType)" - - $toolOutputPath = "$exportLocation\rulesOutput\$($itemType)_$($workspaceId)_$($itemName).txt" - - New-Item -ItemType Directory -Path (Split-Path $toolOutputPath) -ErrorAction SilentlyContinue | Out-Null - - if ($itemType -in @("dataset", "SemanticModel")) - { - $modelPath = "$itemFolderPath\model.bim" - - Start-Process -FilePath "$tabularEditorPath" -ArgumentList """$modelPath"" -A ""$tabularEditorRulesPath"" -V" -NoNewWindow -Wait -RedirectStandardOutput $toolOutputPath - } - elseif ($itemType -ieq "report") - { - $reportPath = "$itemFolderPath\report.json" - - Start-Process -FilePath "$pbiInspectorPath" -ArgumentList "-pbipreport ""$reportPath"" -rules ""$pbiInspectorRulesPath"" -formats ""ADO""" -NoNewWindow -Wait -RedirectStandardOutput $toolOutputPath - } - else { - throw "Invalid item type: $itemType" - } - -} diff --git a/Test/Test-ExportImport.ps1 b/Test/Test-ExportImport.ps1 deleted file mode 100644 index 24614787..00000000 --- a/Test/Test-ExportImport.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -$exportWorkspaceId = "d020f53d-eb41-421d-af50-8279882524f3" - -Export-FabricItems -workspaceId $exportWorkspaceId -path '.\export' - -$importWorkspaceId = "5bff05e0-355e-41d3-a776-08659726f396" - -#Remove-FabricItems -workspaceId "5bff05e0-355e-41d3-a776-08659726f396" - -Import-FabricItems -workspaceId $importWorkspaceId -path '.\export' - diff --git a/Test/Test-Import.ps1 b/Test/Test-Import.ps1 deleted file mode 100644 index ba07eed7..00000000 --- a/Test/Test-Import.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -$workspaceId = "cdfc383c-5eaa-4f39-91de-0eb26fdd2401" - -Import-FabricItems -workspaceId $workspaceId -path '.\SamplePBIP' -filter "*Sales.Report*" \ No newline at end of file diff --git a/Test/Test-InvokeFabricRequest.ps1 b/Test/Test-InvokeFabricRequest.ps1 deleted file mode 100644 index 323d6db5..00000000 --- a/Test/Test-InvokeFabricRequest.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -Invoke-FabricAPIRequest -uri "workspaces" - -Set-FabricAuthToken -reset - -Invoke-FabricAPIRequest -uri "workspaces" \ No newline at end of file diff --git a/Test/Test-ThemeSwap.ps1 b/Test/Test-ThemeSwap.ps1 deleted file mode 100644 index df14a612..00000000 --- a/Test/Test-ThemeSwap.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -$currentPath = (Split-Path $MyInvocation.MyCommand.Definition -Parent) - -Set-Location $currentPath - -Import-Module ".\FabricPS-PBIP" -Force - -$workspaceId = "d020f53d-eb41-421d-af50-8279882524f3" -$newTheme = "$currentPath\sample-resources\Theme_dark.json" -$exportFolder = "$currentPath\exportThemeSwap" - -# Exports all reports from workspace - -Export-FabricItems -workspaceId $workspaceId -path $exportFolder -itemTypes @("report") - -# Only change reports with theme files - -$themeFiles = Get-ChildItem -Path $exportFolder -recurse |? { - - if ($_.FullName -like "*StaticResources\RegisteredResources\*.json") - { - $jsonContent = Get-Content $_.FullName | ConvertFrom-Json - - if ($jsonContent.'$schema' -ilike "*reportThemeSchema*") - { - return $true - } - } -} - -#Swap theme file and import reports - -foreach($themeFile in $themeFiles) -{ - Write-Host "Changing theme: '$themeFile'" - - $newThemeContent = Get-Content $newTheme - - $newThemeContent | Out-File $themeFile -Force - - $reportFolder = "$($themeFile.DirectoryName)\..\.." - - Import-FabricItems -workspaceId $workspaceId -path $reportFolder -} - diff --git a/Test/sample-resources/logo1.jpg b/Test/sample-resources/logo1.jpg deleted file mode 100644 index 00e5de2a0e6c1ee204097684e76289140759209d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 366046 zcmdpeby!qu+wU4iN>WflN(4cr1?dt*MAEH-bV*4gor82^(PaS&NJ<*y)&$P`6ZRhQ%W78mlNbhEb$6#-*2*E zBG0m8Y8OXrhH|V-vZOm$C%vnlU`T>i+o##;>Qm9x$!z0E9&O2G>xX8T=1m9T;fonz zYTPc0AU>R~*Q~PoVi=Ki zn4+4zN>Zy4@P;t&5M$H8H%eDwR^Q{9*rLH0X4#=;!BLo3yj$)%&tRg)7y}hle`tRx z??_<+DXoXV=(Ud99bH{rbt5MBL)L-Ig`<3X#-;fc3|yZ}yF~dKx@Z~nkGR;^*)a2p zleQLTJpM>AWQ9W(#F}?tmo{)IHaD^l2xUh@?Q2SJ-m9do3f7i0;5Bs4H|TSn@3@o4 z7;h_3k+h3Kng^YQo^P0mXAmxwIJ}otv&|kt2D#yudWfLm|JKd&@e-2~*Dcmeh~udP zow53k1BFQ71rA&I8hSAFS6uQPw>K!3h6-rY%KV zr>OdBYlerWQU9_4bl+MWbIfQFikj#27YN0T#ORUIn~D4f>GkjhZGk%Isb?pSL_GO+nya?oKiC(N>l2AreDo-7Ur$6A*25$(yl}?$4%(CQ?5b{ z)gyEXt$d#ELeUy1bgW-)k=`jrNuXjo0~*+>e0ieu=4^zpT`_#TODaUfLkHY8{amL4 z8)^r(@5{Uz3H+NoahG{IRe&e^)s+$mOiCA;V(o3-f*JZmAf64JBXZto#=CO_sC|DzP@Lq@1nB22R32a+(y@_F-d#{$whe;y&LNZ3-GjeA6N0>kmS#1$~2LDf+#H+ryDr zlTN^RAfyuP(=_?5CBV9P?RK0+#tfJ33|xm9g~Oku#k02S0<}0z;7YYDI;y(q)8taAQAB)b!(0@?S9jpJKpa2B)ncy!2^*^q~5R&G?!RRTZ zp@E6qfE|L6b-<2qElD<#FljBw*;tANMJ>Yl}ex-b5v zulzrv3OypoGm>lC`RHm;(_U6W1Zxk$AkoN1F-q`Lg+%TXfjOB8v+S7;z|aHG@z zKiRpg8_+;JTzF{`1*N{oxwic|nS z6?(dh|1c~c!Xn;s==H7e;d%14YMrCwaU{9V^p-dtWRx0$8a{ahXTr8<7!&#XPi8gt#9f&0<2ELWbbA3hrM#R1s(TeS_-_VicUDEHtv8 znYlt^v}yC)l}7#-C80G9y*6V=8fbv6W?c6-OLhQ6G>kZ~+kkLt=`l7ahT)A>P0u}? zx*h@m?ilO*^}HJ%Qo5_YgcXRKd|J3d@$oW@#C5XL;06r3EO}+_#LdNBICV1#)TJ%H z>z&3G7w|yVO+y!P^mg+fl8*aWlq=@aybdOlK77@;f%UY{gm$j6-M}MbPXz{{fGq%W z8$8*fiL=hY4QyG%p@#lZk`HFP3|~$<9b@(QWX>?5b~$oq`JfB_zGy>-&n%fGtDNxyJ8a+-B%6Gy{}5b7 zaC9|qz%ES_hOT;511OG5W|*R%0px~A%jHw1O}+yTImtCvAh*~0nld5_K~5^6<0WpD z`&qqE07)A{rMKWj@LC1O${U!)-}_v@709uGP$=LyN#UD@sg;~^#?M8DO#(v0C}G|naLzOP4-5*&r3(RjV<+rBqW^D-ND97+ z2k}^f)Jp0^VXy&^ona=&s0!PaF17nm3RZ>$Es zsl@63Y<{4dpC$;D#t8!rRO9i5)%U9cPS_-{gPY~!{ggxwHeM@0#0Vf4YM3hkPQb85 zZom%~S4xUc+C&eckXPHkTp-U4_9d|1IT$Ty1Oev&;Nk+4on*{8$nH4$6csxmXC$&k zd?x3^hmmR{oVpkgQjndCJQK=mBdYhLnFv{c$HZt|kx0O6aF9mf(VD=$1vb)BwYZ(hGAw zG3b8|^4UUX&CD=x3t;Os7^V&S0bK>CFbo^fX-^2@2UIyM(%x)5l8~@)^p~0p1U&pd zsF4OrK$$Xb^mgB0(dUEJQzr|RqG2)zLq=v0l<50Ay~H}=d*2rHFv`#lhHM28_jw{% z$VS8Ayx@p{9AR%K71)R?Nl$k_2_*y?0uWm>{1vV#0HDfCoa!eA*zpMfW( z7pVQz|In=#a2Grj(tKi(7#^^?hX7)T0mNMJ7bWkPxa7Oj>HxfjDyaPOY94@zyFy4t z8{p}W&Bn&fg$!t(*X;P%|56lyyufi#-1w{E7O^ow?Ykua`yOcpbu_YtTt=5bI1-daSc?G%b-+Riv*S5~oi$Ot z-L+~IqKG{6piJ-CuYpMFn3@+wg*9DG z6AUf%K4XY>V&?9E)k#;P-}M1f8n%**BriXmci^hKF{pY-L?cb)Tb^16jhVo*McN`D z^FZx z6{5&xmi?Iq{?me96^oTItV;% z_|)G$2_&Wp5QcyV5jb51d=73ExT@M$^$BNltkD(&e*@eX6eIW(CJV2e4zJ@7JwGM7 zevJ_c!Eckx=+!mdCx*Dbn)L%G`}Gz>aO?*fNGr504=fmb1$<95`r+I41tug)K(dYs zHVJ&~g0xXYC?f6LE-@@R_HLQTwQJ{)(u)7|U|IN)AO!3G<)O)^FY8|_ z(-Hw$8#Gsbt(gvII_q6}dDU0She;r-VV^+onOUvs&f|}Z+T#Crzxoa+mRx48dK{k~ zj!U4EQ^FE%*&jaHuAKrX2=tlRH1_}eX9UbsxQG4(C{u#hj0E&l;&`Tud|1s#Q?F89 zgc6wj-7nZZT&0Q2Y2byc0U5m-)nF`i_|_QK0{+Q}^-uyggcLJQ8m&wwK28uWKuLFE zti$~ux_NdF0@}oSAZ}|H%=p#FPLUD;s_KL-eVBQmSzDM!Vpju_vFPt2p*G-T!Q74X zt-}HQ7z-UTjYKtj+JJVEF94Bzku4RZzb}34kPC*gE$Okm8edbrAh^gUU&paKhFJwx zFM##`iclcGun$NQXJjPEg*ic4fdw6A{|YFn8lS>?tl`VZ zAaNur5}a3t8JMhrwDhN!A!n)c`1)_{YnXv#JT;VBl(SiN->y?rpfLXYP*h?W%-3 zo)tFuzpttjhU845KXRj8c`RtYhW9PQT7Xxk1;efrGn|$E)r9a=c7W?gt(7x?r>fCS zh}{MRfZ#RvJGf6mzVNMPUJvdK>y;ouP&}6P#J7O)xH|xD#{jL<2oD3PCB7{>L%(Z{ z3!9RoVKO3|HUr3%Ap%E6gzxYuC=XfJu*w687(kgS)wejg6p9{l=Oy;>7w?4Q<@<(dpfU`DO=jpMEMYw9kW(~NX)V%<>rgs!|_%9x! z`i*EFgsMa!7V%;JQuBZFjb1ol2*Q1+Y2qfY<1Bs$-iW19P8MlD;q91az>M`yuTtfu zz@~+%nOZE~4_!D~f9Vg1h>$J=S!9O>s|fH7QRuP@9FM=UHOvp8=}$2F{7RUB+(Ex? z{wr?-?J+yb69WLTW~`H>sr6`_8D#v{z-2Iy&%AZu^a?kFX2S|JEPhtOxf1)^lpPX9rKI#k}tSPdx6X*-$=Kl z(vUlWD+v9YD?sU1QG@rl=um4z!o43cnGpaZk<|Ur?|18N@kL%24tHVnOah!a+6A=_ zQgURp5UMWukJ!shMB+cTqgfH(6^2RB3iP`OP>|Rk0j(y4PuhLRJEG+oXzd?G2?)rWn$y0CQoPA{nz|{{w^;Nav=UuQ|S{9vtSTGwdguxSMeOV)kLR zt2lK=)15=p`f7pH-GV^^sn^hd^{Cd<-Ao#Ciath9&Y=B{We&80o9fB=nMq}oOgXq_ z7uy8d{YvlJ+ILEoedl3U?b1#lZcUM5S3hBIOMZJ|uTq|dNgnxQX1fd14c4%yc5CY1oz|DT2jyMIh4}4hX=M0oz86yWzt}5^bU9;W zRyf)G%5PXKBaeW?bwR7f_a##ayoaJ@=HwRQ z<0J#wb(W7z+YRcdP}-lTEz!&zAJ{mONGy$T8;H)z4o0$yM%vhepSJlgK4JMNr@aJd9z4AH7?QT#a7E*_CEPxEy zIFaoOqYLT$Mq5W^_h#ppi**m!niF!^r>L{GhM2sm6n)$um1r{vh9!>_SROId-*|hf z1+p<>R?g-`3NV}?gsoK9{www1dTiwSQJpwz9cd3MTX73Xh-z=DCyv}A8&ywSO!o1r zdK5NV9@prjE3DY$UisBVKr>rVzAoUFm!EI3;Zk;N-Fz{jEi*5L5xLDWb<_z&U!1W^ zhzpZ>g3si_(d(0fSS4nE%3RO=wp<{_;GEGi;SdZqoa@GoY-gOJ_bF$6_S@Q2WVlO* zzphkXH`*$Qo%lQ&cfO5shtO}l=tdhiFjS~%oFaX9 zSQ-IirYMKngnsgn9EW$<;%`uzTa5+vr)eQkk_@`}@dI?KYO+JW59?IO`$8|4sX7gw zgKiz4z-XVoR51&|0fNM+o+b10?NN-gSb)xrpHv7v9DvEXM1NYpbcoM`?Jz{ml(X8V z*?FkH>tqS()fkZ4)=^gy5S_Z*%cDCJN~wQj(wr7@#y2WAf$47kikXZ#@osHq$>|k> zF$7AuKYQ55x%tGlM+!G+`HHs;0&p;L87{04!+;~womkc+L95jvK zD@E;Q2bbixt=S68%B>Vz-^su~%G2gS;|UL3un zgBLXP?c;tb=ZtO0k%!Fo$@&U{j_PN*$(n3v?91E-OZ%9~P^Kzj+1qdJI;vYRjsr_p zRip`A<`i}mg#jr@pd_c*93pu06$$52C-t=Y>hknhcOG0Q0Y6D)?GB?>H(Ga7)juhJ z=%mN>*i5F1b?U?I>J!~-Yc_j7PK2gK20FgT2EHgSpIG2Ic1tN$UJ|_^dJq5Lu_0Qw z)T#3cba^coQ{Z$%H^%j-<`tH~V|VREvu3s`UuJm{Uw(b^*qL@wxhOp2)sYL{5%sq~ z)!=2`{KNTx4=$%TPey6$+#_6-?B{B#n54K)jtgZ+y|#0S*jq)ysbX7f_|JreJj~%F z&f}r;dvN@qrssw(W#$2vVF$pK>g(2p(5oj`Kds9dP!-l0J{byk_If+O?3vX^7&a|7 z;hiWPLp6P}QZ#Ov<$UaQ?7ui0seCv$7ilYb+w-&0N%e^5?55CYeA|3Td~3I|6;pL@_F6i4iy^&%P%y@A8bL^&(w ze`wg5r>(zQY4W{W?=2(Rcq=Kb&cpe5v#nCf^WkO-x@XjLb!~5DA>{j=CAN(3%$yn1 zV+(1Y6n4Es^by#?`A<(Jf{Kbu21EQL#1xUJ6#Fh>gL74|gJjjFOW8UOD9FAodlt<^ zATr`Axt!c*lPr}pI$^PMz_s^wC0Or8D(Oj@=*D87i|0veQ);#P=H8-h>iz`&(15$q z*^#rvS%+DuEWdI#>}Cik9c$s1VRFwoplKaqNQDoIORU5(V4tf0&abl1^y91K!l;#l zK*I9qd<#3_USRa1*DVQ(yn?>&+Gc}rZSOU$wUU*y-Ur)Fqm^X`S$&q0-d!>}f)o1- z=-m17)KZ;dJip40+nmnM?&MYCs9TXw>g`iXx0YJ)ILU%Pq=|^)ry%BTz6}dj@pRIf z3O&n;aFpk&Q{DX4@y7z7(2d1oeYva?4iSG1WV7@7tBy@vW*1y8pR4*uQtKj>#YzWF z>GM9?qBA>njILK6Y{VR+CzwW6_p@yd$ZTI7FSfkVSoFS@Qb^HJt2mYsiZo7^-HjZ8$JahLv25-|Few$p`FS};edB& zG%B=GmA&!c(!q|F)Q7&%{Ax|XR$W3 zLV;ao$)+6Wca2ArZBqFQ`ZxIl68m**!kXkdI`u2`ZXfzudJ{$qk8?oDwEuPm=Dyy& zhEj&tXmzJH^jp1dG;Sux5CM47sV2^w=v`F?!We&u*jYWT{`ZG58j}+ zPsw<|hYn8dPHZgkuh{(Y41M~!$CbXH(I59x1Ne^xoqitidom{Q&BVu=9$O#29h^KU z@!4?3%;vIHY7`!2l{kB@-P)&npgk(No1^Fx)1SR``htuVw~-^cL<{sq5wUvq;_9w$ zHH2mHnI^j-zi*gfQ$Cw?NL#JwLj&edcoVx($7k&+OMFX{rV8DEn`UjZ;wrUOy^*Wj zbh1>vU;h4yH~;3mM|zZp!A$1yWa@E4T*lc~Jhlh>$#Fs){E}V)cic1fYkVXxAME?> zw0ipt$`Sc&N7OIL`VtFni=t?tBn`W~GXiALHisl#ksP-^iv`$|rS*P;|Mv`0P6$vZ zkCduESW%UeYNFKcb*;^#pe-I%^s#cIVvwE(uh`JC9Gaw911y%dAeMik!T52jgN7yrDnPLTGcmV6(loPskmuiA>>i_@9q|b z2$9k4ZgviovzT02VNg1ma;>-gI#lH$bkd~3KbrdhbI0dkO2d6Z*jQZN(i7csvyH;B zK-*d%A@9kz_kc_JHdRZ8BeGcLL$AMrKwDf*b=nOg&+_?|8c^;#7a#5uC8=aInL78k zu$45`RMmtDlr~y$?ESb!A$jO96}hbXvihfGPxvSv*U9AJcQVy&H|N859;r;%+Fb@^ z@3C*v9xWBCj)eOZKAt6Gnjgstb$?qr)}%2Rd2Xgk^X|siA*R%W-rGyF%Mr6mB&O(y z-W>rvVJqj1o?A<;=_@2~PgaHjAImGK(?%)@ULIfE&aUWDu@bBta!ZMJmpQLnZ_Z}M zIi+t?@TOp5{<*b}64CrtXZ9#ps@Gzvf@*le=I-2@tf}(KM)M0YB}OrWiB*Zw3MOYe z{U1_fm%JWQp6HqU5S#vB`|1+YaP@(oUn-4{s4S;h@~yDd!KXdTMPe4oJW1m9eF%R@ zx&zyV`pl2jkS?qt0EG#XIioXHj%BSBLM_m(!A=<^Du?IMUKu`a4)Qd@{viKCW# z72002qt`oU$N4B3&r9sbYx3JL@J+4l@!Qbz3vVTtcaxFL`0SO>G^V_BY2ug64wbA> zbeWrGVCt#TJ!!Ep>Y_Bd_^#IQH7zPdc<#HOmoLrH8LVFD|6wsYqzQ=rSl9XFPaE83 zenZyw1^Nu?4!+SP^fAFqCtFh^;quPuTUxw}7?NY5$i!aT{$TuX5pJIQFY(hOnRvZ- zkDhqiBuhFR#M-{A){os)k^6?7O$bon zbFC$-WPE+}4Qni-e=t^tUpq8beCEaQdlyk9o1i)qc4u@K#{9yRZdX7FA+7d9-gKjt z(X)uY$|6i#*X#(tWp7~^_3_bs15+GkWA1R?hiSXp>xoy2ZZ+@rZNnM-p0(U_x0r^L z4^3ZVb}_-_hoyC5lOLTQcyD8NNF@%+`6fPcK8=uWhU}bJ!Ehl3=pm3V ze84797sSGFnDo5QnJhGev(a0VYQIY5zU2anjIT2S$(Lq)MChbgS*K%dQ@I*lO+Aj8 z4snjnYM-+UWuv`!qvffZ7laHSjS|*jQbfFKrk+*Pxwb7eqBi>!o$y^UDzr+sCI{Qs zml_i)vP%?M6id>Xu727-thD8%J|C>OQmj34hN*L{=LhL+UJO{bQ*O{scn>*_Q_7@cejfj98cONUFYH^Vc8_4(ZeN!>^7^*T z1h7PtawY!S$CY4TGF7IHAbdP4ueYKH!&fymV`jj#*x=714Hx-WCT7)|j0HT3pVmPnf2HYP3M$)$WQuyql1zsAW#=@oDr%$XgihG@#S@G&e zJj_pTdH2qh-kjZ|3fjJd@Jh?|(x8ke57*AThFfHe!V3c#DGa;)rL(s2d!DNY2YuDm z#$6n=_Lm4XO)Ln5T+E{|p<-1$^NpCyH`%4gfgmZb79~znqocKYpUr(dtxZr@XVtb| zL)G3vq8_ekCk&s4Juq4bJJyhrIe!i!vhQ&Jx!hgYwveCT4_#nf%;9!Oh>jxsF6J~7 z5?*xX>%sm<+2c~rft3uE1&O-ZssTx|{cu;u?n2cldU4OrOtBP?qCQHm!1_;wCoW~9 zwMFkjH(G`5~Na!~YslGkBYm%_zT&YcmZ~AZnpG zBHbA4UvZ;x;zuZ-hcj6#*Br~eLz|8-)vbxw4KSN%_9$=1sV8mWp3a-uAzpZ+qoX@z zdXt#sN-oyJ&&yYO+&;V)$?wr-`MAMyb;IcKdROGicQ+?nBe%$p7p`V;uDsmH4(L~< zYaL7$+}?r&-On3l(5#Wv#v1E0f4iRS=>N?i0^4)Ia*vR?`YNL4@uIRMw(ZU8_s`;# z>Zc44HHJNO7IHQYFPQwP5!*d|WQuKZMY$6xyG5f`KX*@JxDSVt=1WxLSEu@)wM8+k zO~uucsnTAnacN{?7vDtR-T9%|`8KR@QgNf`y~u~$B?Zy)du38Lye}}XH5PAbxO}~D z`o4#XHRe&(Nmm0~f++nyKcJktMaArgB(*2gj&&(()F8X(-2#1CEdm%Dx9+QC)R_no z*`B=bsZNP&=7i#chioj(-vxOTws^%vt6I1EP+oIc{6r{rhv~=QRzqH+9#7@2)DzRK zWIm_pws+$%zKQd$f5@1LS`ga{@#e=rcAE22T$SHShbRKrn0#p;2Ij&W!0 zEJdPR69zGuhc;Ha#l@^{4O#e-5*sFd=#D#_DzQB_h3!v>Tc8#~AHx@RZ-9j{239!e zRf}61*=Ip389Vr%8B|bavDNnJppQuPWHF>xiC85z#7i=B;AT?&V`mz|y+MP$_~>RI zZ$7rd)p*?=p;_ydOGFEw?P|H6%#tQx4wn4$qg326wehl;IFY;^e|dcFxYb#C8*TcW zJE9M>Ik?)Fl)KNqXQdxXS>MJq8V?kFyr~($oyqDv#C~aBzT{G@iI4mGJv!aE&m)Ez zwQuJOZ+v}0GI`b}fX0pw>R+nAm?i}WJ+tcXp|9R%v}MambspNRf5eK%K?bSdqeUAH z>`T8B`jz=c`<%JPY(3gvAcWz{oj*r8Wcl^uA~lbXXYZkWLwLFEM4^i+Q@7cnUx|w6 zW|e*0Pm#y%qk-b*w6*EApQozDj?!{X>+Qxn5JG=XGtpPhp=FRI^%| zvIh6R&F0c2&obD&NbDcpeDuW_$~hK^L2%i&A zeuHs4e!ZrdZ>QCByLDc5SY&?f;FgqE+uZEFGR?4UXXc?s(^=fX`he&(}OCp(){-JWnf+a+~+lyGK$K8Pt%@07Va z1K7ppI1s+OPwjf`O-;fZMx2)kjOpFuU|Q8(MXkI$FpDa+ z?HxNvC#mo`OcRqQD|6e-qq{}o#(dRoN9^30fGhU+8$Ay`ER^5>I)3)+_*9+-|0}1E zgz}eD#${J}MDjf-JHxIKMJ=7|d~Y+yvB*ow71B$)bkZhud{b3~wD2AQ#EB%Z#VPLS zWI{sa6qo6Q+lk@1;xXw$Ml5aFx=D>hwcNDY#3#xh2!0iFs8SMf{I=Jo}v_FaP>(Ajt0#_&hcM>?4G%3-yeDn!Y zTjy8C*ApzG7i4dNxC@)CSTYFqUPiylt=yS9UiZeu{}lFpfB8pRb&Jtn7`V@w?jVx~4lPUF#7d!F6@>^secWR28W9q(i=qGMf?b!F6s=;2D*cZ&xw{Hh- z8zdA9N{p-N4dyS?$~&`an6T3aM~yPPWshLzVn3fm!iJ8|TLI@5kLTk9Q~b<3Y)xou zWQ>7Fb<^|EfB}zxvVZ{eus;ln410g_zPjt_=!DT6nKSx@DOA#9QBp=?v(C?aK(bvs zM6@!b?=g!RlxmzfDQ#XUc2i`EyS`i%&$1G?W;Gh`=iWx&>9deIlpcPb_jsV1tlUgi z{ic$f_xdx2GbR>>?-kCR`)(gpKp8pPz|Q6sLC&ua2&za7VQ3WNMLgyhJLG-|5(fJ}DmBzy##!;{6tI?Yg)!&wPmmg0L zH}y&g`i#gHHjK`QP3;e~9UAwhRox357PP3>y%#ttu+)K=#9#PW%zmG`{-ud2;}ETG z%ttPT-kwLS^glv!nj$lQJkmd-_KH?L{`><5YF#SYduLy0#=mF3CBY{`kpFOgE&(4h zvi$+ID(Vw^id02r$|FpW_zABWajfvbF#zLVtR%SiNbpPho79+N8!baIO(?G{q*iW| zyK{`qy<}+OQCkt{kQluu96O}FcjQB$a8hQk)}vjrB<6na#sabaDo~46v*gc~=_l&cy zO_I%irEevZilcPqWnaIoZuk1?xVems@D##I^s#}Pkx{EVm84F0k+o{FFlUsE^+XX z;G5SK)LJW4#j|#1dsE%HpkHboc^vRMi>)e~x$1qboGRzjnXIVt)S2tDV07aTUsBIP zqO@J*V;a%#s|mzaV6;6wp5U(z4d0!GoXvo++cgVM(lpT)>X!?NQ1!`;tyPg>{RAw> zoW~r=;etpK1eJ{U#i=+y@z`=R&n;@9`O`k=EBiY<*=0`eSB81Q7bKSR5l>RkmL1uP zUf(v8MRx=Fb|iRR=A2|P3<%57_(oX|L_rmjW(6me>A{Jc>Vu-6eV^!)11 zEo!Rz80(0v5vm6<44N^obDFzfraun7C%b*g{LXI^3+qN#3-6jM@p z5?s5g`oglRQYtUHI?OX@QFu_H+D=#rH9u;o^DNXapKuz1@+yS)6xFD4NLjFF9Hx52 z0QQynV)v6p@$JNqG>>ARx@iY1d4ZHDs`1Ms%};C}E+0_;rmq@q zYE<2>rrIVQNW^x08nrc%)5u+cW4FeltO=3x1K9p+QD_&Mmu$9gdb}MrD(=<}nf4!A zyUyI@%tTi)yL_-yH7u2}yL0U8qb0JELh@uq>)LqCeBFxn1V13$Ta!$smfO+V-=sP{ zmL|O#B<)Ya$HxyGChoYHhdIdZyt6pX|Au4Z`q%k|H&gwCI_IGYGV7k6=_@-8_?Yn1 zJj%+SIBtJxAseQ8aaUbUm6L?~?f2!~@p=V!ifai=q(Y#u30XNs{>hR8iJCNm3`)M} zb8BB|L$y5oM<1+kZ-7vuLcbcmzk*FRIX;4%l9}3ytz>A96k38O<^DzEG#%i&1 z3IAP?X^d4LWrM;!S-)ZDPaUp%Lq3Ul8!zqDDbmAiPbzmsvG&nG{FSRUkR?Lqy&@fu z!G(7zUShce%h|a?`nw^~WQ6fL`?bh}5uc@YA(v%|^@hB->TK-}m#t=WOnA95CT)a2 z#npT|&PT~Lb9B=T6SuY`6)0hcZX2Tuk}Tqu(9!rTQS6{?Zs@MFyq)>+=U647;q?6d z)hglh$ctHpZx#z=Ln)u`-2A?CGiX_nV2CzjDD{=*aY0HcalP`};M!@b)b(mdcO(Dr zZf;Oog~ri61Agp~Z8WPL4g6CDX)IDJfcGTR{c1ixfzbQ_3_K3=2vMfVlq3*|N=AoV zmJN%2QprGTs`F44E9-(-Q(9;ni$O3oXDxGuRgE<;P~Hpv^!5>2;` zj3?eAJ%ctA*EgkZN!qPs8~S+eZ)e2rH#)Oj>R#?VYgV8ET`jPH+FzQ<^-*08dycbR ze>Ie{E~_j4M^WQKIM>h@qLC}=%i$VRUza>`%6&^)DTb~T`jQ4Dzv3=;eB)4VjyJ|9 zT|os`fMD`8=nQ-|M09#}D)_X41yc1-`m};|uxGH}9BkN|+H>Vr%S??-QX55vE z+IC}RzWmAA1K8)0n~keQv6X&7v8d{Uq+7PeWan=7ng*%A`|#uWWfKLN=GqEy26C_< zd~TvSvohXe^y5)H4PAf6QJ(6N89SRH5D7HBsgV$a_(8Gpv2R8nsns`lPKW&YsXu&g zmH|6$=1EJSFhOTTq^th3r2d@ZO>2a>5;*@DH7PF&f$47eXD9}oIklv<`(u&9!A-Z5 zNb12Lh_*M|l$Xp!m1jZQCmX$Q+AtsXmtITsoS% zH+u(UZQ|GOGo`ZICa-tryhtgxpYlV9Io^G>HyA2DU3FI3P36%8??-aZu5!ic7g@5e zCF7!goPA*O+if``bGgt%X-{+eJNGq<-CZ2|F+~Nc`eLd&v-G#<>_rIp9yY6IJrL(F zTPAhCK;HIn;^vCNI0KHgx6jj-=kUBIeCFkkgdE=>I?MrOdLW1LSs#unmK%j+a-o7g zfO9SOhrGBbQ!rizPTdVf${|s^xY|p`TgiAvePT&58~&a*8WA#{qvCF}zEP>-+mo_# zxq3{DPQyRSkm}0Im9A4Q8!6Q$n2jSU;$qi`M@r5oI+o4iO3cAUHoiwYK9Us4cAe5_T*VRcV#7%#PUm95Tpv4Ol6R)}xf7}h`8mdgb$htJsyLQdt z_s;su-=F!~T}iK9qL?rAKs$xQo9E#v4_P}cb0A`LY(p1B{@XM9yD!e&jejc|%{H1$ zQ@}FcUp<;ZTO&=+PBLqbmo}TpZ`Lmq0H%X7R!1Oekj}~}{+(>)*`>B2e-n;($+?a! zs*j+#_^k+N%+^_-k-Yap*y8K5ohW@Vo=nxnDO7-x*x^bLspqcrco5l&$Fiv0=4fwD zrQu!?9TiEn{qaU6bpbVEzm>7ur?;zQPx47awwX=x{>|!;C=wk$zIT`Tiv-xm8>>(Z zgnaM!_}-Tdc5)^lzBF}y`EjpX22xXJyLy$v954D+eb9qPH+!Zme-z4Wvu2fCO1ZFq zYhr)qkdO#X)#$Xa$@67TLEKpP3^ce)4$k?)JKegt@DFfg4gdHcCc5BB_DK=HMA9EU?d>(K4_Xz?t_UiPbJPv)|MvxDXvH`lUj6lBB!{c6 z5x`kvnyj6~+Qf87@PsT_>!(;rw=2ryh0jNj)+xwbC5XSj_nq?n38v)e=(sHRNKzP+ zPQvrE!t>`AQBVie?z7f@yn(-ydbEieK-wm6b)#riN2bN*X6(sE1+Ak!_1#yi!gu}9 z7EmxxYtK2euP-0nj46P~wOVmd*#fsTX>&iC(n_m8&wN*##u;IXn)~?Fi8dkI@1WW~ z$&!`(#p*@WccXW+>|m3}=`Q>;IKNY1H!unLSi?Xp#!=ILltP2^%xBzHUMKjlI5=^t z-mthkzIGl4Rw{_z%p3w6F7(V1Meow(id&Sd%tDTSiWo_$+#ZWf_chTUO*(0#a~M64 zZ%#=(Qs2=N84r}Jv(*zX8h!rSi<%fCHao9vCgnD?lwp&6e6#v{)Ue2t)lvDMQp%OR zJ{os)mQzY64*lQ!EPcbNYk%v$g4WG=a_H)$1r+`2YvBD$0X^>ndLISePY+Nxxy8t- zMwMatW8-OY12sL5h`iOUiQW01SJ)-)5oTm6B5J@~*K2Mh{G&U)%f3BeS|6TaUOsKr z@SK|iqdgIN=}j@jm_B2raTxGMp3}MAL4eu$+q7Ll=dj5%z4B603)fiTxvJ)n)=-~( z$_)-g4cl?M0Q8zf-t3b1)x%f*s%2%*CnuI4$&3QJTNe){|v8v_&&DIx$7y}SRNL#mvcCb(syxOA8kx4p-B1?imMIeIZ|h~eRtmB&@_ z*Wp74@cGlf>+`cHwA%Hme{>NkBSASD4H_`;0o>aK`DP)*UlMQ9=1;wgdh;U2pf~J6 zTs--Unu!afl=jz4bs}*5y14A(!(;HW>gEiXR_4gtLwglCOtS)dx0V)2-H9DUG#^+N zmRl94%cS<~Wz*^=M0~?bVEVb_$2>quwMiY;V{=VALOt>Ojf1i)`HxG;GeY`FY%Je) zWxEWK)EhqXdF?aXU!qS~a#5^Ly7^^F{%?+Kw_=mu7&}IXH7IF?osMl1n?nr@j|qw` zzoenbc24ZDdPDf#un=wKyiL1pb;;kX&zjp=F04JaxcFhRdSY1RfqGooZ5{H;%Fw=rE{zAhrNC5eLHrF&9QOTB1iSa_`*#d@jyqmdW%BAlmc|P z8NnVkbAgMhM9R)~-Pa78+a4y;O5Y+9)lar?M@Kl)x3mRks75pPn?08>a&6o3+x?Yc z@|%fkRx2O;FbP8GC2eCF`bi(Iw33acp3^A4Ue2nT&#v$wj^aTqwLLF;roi31@k&?b zt=&Qk#4o)Q(0uUx%~h3qu_o37FV5hF-wL{6dtHzBJRKKRSQyoQwwjILDWRP8t?Z{I0tJJ^ z-z0legwJaRSc**x95&d^6!J?|oJ`KzdT+F8RC}(p*h)TLU&epBlPPq(F`^n-C?(dG z>~KAaIdFrMxIg^Mt2xJG#~Twea#Uh`gGtm@=|rS&N93}eupen?DRlWzEZB*5)O}P_ z8%nmxdZoU^9oF(Fuk*UKdrwcxX_tgE<`m>n-#llYrr%~sKg&-9ney{#alfIhX}wnS z85a`rC`iR4+=n_LX=7AGYwdJF;=;V{&{)y&kb-lHLOLOM5%JV}=X2lUhQsow zxX_%}ag4jV3F76B*t#g2p;C510~e*O zYJEP6N!+8hZyW5v`Oo@u)D!bNtSv71ldOflqVR2DWS){6697Li@E1n*s=2%t;uFb+ zK(9Wk%A9vU`(wPIEJx6&pp5I{8pVZ-E=+o%eZLh|mS4=?W+}_dYMTT?Zb@8jNg@@F zgALQJ4}lw7>h~&kfpo3iAe5AM0S#J0VU!*_Ziq%fslRb=y|BlY<3=g!yMK8%RE zo?0c(`^*cbX70Pa@7gO|A9z<>WLJMiCx2xlKKiMvuSyCXS6h$Xb82QSYNiVAyTyE0 z>UpjUT$ash4!t`&Cm>=Uf-ZQX=uBPhFxSE(c|5c9{Rt^QLkKTT?6b-*W94IlDW3(8 zjYM5~f7;a!N*9KHwV+Dbb19vxZ0&q*M) z`ny7%8~hN-oh8E6^o)~u;NVP}%*Z^N_N8d<5M8;I$E=uJd|=A*#0atz&M7Jv>qTh3hfxxO0T_AF)AsS1Nv=76n<<(snblC z(4M$`!_PC78R8yC{p?J7!hCj?o}Sar=fSL0q47fT55?bvFp%7V!_%OwOmyhPQGUdxl9i~N z%u{7!AgH~po^Z~;Q$2ep$0qh`?XhB(TjEyH4WDo3nJy2*>#dC*d{$JYzmG&;7LLo6L`GSk8)RjDnXdVeqmpgNqsrKQJ*B&pvkiC0+ zx=Us!;{%z%a3I5NlJ{Hh?da}4r_y^)b@%ysy*oZswlC@wKXeBd6g74_UM&|03%&Ba z^PE{nOZHvt0Qn$Q)x!grD?i<5ubY0klCLzA=SL2 zjW9Q%UA}h`8tc91$Obw-x!vt|J6h^JYnR&-lhS!2(%T3JdT%qfcrPD6b-}dcmT|c) zZvAMNswkQ$H{rL@Si#ueGO;+lhCI8T_iy6R!(p4ZO$tp%c-sOM8{+mUEUkr0y}CR)3No|-k2{yx z1liGf4W{QL(a)$pJMY`7G~k9UR($;^q@PFACagpqob&9}n|IGjTgjUUHR_^T_jw}N z^-kuyCZZxudodkLcSfGS)=a*vnmKqz@1?Tkk1+Gx_{? zV;J&9ZdCDcKa8h+rWIE?**+qhN`EpN;*;!@EJRVLV8Cq3!6(*#U}gQX%Orn5`DLBT z3vw>=fV%vLJ47pxfxipY#zcxC*+mcX1bc!N3LE#AyK5Df%*oDwp7T>cqU1a)=GHaO z=AQ|w?RI@Gc_=lMlS=Lo9t}=8IO-49iaA&w%$^;kNIW(>K}mgzJBhmVVxNc6yWxwk zSH_6T%3;Gw{6RfxK8iT*`s%hw+fM41XEm*?O`^ED+;b&jXyJKt^g6Mdrq=BTF>fw{ z9^~!!2dYc?6c3>YyCECiZ$tnO8v4;El%R8ZEyxbRyA;-Iu2Os z;A^)2CnIy)lpW1i(5cjo*4>2GfrLL135E_nj_%-3)t)WK6T=3cy>~}3d5!NMaJFd6 zqNbA8@_ShQ%)4#tc|8_PQR-*E?@`T`!OnYv&6bn9QZXLBlfEk8OX;~!=|=(64%#J3 zVHTBPmJHe9X3Wr{ZhVLejT~yGqvDMz7fK7zhbY@xLlR&NV%ARX6OImR)pEYfOXRrU zYSa%SrVbS&guapeWao6Zs-zZ|OeQDz&TO1TNt~L0s+u58@Zm)4$4O3yziYPPM4~UQ zP3@bug=K3O;zJhdN#VK4OGf^rRD|6uH%<_b(I0j*uYzbJ&8_@6DO@TL4+In_;)K?$ z*0`h;i4d&)Ody2932GW4=w^4kE|0ztZ994=>8kSW(}G9iHpOwG$(*pm%@xUV&Ro^q zbV3%vxI^FfORPnMX62M_+M#8TCiSEoI>MNw^o>E~q9 z>gml~vYG~O3=_Iu1b;)=>HPa-Uz*J`9|5f{orV0n<|^ zsGl8+Qp)S{(499BBsUQrr+D0N3)!Sa!xuJFxG*1nmCtleJ>)t=n|9u%^&AkHans}U z2e;A2Nu;Q98;ok3)=tb|hmYSvwCNB08X>3hg@AWeBg;I;f z67mEx>BN&r3{%K-aZgN&mJBJ{%2;g8Z7d|dv*pqir!lC1bg*|;#?|93sGL-PU!UBih5eZF_V}!SEvZmuZ`XnKbLBzAplv^9{qp`HifIsy4QJu>OpIKE%t<&jzW@u z31tqf!pOYh3HQZX{FW;CwSje4L0q zl*=lJE5)Lh*{Z7=&_kUp7V%7~8kL16ogK!69b7_Z^lIO05R z??}$+03yWUKhhq}r*3k9mErNqU+RSf_skeTz*TMv{vu|77~evKXoy)Nm3pZ1WfsEy z(?Q0JvleT_ROf!)dzn26Zh0LDY6%x0CHw65!xxcriljPzw?pXR>WqCQihV4WWkmZ# z5o`IY6b)NQac%f0xpSoYenPy9XKEi=@S0Qt-lV9_ts%=F7SivZ&%M zk2=^Q5mOVLP?Hc-sRF4XwtU&X`@TL?_OfY)?1$EMXu7^_YkB|TO!D{VpxNGN^oMi1Ofeu}k~2U(u6PL_3;H11$U z)7=J_hOO6_j8%{(MV zmt7lTRdf|?7X8>*D9h50*$00W5)iE@~LkzTU=0sLDz8ARF98KV6WAG&Fh)>g#irwJz5)`3#h z3~Fdug=<+ECaEymPT3$48>Z3J$R+5|sYR84z>Y;gkiFOmvk( z`kJ`7`lM3b!Q~1iZk_FY4OYzjPEDsnK2^YGTpkFtKvNl1vT6>|==40b~EiT%d8 zowM@eV);O`;=w}uQgz`DCFss6Wwgy_d&Yne>!%<5o!7%kz+6nr@oB*B*Sqn{tmj8o zw`@`lzf*EI&Cl!S_0oXc zx4$X#(2uBk{(MRHV)-AdB99FNpruRP0P;jTKzP=;F!u(imTx4V@mGHtgtV>d+$II2 zBPj8eEZBc1u2QK7-*&O5RMbs+?=-Znzj)AKZ_E;~c_U!O&97gD!Pl~~&LS@YrP8lOQS5=9|CQi-11?pvQf>6{SG?W zn?RStha%1s=!Z5FoI7Lrt%u%AzWprf(Mj@kS+5UdE9HWzl=>MJ)r0D@wb3CB{jaF8 z{)SVIpjC{TnXoA)O4vv`m#Mf3ygO9$-Tl?@jU*jqBXrn2bja+5VgVl5C2C)nsgO?v zOi1a=Aoggj(Nni|%U^G9ERQapT38(k2KR@}_J&g`M2I9vFb9^(C_35Hf3r189MVdAs7eKV%J-){?VHf_TUY>2~US0>92b zn~MefcFd)1>fQcml6mO7P=#62yZ({z2)KhhVf{15?UzNCRal@6n${VKX|jhufXiV1 z;y~p~uNij+8Q;2;&Y(R1U8gc%puefaG3Wl*Dp|*fC7%HAbIQ4@=ZmSmfaI7~^L5Re zv4GQKy_*S;^TA=iQ`*I$a{D-Ds&MJejBI!uvahjvdS3Ow#cD=A!(j2hIRzvF;cYFK zSgnREQVQ+lzPPTp4}C86uNsX~c*W)mtYXJ9xe1CfgOoCn+l2#y^;ldU#QCF36ao-% z!;DT|a?ko_IH`Wb+5jAq|45%e+`mP%_8u5A$^)SJe^#X@9e)%jG)_=0R)l-gYwa(i zbggbWO>cUgqms)k&zOH1(zTVX$4nO!ie2>|(#W7Zq%(CR&81-l!L3(G!y}9K_P#XA z=oEz~^&q8u7-rr~LP^B`qG|C(lb?XzkwIaxR@`*7y$Rgn!EF51_=i!8h2Pdz4{Kt= zBR!pC9vuyGjOr*<9>R>KP%v`%TpX}H z{Qz^o=gS2;;esihs=eR#O33DoQ_zuw*IV z6ksLh+CB1#|6Xj#>$-DgnxZA(cM+{=c~#Vgp^wdVFX)?`V$gHB z?lY5-gkVd1G%_gtWymnji{Z`3@(pX`d5PyTHi%}tic#_puEmb{fIsC$E8d@~#cFP= zJV|2_He^(sgkP?D0Rs2`SA1{)&iwzaJ;=c_U#6-35!sm0_GpaP%&kt)>DV&z)(^wc z=`nUL4h>v@pnspQD?H=ACN3!Ou+t9Z{NP6sc)JH$Y!iherHB(6z4-%A+JPa)(Cw{% zuklq^3V!`XNO94JhAj!aXK9;_QIzG(`u!Q9$Lf5?^CbL&y_y@Na8jZ2kepbhWz?*wx7iu(jo%J@5fLPc zi^^^P3Us-v8k#*T9m6vss8`PYGh*fVaYfpgYAbxMqeZyr@H!@a#b%bDmf-s9=tBqi z01mU*8AX>_;e1Z>`zz|xe<=Gf;+=#Xobgx8Z!@OS?8mXs} z>ubM72Km^?sQD;-giy0SHEQ*11a6$X`WgFdQT2GPYc$#-AFph@IsdsCVlu z&-u3#U0Ql7GlFIfIA11ZzV!VLt_rJ_;X&phx%^n~#Q$GQ&PLs_!v8%pNT7OU|EU}b z<`K(^1?xHzWf`f0QQL-j3`;ZYl}Wp&!GP;n+-H`%CdWBp+A@^Ja<79QTb~^nCkAeM zKI74s4QuM)m+EYNVHv^9hVJkSnv(O1It*84uE{e7CTcteB%}>8exlaDQH{FD!6TGY zo)%)kH)4^prp2g+;n=0q(pnkhIkk2f-Xzo&NbrGguWv1W=~`0pM~9%Ms*ZV6m? z(sK4AnsKOr(X&X+CZ?JC>zH^xU)sv&q@LRo%;i=v1x!)%QvK|vnvz6*lem8` zhy8KJ+VNwY%rpvu4=bpaxUTQzpuFC%>SQ?6)Q)P}zg+9*ix{kDxVLN4864V|YZ(Y{-n9 zmN^!UA#TACzp3#uYa05G1LJMb5i? z9&&-*i#yS^!8Elq8+@EbJ{v76`{g&{^&vs&knm`$7O(~BdxOlj%Jt;o7gG>L*KUHH z&tvjB_r*hdM}Nq!8k822loqyP))@ERYfgpGmmp8>2f4!h74q$EL(CLf{cST{qg382 zM}t+qf@9EbC~=8-s1(HXN-~pkv!Uf_85Dx-Qo`KEyxi{vxm7qt`K*yg(Iye5zr&}g z`NzLvUB;rP7G30ExA^E^_AxGACBB=x`%p1d`e@Wb}+EK%)g7dxr6!Pi_1Qs+y ze@L)dP+0^TW1M!fw0e{VYa19Utr`D4GHOGv!)S=r4>|;sQ;ub8V3JWMtFDMmIYPx8 z!dP^%e~9}{$#Qtkbdcs&GGDUBj}Gc80eu$(RQ--U(Ip;`0BY4^IOwBC9q{>qtcTsr z8K-60_{6otiv9$L>#5W3so-WM{-)1~Itor<;wq)Jk~8P{c(Ad@KN@&EcE^MGdSCpY zY^6Q)fz`Wu^|9ocZyOH3$lf~<-o24J`3aeN>u_}(Yp^%8gUe%jyIAOj3sfJ6>ZFWUg)27on6abGZPB?ew)dCyTNYpn0%UTD`l zFLz|k$9P`%DPD`4SrQftuL$2sOK$(3UD${ClyA3m6P{q?T(}N|7^VLdo~gf(vfLT4 zvSjpklic|m&dP#HN!ey&>%aDRSzENQ*qgR^C!fciA4)IdcrA}ckwel+^AM-_iU{a( z#Mpw<6G~Il-*B;tSu}smn|;0{nr+aXy(}CU9K=q<;oaPvF)x4?P>0IcKFWqUP>^DC zuu*?B`>@s%0m>QZ99#t1Drl_{%`S58&(6$G@P?%Hg;!A!UQad5k7}CpDMzmC;(qr` z@EoN%Qp_iGmE=2$#HbjK4s!FF5`Ms0UCFq;&hBaSl5YC}ey}y(V*ZI;V-Ze1tVXE} zCRYKID`4W*pqCSvN#GBIw5;)2SJ|TxsPFr0xr}sy*S$*;LnVz+gBGba0e-Dw9?Q%J ztv;EVKHKsFsi7`>TGMm0+0PE7v$Xq#%{@J^u&}n~WsjD2p);hazBWpbwaztCr{^^- z7Mq!KybKkHXt$g7B`E#rHQQe+%)k9%g^%_@OgE?E?z2IPLg!ynr!sJFR^FmNJVb>* zr-~k4-;J-do&H3qM_J*rQ_Jxgzc%4GjoWlO;rChtTwJX#Q;9(&_iL|XbDqayzU2ch zh#aI~T@n!AZ|Rx}`FXX`6?g@UJSuY(I(y*_xU!YycL!6Tk#x*sY{;GP?EdCb7nZ}4 z;hRK#_&E|OIinv&sADw`qOQI6k7eV!g+|i$lYdyIe;9h8H2oVT0dT6qVa&}@z)D#v zTd=(r)JaT<9;7LGnk#r&o>&LA&*hgh*&cr~DzQN4|k z+pPOX>Gu|Mox{m7KS6i>_Y-0E&n{_!xZH^Tuuy-l(10-ds}ie$0B)^l*Y9qcv)t}O zX`E4lXL->H?+8n*|CUT?h3@6gvToOBtL?U_RY7N~t!dt^{T0h(J7cTE)2jhS$GzV6lNn|Dx-w zMLAYc#eqh*ZDN;Q0?%yM?E6CnDuhuG^z1ES?S6+w3FB;d%Eaq8tW!yS)J->81L9QN(gl)a6R63B=l8T!)*c0PPwjei zz>Lxg4y71e5cXxKSP36TZ~Gv!hMuVT2vR%S9fyBvdm+Ie6^+n2nEha7azOVIBpmyR zucdce=>34K<2eP@bkyoo+u=6C9pxik>qc_eP~bZ8TutDflbh^yj`(hMZ>VDy_h~V4 zsM9t1qSrC^oO1gK`{v03P_8~lssaZ8rkftCnW8(Y-k(&x8z=~oDWG1UaP3z6BS{#M z(X+=Iw9#w%i1>M=Il37EE%tG9KwTdAr#rgH**OLP%M)!i5m&x{F21m%?!nIoV8u=>kaLFdIq z&rU*`pAga(Y3n{!)OV*?ePJEa8XZ&{QAp$+e4U+4%puC`Y^hS~^wmry+26C--!p22 zU!9L%k5^oemz~qzQIY`zRKGF9?UBd5q1Dcqp2lc8jRV%il?3L5+2^WiqjFlRv39=4 z9fJ4$gJ-rmVQRj^fe{#KM64OP7$lU$0-`eVqj)1xmi`60^QVz$>80pzR>e7}gd}*x zhJyUH-txXD6%gYJCLvr`RYtwZA!@eC_kDUf+gwM*9;?+ZAX(>M>c60NH}xrFXF{%) z#h{2mb*`s8BBE@fhUfq0;I>I>t)Dx3Q6o z5X+=6hL$o!mdz(^vXATh4sh9eVM=H=YoM%6FZ`Ghw$Hedk*65yHzQv*X=&2r0;lo+; z_wQ5rpRs+KOm2@e_>i6*&UQ(6L}#p45Ci^-dTfqN&Z$x^d5ukUJczh&ZuL86iQdxX z(8IaT+x>PhGxB{ok55$qOT@f+%kPtzS5?Es*mj!nY)wqOrCi}-Eo;o${(0kj;X zq)ubnQUyyB3~gw`PwL4U5gSDtGiUYlS6PR`vQLb5h)b)2xT+M8Cb!}Z?+2cV_jf8Qhjn1%wX+u-MK!JiDmx08@zcw=K@ z?JPK+8Y_Nq70ZUg3f=9fr&qK2Wd#$oUY4!28tG`gxbhQ@JH%S1xG~c>ox)o<|1(kK zlaBN=&&^G%JxyWNz4wr(_YV&e1|m223rmZ;@e$1QEOHTc#X1hfp8|?)G{W|Qumt2_ zq(RdQ&38K`AI4Ot#}wo8{589*XzeWN!XtNU>VYqc?Eq_%@?_lsJiu%#F&{G1Q@R#e zcP=j!uA1rk%Hd5-esOPw^|v?G*tV|#a`Iye=*@@NIpoQ$x1Yb9VgH&5JI#$u&mA;H z5X&>5D|@0V<^QT)MOmcJ&1G>l@D04lbMSfCgs9bKB^RTNDK9B1?E-`%f}$tV-!0kNx`-%qAsv^?k;59x%f zcwtNcvb9{A$}h#|^Ow@nA>PA*xuXi^P+I-#A_RPFDlm2wld_sk#4~)YRDXH#GB&=& zBgg1=QIaf{U^pDz@_ImBvds?G%yPK&V*cXB>gsTt6zGehIv7EYC?A;L8E%WH8-%*)AuQ~KV#mGlIIknjAJY4DrgxaEe^ zPhuB2$4A0@eKxocoU4P<+F93f*LuK+f9J*^jRu_&~psqZF+{xqMLiiPyO-qbmu7UZ3Y*_gFR>u*Sx^ zFBLCU5rJNZ6ocMYcHI6dL(NMD0joIx?3BCg(LaQxsMduJsE4T2N&3=6k7~w#D@C3gw7-ISo~mfx?u_nBF;P3Qvmb z3ktCuzC9h?X$gJ~>mUTD9LE=Z-`-w5&7 z!g;bbMkvdV-XG?LfIp+OwWRs zqr@lJ_RmwlO)==D$*AY4Yk&DNV=GzSJ?FSgrcYKR`lf<0V$tKY6YWXw%qCEOBb|)k zUUyo5^`pn*bi&gaN$kBT-s%9sQ zC#jpOIG=;YPvAK_1wT73CrtgPIq16@gWC@|NTCnoX%l|;^X={9s{%)8Bp}DMYm1yR z)fJw-Nz#51xjP0EIe$pw>KKuSho^*TK8%aIknc+XqJ*?n zqXN7PuGGvxO+xywc^4g!qoEY#;eWpzZOq-n4hFL@>u1%R=J;{?eKx$eOH`%2=$(d8 zeHa^!9)y`5=V!F_!~M6OpT2nxb8M%tm(Lc=+VxKn zu|LR_~a+lCaIvyOkj(=7TvxeviV1S~SFfc&n zi*gVjU3UjbCF%V9w_5SU$(pU3Rpa79HUd`paanEah3g}NiUrVGq13i9sYHY)tbWX= z4Y0(a14x-ZWEfH6!o=qvkOau~Hw}8R^;?C_yy0Rw__#W;FFsiQjHB)2c9@;^NF_H8>CvxTW}=0VQl$}x(HaG zoxCLwtqZh&6!UhPQd<5DU6RzdDic$<;T zo}vEsW;Age@bM3;kBvfAM=vvy55!yEdc*m8H;&4C4Q8XyXQJIC18@eaDuWEN;U(^zL6zF)= zc&K8KgL7T55zODc==*RVH;o)KW$$nN(^u{^S_)Dj^QAJ4tj@>Rz4HP-+!tP*gI9NV zmkWuj%)Gu7TKy^i{``ETGoMD;$oLO4`!vxTt-_@y<`}w9r{#FptmfM;TDoZ}kDq?u zq$^y@rX!@UMu}SwZP8oQ?RiI*&qoP%#M*7m44!5s$8byUIHWZ#KK|Gu2 z==o|~t$M$F38W(24fS3r5-$>e*Sa2RC?HE|vd-wNu8Ex68>D$PuJW>j03?B2E2KA< zb3jUpdiQ7hk)F!ntN)70kDKDt?hdqydqw|kkF$l2LW7of zVKGENdtK4Y20V`3jmo6+RvYF#h)QTi#6k8}5~kFnARf>yJ}r|SS1;(z{Ndg+bH#Hk zzT}k&MMt%W=^ebO>1KFt%l2uCI;IEWQE$6y^L+yBigqXK4Gyb%`;b-5`0tRt{vWvW z%-BDxW9XwTXf5$nK0bwRQARaWW<4J@dU~HHBN8wZKTn!A@@19;iP;F^!(IQ-Ead;( zgEPM>@NXQ{KAvV-q07l2*ToB`nZO42a^_rArXJN(0s8&hr}|hrK!j@6FoI^F|9>dgZe5hEjJUIo1KNn z$4~2TY}@nLAU9b!AVKLcu~oXpI0>_(>A z5si_KFC5Z&LQ{0_r(ILc5F|=JXXK!=VSygmN4bM%2gU6F_H+ML3iG%79i%w0lB1+* z8;#OK@zik|`b-+M%Rl%KR1*6Ls<)g&vWA0C+b0nX0TEU?ry{Mu+SnuAi;q#QeMd8dO9jBH>&{|sIbYxKd7TFHiVHV#iy!%rwn$= z+r<(aQclDpwfc;rUQZno@D4I#UzK?tt1%LXo<&-bPF|H+1Rb9kvXo}__n@lZ)S5Q8 zsTz-1CE*jSjyzQX;XmL_5?$-%Yvw~W^R@6;pXjB_=xwCRBI9jsC0|A(;PP_ia1gg; z<;KUNr^KRRQIJ!Ja8Wz!&L{t!&B)PoASqwu;Bvos=`by$Wk zCt{ph+uBvv^q2nBPsmRCJl30k)_*DP&?3Jipp_mXgn+L+MU25A4S|B()IDNaQ3lwhFCkt${3f#>)0hh*Y_2!2}~h*+6LLF z-Za8z)?iyqe!Jx(wb?`mW8r-?xb3QEIXMV8%4aNT}eX;nU5ycmDGTy;Jtt z2d7`GiIi=oU7?NtlFeKF_e#E7lm#bO>a6H&@~_I2QlKSaGnPn6bV%T0jZgSIh=sR0 z#rCGeZx?E)eKhr`zt@SlNa?-!4wOCzy+_6ENOp>m)JAbjWqBakVI@_*L;5s`P9RDy z?AOTdSK~HaL6!njtzSl=+9Lj+z}9~rqKrL&S#wn9HK_Ie@Ar(`3V}(!E6;+{Samj0 z>?Axx-bGAU2=Io!(vLK*2l%Pg|WWW-F_^(FGD7r2wX^n}f%QZp`#1wjz)>NDZG3F0HL}gmo zd8=vsZiK;ps}+oor*aD=HQE7JD$6&N6ryK)lbR-E!e_H}IeS?K2&`6@=3PuoBV$;eY6}%CRXU! zQ|KV3GT5=jcgx4)p;F_)SK~HM-Q$!5eKf)D+VdqN#qE3y+{`HPJRMr+^o{wi^d(?x@_xhkWtwb zzXl#{E2FrJ0HYC;*Pw=bAh})j&!wjQJX0RkQ#3m`Y&&JlqPU=&Nur-bUeEqFru!qE zr_lZmLU1x%8)1A9l`0NcK?smmgn_x1nktn@!80?01jB+gRCX8ZP-i_cuu`Re;jdV?-Jihm2F@|e$j}+I#1H{mfzXb zcp~9(tq2JQK^lVGMG?B%OtR;YcDUHUp3AlF6y<-}ZG^ty=0!t+p?^IVemq*L$$I=N zPTFzT=d({!Ca(P2DiY3PBOn_yqxXy0?7k>+`?X6ZRdw>i0_1Mc1|FyU91cHh>F^J_ ztx#1E^LINj=%1>HroJYySBf(tD9WrsOD7}{kUdfj{ndI4%px0(Q=*+7v+x*62`PB! zSU&1TtcEdcU%ot=2^(-({I;IH#)++vlUiFx^DOeaY}v4R!!}wS z%7*Ju{mSEE(`4Y-I*$;!W!|-w!Iu?#?!|d}q%%A7;NsX_Gm55L3Xmt-o2SqvOKO$q zUlt;&a#`6TiQ2Qwt(7``4{fgS1?J-Yu`1r6rP{q_#?S{4-CTV-%f ziWB}j&i&7IHzwi$>$TaVy!%DN!X2~-@DcJsX`NV{VAUd# z`Uc5O#Vqm2!M)JP;e8Dqu$6u3z7exSn;u41heKhjU)sd}d{iG}bFNXx8BtrvK=< zzq9QFQ}%nufBY(lQ?;GLWjbbnuq2!{r^2XSWHQL%Gj*9z(A?5?e@D>CMc+wBBiwb! z2mWG`A7_}o5KgFlOv?7g{2k{(WZAE>8ZRD_WxAm?K26_iFE>PU2b!-k8@4~u+58Y) z_;4C;u}`jg0=^2Uv^e~YxF9rp_~rPx=)K-OE&luIAk^UtlSeo^dxW|&<^?5ogXO>u zemA0yD(b~e>>SHTL{<)gD43AWcuVRakOa}+Jv~w%s+wx=YCT~Q>u;N6tFN?MNHpvG z{dxMmoz0%OhGIpK3@WJdo|3eB3*n=Cs=rn#5hz60Rn0M<_@lru$nG;zP5$R$V=_hq zPK%4yw7P?*>s@Lr0&A>oJ0@-@t^81aPel3UZu&^+enahs_d+%50-Lg~y1l(a`t3dF zVPwM9=;F1+0eo$d4o1RCUlWT~8ljdXE34Hfjo0^~l`LL6Rm0o;gz+iP^Sa2`Gj!Iv zcENgCwegF|7{9S7==3$<^k|u0w=ki%dPjg;w=^Q7DN1!RKbRloRTEuv@38WvZEE!d zdUmb62Y%{u=Vwb_3_5me9PuZ4397q45j|T6QnyVukD+muWTb`(fL#;{F1|~l<;4Q& zXg?n?e+21jLt9(f9HhaE<>xcsr3Xnp`)Ge32VV3C?L}r;f-<`IJ%w5MDi&-?S|V2< zn+Ot>a88grPVU&IME!rZdX~lpw`2i8h<|&|vNxCzf`1Fpg`SQ4tf$L>Ci_}$h@CXd zw6OA-HNJ7v9{eqMjvtku1a2w#+}D0<<}g(Gv_9=s@%Q+Zfl@S;x9{10SB3sWnj65j3;ZN)Jbu) zXKGjtPx0=3pp(sGe3O{_Z=L7Ilfy<#l#|g6@+yDF?sJrs5&!+7aPzGUYogRFJWftS zF~c9gwR&+o@phGAHt8E{0njF*8uIJ^^wB7^hpF)La@sd&s~Ay$l4f?Dk$4r}IY}ad z@CgK{Ery-9i%p|qySEQM5B}D;{DOTxyV>t*%!sd1I1L;8e@XCsu!euRecY!x=##~i-6#RJ)4r+@9me#R<}A9x8Op}jN$3h z*zL0Xs}K(equsFP?<87Hb|`&Ksgf}`1uhAhg2BROp=EYaIFC0aJ>U}oz(-|Da%6e! zi?j^D1=cyHmtP`E{C&AEg30s0OJ^5Ro0b5lEyBN<24f&ED1z{5T4Bkt(SXcTn;6$h z)Fqo9*f!uh?L9J%PvVE1b!qNu2u7i>d1q>;QGesBAm4b^~t zJVuFq`F>?k^%?v;iZwR9!F~TXGJb6R;WKW-EHgyZk2irqo@^oTOMK?Q~JJ8 zw|it?J4Xd_ad_#Er%*Z!wO=ZT8&M+6FQAJ#>%WFNH|I2_tdDgKI$!_1EC_UZ(I`6i3Fnvmk z1Tnt?)}^QkMVX&{PC)^Ewgf@ImHFxMXd!k)_o3x%VolUcO-iKt715=NGg`gjDZPE# zbm51ngIs2n@ zLe%Bls1Cu;EX88LlGf31)P|QcdVFk2(x&uFBdi>!zRreZnYJ!9Tl|&gpSy*XF9?~O-ELeC7c#c zt?h*B46N+v*Z+L<5&~u}JvD`SLH100Y-8VKkSd_Ph`27ZxU!;kbGSN)RF{DQs(-8^ z9$1DEfAFgYt?;Kj|~ z+AP@K{Qk<;FwE7kDI}lsqgJ7PA)mFy2M)giXYV(>3f(bU-0!qLbtl2Qk$|VgJINQ> z=LUj?ZKj4{&uYE<{d1#1{>y)W0Y{2Om^8&Av2i_XgNSe;16VILpGnCqYGYycS#pm( zSDYId)P5WCE|O4}?j0;AzL%2oXoZFJA~H2mbLDE`8WQA{PxX=wsvX8o5fo_wmea6< z*7y}Fm1)65_&!1~4!4BnYiB_RmBftSBbL`hEe>0SD^G}2PB^gJdB84o0=L_r6fbH& zo7*zyr%7JP&b)a}9)%wqu6K}tkd*?`wVy{C=xa#*f}Cw^ouz#xe3+`>jaqz2!OHkH zGZ58hok9Ro_!`6xtq0ZGI$b93U-Hf}Dh|1`lPUS-@p+sfkdK1a@$XKupQ2>4WPpG| zET3j*S~UxDu!;i9N%hPBdYpiT2oeBtEsV%MdAeApP3oyH6l757uX?%2 z(%Y5l!Cw6G4nGX#Bm#XY<6qoxbM@&0kCPuZjz){2;#bfuujr%y(;#wmjf-!d6vH#L z7a;K{d}fOVub5FeLQGigtNDq>d3a7wjEkTI~@N)45GSO*Oj zP7AC?-v+F|*H&bNy#{Q=?d`@ja~ZL8EwejjgQ25Y*|NLVRXqxuLq3X6J{9f1OsCMD zL^$5uFt(h@M#BlsgcUP$GDXkM*Y))*H?~GAK6f-&_uLoNzYEc}>}8Rkz5jsxRjo1y zuPBwMD)mi~1#vJ+FzNz>L+1cPkSFQh_=lHYUt#3}Z5ELqs(#lp2Tc~IF#UzB#ayw{N{Ks?g+gFLl8O@5* ztNw;R-` z{N2XbHcU0fU!x(W7gXhMCcypv9ccv`896=&lP~%xNNJ0J6EOxA%eI+;L5oj=l`=0O zR*O-#Ea~#E6~2Lt^bpIK!T1sD-qxZ+VhV}RCEGp<|Lf;6~zw^S1 zgZqLf&fVAV;6$7t)VUY597XbZkimUdCa5Kd3Yp{bORy-3G&VYDsQEUxIhFN`*<)Pa zOR{LcI$dHl+=6nI;xiyL+XdHAXcxL5kA>)9fhyGq0v(1xj$dYFaPx=r>G|F*za6jv zz&C(lWC2o5X=3Q&cG%u=fC=Z^IenJFd!}_6ADwgw%H-bBL6pdej9cb915BtW*V)%w zIkL+ao#^I#hto(pbq7J#pn1bIl}LnKpDT9c;8}dehWnD=DRlpYVipx+Bb<%~5y$ns z8NJyv?qPGC*`gY6L80>2wX0`;dpghNUfzcRO<<)FcIgFx!}>4BDtDHf)zyZWUhBEx zgJokS4?Sk+sO0d<3C3S;1+S`zZ*4>sf4&jdGqJiWt5|#MEsn)8iQ7Nk_?VT`fA%#% zWS+_LtmDe>Z3es_>4yZu%oSBC+Mdy}=yhF|l$9!kre0G~->yFrlKlLGoKZDtU(q;k zEy@b}nehT-Cu1j@cS%?tB&7Dc2;i^XRjt_C7vi^OwRKEy)u!%Z@!`hOA}6lc^45QU zgEBbwwi36_AaZt#kJM;m3A(_$&O@eWO`2|Hjl-cE>rUPaGN(Isa`^i!bx9ww>GlwA zbV44$C@nm8QWPJ^J{FUt8ke9t$uw^jDZ|hwJJ2sPAoB$!?rT2rTQzU;x4}?&e9~zV zLKi8b%;!na8xe)x7%-zu1*CiS(|Flqwx&G^_3CY=Yq#3=z3Grgcaj|-1b;?iBW*AR zmL7ubE*h|*Nb0->vqfd)FdBFdFpx4ymUw2+dREc|mC64-!rB@76)d~}T}FU-i_%`E zW{H(v^{I(G*4On%JdeB#Tdeke#u+Dj%=$WX@? z{>fox<8dB^c&5m$wICa<(o9;_V+sRkIf;W7Z^Us@%=FYcySWYCo8kl&->#|~*!`D2 zEZBHMlh@3Jonr69hDr6y`**;uxNAp!{p7j|WBV?*ExvS=kudG1YAMP#iqh$kD}$dj z7H$&FeC>zZMowDOzPpv0CaOv@W<{gc@v6fXo}XT}*c)etVfnYBk_A1Jl6JQbIu%3ZP`NhB8 zfJ}?egIpVyi$+AcFRuL^VmMIjqR^pEm9bEQ7_J6L4tfuLq-(-7$KSiIP&9tb>^M}9o(U&3!bD7c^z1vC1uKR;4w!NH zv}x>_6#u9tH~bI@*M4SZn$p%PtOnYAFj-?hkE2bQqq^Q>2(0OD+vW1Nm(|Ox!GzZy zS6-A8>yues*4B8{BIYI-G8IA0epvNO41cC__H1EQ(%#Rnvvxm!c%Bj=ZBYBS)E;3? zpc{Y#BK9Nv18M);1fS5^&4lKD($HBeI5Bd-h7Q8}M@wWDLYrl+XbcBQ0P*+xC8Zt$ zD0GdI941|&z`PQ8Spl>vIYRH1FnS=)i#-QBjA!3^#(g@XD1TOC6W!5vot5KDc&UDx z#nUtV>ib1nHPg58qIJ~lBQN{Ihsm7RpHE)*E%(Qz!m@sLcn=t{N?P^{#sNFQyMB-B^>z_3)oKr6Yb^rfAy z?yobjQGDb2cYOS&C*KJ}Q5Vp87su-oX5r>EP>_yhq^FwcGCs71Urud<4iIPG37|Gy zY_lK)oXkB(4!}nKx9xR{%MtP49SS9N1Uu|#qNU)RuL=fcD#CEz@W6f*yxp7lotv7-jb`n(v_Ed;2hiqqVAs?M$nfRkWf1Ln0UvgXV4gwHfgs8U;1+Ri_ zDa+@SKpM2}(d#+z&L%XpRhFj{bu%(|V^AKEjiTM_XJz#HE4vf{_rYKJr-3wbMNVNJ zKNL00LQe7OtnpBIppmto-%!Yvz}{$mV^S#SzD9hy2C}eTMsGkQ4S!sYVrdd}r_p>r zF0;JsmP>D_cjs?seW0c}DX2|>0`dk?JwDn=dfT*F<5 z>o$Dc*Zk;UL3_jbIwd58~X!Wx+IWpy#IJG=WFjJ0`676McjuqGo1(}I1z`5f3_P5_1YKdG9u%Q8j2#yW84RQ_}GY4tB<)~jOKIM@W;PQ~W zjx!?Qjs-bj1R#GUI5uRug8J`11T8SH7~p&AfMW;Xeo=mu6fX)egLvb9Fqy6$dl^m4 z;_hA-6lt)h(uWjwn$UEbHuzUzp%xg&P_?KTYzdj5WV|R4cJ1du=(RV zl(_oM54<2e16smtq79iXyDsq^eI|K zlPt7&N}|wE6`}LII59)J{CV+vA@wGcDCbSVp3hZMgRktexr|5R2N~JM?66q|qlA;d zAq{laC@oC}>lm{W(6I2iaI&LS__blWH2fOC^)m-ziLARm69||w5h4BAj(`*X5vWx4 zHF3zpJ+?cIPnlWlSyGY8E%@fgU%L1Pp>tFk$nprm1<1Eii*9`H^-o(|hH{ZBYBs;`i7R2Fi6RTmigI?>IwvW8`($agu29)5bQ+5arGFYmYI2 z(|9Q!Msd4haQ^$2$Q*fUtAbTYM?by&QGpl>0H+_ooS{t#Lh8%>h<`jJt7`V7gp7?} z_MMRyxgtVm4t_c*u%*qwR`OckncDxbJ+G6Z-$-{fy=u3tcEqD*f)Wb={cW@@H`rm3 z1o|L-kd7c2ck(si*Dd>kXAlhdTlqHi<35oIB>Pg?+Fij@?!2S3xLETd# zz}K_;$?}*LSQk8MFDp<^2{ok#(k5kLo$;Gs8 zo!j!zaP5(TCnV&6kJsrXx_H1Z2r8r#TM`vOMl?RY_>)t$j#BXP^N##ri>hxJar>s> zHtnB>GyGXq7I9ORh2P4y?@qn|r!#rLd)XZkbs)TxH)d>J@rO6IZu?b;(q$w!Uj_W` zrpXfLqUlRpO|Ef8RLAgltNpUTIT7F5zrJjfzT|{r2WDby<_MVlp5T?NZ}47?itLMS z+mg=v=iP*iToidf=rjw=ua9J?2pgqenb>VLrIgkP!YxOp3Pz>^f$oehT&P+)rx%0& zp3=}eB2>wni{#j39{+X5pn_uj5&kfDD(2KZLH^0}eB%#zfqknClAN*FlAk(uK2FgH z;1mI()N1akXbC1BSevFV)IP&UoeL52@0!(!@0-<%B`Vb{^r87PV)8Em^}Gj;DhBfS zVSakl@N+mID}evtJ>2hq=1ET!P}i9gAk|5LKI5yS;~94vVJ_bX7^`%f8KQxPcuB{hb{P|T^=1$o8T-Y!E7>22XcV%B_ zh}uR5#S0I_v@dy|9jYi?p%@iIuv_1?JoP+Rcrqi%@@wQ7tAnc5&;phvT1YD!dJv`z zAwAzKv;Kpk>WJcp@&VHzKE?3+$pxf^nwR!CE;4|piLV2|D$u?DYh0K10R_PO1*OIv zjvUL>`QZjZdb%tg%FZ_W+j>?)$1_S!$!9Ckvi)u`clBsgPR|bWZyd&$#%bNV%X)3c zhQ(Fu`bl}ql8&t}D+bOq-uhzE8SZ0M?4O6zZ3iT#Q@q+=+(&ELr$KHP@YN$U3dOzjA*WnYwrF|SK$Z$ zBib$)y8c)eMBjpo?uT$TJio1i^2Uvskg25*@l91r=vfs$Q_rJi(?p=Up8-=O_@1QF zVX9#ZR6od|BLbM!AH#G~Z?tK-Au$}tg70bKdrYftPoBHG3u4FCQ6t1zia`Wl!uwtv z*P&EAQ87{Z1o55w>Z=tm+w{r2h+$l*_VOiG1vrraW&oN&`h8n8NNjYID}u4WfY^~e zqUjvD3BjYJQWK3BDfA(lr~!U=2Zd%#q{Ilym}lxgJrZoHzm_ls%xCSYfH^ zbS%BQ{pi=j;)Y^WPDh@lGLDCP3o@meK(RKW@z&pBAWerTAUA|ZUeTyxM9hQNW34aS z#Vt&JG;OECf`(nA6E^};Ht2>ADrsvRJ}M)<#GNhHNlpyl83k-1?a;C3aOC7Tcyiwh zXMlvo*2+drucdq1LBl>7j6rNI0(E~o=rZBg@J8DZp_pBQFS!&@f4jpJyl0CuzOYvK zV%xI)Pmg0{p|#%f?pN*RDxh`g+l{o}cqEoJhd=@TC_LmMQ+y(p+kgWvAjz5)I3<{r zJm^l|iQrk)n55yEe4Lp4Q{f?aMc(IGB9L?Jx@D!Y+0+g@+kRG3`0zpJUPi%!6eviN;?6 zpk9yl5%(u1rZP!Q>cy+piCvo_C!ofR2!1afGrXxLuJvm!X!;2!A*Ssocx2g_CXbn8 zZ@O+ta>O^TcD(IYd?ArN%UNx4rO8KG#M9 z3|F2IKbg}buR=O~8>Jt9u!Z9KF-XTNKIkFe^|b$vwyWqrpz^u980?r`-o5?RlKPp4 zGP>4>zgywO+;iuSwA6gPfReO^9(N3Y{n}q_ z*NtqDq-q%14|}$;UD}$?+0x}b)j@Gx>ah{2-S3nZHn1S!NyXS1;smbcOI|w)8zpv2 zGQH&k6o1^!PP#lu!dv|NLnSwksX}o9o%;>bOTZDBp**4;TiBH>Vh@3Ca?%E> zD-FORL@1ahr$egsrbE=>fg#-UU>-OU8b=9y?Uj{%fU_?R{_(FeC=fwKJ?~J8A7r62fK^ zf|H27_bppKTF#ItkqbR|n@%3LfKgxTvE};bZ~vK#8;YUrf=4}sNkU&yo!=)kxD{KC z->3QDAX*U;Vi{xJ?kKqb%@m1)s=~9a+~n@vUh+YgSg-iQl?;Q4p>8WwA1i(^NDeaM zVh66a!N+kR;EXlKmSBTOSf;RahW6vHwLl-E)Zm*)bz7^2qe^|`Vo!a4Nc^ihdR;fn z%R)6eqTIc?yFC>v`>`D`qN%mz98i#qSegK_*{sECsTMN?w~YtZ4PQI{<~@n^+U?H$ zi%*HdUoD&;PUT7kl&%O{qNv%!;Vmg6XWwfl#IYxsU1dP z;Sjf%!PA_bI2-8}DgRDHzJga)<9`;YCPD z{tE2z8g3rhE(xO&ddwM$(54g;EnN_|kUhSWOY5!S;vc=;A4Hk?aVG+g1rAPly?%f2 z;D&cEO?%@P?5P{=EBM!m9r>D`-D({GH9CO01(^aQT*!z@$bdpjlMOFm=5(*`G(`z}5-)=6@A_bxNlHWc<+q5Ys(vs97ABXZs!){44f2ya|W?AOgr&Yt#f3 zPId=1rv^IHDRY@iWI)`vSYwglfbr(i2b~!h6d*UwFY)|^NZ&+d>4)D37v<8XhOQtz zpv^kvr)FV5Ay%C)f`wuC`inaXZNWDudoQ#u6w?i~7=*6z{TncloU4UYLgd1GQf81f$;k|#R$ zi#+qITnnBH+|wW#*Jo^4{DbQCNH3Gc(Flh#`oI#tZ)?7EONUg`sF5YJU3?uw0bUzA zzwCB_u6JW{ORF;s9qAQZtC?+-N|2;BiSSYkuL(xPR0lj8iN^RSk4=}x$xP}E?bg5V zJsN4YF=$*~Rj8!*C#88(VS^I!SrzY0$%pi3S!>EQ;1C-3#`rX=-)P}Iq^t}2)B z?KK(-A11(n4onu zlwquTAaeOurVK*z$FTpPxc}F{g?vSapaOUW9S%xXodcvDUX1^VUX-nqFIiwh@8mX% z)K+DB zfb|U=GRQ5_f)MyTVoTWCoy*QE%*dh0w+`)-xcyznD`3E4kFx|@O;|&&^6TTZN;*@9 zcm|IlS)t|$- zo}Pihw~S}Sc)b5(1p#WDy`oA4Tmt)+w!9lbWdnx$${BWFGOP!@Evb)?a09;N)z~rf zlri%RzvjV01D&0?0dwKIDdEfuXN<&%#e%Gjx8@B-+)p0R$nwVOE2k&llVcad}?br#`(m4wo*NB2+G3%L_OXs0y#Ca9% zHP0qCBRb{)0qe^N)|VQrFXJVFQfvktDuv1=Dh!>2NPdnOP8JLSHVkAGP7a*PmpHXc zB*yAH_PTqfv75Clf5z)*bIk58ssx15dsXqFV>;p(*khGiz#Gfr*N}j(W_OIU9#^hY z#$KmGfBZNy`;$eJcw8D!LI{}Q=HEbE%==C05k5H>etUa)jA$%7D99cJoZLv)hYw3) zh9(UYmZJuQN9y=qZ4WuL%jP8QzTFhQi;wR3R&+l|=jJd#el$0`?7%;EVO6?5CD^On zJ{1hiFqfismZz2jo~aI%lMm5t7JF98cNm9UHN&6^Avavxoqnj4W`_b%$gqb(!X@;n zT~P+j!de?S^K)!yATDI}If55jKLG-alsYtEn4T6(s%g<-D4IE_u$I&VCOYXT!Q z2)@5X0s@FxDGE=8FLq(a8>e7WKEJ!##-?EPIdwf%EUX@-c#~SB^lUj3cD1ppt&f1s z&<@{RPC{LN^ns6)|1tE+~_gi%`i15A2|N;8+x;N zDQmxlVSvWr`nzL{LOdFY`a?aBbu*iF!HW;|%=Y!{Teb#&o0?;jKRO2Voj%6}6rN7I zCab#Y-qP{?%&mJ|uWKi$6OZRK!~=X8J3WJ7Ha({#VdXTuy%r};qB;A>W?*RbVg*KibAo=S*hK6J zUT6b)SU{UFgP{V_N-u6ixTU`n+6vNa^=Hg$X>kZiK&>L}uK+;u8Ky3!_D1$C3q~w! z;1Y<{YZlDCV+aCf z$d%}k08Wk=?s<|AdX)Cf9Ejwdh&iyL%vnQ+`yi=Tx8()5N7(m4L+5R->IaKBJ%p+m zkCYHD*cALom*mrkmp~44{v`saQY8Vge2u%$%Ea%k?v*m_rUyaoE zrXPOJ+@(gM_AkIgr1q}oKeM$PyGUMPUf7ZFCM<+e+y7wjZbYv3?-Uf26w8B=#zO(E z6&^?Q3+in6jeOJ~1{B*KiT+jOprVrRb^XrsSB;dC(KO1$w|N1;rj8Y)A!9)`l>4Gg+)}L6%8D*6M?O@*o~M z4g6g0+?HLilU$mLZPY+=M|H@?+RjpmJ4Y;r809Y|=VGdrMx4Dy9J&sb5)CT1h))su0DWdA#alGq16gwck@V-Qunz=i?0CkCpCaHj&k z7Cj$_XiYr5HU=ZJ4IUPgUWP)@M=7pzp$<+CGnz17=eIl3+g&N_vee~kSYF|ziOW@3 z(j4KN;#G6oe&{9wuratkD4_$HF(>i#xUetRi*nsJ6w=a6G(mTM+!=Rosti-b*fxC+sZR%yNsmzf&g13Lkr`8OTW0T==I_{-5}CC8lso1E zSA?JbzR>oT=_NBAaqCGtj7mQY&$Nroe1VijiAcR?%a-@FQq)}8-`w|SE-6kg{P9nh z8y=mT6rI~+uA9HbBnO^&J%rVYD=;=K%|TQQb7rdXm{t?NDsS4AGTvDCSD=r` zgyy#sGCyhlS5ZFmlf3J&j#6HsTtsl}Rrq>m2lh`Ibi2F}3lHrMxTPT+?*qAGwEz}` z)Y)iu*&bu!L6ZfIF#)L7vPcCY6_X>A!sk+n4X@TdZwXY%=JO(G{F!VGyuX|g1M|G= z;0^5o*zkT2)%zYAfRL1cLUx3Kc<}~~IpC{209PJ_4oAY3o!c4tcP+ zuE_%hO~-1Yz;y@xgfQI%{JYZmZSP0x=Aa;U1h$wPU0a-nF9)#aMi{2vk9Kc)Cv#}7^i8bzGln4*W|sDgwgob``t&TmkM+#n1vD1T#NjDL zSm~u086Q#8ZBZ#IP&84W*#MttPGh8}!Y-C~_@)5Q7=F)aUhn9?B9|Gc>Gr3)!jx0) zYoiah4sj>NsJ73h1ihv??C(Rg_doM5gDYZavNy;;wp;y(M}ilZk-Obe=XU#PPI6ZC z==s2OtAm=wdWKB$LbF09BI{=0)_?U+IKnnv@I%R>M;94dfRL%z37>RDXUMFYWY)HC zZ244I0|qx3NR|mbm1>ude2=}Ucc?hQ5$+_SVWJyXB{nx)QBH{1>E!Uk;5Cy!s-#!= zX*+J0-_N`IAh&@B*FiYeAVSm|cyuH@>Z}zE(Vvj0>NfIgc_P)xG~cic9@m!+d3z@% zi#~;WT)_y0s+tIL@rAF^8L8o!{i&q`$->|GTxd6?mcykVg=e_De<-s(9Tr`YbeNy>9iadKQcZcB~pE93B=7J~kW!r+{in_*Vd~E)mq$9vUM43TbHt z_$`z%$NVMb)Lm*)_kKixctzvh@};4D(CHo#5CNp=et5uquz57oT>4vdzz2=xd~bN_ zex5Cm>Vo_-Cc|dPdUpB@9WHFbBln(C+6B7Ws)on_JgqtekGXX>vHsF>31ogG&kp6- z5^%@r$ISee-Nrs~J5Z4t%C9+jKm5f`70XY2#-gU<8EWSg(4zc!)k(Ze1W`K~*SO$~ zb(=^$uer&uyZdh3@^^P?!(|bVV3-E0kW!-vMQ5-Kxx6#x1D2E-W#yZ zR=#t%AhmO+vUYq{dPV7QK`y#J=_Tq_d3zl02RS9_hWa7}c*p?3CcdMXiZ1~V4D*^q zNoIyk@pGB3H%lGn|3h$rc@|+f9Xnx9ozitG^nN~29lnkH;#x6_y!QpCATH;rOL9e3 zQsPXMc`z~F9kSED5ov=0`nh=QzDwR%z3A|{YsBdiVhKV&@qG^4b%_1}h5!m=h6EWJ zFiT?c?zxo?KCo-lF-F#jt+rvKcTgC=YVvizj&b&<6U_6rsd+$qjvM6*q3UEQUcmD# zD|~W5Lr@EeFe-B-RWMo1P1I$JDOTKv-es$Ax8u*c_ve_lKU!n3jqcm2hpW#G?KdoB zXiccY7$F$6To0uuO|;k;u-p=pDNWCaF(TqS^{A2av8$_gc`3l#=-aOUr2rdUj3(u| zm`KyTusy9osV_qfi2IZnSXz=%9kYu~Rl}He)u(faS(x4j^AmKf zrNJMUkeab|=~i;-c16g8QJdvtC_M$PV|~3i7Wp#&uwo%mvPQ6Y)S97-3>5DP&9m^_PK+T2+!{f$VA>)Pbu^9fV^7L!)AQ$j;_7gWpI z*^XwN+`r8&D6IzVZerNtMqCrZzWW@rBx9wz+)dy&!QEET=i2cgyT3z+@^wbv8hecctKt1 z+m*=*t-)hR-Ge_wEJ&#~{PM=~IG6J1mu`&H-kK*k$zh@f+&(HqR-=|wqb|@|ImGHZ zWxd9;e?r(b@~DXMFN(oKl?=Qum&FKTWo*Mli5BEp);L8mEas5VZt%Fn+y#OGUP5JF zXwQ1|i(I#SI=&uL%g?_{&KOV2I%j!$LC~?8CKAV~^OqLqB`9Poq$?Oj?`YP9*N>KCcJtQcGz4A{O>TUa7%eM7;M_wD##N{u7 zGP)QlvD}F%d{MERRNTbqZ0uBogZkTf+sm3I>>C|p$O(7cIrp3tYNwV!kX^e4_twjg z)`7{}KDmr~w1xAu1yaDAbfNw)@Kx~qHMEzk&;1DuspT6*{zn>>hh>E)`>yB_y6Eq4 zOWRj_!`>?1m;#8{e7ag2$}spbMk5AwE8kiqumbq{vT)8;u$#LnHYqddxBrvcu0f}( z*4ZOl*MP*+H1fcf)3jRqmR}Gz*){pV#*`e`^ix{>H;l}x;a^w=6Cw^q!nz=uHtTzQ zv>92BTxhVTC?s*Xi!vaSw6nRkwlSc48dq3|$13Idkzt%oo5bV~+NDbF-#=(aE{vmk z+O77_6hLBpew=f^?Wb0cl!ebJMJKXP+vS_qI>iqMMTy5baaJF}rd{iRnJT+5@sM;` zBTd99&kEE`hLUr3=YAE1{^~FIgr;=rNx%%e$hG_&(H?NZ{IqMj0z8|LcOK1T^{u+= zB(r0Nhh2@V&4-`w7N(5iJYMd@jHDmDZoc>~*c6A;tzdW!DmW_lnK^rj(lifAQqjG; zs;DrxUXPg=8FU>IT@rrq_3NE7?Il?DyFBF_#mXMZ1Bvpwgv+Yn3yy6psPW5f^Xf%8 z?a5K!-1re}Smbi!p4iq0a-P4q0<)BSvv^#Kc*2X6!i&$jC-J!_m7HDf7?0t0SCn1X z_fVEySmUgDBbptF|Ifr&DJk?NJ^iJE&_NLl$ip} zK>4v{WOi2M(F~kVyx*wM53P5@7T2nmIEPEXg?}A4amFYIVv9VKnfVQG`VG;*382Q< z*pRT&3$KcQ6u z)HFC1uXbs6%5_?1D?&g$({C#*^jj-zu<~s5sV=bEYk@DJ!fQEe=(kuHcavM3II7Zr zPzFBJeZ+r-BiWmFBT(~h38{XjkVU9Zf`<>|0G%%=I1xz-tx?^!!BiLlnH0YX=*mf* zx?}T@Y{lt6GDsVpk}gxP9u!hF9w;^l$J)AcN)=Ks)YHxuaraQqFPCxa3iK=%bM{p; zjg|Jrv_i8q*6Lci4SXtEq^eq+;#xig)=0I-8Wd#V)C1V zt2t)6%AtgT=tLZkj<)ce4!@ovI^f0Pc(idkRAf5KlLZ0$G!^sVcjl_;{Tapm$)zmh z6(vblUnC4KUYPifq>*$UoK+%l&XzhfVGK5^Qq!;Gnlm&#<0`FUSc@F4yb0j|kKCalC{2TIg=oG{td0cKR2s9!`p*b7 zwgaJWi9L9=crMZoq`!%m9l5Ta4$PcR&C>`4%iE0KI3Mg~mNeERvS@Fy~U z*w0rjKVR*~@vjHIlF=(eYa@QVAhkt5D93ORyhOt1pymw|;g%C6CXnL&P$esRlAOvP z(6UrcX-^+%zL>hgOq9Q1>0kVzhtZzGKDjHCBw_gezdN%31sea)$C>&3^;#jQPkY2d zfdF6yrGAkW7@Q^%dM{3rT|&D=E^S1&bQ0F#ArzaP{%qrY@(1mVyyTTt*h^K$p7~?5 zdd;^#yqvOhov4|CW}h}{IKrLW(W?;qP^5Bd;Lhuc8VFFSkG}L@&T(NJH2kQLqfMwH z@V#T^o5<8V-pP88(kj=|cF)oRD_=RlnL))&d5Ip-ov^Gfu&FNGJ7d^$zEdT-(yD70 zbHR%E9&x28iaaFNOs``o>suc?YL50y5%t$a$@>lN+ck^1Zo2f`)G(t5H@oe1`Oz6= z7H`N30NP?e8^K0vRKkPO3f#_+!I{RLyI`*w$q8+pqKd!( zZ(Rp~GR{E@MW7aQ4~27njH!MMTH_Lx&TFv7pMHpiFw3FOfFSl?0_d16&-e%ag^!|R zX4n;B`s66j7w3w7i(}e7S21G0+cY8(tX0lX1L3^{=U(cNMhf~!uxQ~3N=#PbUpxE{ z&jqf{3F$&BHjVP7HgSEd2m$1N4VIh|;I?g#@Wo{9l8<>^2K`iU3{?;?@n;thf&Q7W2l`GXh4!CNKaLSc=H zy~T0S%d)^u1O-S=ekpw|r>60CFk2Lm@rFTKfL`aFN> z^(-D*IXMW;=_oC1|CqID;UUw5>+_7IEIRNDTJGWLIyGQGV@e?vpB|`T0GXg@*PvJ; zU=v?YA{+L1yz9NbZ^q`w-|gsk`hk`s5pgIPziHgEYaG@u*>dX`c4-y1Xj~%G`z$eq zF3d3P4jNJZS&rKHJ-c@Ix+x0oNj}t3M1eT+9YE;_#?p(UW#I+xFbNK()a(DwjpzXh zDF3wo`>tj+2F6YQ|ELEI3SWhh-c)50;{xH2fNC%j;82=}0MK^7w^&^W9>dS{@ba=Y z?9P<=<$!mcndX&P>e=v#zTxY;>9co>uCT~9Jcbw93@?6mPXG^?ioTfF-eiP6q{Lpk z+ol(1i#wr&`u9@{z}Ep&owEe>J1LOy4%cYQ@`=ZCszZB!g;GGkL4UliM(6HOQSngG-cZrpP`@{6q3XLspE{A&`h>@OZE6;dAvO#l z=obY?3a4_K5m66331Z{7m`@jRq~>ks8Skf0DtumL?j(B_n{LFdW5pavq&zm@m@QCb zUe%#M)XuoyUH;X+;?=sSo!K&PnH+r6KWdwr`sG#l@cBsdnN)p;6L)OY)N9JaH%VD? zF&<%GJdl(n?~boBch55RPSUIpPy#^71GtVuR(R?hh7Lb7+A!JJFdVyN zA*vwQsa7K65TviKskYePQB7P<8|?6h_;b=A=8SKiHegTSoUAb30X{UH|4$E1jUNGI zv?PQYEuYcCHCE0L$=Ru}`%Hqf08&U2ph5p&Z*Bc7`?%Y8x>jd&?On?+!NYe(+w0b^ zHa~>kOJfdx!W?*re&ZYUxlI{eObNXe^W691+^-6m&~s>nVn%gCs)1h=-rmP#R z9pZ6W;|#v9ja$FviB2}HyN!>QT#_|dGE+X1)jVQ@#`5WYO@u`Yw;-~`$_1<$o5OwU_CbclCy{F##w8ZovJ19QW z7@m0~mIl0~dwmojC&@==X?u>DR+rBiCt#7=s7EI#g-_WGXA2UNuV5`dE#?CmJ`wM? zv-5Z-N;#B>x6M{|%-UwnmRHQ?7B1CSyPnm+45{srkbQK2#aIPlDZB(4keZZ03V-EX z1B=D!X=?Sm(1VxMZ0oU5LVp<;YM@hQVYl*1-kdD~E(;+`k$vlChdk%Mgr0A>=RHq)T4^Lf-X0RNt_r-HXVR|(qxR3V-u-FES*_HB6jxm z5Chy`vVM6Tx_=xwHcyq);2;MKefDKV7iB|ljmCJuIzzxj_l*dZEYAaQwSl2%MGB9C zR5Bu;yf5#FJH}%9eXCDwU5?(-#qzvHm*f+a9JygW05l4n3yPgj4m$w*hj_8sSaA&! z2|w;#TcXwxE1|1j$4i09!3J^BvWd9c+-t1s1Ut0dxTl^w7{MbC`z}OtE4dUG^7ANE zHe^dzDjK;E?gDghcsIo zB~q5jwKTl#Xc$S;I$kia@v$}Pxs8YsiV~6rNXZ}M5TUrwih$A>vnquvk$w?ErvIRb z|1tB?cQ6tDLxlqyc?%9qxBnb4u#Zfs@&7zc2sk%1H8YUDi%|x9G-FyW8G1yBF&bi| zA|`t($`h-}SuiHMN-piiaDIulTx$g$$Bk#U`Rk~oCC)a4xMmdTiz^he1eBzN8$H5X zooyezEuU0k#AigIq{Me*gq}gb&epul-E@D$JH^R{V#np=4=+h_x8D+FhLGK5vWw0q z@fc+COrqY3TUImp};1Ez~1nJEbnj@gt5&zFy~MbNNE-Y(Nz z$#ombX1%2S35^++U8ec#V=fiVf1+c#tkFimec49lU63g}VL!t|umA3(wj#GQu_&c* zNyUSL4@V}S2vVte!Zm+X&a!XKv~P2jR`d8pGECm~()Bz(o^h8N_&~sm3v?7w9w$>a z^s6LBXpL5Z6{5NWR(*=Y447S@gnyTiy<)oOJ+-FJ`^WjOUjXpconVIT^am#Wsu>)l34%lxPlao>R5Ftj>~F|g(LzmZS< z9Oy^`{$k*L{`gX6e9q^yUhf%A3ZZtF6l70P)i8DcjEZ;eHp>& zC2X@rqa8!CUG@704JA7V;V<++EFuXa9oAs9s@T|wU`1(mk_e#_2imnQ^LCxeAt~RR z=hF0@D0^PnC(orjQlEYw-xyAeBL?|#-QDM2UWYvUj+A_GDZp}8#o7rqHY7CDBeYc~ zHGa2Zr?XO=X<3nInVV{3t!~o6GpQypttZfH$g{{_$yIpp?R%6{_)YZeL^SRZ&6=gM zRuDn}Dc}`vcG24g+fw2OX5*t{?==kV!&8Kl?@jcVjQw7_OwLn15Mgf8TJCb2ASjv; zKNNFrCq-heY6g!~d!^L=>=n7sq!8!On1Cx?Z}7N@IWz8IsbqN1O?$N@=6_Ahu1=(k z+H6yH;*g}|ucl-W5yh-;AuC3j7vKNEMq+htgVJUfN7)}=`vXm{50T9nA0uGYHKy0B z<(`xn=rqmi@oP-BeOMR$8l_l(Lw1qiKEP<0?|vL()}{e|At=v zNpWTpr1Tpi4bnv%n>0byVz09Z@B6cdmj0oeFa(9)xL!0{U(rafUm)lr<#Yr@+$e^3 z1oTStlfzbx`VTPT)ScB`*lZ-O7{b^4DL`c>S5o$sH2tS%W-=S{zUATlE6y~}32>4= zm3FWabA0YOLF)h8F6$d9^vNrj8W+|>@N_x;K=yRIb`bVN?sv>VQPE}~=5jUDCu{y> z?B(#ZWT>YnZ*v8dnSIP#d-(e&(PE_aR{Gw@;R<#Wc5dW*v6Zj7Wa)n$;dx}!eM4Nu zPItuDZ6y22JSsebgf`R_0d@U-_cry?*Kn@hCTvc`NOG>!9!qYWK~c(5bl?w!cBt(SREzV>zB~-_|nxua#?(Ai};B_QjPQcW6pu zL4}7jkvbQvv;7_gso7V>6^2sbuU}VQ269HzE8YkFEbv+?XfiBlqAX~#XJ}$)Xj1tt zboINbzJAki&k4Ce&i;FT`@FQPawPoB(&lWGe`A=}Qt2;A`&gXFJpM8as$_huc!&n? zb!?$lB8U~4l^**pHi88lZ9<%BLhMXudd5ZhLZ180+0x^BdSGR_*mz6WCCauG|s#6ncBg?KqiK`rGZeJ~0;3%m<1wMX^)<<1s{u&1D1c!!Q zpx{)Nk$Xr__!nUhy;l~#0m+lQW@b*bNFEMH@)U$eQIyTqvChbA86!GuSpB{K8uG44Ga3w|oL3Hw^tI|E7@ z1_OSf)xz|kP6e84K{G9uIX@*hX7QiwrV9P;OptK+4-lwgn_nG)AVQ2>QasoUhb9CO zpQlv@6(Ey`QPKcV2J%FqHI%nN*26e0hAO2+MvUOEZ}ITNS@Q50&MwaO;4yiO>4L|r zrK+DxwG)Np+|`&SYv0sktP>pZceryet(dpXPT+gU9Xk{e+ZH}MYGfRE zEif=CIM7A&g4NgPYNF?y>fx^G$e zV#X}BqrJKz?sH;tYie|9OLSUlQ)y%%DF3up{5&`G<{oJ=DV&48(uZ2^F69AA?#L>x zHvvFYty`a9mQ_a$jH6sbd!}}k{6@Cl;yQ?SRg%fn*DlwideV#(1&krtjlBVOiq(&LS z>_(#I(~8vf8ba=W3I8o zcSUL4=3=_z242|8v5jYn#wCR&h$=w*X)LBe7-D~dU=$w}BN`V=x6+llLH!3fLwW|E zMhAo>HThGqGJ(Op=c!Md++>J==l)tqUwqhA_x=2_;@`o`i2-LGrYr9GZA(mO?6LLN z?GInKEmF2WrfmmvKsSVT&FAJ0&nHJ+{|-CX&QcXWcgQESd-mt8o@=|9Un_h2li6=s zzf(m6P8G`1flNCC7C}a_ur9lS2{+|y@Q6p_H6So#;U}w zR={s*WW!6o=5XmMh3j!Dgt?t@pesei`!Bgug~ZXU=Xgv0Kuz61)#H!dD)mDIuhHKq z;?Iw0)=lZw?b+8YuRYV2zds0GR!0>EiFH16FX*v0fAJ$ZB_w?u$#3y)Sw(iGTe`(v>J%paYy^VjZLwl4M+* zzt#CQg6Weg^8s`={JtdxXDo7k1#*40yF?i!MY?`lWOC%e(v?u|KZmFHyX!+Q%Laa_ zhpKLl)aOM{7JQi^Y@10InvI=-6`#Dnzebnba#g&${r=Zwm@8M4c>;@>ZW4(=`Gw!* z)2gB6r$Mg7XPCsNZhvSKVbQ(!h}jn%zOA_)4~Yf{+=s5oUmlGFE=Ajp&30}gtKT}Q zHqDA1o7QoS*_sAay^k$wZGoT$fZ4RQp)DsSIw!H{YfK#2mJtKn21jLKw4>vafG9)r zVsFbr?;JuwOWP~`#aHuM6ELzoSomYWqA^Pztfbu{{E?Vtmr4TCS=+E!*?_>gW5>5+ zH&w_7c-IocGx*!4h*QhiTT+l4=olY^1QfXpJ*2^o6Je9H1#Iy_JJElct(AU(t_IN4 z$y9UNDV==yzvNi~so4w=3Bdo2Ui@RTyCC2~a?Y@u=~!?ctra0qZrsGb?k(;vn(dmQ z20tR{>jd4wxtO+y|F~A(N&>z$>fa1(`E_79)uHU99T=T2+-wC>e>fBA2 z9HQ!+jhg?5w6~0l^6lQeuNgqPTWV;L?rsDX1Q8Vk>F(|h2|-doKuQo4>F#c%r5U=0 zj-i=hkH7o=?|bj(+41>2&+F^$y3TW*YaPe;=-d?S+?WU@e}xuosd$k5%CFibB$*VN zb;9pAuc9G6Fm+?}XI$1JE1<3Edc7m1?J_H%>Tq_BRNt|-?I!MQW~@%F8X{N!hA@JA zh40A~Z7UH4{6)LQ);z!XQ#D|P8KiX2N?@54%s_&d-9fT0d^-m|EI-XWlBs!)Tf-Ua zCPlUG#iA@Ty;wfhS3ia+)c&J=l5csqynU*?oqBB4HLOBD>sLT2ZZ4*QE{8hK>eiB_ z3+_nmp?`&-`<2>-8FReIt5nfR>EN#j+A;3n_bL`GcO)o5D~ zQ2s$dB!hFKYdY-9`wpp$TVaL1*5=|d!5c48EZ{{!%h7o6A%2AIdmm*Fi!G@`tCsOm z2i5pZWkb^s5L|Ile{v1izcvv}Rzp1^U=1f|oQ)TuqVJ{_yV2Xsw<7z(li}#(>;8v& z2ENHX{s}O6MPTAkVB%R8vJV!RGwk8C+1<9W*v8R9KDpqqI4M77Gdr0-vyh|cIQznJ z3A#`;K5sRrQfKQh$@|FXa(?lR%jU81v#z-O*FXT#ps~kaW6z@HKN9gTVW@d8gYOJV z;r7@1_un_r4k7=K=Tqn#Q2j@A-1ws>CVsq+%_yGUN25c~h$7Q8a-b%h|!;~y03lf z5dKDuM;1=Gk)3_g&`xR6@nK3MlplSJlGpDozobc0Mk}<-I-@Yj!mV81rdVFN*(Iz^ z_kHAB-D(GQfgd3j%3l>wcq6RkgMowvt#2OrO8o`OkMo~AE>L-78TQK}?A=?7qk-tx zF=sD7dd`dx{29W1tLnBuWpefUONvjf|0C(`eqVe`zHsw2jy*;b?{yjN&Wj$AIkOmg$PJEPjgmk&( zh>*95ybs3GB>q}yZ_9_8;wFKL%!`K1XP2^JkFtK3QiA5deId!(^7RVA+ni(*%L>u< zA~CDzw$^~GK)>bj#8gRvUK_3OkqtHBP4zm97ew1gNA-IEOAdaVwsBVSuI#)!cADkW zRC3Fo)TMwT2alb{hc^t%XYJdam)%t(YuLtg?!3pzk~i5f>O||KWLUoJ#A3^}ThcSr z!hzO7m4jjtOfftBTJMn|Igak<({D6}7hK)|#qvO;FA!6E^H3UQp8u7U*gv4%GjU5` z=|ETzkY9GJoiVRRnRg~1Ioz%D#Z0yx{5>KwK(vSf*L&2nh!d8*WTRj!2s`B4+vPiS zKk11?UGHh{JNETJ?g&r2eZa6NP*-Q2v-leib_tof9E) z<&Ve=j4QSpqOX!DiuG8B_a`St(JEqM^8u(4Ws3sYk%NU z_%w3$ggB|EZO|-`dBgwlRzOtuG83GTvGBeu5KHpy&;9KR$;WD}vyM&K5hgnAho8kl z7ljUflYPq9Ne=Vc?Dm?<7pvL0${cWRdSz1c%G9$^nKR7Wsuy$Tdj#zJgM*JJFY!HR zsbR%w8y0SSetI9LCW(FBhTcyUKDSwRbhvYc{?sO9$c8Ro_zdF)X5U@SN4S1_@-&I> z9h&lE0PgbFNRSsBex6B~pTLM%^u9M9ej6%r)E>)%1GR^g3T_{Z>-!9g?nC zKZjKvh$qp_`Q!Iwx-%FcABYHE<*t^?=^S&?YoiAUaLPU8hQFw0OjA!+^IT>wkZkdHuEEI2yXVq&UWFRKZP=Z zD5GlQ|6kocpu)n3XdLr`OilzCD(31hiX zq*bl&RDUVStex{V<`j?EVVW@f4uV^2y9}X^+ z^2&|sF7sKRg$_18OFtWET3@eOGxg|sau^#urt$I53%sc)+zC}Yfu{rl>exbRr!(rO zP!i*^&#>=_n~favay;^VHIG-0^;90T?b;($-48O@Z49Nx&lX}uWqof+r&CkMPib3K zy*5h?o7bjofMy8Fzl&l#5CJ@ERq;Cyv)w;6ggK(EbeyC=`mO929@IzK)p?atchz7O z;CMv6{<=*1St;8GdF2oCSMo+r0&YGm`GleEkT+2uo&)%^nVUHo2WIi>1<7VrlH()a z_>5F@kRz|R%WGSDwlf6#Yy|uEqDRinVrpMb1a1ihVL;BlAC~6|Mr})&uxGd$z@lk> z4Aa=F+Djo%?-A#Xz%sbGxxamn;4P~P7WnvqLK96>?InTK#1yk)-cYr>qs{&+u~(Z* ze~_G*zH>&N4Mq`#`sZ&H>^#NxY_ApueC9)dM>jeH4KxD{FjTQ(q&ec$1_-tDm=rAg zSNC~0&usUg=DAr_2oW^YyopX(0(b=!N6fEeb73x=MBO7PCf|Vg^6L`70BqUh37~rc z7qNkhKE|)^C#)SJbo&MOIC3~DcW@BqhcK|3iFyXD`~2?lhwO}0l1f43WdeMhKiX%b4Mi+a_sJ^(Z3FMIQ> zDZqQjz2y7v((CS6N!AT^8E*sFI}gl|!@c+xGRE>`%0qs{aXZoBF~UGIvSPFLUurd3 z96T{Dg;(ssj*wvHUb)!RcBZA(L**wNj z0aPDQ3_0=|$c)LFrKaTWZnAG5*04*+i!wf8mwCo1q0T9xEGs_#hF%!Dm3>m zC(i>e_qBPEs32zsf%^fLLqdZ!p2{mV9o_nwm%%#yj0OSOD7jXUmu?zSE9d8K3Z?c; z%dSB*%B!8d``^qRmTTe}aHEXom$#K)^q)CQZzjyoWi?enUksuN3)l}o2G`(zbwAB? zcgZ|=`FdWZk6-y7PY?=Z;!%Ehq}5_qHdyCFy3?0*qyDB@JY8h*v)~lInYz1ifuume zykLQ@R-vAj#XPCy0_ms+>u--m1jGP7h1e~m=m6iL&E%f-(hJAeM~-i)?4bOA8XD^K{k>l1 z<#iR;K=WGpf0kg>@spm^EA6E2bNvuzOPyJtwc#CLcT z_t4YJxGvC42bGRNBwM1SqR#D5atRw zhPbMJq`!qtue6EKUXfri09pzVG(7%B9yvp$u~-i(~Y297PQBhFosmNQSZ=OR0v z*wse%tnr;Fb|2f+KLa3$G6PUMxLuj)ibMzSVnVN#!$Ql(2#nnJF2?&rlI_|{Ew+}5 zA1%nF&1-Rb%vN+R8+y#Q?CGT6;)p99c2n%_1Dgx=!k3sQ=aFVweUs)Y3K4ACZfA@8 z>y6ME1CCkerih=FsoyK>$g$c*co&YygJvmv7gc=Zfo8)j{zFGsU*|u57LosP3jcRl|IgWY;e-Y=i>;^hkdK@N6F7(5N|bZeHwJZ665r6~u8wZG-Wb&O zOPq3|UG|=}>61bKn)<_@ZrlR{)c*H`rd_PW1R7 zjXMm5U?;R(R{nI<1O{;4JHBUh+dUAqyP$A5KfkF79VRXHczm*Yk<8k7{qy;=+>ac_ zO}HeltC&QUeK{ARXZzx)l~mtQJJSuoS?8qe}?1xUFZ{^uGwXs zbj^J+r`PwZeqVNp`?>SZLd$zcWT?2?ho}^t$PO~Tu=tCU4?#zifJneY?pD2YLR^(d zO0XaC9fnfm&=waDZ!o97c;IGYJb#>0a?5$jGx=$1#$Jv92h1cjT(A(y0bOjbmDxNA zc*P|esHfVj-&d=A9bNS*x{gzNIYn@s3hG{T%q8txDe0S-BK~aX%l`B1PH?=)6c`*Y zHl8ImMJxy z@km=j<^Nu&(CB3^2L37YbPEmar*IT~XQd}DW<%18_NgX^hslCIH%Q$ya<;G%Vln>IV5N3p_6lnSt*@X9? z5q>NA3~z@n9GV#pJ@A2+%vW0sFR|6MQhW&ZmgHdnU-!6V6eAHZMf%HZCCfa=DvFEj452|)-=)4yYDe~|KabY^ zy_IiWH?+EH3&X6E3w|D?ax;l?vx!SHU~Q5MZ<^Y#ud&{=xa?(O z+^}JmJid3U-hOqIAj`0PzuP)rSHvd!5IK3i?9YAY2h!gTsV641Lxl{27t($3$hp2ePUM z)FY8B*z5M#q?zMdnVOTC34)oLf(On?2R}^@IOwKclw~)4R7!2&;?oo%5%iMM$$|hd zLEguIrlhB*xfG8+!*>1hD-K{?Z$=IfIOEuQ&C(lw`Wyaqo6W_qo9)P?ORzxYDQ6;i zI6-*{o`I;BM%PvRWCM0mf5`q69hz4d@R;C!Y z^b?Aj^wefdgTXoKj+(o+Nk@Ar2ZfG@`}5*_l~pZWKfcecD4G8;nqj{}Qa6XLxVu!L z6+>gbKfP{{Lq1=U;cP(GVxm?xK98bp^&d6-wUGQ@E?ci(_MMCvJkB~Z)H7w+bE%Pc zH;q(j^bCI%mu9u&`i< zwa1!Wm)$qyh}~r7yKo!pCR5DB4CI^(9Lg-*COLOw0BygO z<_!$EAl;IsvLdDp>akSVVV}I$1wm-#uxi0Zn2=U9jo+B1zxCO-(uP)?jSdV=1xf`{ z-eDmuVb6PEXby9lY|aL;9}-G2P2B|+s|D!VGyqh)cS0J1UwLpYNLPnRW5d=!K3r`P zTWuCoZTSLj_UY0>4PNlVEeRYiHJ2_ll`b|-EW1Q5JV%c5F32okcdL1_YPg}uW*46> z?jri?+f741wtxM7HU|N+k2N>-0$&!(oDHQ&q#+&Bmgv(8YPU5+b^dr-nQu9soW44C zO}?zvKCXRxQjvUCeJ2lc5^=UGb7i1&OQ)^LpckgjlWdW)7Ll)_+N-2JsHB1k^}$dS zQ&GBorV)fGsJ$UDN2{z&wXQ75OxqDN=ZWpqw;JGKHO15t0jp1)R7;?$O5Dv#q=iPA zv1g&d0rp;R(h<`%ZAIHlbU>$xS~cO+RsK}bJEM5xW`X|So!$Rx+^zAq`2OkY2*sIb z>7D82tb32Nj-k5pLkc8;RyKpiv}ei` z?evv*Ds_%aA~j^3R%D+_ogc;dKOW3-u1+C55#}7>@7I_aR1@MDSxTb)o=RaKPhOZx zmzPXmkVTS{vh^zgmZ!f{mb_j>W%(`1-c)xVLi5e+v;C_xgEwc(wz{%EtP!sE%Z5!$ zcQ?tnoxD-Eph#teExOSZC@lYLd>qjVBM9^Ij8=E{HlH)xQ1^?WMLSzNWh8|T z7uIsw{h8xdZkEAyMehlHskvtOyR4>H2dKV@1zhe!S{|)gkmQhFqgk4#89S6^7O~yp z_HF*L3wz^4_>Sv@}aVHKqp(dx$0Qz_M+GxTUecIXl~o zd&Y$uri5U~p0V>U;L%mOBYt_3b4_)zEKFQxUr5-3U&cyc$?={E3xJvwV@|Xw0cznA zDA~NyjU#GmZNz%BHzrwqD;`^v;UN8cQ^!`z6<`o9W^e`J>b zo13%UM2?nXO@NU|Apk{Vjl=uwei+K#1LrPW^50m=X#44<*xO*I1@9OozL}YdmRcpL zm@L~rud7*P89<8UzP{HqYB!Wm-24E(N;0!IV{bfTBtDH|Pt#<7tP4XLhEOpHehDJt z1U9yk&+bK6PP+X>D3?Q3g*y5gGP;cQn|!GI?15Ke8r`cn;tJJVWQJzWZ6?n050E`u z7NQ~?jo$qY4K(a*x?B0S-M2Oc*VdLFY_`L!H^b1<=Zw?ebrd}_*nL)7fB{13Z+2H7 z+?*8DihDF->kjy9-Wu{+-3`HF+k|3Um+$)=1AG(CxG^hMRpoXHv4DW2n2c^kg8JDI zbfq@A72w;iuTSWe?0I~x)1Fc2>gY7~8tYS(pZQu%FoT`ChtfeROm=d$QP5=3hm z%2oIVR4z19F*8&?qw1*{Q!c=PrLkW!M1KSVJ!L5Tdv=Wnk0LZgSk-Iu)jZf=zsBe*9)2w!}TVt@!@UFXRK@b3U?TOwm6i%hPdN@JheTir={j!aSASm z(&e-|Yw2f9uL^o37uI^;{J8RU@T}6jaDRDTv#{#WI(7RNR1Sc=y1RO>;KWA*n%gkv z$pjD)ut(YZg3V`Gr6~%&{G=f86@Z7Soq<3}uaCxHM`5Ut)Wf5fd7ecVHNPhHJSWoS z0imW0;YP`+Cf^N7pJXe=j0}lCFhsikL8J8Wt}yw}?QpsK{+7`9#3a)Wy(p&bdq~_x z9bv-mkLoAnMexC-B#ak^;uUAs)CK1i08%pt~`G=KZrW*rl&<>LHJWXzc=Wlwk2 zr<~mbp1P?pf4voN(#UR3eP~y9e4DeGVOM%06Mr*Fq|>);;`QXo3l`BgU7tK6xBWT{ zn~zNGPm)m_%?{bsJ#4TOfMW;3qy?S_Ydc+|7HE}1202&8rfM?b+$8+oU(I>qaOs0@ zS#M%HTD!Ya&X+_fD>X95Pfj)6s%!dV1xD~^25_dH6V9a(*K)GkSgL>4wWwFNh(y_) zO=9wti%T?%qBXxAWI2WEC+KD%pxd71D} z17DF*p@PNJQ=C9#bha0p1rs9*8HXiX#?d@$3Iv*`Mh#5)6Q0Rr%ifRs@^$p^_|ZaYUL(3k1lN< zk|i{kEHnp#$vS`I_OHZTipt1&qUIK%?jE7$89wT!G79gGt6_(WWcj(@Xm#Je@6S?P z^R57DpJ9!aaaVc@Ps>cdWfuGsst0;AV^?IaDeX3RVK)4Lfwu=ZIw+kkl2PAXA~-Eg zsmwyTZc048^!AU1-v+@o-h*aWEidaMG6yPfvVZcYreWu;$|Rl0XSlAjnYs}H?RNP? zZdAB{Qs7r4X4Bgd1SyOmXr~N1^L=Xh`+Nk3TuI%F(CNyg>2Gt;=Qu%js)Y~zuNroJ z-aOQAXmKYjd6BzSuCF>pc{SqWXOCmZ9WsmtnZ~js1R5%cC7JiRL=W;`87w;J8AQuz_Z^k*SN#rCk zg`fwV4Qs#WX_iyPRRi70hP|l`;#`wkxy8v%YHP`fU*=8vbE!0!(Z=NkJNX(_s3n<= z^O&%d{wjQj8g6?*LL)!YvaS|*y8`DmiBiJLjbtTqW;Ax~F;0;ALN+)mLq!TNB~EW9p2II{^wz;R>xEzD%>Ai>i+9DhPYn(_LEg`cpIM-^K9xQ` zF@82-CLYxa7CR#$Pd_a}deBwQ#U$6&w5xqR|cY_-qL4Qcf+z;zfV%vT;)ob9xUh_4FQ zWfYVtL=qE|`&o3hsdNR@>l-hL^HR4`+~l zNCL8Ku2+3BAsssn$6Q)trA~n)bzt`s;ci)?rq`n8xXoK+Q#&JUFYVIp zU#{KTWvms1%1g^A{`51YE;_s^^ka1Bw+MuYVe~MvKo5*xi%Iq9CjIf1uJpD=NmeqM zn4pQIh>@VAg_MYq_=KY@znP4JAl>)07&sN7Ck?(kHJ&?~PbjnzO@BUA-}iyUCEOE@ z2;q5oZgqaS!Y1dh@3pwlP*tZDI-NT{S)#o6c6aCP?ye>b_GT~lrA?vBP z<89ZYPp+7*J|;%G!$@=u!s+7Ty79h+Lik3LxzQ3y0@G*^CX@Y$RI9gVZ4d0|N@?>iX_h7Yt7j_Om^(f;sQq z0jyq7;3sq3fv-lv4wQXx$y*@J;iW?0;-C9vKRBz3Q*ckn#_2GVgOGxB_r2Jkxm$jx z_+F4ZwvZg8w{RBa?yp^IQi#IzP5?u?hv8B1xT7)F(S9LR()yxl*{i1e>2R~zK_wR|+w%Ii(QwGiyviT?)`tsd z@HdM1pIr5`a5SM+qcHyagN1Lx50@Ni5H|3!4P=(5-V$OBW%Ud&KB;nei;7D(<_CzODEhN5J)ljF35jpxlR&NC0^&${U0_k9IDKl68Y zZE>r7FAv02>Hl_t7P~BRyDY+A*$?9`)=U;xPy9YV+c!5o|1+`A>`Q1q?9oF|(-Q}` z_#Mmsf}s1;1&f*+ySm&>A2?n>84dF(?a`+m@ED~Gl#*YSknR9$&py5WD%&w?PqW@N{Ee)2^AWZQ#`8`OeQ>`NPTmJXX=n%?tAnD8OZW{ zu7RN;a@!Lb-0k`lPd0JQ)-`et-91@joY5irwY?Yn<^x6$_dm3S&s~4<_^J)m z&x{IC*3;#9RHqQ}@t@%U zYm-2I=sRs9C(WYsX-5(^igm(#fTDyldyi5n zc7K=w9~EPm9N_R_pW?J%qTmgr{NYhdj4@GF>+we%xq&E)jGPx*Wgq1C?irex0v-%f zKHCs~7uKnY*Xzzh**f!v|0ZUb!`5-Z!j}06W|5d#lt$aa-KiohtR^k_o|*Y2>wv!W zz#Dd`K6{U*@ce9jzq?jsIBF;JyfW*3wU3>K%}m=)pP}@dOABcKZCQE0a`cuF* z&^s1e#Zm^TY+Hu50@rB7|0&8sn*h)PDa5K_qA`vssYWdtE%$m*KFZWYuVgISE-Awn zJBO<6_LEm^4)1Fy0lEwp3oBj$(Jpww$oGf`H2o992hy zFc~>3PnC7Y#=U1CuYEUsto8!Zh?4L|Gw|1^pqC!yf1EtWoch2yYd6y1@{S$_W@&XM zK$(wzw~B!V9TWt2l$y5{QV`T32QnG`rWqg;fvFaOga9m?3=sAyeAq(s^56jc+K2CB zX6AE!{Xr=7v9$CcRH$dfp`n)Gyf-`uSz7mw=q@T4TfLX!xM7(SP+<96dQ!G=2X%j$ zMj7vJ&*~_iSKNmG^`5={%lbwNq7l^Bd*i8-n=mq|C%ZbQeqmxWtnVh~_j>A-+;Ybq zQ4=(l`hK+z>-%{ON8v#r8?5^RO{;D0aawvAyCyuhyb< z*Rrcg;!?x?Dm1AUuzI>*hN$xreL{P$s&zfoH;+_y^lh>v=GHgXH>pZ->LQ_URSj_? zTuN_E#5&HzON8?V50DyIx*k6X`o2d=9LUhp0h#XN8}5^m?H3yzh0H8K`nn)}!+Zu+PY4int6t*sUDbC9~x>;Ih0rfAih1@ zBy4xz$7AoNmf!TuDfdjh?V5x|ps#pax)KEi4KNNR$ehg)`%@8)+pA!{Q1*$Mysw^` zyw{IMIrN{!*h-WCU0)`PQVp0MnONIf@^`#-e=|Seh7oU_12|xKEj%>T}nIiiX z-}9t)tugWE2hNfht#@3;pUDG8ec(Qe(3w{kWE`*PA^*9Nmt}B1n_S|f)ZpGf{({00 zmJev17|+g}3**G@u~!@wfbJm${xG8p%ClvK(<{p8a|RT(iE;ZhsWQs^@vTJmpnd8< zzP;GZq2r*z^^@HkAK2#@@3pkK%QB~v0lTY59a5=Mq4);QlP9P!3fKCN8*+aQW_OE^ z!I4)=m@-%X0)eDu*9yckdm2x{iYz8UfD^G;d&xR0Pr_}~2@@rE=a&ebE5&KIi$KQ? zFr@SHaXfwsfOW$%BhJ)c8O`LTA5`k}L>pb~9z>~A9R2=KTkS#XM9A1luuNAxknzXs zT+TDP7?m;N7P{{=H|I69H+27JW+l=G_)uiEIw}NP0Om6l9T}e%<4_)w@SmDP30t6>ai%M=j6Z zzGF z**9^SfRqsIlJ$l|wzRGJ@0?Svz+D$TgXxN(jTYU2i=1BLg=llRhzy|QNTncbtg znuiGC9qIOxZ6xCf8UzSKgRj7s<=lh%2Fyt z(4hLO%fcWAROG}U8vf>S>t6+}L-<{A6Z!x9I1|MV0xtb|btyst$c5)nDkGvzRu*&o zUMuW#=c~FWanTyAy zg9^zaL%3X`DlfN);Y%9dV-%Cr;^p2cD*-0Np63NlF7%Fe z8?&He3<^9|E~6-U!0UJm96CURW7Nxo{ceS2ouSz@lfv#R3cY-2{$sK%B zoI)=;jolsR(f3JV@#qun5q|a#ZrU#HmwoKa?mBc9!s=1K>$2!_9i>jzV65&l!7#2{ z9}&3FkkFLOxT^1|8e)3ahX`mHxlu9k3nbmiCtCTox?MTdp*FnWT>{wQH8KLD66rY+5WEN#ORN4PMlxi6S5T@1GaHA?al67qan>kH;!%IWXk2K2{)tw#9UT-5$GF&p3b2R->ivZk!B3?VuHdOwm zLBVVIq%$=1Q;M}b+fU?;b(Rn8uraN)agL{{>Z^yj*p8X_k$TypT(dIB7ag`0w5N}G>{2d4!}PjK-aj$)R_3jW_5xn zsX1!C6DBbv$KC$$4TW$cVQiAW*~+3I3SzhZ1$Mgmq3(N-cLG9PdBHrAXp17V)c28q z)Vf*G*M|qf>MTfD2eeBlPd>fJs%_kO1oC)2GamgiGCse^q@o1p=4*-OI|F!8YU^5KMR zu{jaY>`Dx7SCk@ni~jez40`8l_ctHo`(g|W-(UQRA<#I2Melxn zArM9VC2C!UOY$EcZ~y+qU{jd+`g!qE!bH(av09G%yOfDKdW~!!DQ~5Qjb;-zd)JkK z;ha0iCt|skIYn5*{=OLZAn%7Vr)}JT(oO_fbs`cQq?3e1O@oNQkc|~(MYA%=U;amT z5R`KN*c^R#xFbleU9am?a7;g^>45tKro*y^dBAjF>cOczD6!xI7`Y_+>21Fz;a^1lKRsYMvS1Sa^M*KJ%6z$ee3qEfU7fhR2v0XV6+&L)!L!wWV(To zZ7$$}xWNPOET0eb?OAr)nFDX<-%U3*q&)n2lWwitC|;|Z7OSVgr6?I^seTy@QZ=mIyYIIobS<|~Jq-cS38!VJ7&4RP*-g?Y$vKh#N3h7>a zp7)H}^ z@&)q*#6H%8=x$(d&2>Hrj~sx^ry43fYC_yGTk!id`1^h=Fqg9Z{Bg-k=D<8{Y&CN; zaQ3s?+vDD|4h3KeJxWE z>hFM38arSCS(RUgH+ftd&cGdsa@BaIGI1EWfi*3p0Tn3wJ$JSh4o2C@@Yoey4Asd8 z+SW*HtZv#<0)xfK$kEQsmC$6OGoT7nq;nH{)( zf?Hwyn@n&vD6(gTr>n5zk}WRQk1=kzSHG+?WaI4GP9(K!bN%R%0^ihkb2ha|_LWfU zl{!EM>nqiktAjN)?MkK2*u~(xz29ERe4|j9riA!-QPklLhWsEl0qRfiv}`&Yad{aj zIHo476*+JI*^)Jncip+WaBb1pXlJ?0FqgM1PZjs;5Ckyk&NQqa+;%;A-qW|IhXK6e zz)T?&162CWaj%D%mO^x#$WM5g4t=#-ordD-hH-?|&MF=ghzwp%aUM?$dQ6NskB>M{ zpiaUOk(m(z47WKZJdxJ?NB1XpA$l`7rej{DtWU=ll#_ znFB3Xj9j^f68m#U>3?EiKE!YWf{$@%5SWaYQRCZ^4+G}L@1CmSasZX?g;RU6_e|55 z4OT}bOCHZ4_4({$&GNCD z{)sMc&X{8A(P8r5w*ilfeh+x!ie50y$iBoo@165hW6X_JVNP8Oo}kPm%E*-n*C)W0 zNi#+Mi=cd|l|{c>r9xkXXG7v z@kq1_QBKUyddccP^6>`QG=7lZAEtKX(U2Hu4f+p3mxQ+@Cr$;59G8Zkg#nqAHhD}E)LQX7>BZR09;$(xVo_0**>~m zC&m%*mD%IaVRP(?2SoPSSMP}6npROnhh=Pg9t6SeUh$mvESy zv}gN@s$6hHmtRR+bil+jCf6_Lqj^G0OsU$Tmotnr!eu1olfR%Zo&I{x=1>eSUPm+0 ziLr5qGQnggSdPo=bnOA@2~{MTA$kf!B%w;b1#SZ#qvJD{FQg|Opw<;`(Kay$l0Ew@ zdaKVc(^orX2_|KR8x8E9s7(#3*_TMW*d2(q8Cc>+oMW3x8>871n?e(kq>V}SjY*-a z>ri4-A+XOCm9*D4S)(FZBcFE5V#$lemsBmR1$nEUF%7)&JCX@IB&v7V3ZQ>B9>CSh!xZg#alnN<)5^6JRcR?sxJWVS?jMQd$s8-< zwiqw-^V*ac_s8!?VvA=Y5sMHVvb(RFx zOKo*doO90b7-Th7_Uo>FbTXlgn)3sN4(Y9}OKNLRNE)_NV2Lwv;dPUPz*sG6#I3e%IFZ$rDURWdT+)BM<6rH6>mkv z(lU}+Wf^Qr61ME$(U;KM7m^+sWpCGU{&^`ot;TE?_q@AQ+FDs4C627sdA3gSV%8Tw zQ1|LgK=&%;eE93iIC;f*9ykmd zuZx zi9)o3dG#**$GZ<3q9PyEij+rpSzp*bn6mXw_AaE;Q`(UrR4AcB2iE#+vL)*$N&1KH z-+)$D!*}axUVIx0Tr>kOvFIMSQc{+lT)!7vis$y}pFFc;X&uSR*w)u08L1*l`lN$L z?3&f&m5jY?^c^7`frS_XBbnkPM|&96NY*HOb{5RxCo`&nH`_hEOLLPYrkj0DuW&Um zdXgoszo$v6(9C^+>0p*H2DNMS_4bL2aqv|@Z0~pE^^(`g8Fu|H7kHd9KP!}0Z zUQ;F-PQxmWCWHo2lE3M!?D0_=WClq8ZX6y3r z>DRht%hQofR!TR83E&{uK6CxB;Qzx(X-*W|NZhpB(q2>1?v z_Te+fhi;CBtDhyr%nM!0LhFS>3Jcsx^8qEW>*=;_UKi)-zR@)%^q}bO_^Nj%Yi5#u zH-YZ}h+@+AjB}nlzl`jQ+K~vk-;_9C-Y+c**v9slW1S3_q|QlUy*Wc60_Bw`I`(Sb zlSBG9%kHk%M@~{X_ZkH+Gh+uz%Fk5=JU&fV;=B?#j-T{^RxG$TrO=cs4!Pd)wEIS+ z#NB1dkt518*(!9muyqSgsH${AtU{i0pJACx8?x#%VAvDRK}QyZ=tO=k*uAcq|6x+v zyfDx$AtY6Lr*q!kpe=I+w?w?!oL#&t3`Z9UV`-9SN`V(gSMdYKDFWr?Wz9Rx$=5|(4l(h|pU zlex6NLXYHtq=;nS51gLQDt>4r=6Nd~%tOv^gFWr*A~lT@Lo$`CmXvVD4S#bCfOMKP{Hil=NxuZClI4%T2LQPsmy2pkycX%zFvXI+n@LiM^NJPOn~l;QhP_F5OS zwq#!<2+ zo*(a&Z5PVu5%Z{kTj_Ri!6~QCwupd-mKXx%WU{^zjr}MRw_D$i@`#hAJ z(hfF04$m6;(tSto*P}7Mxc0+==Aa(jA<~q`8?DzETZ<$~xSYhxsNWs}3LS+wYNDWe zcmy)8gf*>e6-=BTJuw@o*fD;Fi^I&W^iZ%6GUHn2j=>x$ImW5A>tkrqPqvR0^BTSm zf`b)BJ}no=-6z_uUh5={9(3ILlsuyq?~XOg2U);!LZCH1)c8XgkIkf1uSxUMjsfSI#=XrP{;#vJVRB!v;|u7;*x{yf3a|JyeF_Duf>Sgr5FkO4&^C0fBA* zFgv!0tbE}?xDn{;g*zf#YbryX_N|2e_0ZF!k_4IOSp>RS>W@c6P+|&kRDxtBHW3F9 zH@i#ynnEIG+^yGYmsC0*up)Fz7Nwh` zx7dq~y#{?e80|Y0NpO(b9bxBaPWI-ZoC!J06e`;+NqDEN2GU;u- z;VsUw9~|$dPI%`wbj@5>_Itn7TADO^9{Rpu^xPUao~FZwemu#nBzE6@M%N^uBJyBr z=ev+>5yPLek+xRmQ&BV9R{*4J#h(@#$KFXzWVeD^x_?JCwf|MeEb3nxHv-V206=?d zOCEe2Lp1I!AA~&TKC5Z-*Pj-m(;v5{bCrX#z8jHHpObkr$!)ms%Pm!JfM-VFMe1p$ z`mLt~-5;momvv=})@_CZLRxb|FNT@4CfT%yO64XS<%Xp+#vl1MdCU`Js=b5KdbfZv z5DKhaB<6Xq$kzqjlMs?GE{H!9f8H13Ub5ExVVqdzkeFxl8C08-lL{#$tuxj5eF05) zT<{Dh+*z`^OXL90%V5|xG<-WnpI)+BDm$UhM|TN+QX&!OFwa^y^q9;0sGBn|%QWx$ z$yBnGA zp3Lg~9NoY+A2@yn(&+>T1J}yE_h}2;yBHK2)K`OfLO&m2#bMZe7d-f!@p9;^NJZ)Fns7q zV}${VWH^YjE4v1;ktj%xSJb&>6fmaC3NAmn`fsG2WlWsm*XAGGinh3Wp}4!dyL)jh z?l4G;TXA!~IWq zP+D!&UsR+nMq3wTR7`LpM$Fx>D0iDNBZvk5iRQbN_X>y>bFjqKb)?_fPRltZIJxyM z@l-4grWuO?4Gni9PjDQrQyj>5c9s>h{GM$dJ})*LA)kOPGQe>#gcbnfRlvc-&5u=` z0{;Ljj0xHL{}yHbg_7j{CkQhe3#V5EeXUfi*ICyw!sjJ}CIdILcow5^V5?Z88^l@% z+)MUgZqG4tD2KTJAYiA*S;m+p&1P>We_r{qGZ}>tR8tZZE?q|$cZ-P5ZtW;vqfAeG zNx$4CyR(APDRzKQDgf8{IrszZ4+LR}?q{wSVhUlR$!`7vM?OJ{FRWpP8xZ#!(js@; z2#i%+fN69;HpaIgzuZuAw5@a(X2`X00A7p^UEw{JMH*`kkf{otIDflby_~z;m_fA3 zX47x|4706d7GkUY%%}YfNtuixUpb_25HhRxW#@EgRxjF;i&)j2r)bYZZ}u%Xd{r7V z{UhyMh^yWg_r+6nj^Q{~x#ZdLSk{4`oFnQ?bSljBn(V8U)?P}pbt-)iv1CR{xjFu^ z;7}r?>^uRbB1@$_%PLLbN+D%I%{~&v(a(yjTn@$BSw)_23=w?l(;w8YKe$G?AP+}o zYA`7%*GkP)bI2_vg^zar$=f8phrG(kYuAAhPiV>0+6Vf++3Bd~_`Nk6zT~ z>_MRuv*2fT6H)vq)<-arKwYJc< zu}C{K$2$hUTBO-fYUAxa4oLEz3?V3b=dLHO;qWXT^Ddoo3PVBm9|(6@8g49r)*oW; zdqKzW^atOW``7kDQ@a-&TMT;!1xe4co69<@wK_!y+?+NREB+d~8AEuq6}>f2&*H}r zX>coFTBeLQz^_0{#Wz9b9GOT8zyZ)k@iCX?f`0sXR4%e9~1Z-wRz>ZPA?)*P)A9TA!#Dj6Fd1G-uL!!o-4PO zwAMKmN59qWd(4D7O7xiVF1=zZwPo>i!{3{MqVcl*h2#GDa4EO$$o!2+r8(chtEMK}ulqjBQ&celB_@2Z2rhO0ITELMaC z6ZA)0nypI}BZM6}BzO{|EkB0~vmi<^^dJ=Hg7AV6`T}-DfI=Er*BsfUV@(|wU2|PNX9M*7H4c&R_FnGJJ+*vgfs$FxH z?5G!RdCJTl*U?7uE0%a>sJIH29DLK7eM~7jmZra-)~P6FBZw(kB2a1gtjW%(#5%6b zJ`9r}e+~XgltWC2yr;!UrolN^W%{ke#8+nIp|*C>6xc0|GmJ;PW~@SLqep7vB((Mb zFnrZ3GS&NSuG27Gr!`%mHQe+q*|DQY_)OuI4*B&)e-D>;R22fb_LJ+hDdseVS-gjM zJQvKi7Ui^udIW`fsi}G?1?BYvA=M=B2lB?l;5U=_Sw>FXG;cG_`?jaX@l;JxyPhd@ zlhGivr&IUr9*mDj#`TO_%LHP1i~`RXZwQeGM((x7KC#9M!6JdJdTISxubaBlw%Oq` zlXOmMGMruvT9Z{HPhp__hW>%A9(ir&Mwyy|^=u2zfp{+Xk!1yoga1U3dy8}NoO2PIX_A;t0Edl(fJxwl zllh3B`-q)?kDLF5c@3L|^%IB4wOxhZckXw|UjLwR4@}biL5RobqT(eT|HXxy;iHz~ z(qj%n{vw})E>-0Ke5e)mz?I!q+XOJ=YSq?k^~AktdB{s=$TMxo8z0JP&0N=~+|(AC zKzB@QdRbobP{-HqDc)D7=QIuKlYhd>>%w1COzP zam=Q0%nl@-uE8~MQ@>b6yA$RjmC5S$nultcK3VkKkd|(&*&4A?OKA343ey>Y<6AwN z=-hxfF<`F1AM63Ei{$&w+@ct{)!XdQG4Op+AAp|1Ot z=pZtfZIu&10i+c(Y01vSM7YmtVsZ?O9lw+;*%RCnKgzfW! zRcx_+LaA+Rk$FbPG@V7U8BvZ&8l^)*gQ%c(PlML%Z=r5B?{woOtk=5HA0^9jHEt=p z1F1W-x}QF$nhSP*d6A}cfZ9Kax2yDdi9$>I+vnL`cs$&+86OJ;o*K1xw0(;^_>3{i zNH==jTvef{h*I*vZT>*|Sz%#(#I~yf;2$4!K1TF)$B2_R zv`3IBm?=j5jxQ9~%Q2J5k^bGOECKRM%C=7ebAG5#>2z21WP9{vSLIaWMds;^VGZcv z$o{ql7XN*r`s+&t*A4MP4GWHJjEs4TiFuNX^j3)SmW%O{Lv4+|hZ{u+iJbD)IX1_x zc*8;%zrr?0UpH$rEG)ciPMVX%Pxn_}zEIPL!U7Gwj*G$CU%6|dxUFv*=Wl$^f7d{L zF3)OkBAN>)Dv}|hlF2F}0n%S>O;4gub!}`9^s#ze?7YB1r=e&debM$-*k_tU!}?BC z@O@}9Tkf5r=AmLe{htph>&%XS6?_p|npv!^&QFJ)61?!SxZk@QBSgNv!h{>I<=Utd zF;VQg2VFPY_UZ}+!kDCrK83tOb5`dQFc(NYo;wc5jQum1SCS#LeELTO__zJvYV(=+ zAJPxpXnw2AJrVr3UGa}G-QxerMN^EuhGghk9xhV0)E1R3Rg^qaawZKZyI4PRg;?lxB^iHNgEZ}2?yPq(+;d$Kej z0DePBIeRPWcI^aDW-A>x}O^J!!MN!n(rW5nDuE|KX#or0d|F zIVbAHTiVEzhO7DJ#hRH(?&S@Nul6yt$o*f@UUx@^cI59M!;AW8%{Krc2r;nXecu>@ ziAJgG6HUrVpAjyJ4^Y#A9{v2K>v&dBU2Yh{ES|+|+ev89CA((P5Azx-O(SGx6qOeL zJeid)o-^w=9;lEP6y6?2i^DmniYEOy4oR_ag?}8rwcrXYX|F9c zpYb#+@NjE*X`55QaQyI8sF^G>51Ia207!|sIX$yuP05Le%;A;_I_Pwcm3w~$563`U zmoZ?CY(?t*6$7Hl@r=0Ge`(Ghtnq?*i~GFZ;C=KgyYr>FS@QL{8asc_Ws7Woq!t*H zajY#_CLad4ODwRbR1q9ocgEQGa!>|U`1DTuGe`En*W$qWtJVMNMVJ=2#~XU9{paf= zV)duA3*vuhvXuw^Fk4r&4eA^P7r*gU;nrw;08!mT6}7 zXVKU-ML})iU{;qg!QAu#U>rw zMQr)yo?eQ5UtXz%G-xF5FKlmj;28Z}ZUyBiAXB>WfzSh|w6FZ~h z)Ud0POqI$wa(HGgoahZP;T<0F4R|I(h<}3*^J9}RyIOhtQn_(ZIp$D#UzrD9sLQC) z&_Z>%Q^4orr9^V29C_UJ4jO#dbd-Dy2Zx}}{DX_#=@0Ud1ll36i67Ak%x5Sr6a(Bm zbZu{H|1CM6+g2SG2LLB|jfe6#R>-br1SegYeid2In$&s!cmdObJAROKfD=Ez&G;)+ z>>C8j>pCqkUgv6mocqPy-C;$u<36gfg9=ZNG|8wOppyYB$%=jSf;`7ycYbF$#evFy zv$ra8eNO)Eki;T(wg0GjFZ&1a3i&W}WPpk;X6{o=MZvnFj)YX*6iSN58VY8jpRuHm zVvI6>>xm2#d=Xt1RA((v#v)7npORgg<`2LN3>4F+eZ1@K^EHA=94@_ zbVaOCa?H7UwAGqX`dPdo66w{n_*`IY^+@{=`iSsG@C`~Fu_3e;IuL+H58m-2CEubx z6d{$O>+LiVYl1!mJoc&Cg}THuJbPQCp=C{of(F0RUwoerOPa_IpL`r&*bVAz9JX56 zOS&)9FA5y@dtKlW=ahQyukW#D53<>5Q?$M2DZ!dFjuj}vnPMQG`F*gSx3iwNw_G>f zs6E{(-BWAOclv2@Lvpy)tI@r_sGkOT|Eo1h$S&Q`?x-y0s7&I>9c_B@ue8KpsmYMx zRAv;tgKuP>+diK0dO`?HqZ%xuYAo`aEVATM${g~loI`6?v5nI?LaS)$02crcYLiYa z8|<*7dTvZ1S}}F{79_oUU6eSUPPk~~b}(=4Iex~lX0MlP>npO_Av1edkT+`_8*JgW zYdiNvqlOP&m{_SnplLPdpqYQM#(Y;-{!XBlT2_KXZO;ljwV1%Pp@~2D;JPjR{^Rm# zmTFbZa7oMGKM`+#FxxUnL!CYJ26oY~u{~bC@SU+!bU@p@=L-s0Sma=y7bauZ@J|Yt z@;y{bL47xAv{1R-4X4WrpNO4ILMWFl_ieA;U(NOGg~kaT{Zz{>@&baO-FmyDa-&m*Xp4UE z^1%LZ;Imh%{eAVqh6DAs@#(~>DgT5G4@-`;%{*c8Io=5XLE`W@i*t}%;Gfvxx9jJ# z(Ip=-c{0{!K+z5#x|BXcyybC#-`y_@JK5!b#Gt}YDJKE@G=og!Z z3a<&P*so~7LhiS(bpJ^8<11GYwP0iAd#O|)1I&Mz6X3;daISm*KR_PXkGW#=`FZ?b z$$1Xsl%3sfpr5jZ5{W$6X1pasTPjl+h?&5KGy|=;F_uZMnK*;XKdio=%_`c>NLjBQ zxa-k(*jYGL+L}I(B}~e9pYrBCH%d%4irv{qoFzsO$3~nl+l(u~u_4Srj<4m4v zm(cnpDmPIR-2^8%ETspD#OKTfM&x!7e@R2uUa{lI)T%jjx^xvajb50B;^8SC=*R$ z!4n9P0W>n^(zHc1pCFX}LRM+66}a#kH8`Z3^=Xv$=-NfwR&yhEf!%{%cH^qnD2X+D zed!;ZvZfV$-4v&;y)m-qB7Mc?4ELn*QzawK{ER;(RGH>b*w7vjGeN$F9_ z!Nua%IocYb3TfFIEy?yo@F2J|Ot$5zoGyjRE@{?bPuYxG;`TBBP}t`6uXXER6VBMD z&iVNcIN45E*-qH`wgh;#_&9brSgi$TkRUf^*Kvi;>HJ>%l6&=*{`AoVuZ$}M053%i zXPWiMon;liFFRrTU+%sTe)gl9*{6cIsA^UR8o+@P_(s&*ryC2)=KNxuxP%iq^;R)p z-NM!SgPRrFKd<~WDVU)~!hPSF_QuTXTSyk;u3fdSkVqnmQ@chbL1W~WG_!ZCgmOB;M5hsE-WI7$1JR~o zByAN}X8mZbYPD6IvP_dZkbzik8Eb)oc&Z#{r37c@?tOt5sjDff&EdQrIepQf5_@zr zg~rzW$G0h__>Uo}qa$}U%vvpLw^i%6UMp*f%XL@@pOS<boT0h|y!&J(7qv!#o9DbDDqA5;+na3XBUQeGxzJm?7f zke?Gj-YgsJa_X;Ey$0~M^H(9a&V~!rV-NrhJ{FqUStlx2edWB_tF3S-RVO#38cr$8 zOKiiG#shd)O0tSs#YVB%LN2VaOC?i2HeS+#2KZh?bfYNl%=ePnlkE zK}g)cr`ca4A9(0)e%A@Mv&+}F3-Xm;-Ee;Y{Hi$*p`q>?`$hx7RQen4Gx*gqMNR9B z0&Qt8YyV{2Ge$@r)t?<1k_7)>HYW*eem)R&rH6a7zZceJ{@xi*dSGG{kK0AX3X;P;%E&}hVYm(TF*A_HYeV-fSp^3fe~>d#)TFJCPa;IS;{+*rUV0H-G{vgK1=l4 z3+!TbejVESc$1(SdqOzRwrHx(ilsTW@YzvY#hB1JskQE}qcS@NNE{hFfILl7OQXo- zL|XL|xuBvmL?D}lAj+kEm@%s!^S^VAznE?cU9K^uV&RWBl%T&fe#Xdkp zX+Ruii%m}liO|x$6LUV@sq0;9JI#<%^)o!O`6`#Fdxg15G;eR=?QO|;>7cXHue&;8 zlIQ-6Q|gi5ZlzChVu5kHOyqDZjHXGv*;lG(4B}$QEu((f z5v$D{wWZsgbJ?4E*_(IS`%4?%8SGgnD^6bLxq=qtw12TG80Eg<)(;wOSUZ+0rXw7d z^-aZ;W_3#+xXnawHl6K2qNI0tzspab-#sW`^uP>ki)s-K@q@IRM{YJOZdm`1<9 z^|EZfiGPj~dF5+ls5U@9{#pCS!}V9s8FV~yt$(%0QLve+XqPt0wKcowbaS zuU2jB_psMz`r0otM$34ic4 zd~fSdIAgDhtXK%^+!r8 z{vntidpntVagq5mKLQ{DcH$e@^C6jruD|pB6ogqgdDmoy*8gD30<~ZYTQw;0peJ_T_`M4hyl0AUF{|cVRB-Y7x+3uBPdG{EFJi=vZt-|&!0_g$BpVD! zGn$(xEEgs(GyG(W`Z?EE#rb{G2iQXebO%}jD?MG9D(TIx9om4e1YCKS!P z{UCCP9$+TL3ugwOKmD5WiZWO$jd#y2hCNQSbR(Qm3nFq7%St^{`4ba5&Knwk>!1*} zYQk^vIx<_x${9BL5$RACk1z>wW7bNV-oo*_jYgQad9Js4n6o)Qr+$~TgQ>*@w{}!U znU}d&qN)r{69i{@XyUi5WRGtCdLt+64&4`Sg9qi}9R8E8)iMB#R`BB`y(e!QjDMmq z&5^9~Y2Y0nH2;3$aT+!>C#VL-$fWT(R^imvm=3ob_x~|lmTVxFAGil zGI_evE+wH1$deEtb^z~2l#&hW1YgZSOjkR~U{h%&SA@YalSJg+mN_bw(`?`RnsQQM znKjA)8UOx>;8@?73jfv8aqh--nxpQ-_D#$w;^B8lQ=!PX)dbCs&dr4}z2`D#uI7PD z=$mA{)vh)20B`~|(Ne{FgZ`w@zSkk~m|5honf77-VX;<&YIG@qR*=WTW_~3{wBF9}wxU`;ZNu+4x%QAHcuEv90c50qZSzC(o<|TF+59 zZFu>7xC`cN;8V}(M;+I9(i>D*u#cJC^Z?FM&kqt(#9^NBd!DMIr9i7|Z3M|!W7{EK zquW!>_L(eW6T=s0JvF-yG1CSyliFVaaoP}*>)h=iNkqVgd!4p6y18T4Tsu=;BKNDt z*+VnFJNwHMk!K6GMy!uQGDg&8z6KiJ6(W%OwmBUKbR`pJo>M=0dDUBf_DXnDUV*D~ z5}`hC_l}<^ouyrD_r6cq5iKL33oM&yf2?4!t5wohw&P=Oo#kv@Vd2$1 z6Zt|@(p*tZy^sq>U}3=;5l$u0J0Ifk+yqpiNjs&@&5Y(TAi@7E^fHdob@-zdU! z{^)OvX>yK*omt;0a*@txA4ffQ+dKOF1jmx68^vSaeUzWhDHJz@*Nmr+np?w`Gka%M z&kvHAhnhy2fQ#kS0r+)%Idi7-pZPz->nCfhmrj-XlK=obkVAduK5P`^U!?MKwkBW6 zK1}s}G^^oj{cbo{ai(@d>(9UCsJZ27Z&S4H5$y4+4IcZi9Pi|Pf-Rxx1wf94lnpE_ zEFmobQi<>j{9Q49cT~skWyjcpQn;AmJz{?r*@diqHf&;UJDS!;-{<()RszP>oriCf zBV;sx!GH!ea|}7ko1`_d7`%Lg2*r@b4(-jA|JjVu{io>ffw`ZLs;HLPrkRNU*|NhC zyom%9ywGKDpvhA6^+&|3zu8=3f__~lrzXddmi-AzUTPO;5EqW{>4!L{+X)1wsW>kY z3pvS26k>XD3RY!%GxH-&E^*45+jN`G!MCw*a7aMn8-&z*0>H&~g9OsgoFD<*g1|}0 zf=r)Z=S822p}U%XIv7w^|H4wFt86GOXXejvzUk@omGjFT=rf2ax0H(Bvaj{##EzD+S`Z|W!!x;Ly>#fVj-qv+Dh4H16q{Qvc!Xdt^ z*!?4k(gLSpah2;nmDV!Y;t@mxuUuI?48UXDfD-qk;fU6{dG)U$xFo7%TZ1Y+Qw3O=P%c#O|BuNPpw6nTZNg85xnw>UoN%gw`y8OSqh`8wNC1!1`K8n zGPF;#v>~hgyq-KgTn*KF27g-=s@F^nEm%=1d4|5N>|BoxTJTSeE6R>%X1L6;KjGNC z2}oSn_jhH+zHviXW)LH?yq-f02>ZdJ9x0-%AJ$H@ zu5GBR2fIL=Apv6rAsH}*BYbTHp+&MQ$$?~2c#A}fv;@C36Xie)Z{lQ^sV=PC3G8GO z`=5}!YDGV$$sI>Z1p!rC*dSoxT?rtFhZ_ADWLX{X7auFc;HSdgd^P@2v3|?Wg3>rT z(gK-)n18eo+rvtyYCe47J1qLf1PScPv3SERSvpUyJU?-sAWVV(af<})D?W5rU7BK{ zf7h>Y2R#}lnvc5k^>V)}YRk%Nn!nK=TQ3*yG(y$a;B~mkBoZNj(~8^$yRCF~b>Wp&BMDlj(gZ9d zqRHIFSX{+>&Z@EmthBga3hNeB;`^c+hHVJ3GaKPoV_gdfev*Ykx3mIBFv=>kf@P>} zA@EXJDPkxjlD;1&Km{|Y=7}|mbMqyt`PF37N+OAarcQ|+vQd9V`2!bpXLg7h_`W?N zv>v?Lf3Yd5_{dUAOUPoqs?pfm;WbIs9GysMZh?@&dc4Z=n~!yg3HdqT9yoX~WHrx!d(u=;DfR|hp>^_;5e#w))# zmKA+QkTdz!a*veFTuo}O+WC**``O3Q~ zrXM<{5@r>9o`5`Tt3j3GAXPOe%{~;JQ+}UI?}nc~;%LCedT+AU-}qKId2j6^lPZI( zexPJ&j3A}{gA1%{3Dl=SuJt(NrZHVg^1DMOQ@0k2D`=;(eLdb9TxR8WE2x1aR&R zY@YgCW$ss3XRKf*%$h5BcRMeRS8ih#>#!i#;! zZ$7~Xl&JwbxNO}#<5A+`qXA`+pAhHi6sR~=DcE}Lnq31Yu0ijEW-3#&jEMLY%aV9- z2PFg4!9oB}rr~h>g#6(KM?lWxucsZBRGBa2=bAslcR60i_?=d7W_*p8OK~ql^pZ5W z%@bI$XFbZSoD3Km%|D4HpuUPT7=wVzctv&cy?2ImT7clDdv6D(9Z;tk zL!kL}lh1;j1j8Q=Zh@X>;3XKhEg8LLn)zFC5b>?v+Nzo|ev*oMCT{&?(v`=(24$9Mv2 zN2FA%l?@91{(IgC4tR1F425lh4zNeju&m?P8MPCsuYGBgo?tsij^LjwY_*q5ulH}B zUSf(VSFT7GEw+ttS;yD~PubkW)hsU^Wq=8gAenN467->e6+o8|E)48R;fbWoY7U+^N-&k&33}avE#=wvsGs^M0=TBn=6>)9X@mG5_yT$;5vzP*ZIh#FdQ3vYK8?V!`MY}v_>!9XQNFi zK&hx8FIyvOjCt5aj4}6+s-LF#iTYqulQw~y)I>z?WY+rZ?#^d^a<*I|tTvsbqTQe0 z{#rZvpqw>1E4QC8jm;sZKg}zB#$;|v;tY|LJU=>1uTQ#izmT&k{`cw>{_4~*I&S?2 zP?4(sX-r$40e6?-(99k;_D%NR=_1|1#J-s^9N4q6L6Edgl(il(*iB6Zm+5rNbHwJ# z*zeyd3~0SQKTUTM54}>LJ)xrrgdD7e5cr@jJfBwIub*x-hMXls9j*_)lPtfZac*oR zc=j)>>C)@78aC)kv1VEEa$D~o{_tN@^6}HW1nMLeT|Og74XJ@TUvnBYOHNgPri&{M zn1)9`XX0T}xI;QIr-jN~Bk~uRt7qdexgh6lwFZR1`YsTEfYVyo2nx{t{1FO52nF