SRxCore.psm1
#Region '.\Enum\SRxLogLevel.ps1' 0 enum SRxLogLevel { SILENT ERROR WARNING INFO VERBOSE DEBUG } #EndRegion '.\Enum\SRxLogLevel.ps1' 9 #Region '.\Classes\CustomizationWriter.ps1' 0 class CustomizationWriter { hidden $_connection = $null hidden $_onStatusCallback = $null hidden [string] $_environment = $null hidden [string] $_siteDesign = $null hidden [string] $_termID = $null hidden [int]$_scope = -1 #-------------------- # Constructor #-------------------- CustomizationWriter () { Write-Host " > constructor CustomizationWriter" Connect-SRxProvisioningDatabase_JSON $this._connection = Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl } hidden [pscustomobject] getProvisioningTerm( $hostingTerm, [string]$name ) { $customizationTerm = $null try { Get-PnPProperty -ClientObject $hostingTerm -Property Name, Id, Terms -Connection $this._connection | Out-Null $hostingTerm.Terms | ForEach-Object { $nodeName = $($_.Name).trim() $n = $name.trim() $e = $nodeName.equals($n) if( $e ) { $customizationTerm = $_ } } if($customizationTerm) { Write-Host " > Found $name term." } } catch { Write-Host ($_.Exception.Message) Write-Host ($_.Exception) return $null } return $customizationTerm } hidden [boolean] setTermCustomProperties( $customizationTerm, [hashtable]$customProperties ) { try { $customProperties.Keys | ForEach-Object { #Write-SRx WARNING " | - $_ = $($customProperties[$_])" $key = $_ $value = $($customProperties[$_]) Write-Host("value length = $($value.Length)") $chunks = @($value -split '(.{9000})' | Where-Object{$_}) Write-Host("chunks count = $($chunks.Count )") if($chunks.Count -eq 1) { $customizationTerm.SetCustomProperty($key, $value) } else { # key_0, key_1 ... for( [int]$i = 0; $i -lt $chunks.Count; $i++) { $chunk_key = "$($key)_$($i)" $chunk_val = $($chunks[$i]) #Write-Host ("$i = $($chunks[$i])") $customizationTerm.SetCustomProperty($chunk_key, $chunk_val) } } } } catch { Write-Host ($_.Exception.Message) Write-Host ($_.Exception) return $false } return $true } hidden [pscustomobject] setProvisioningTerm( $hostingTerm, [string]$name, [hashtable]$customProperties, $isCustomizationTarget, $remove, [boolean]$disable) { try { $customizationTerm = $null Get-PnPProperty -ClientObject $hostingTerm -Property Id, Terms -Connection $this._connection | Out-Null $hostingTerm.Terms | ForEach-Object { if( $($_.Name) -eq $name) { $customizationTerm = $_ } } if(-not $customizationTerm -and -not $remove ) { #Write-SRx VERBOSE " > Creating new term..." $customizationTerm = Add-PnPTermToTerm -ParentTerm $hostingTerm.Id -Name $name -CustomProperties $customProperties -Connection $this._connection #Write-SRx VERBOSE " > Added new term $name id=$($customizationTerm.Id)" Start-Sleep -Seconds 5 #added wait to avoid save conflict try { if($disable) { Write-Host " > Deprecating $name term...." $customizationTerm.Deprecate($disable) Invoke-PnPQuery -RetryCount 5 -Connection $this._connection } #Write-SRx VERBOSE " > Term $name id=$($customizationTerm.Id) created." } catch { Write-Host " ? Failed to deprecate $name term." } Write-Host " > Created $name term." } else { if( $isCustomizationTarget ) { if($remove) { $id = $customizationTerm.Id #Write-SRx VERBOSE " > Deleting term id = $id" Remove-PnPTerm -Identity $id -Connection $this._connection Write-Host " > Deleted $name term" } else { <# $customProperties.Keys | ForEach-Object { #Write-SRx WARNING " | - $_ = $($customProperties[$_])" $customizationTerm.SetCustomProperty($_, $($customProperties[$_])) } #> $this.setTermCustomProperties( $customizationTerm, $customProperties) if($disable) { Write-Host " > Disabling $name term ..." } else { Write-Host " > Enabling $name term ..." } $customizationTerm.Deprecate($disable) #} Invoke-PnPQuery -RetryCount 5 -Connection $this._connection #Write-SRx VERBOSE " > Term $name id=$($customizationTerm.Id) updated." Write-Host " > Updated $name term" } } else { Write-Host " > Term $name found." } } return $customizationTerm } catch { Write-Host ($_.Exception.Message) Write-Host ($_.Exception) return $null } } hidden [pscustomobject] addProvisioningTerm($hostingTerm,[string]$name,[hashtable]$customProperties, $isCustomizationTarget, $remove, [boolean]$disable) { try { $customizationTerm = $null if( -not $isCustomizationTarget) { $customizationTerm = $this.getProvisioningTerm( $hostingTerm, $name) } if( $customizationTerm ) { return $customizationTerm } else { if( $isCustomizationTarget ) { if($remove) { $customizationTerm = $this.setProvisioningTerm( $hostingTerm, $name, $customProperties, $true, $true, $disable) } else { $customizationTerm = $this.setProvisioningTerm( $hostingTerm, $name, $customProperties, $true, $false, $disable) } } else { $customizationTerm = $this.setProvisioningTerm( $hostingTerm, $name, $customProperties, $false, $false, $disable) } return $customizationTerm } } catch { Write-Host ($_.Exception.Message) Write-Host ($_.Exception) return $null } } hidden [pscustomobject] newPnPXMLTermNode($hostingTerm, $termNode, $isTopNode, $isCustomizationTarget) { $disable = $false $customProperties = @{} $nodeName = $termNode.Name foreach ($attr in $termNode.Attributes.GetEnumerator() ) { if( $attr.Name -eq "ts_KeyAttributeValue" ) { $nodeName = $attr.Value $customProperties.add($attr.Name, $attr.Value) #add ts_KeyAttributeValue } elseif( $attr.Name -eq "ts_Deprecated" ) { $disable = [boolean]$($attr.Value) } #bypass ts_Deprecated elseif( $attr.Name -eq "ts_Scope" ) {} #bypass ts_Scope else { $customProperties.add($attr.Name, $attr.Value) } } #if($isCustomizationTarget) { $customProperties.add("ts_CustomizationTarget", $true) } #replace not allowed for use at term title symbols $nodeName = $nodeName.Replace('|','-') $nodeName = $nodeName.Replace(';','-') $nodeName = $nodeName.Replace('"','-') $nodeName = $nodeName.Replace('<','-') $nodeName = $nodeName.Replace('>','-') $nodeName = $nodeName.Replace('&','-') if( $isCustomizationTarget ) { if($termNode.Operation -eq "remove") { return $this.addProvisioningTerm( $hostingTerm, $nodeName, $customProperties, $true, $true, $disable) } else { return $this.addProvisioningTerm( $hostingTerm, $nodeName, $customProperties, $true, $false, $disable) } } return $this.addProvisioningTerm( $hostingTerm, $nodeName, $customProperties, $false, $false, $disable) } hidden [pscustomobject] getSiteTerm() { $siteTerm = $null if( $this._scope -eq 3) { $siteTerm = Get-SRxHostingTerm -Site -siteID $this._termID -Connection $this._connection } #Site elseif( $this._scope -eq 2) { $siteTerm = Get-SRxHostingTerm -Design -siteDesign $this._siteDesign -siteEnvironment $this._environment -Connection $this._connection } #Design elseif( $this._scope -eq 1) { $siteTerm = Get-SRxHostingTerm -Environment -siteEnvironment $this._environment -Connection $this._connection } #Environment elseif( $this._scope -eq 0) { $siteTerm = Get-SRxHostingTerm -Profile -Connection $this._connection } #Profile return $siteTerm } hidden [pscustomobject] setProvisioningBlock([int]$optype) { try { $termDesign = $null if( $optype -eq 0 ) { $siteTerm = Get-SRxHostingTerm -Sites -siteDesign $this._siteDesign -siteEnvironment $this._environment -Connection $this._connection } #sites else { $siteTerm = $this.getSiteTerm() } #environment, design, site if($null -eq $siteTerm ) { Write-Host $(" > Hosting term for target customization scope not found") return $null } # Provisioning template Write-Host $(" > Target customization scope: $($siteTerm.Name)") #-ForegroundColor Cyan #return $null $tID = $siteTerm.CustomProperties.ts_ProvisioningTemplate #Write-SRx VERBOSE $(" > ts_ProvisioningTemplate = $tID") $provisioningTemplateTerm = $null if($tID -and ($this._scope -ne 2)) { #for environment & site, exdesign scope from Master only $provisioningTemplateTerm = Get-PnPTerm -Identity $([GUID]$tID) -ErrorAction SilentlyContinue -IncludeChildTerms -Connection $this._connection } if( $null -eq $provisioningTemplateTerm) { #$decisionCreate = $Host.UI.PromptForChoice("", "Create provisionig block ?:", @('&Yes'; '&Cancel'), 1) #if( $decisionCreate -eq 1) { # return "cancelled" #} if( $this._scope -eq 0 ) { # Profile --> termSet Write-Host $(" > Creating profile provisioning block ...") $provisioningTemplateTerm = New-PnPTerm -TermSet $siteTerm.Name -TermGroup $global:SRxEnv.Tenancy.TermGroupName -Name "Customizations" -Connection $this._connection # -CustomProperties $customProperties } elseif($this._scope -eq 2) { #Design --> term $termDesign = Get-SRxHostingTerm -Design -siteDesign $this._siteDesign -siteEnvironment "Master" -Connection $this._connection if( -not $termDesign ) { $termDesign = Set-SRxTermDesign -siteDesign $this._siteDesign -siteEnvironment "Master" -Connection $this._connection } $dID = $termDesign.CustomProperties.ts_ProvisioningTemplate if($dID ) { $provisioningTemplateTerm = Get-PnPTerm -Identity $([GUID]$dID) -ErrorAction SilentlyContinue -IncludeChildTerms -Connection $this._connection } if( $null -eq $provisioningTemplateTerm) { Write-Host $(" > Creating design provisioning block ...") $provisioningTemplateTerm = Add-PnPTermToTerm -ParentTerm $termDesign.Id -Name "Customizations" -Connection $this._connection # -CustomProperties $customProperties } } else { #Term --> term Write-Host $(" > Creating provisioning block ...") $provisioningTemplateTerm = Add-PnPTermToTerm -ParentTerm $siteTerm.Id -Name "Customizations" -Connection $this._connection # -CustomProperties $customProperties } if($null -ne $provisioningTemplateTerm) { $s = [string]$($provisioningTemplateTerm.Id) if($this._scope -eq 2) { $termDesign.SetCustomProperty("ts_ProvisioningTemplate", $s) } else { $siteTerm.SetCustomProperty("ts_ProvisioningTemplate", $s) } Invoke-PnPQuery -RetryCount 5 -Connection $this._connection #Write-SRx VERBOSE $(" > ts_ProvisioningTemplate = $tID") } else { Write-Host $(" > Failed to create customization block") } } else { #Write-SRx INFO $(" > Target customization scope: $($siteTerm.Name)") -ForegroundColor Cyan } return $provisioningTemplateTerm } catch { Write-Host ($_.Exception.Message) Write-Host ($_.Exception) return $null } } [void] Start ([pscustomobject]$nodesPack) { #Write-Host "2: ------------ received nodePack -------------------" #Write-Host ( $nodesPack | FL | Out-String ) [System.Collections.Stack]$nodesStack = $nodesPack.nodesStack $this._environment = $nodesPack.ts_Environment $this._siteDesign = $nodesPack.ts_Design $this._termID = $nodesPack.ts_TermID #Write-Host ("2: environment = $environment siteDesign = $siteDesign termID = $termID") #Write-Host "2: ------------ received nodeStack -------------------" #Write-Host ( $nodesStack | FT | Out-String ) #Write-Host "2: ------------ termNode -------------------" $termNode = $nodesStack.Peek() #Write-Host ( $termNode | FT | Out-String ) $this._scope = $($termNode.Scope) #[int]$($termNode.Attributes["ts_Scope"]) [string]$operation = $($termNode.Operation) [int]$optype = $($termNode.optype) # Write-Host ("3: environment = $($this._environment) siteDesign = $($this._siteDesign) termID = $($this._termID)") $count = $nodesStack.Count $max = $count + 1 $counter = 0 #$Activity = "Creating customization filter on TermStore" $status = "Setting customization block ..." #Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $max) * 100) $counter++ if($this._onStatusCallback) { $this._onStatusCallback.Invoke( $status, $counter-1, $max )} $hostingTerm = $this.setProvisioningBlock($optype) if( $null -eq $hostingTerm) { Write-Host " > Failure - no host ?" #return 'cancelled' return } #return $index = 1 $parentTerm = $hostingTerm do { $termNode = $nodesStack.Peek() $status = "Adding $($termNode.Name) node ..." #Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $max) * 100) $counter++ if($this._onStatusCallback) { $this._onStatusCallback.Invoke( $status, $counter-1, $max )} #Start-Sleep -Seconds 2 if($index -eq $count) { if($index -eq 1) { $term = $this.newPnPXMLTermNode($parentTerm, $termNode, $true, $true) } else { $term = $this.newPnPXMLTermNode($parentTerm, $termNode, $false, $true) } } else { if($index -eq 1) { $term = $this.newPnPXMLTermNode($parentTerm, $termNode, $true, $false) } else { $term = $this.newPnPXMLTermNode($parentTerm, $termNode, $false, $false) } } if($null -eq $term) { Write-Host " > Failure - ?" return #'cancelled' } else { $parentTerm = $term } $nodesStack.Pop() $index++ } while( $($nodesStack.Count) -gt 0 ) Write-Host " > Completed termstore modifications" } #------------------ # Callbacks #------------------ [void] OnStatusCallback( [System.Management.Automation.ScriptBlock]$onStatusCallback ) { $this._onStatusCallback = $onStatusCallback } } #EndRegion '.\Classes\CustomizationWriter.ps1' 348 #Region '.\Private\_Cache.ps1' 0 function _Cache { $s = $global:SRxEnv.MasterCachePath $s = $s -replace "MasterCache","" if (!(Test-Path $s)) { Write-SRx VERBOSE "Create the new folder $($s)" New-Item -itemType Directory -Path $s | Out-Null } Start-Process explorer $s } $global:_Cache = { $s = $global:SRxEnv.MasterCachePath $s = $s -replace "MasterCache","" if (!(Test-Path $s)) { Write-SRx VERBOSE "Create the new folder $($s)" New-Item -itemType Directory -Path $s | Out-Null } Start-Process explorer $s } #EndRegion '.\Private\_Cache.ps1' 21 #Region '.\Private\_Docs.ps1' 0 function _Docs { Start-Process ((Resolve-Path ".\..\Provisioning User Guide.pdf").Path) } $global:_Docs = { Start-Process ((Resolve-Path ".\..\Provisioning User Guide.pdf").Path) } #EndRegion '.\Private\_Docs.ps1' 7 #Region '.\Private\_EcoSystem.ps1' 0 function _EcoSystem { $str = $global:SRxEnv.DefaultSettings.EcoSystem.ts_URL $str = $str.replace('{tenancy}', $global:SRxEnv.Tenancy.Name) $ecosystemURL = $str.replace('{ecosystem}', $global:SRxEnv.Tenancy.EcoSystem) Start-Process $ecosystemURL | Out-Null } $global:_EcoSystem = { $str = $global:SRxEnv.DefaultSettings.EcoSystem.ts_URL $str = $str.replace('{tenancy}', $global:SRxEnv.Tenancy.Name) $ecosystemURL = $str.replace('{ecosystem}', $global:SRxEnv.Tenancy.EcoSystem) Start-Process $ecosystemURL | Out-Null } #EndRegion '.\Private\_EcoSystem.ps1' 13 #Region '.\Private\_Pipeline.ps1' 0 <# function _Pipeline { $s = $global:SRxEnv.MasterCachePath $s = $s -replace "MasterCache","" $s = $s + "Scheduler\Pipeline.json" Start-Process notepad $s } #> function _Pipeline { $global:SRxEnv.Pipeline } #EndRegion '.\Private\_Pipeline.ps1' 12 #Region '.\Private\_Profile.ps1' 0 function _Profile { Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -Command "".\shell.ps1 -Setup -Verbose""' #Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -Command "".\shell.ps1 -Setup""' } $global:_Profile = { Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -Command "".\shell.ps1 -Setup -Verbose""' #Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -Command "".\shell.ps1 -Setup""' } #EndRegion '.\Private\_Profile.ps1' 9 #Region '.\Private\_Shell_Verbose.ps1' 0 function _Shell_Verbose { Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -Command "".\shell.ps1 -Verbose""' } $global:_Shell_Verbose = { Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -Command "".\shell.ps1 -Verbose""' } #EndRegion '.\Private\_Shell_Verbose.ps1' 7 #Region '.\Private\_Shell.ps1' 0 function _Shell { #Start-Process cmd.exe -ArgumentList "/c .\..\shell.bat" Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -File "".\shell.ps1""' } $global:_Shell = { Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -File "".\shell.ps1""' } #EndRegion '.\Private\_Shell.ps1' 8 #Region '.\Private\_Task.ps1' 0 function _Task { #Start-Process cmd.exe -ArgumentList "/c .\..\task-verbose.bat" #Start-Process cmd.exe -ArgumentList "/c .\..\task.bat" Start-Process PowerShell -ArgumentList '-NoProfile -noexit -ExecutionPolicy Bypass -Command "".\shell.ps1 -Verbose -ControlFile Update-Pipeline""' } #EndRegion '.\Private\_Task.ps1' 6 #Region '.\Private\Add-Types.ps1' 0 Add-Type -assembly System Add-Type -assembly System.Runtime.InteropServices $TypeDefinition = @' using System; using System.Runtime.InteropServices; namespace Win32Functions { public class Win32Windows { [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr hWnd); } } '@ Add-Type -TypeDefinition $TypeDefinition #-PassThru #EndRegion '.\Private\Add-Types.ps1' 17 #Region '.\Private\ColorByLevel.ps1' 0 function ColorByLevel { <# .SYNOPSIS synopsis .DESCRIPTION description .NOTES ========================================= Project : Search Health Reports (SRx) ----------------------------------------- File Name : Module-Name.psm1 Requires : PowerShell Version 5.1, Search Health Reports (SRx), Microsoft.SharePoint.PowerShell, Patterns and Practices v15 PowerShell ======================================================================================== This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys' fees, that arise or result from the use or distribution of the Sample Code. ======================================================================================== .INPUTS input1 .EXAMPLE Module-Name #> [CmdletBinding()] param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)] $line, [switch]$IncludeDetails ) BEGIN { [System.ConsoleColor]$defaultColor = (Get-Host).UI.RawUI.ForegroundColor if($defaultColor -lt 0) {$defaultColor = [System.ConsoleColor]::White} $incomingResultRow = 0 $headlineOffset = 0 } PROCESS { if([string]::IsNullOrEmpty($line)){return} $words = $line.Split() if ($incomingResultRow -eq 0) { $headlineOffset = $line.indexOf("Headline") #Write-Host $("-" * $line.length) -foregroundColor Cyan #Write-Host $line -foregroundColor Cyan #Write-Host $("-" * $line.length) -foregroundColor Cyan } $count = 0 foreach($word in $words[1..$words.Length]) { if([string]::IsNullOrWhiteSpace($word)) { $count++ continue } $level = $word break } switch($level) { "INFO" { Write-Host $words[0..$count] -NoNewline Write-Host " Info " -NoNewline Write-Host $words[($count+2)..$words.Length] -ForegroundColor $defaultColor } "Normal" { Write-Host $words[0..$count] -NoNewline Write-Host " $level " -ForegroundColor Green -NoNewline Write-Host $words[($count+2)..$words.Length] -ForegroundColor $defaultColor } "Warning" { Write-Host $words[0..$count] -NoNewline Write-Host " $level " -ForegroundColor Yellow -NoNewline Write-Host $words[($count+2)..$words.Length] -ForegroundColor $defaultColor } "Error" { Write-Host $words[0..$count] -NoNewline Write-Host " $level " -ForegroundColor Red -NoNewline Write-Host $words[($count+2)..$words.Length] -ForegroundColor $defaultColor } "Exception" { Write-Host $words[0..$count] -NoNewline Write-Host " $level " -ForegroundColor Magenta -NoNewline Write-Host $words[($count+2)..$words.Length] -ForegroundColor $defaultColor } "Skipped" { Write-Host $words[0..$count] -NoNewline Write-Host " $level " -ForegroundColor DarkGray -NoNewline Write-Host $words[($count+2)..$words.Length] -ForegroundColor $defaultColor } default {Write-Host $line} } if ($IncludeDetails -and ($incomingResultRow -gt 1) -and ($dataList[$incomingResultRow-2].Details -ne $null)) { if ($dataList[$incomingResultRow-2].Details -is [String]) { $details = $dataList[$incomingResultRow-2].Details.split("`n").trimEnd() } else { $detailStrings = $dataList[$incomingResultRow-2].Details | foreach { $_ | Out-String -stream } $details = $detailStrings | foreach { $_.trimEnd() } } $lineInDetails = 0 foreach ($d in $details) { if ($d.length -gt $($Host.UI.RawUI.WindowSize.Width - $headlineOffset - 1)) { $d = $d.Substring(0, $($Host.UI.RawUI.WindowSize.Width - $headlineOffset -3)) + "..." } if (($lineInDetails -eq 0) -or ($details[$lineInDetails-1] -ne $details[$lineInDetails] -ne "")) { Write-Host $(" " * $headlineOffset) -NoNewline Write-Host $d } $lineInDetails++ } } $incomingResultRow++ } END {} } #EndRegion '.\Private\ColorByLevel.ps1' 148 #Region '.\Private\Format-SRxFileShortcut.ps1' 0 function Format-SRxFileShortcut { param ( $programs, [string]$startMenu, [switch]$remove ) begin { try { $programs.GetEnumerator() | ForEach-Object { $shortcutPath = if( $null -ne $($_.Value[1])) { "$startMenu\$($_.Value[1])\$($_.Key).lnk" } else { "$startMenu\$($_.Key).lnk" } $folderPath = if( $null -ne $($_.Value[1])) { "$startMenu\$($_.Value[1])" } else { "$startMenu" } if (-not (Test-Path $shortcutPath) -and -not $remove ) { if (-not (Test-Path $folderPath)) { write-host ("Specified folder {0} doesn't exist for the {1} shortcut, creating now..." -f $_.Value[1], $_.Key) New-Item -ItemType Directory -Path $folderPath -Force | Out-Null write-host ("Creating shortcut for {0} with path {1} in folder {2}..." -f $_.Key, $_.Value[0], $_.Value[1]) } else { write-host ("Shortcut for {0} not found with path {1} in existing folder {2}, creating it now..." -f $_.Key, $_.Value[0], $_.Value[1]) } $shortcut = $shortcutPath #"$startMenu\$($_.Value[1])\$($_.Key).lnk" $target = $_.Value[0] $windowStyle = $_.Value[2] $arguments = $_.Value[3] $description = $_.Key $workingdirectory = $SRxEnv.Paths.SRxRoot $WshShell = New-Object -ComObject WScript.Shell $Shortcut = $WshShell.CreateShortcut($shortcut) $Shortcut.TargetPath = $target $Shortcut.Description = $description $shortcut.WorkingDirectory = $workingdirectory $Shortcut.WindowStyle = $windowStyle $Shortcut.Arguments = $arguments $Shortcut.Save() } elseif($remove) { Remove-Item $shortcutPath #$shortcutPath"$startMenu\$($_.Value[1])\$($_.Key).lnk" } } } catch { } } } #EndRegion '.\Private\Format-SRxFileShortcut.ps1' 52 #Region '.\Private\Format-SRxJson.ps1' 0 function Format-SRxJson { <# .SYNOPSIS Prettifies JSON output. .DESCRIPTION Reformats a JSON string so the output looks better than what ConvertTo-Json outputs. .PARAMETER Json Required: [string] The JSON text to prettify. .PARAMETER Minify Optional: Returns the json string compressed. .PARAMETER Indentation Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4. .PARAMETER AsArray Optional: If set, the output will be in the form of a string array, otherwise a single string is output. .EXAMPLE $json | ConvertTo-Json | Format-SRxJson -Indentation 2 #> [CmdletBinding(DefaultParameterSetName = 'Prettify')] Param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [string]$Json, [Parameter(ParameterSetName = 'Minify')] [switch]$Minify, [Parameter(ParameterSetName = 'Prettify')] [ValidateRange(1, 1024)] [int]$Indentation = 4, [Parameter(ParameterSetName = 'Prettify')] [switch]$AsArray ) if ($PSCmdlet.ParameterSetName -eq 'Minify') { return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress } # If the input JSON text has been created with ConvertTo-Json -Compress # then we first need to reconvert it without compression if ($Json -notmatch '\r?\n') { $Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 } $indent = 0 $regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)' $result = $Json -split '\r?\n' | ForEach-Object { # If the line contains a ] or } character, # we need to decrement the indentation level unless it is inside quotes. if ($_ -match "[}\]]$regexUnlessQuoted") { $indent = [Math]::Max($indent - $Indentation, 0) } # Replace all colon-space combinations by ": " unless it is inside quotes. $line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ') # If the line contains a [ or { character, # we need to increment the indentation level unless it is inside quotes. if ($_ -match "[\{\[]$regexUnlessQuoted") { $indent += $Indentation } $line } if ($AsArray) { return $result } return $result -Join [Environment]::NewLine } #EndRegion '.\Private\Format-SRxJson.ps1' 70 #Region '.\Private\ListControls.ps1' 0 function ListControls { Write-Host Write-Host "Control files that evaluate the test $Test" -ForegroundColor Cyan $__ruleControlFiles | % {$filename=$_.FullName;Get-Content $filename} | ? {$_ -match $Test.Substring(0,$Test.Length-4)} | % {$filename} } #EndRegion '.\Private\ListControls.ps1' 7 #Region '.\Private\ListTests.ps1' 0 function ListTests { Write-Host Write-Host "Test evaluated by control file $ControlFile" -ForegroundColor Cyan foreach($rule in $__rulesControl) { Write-Host " $($rule.Rule)" } } #EndRegion '.\Private\ListTests.ps1' 10 #Region '.\Private\LoadModule.ps1' 0 $LoadModule = { param( $name, $version, $debug2) #param( $name ) if( -not $global:SRxEnv.Exists ) { $coreConfigPath = $("$PSScriptRoot\Config\core.config.json") $coreConfigRaw = Get-Content $coreConfigPath -Raw -Encoding UTF8 -ErrorAction Stop $coreConfigJSON = ConvertFrom-Json $coreConfigRaw $version = $coreConfigJSON.Version $debug2 = $coreConfigJSON.Debug } $version = if ( $null -eq $version ) { $global:SRxEnv.Version } else { $version } $debug = if ( $null -eq $debug2 ) { $global:SRxEnv.Debug } else { $debug2 } $module = $null if ($debug) { #write-host "Module $name debug $version imported." if ($debug2) { $module = $("$PSScriptRoot\Modules\$name\$version\$name.psm1") } else { $module = $(Join-Path $global:SRxEnv.paths.SRxRoot "Modules\$name\$($global:SRxEnv.Version)\$name.psm1") } if ( -not $(Test-Path -path $module -PathType Leaf )) { $module = $null } } # If module is imported say that and do nothing if (Get-Module -Name $name -Verbose:$false ) { #write-host "Module $name $version is already imported." } else { if ($debug -and $module) { write-host "Module $name local source code version $version imported." Import-Module $module -DisableNameChecking -Verbose:$false -Force } else { # If module is not imported, but available on disk then import if( $null -ne $(Get-Module -ListAvailable -Name $name -Verbose:$false | Where-Object { $_.Name -eq $name -and $_.Version -eq [System.Version]$version })) { if ( (Import-Module -Name $name -RequiredVersion $version -PassThru -Verbose:$false -WarningAction silentlyContinue).Version -eq [System.Version]$version) { #write-host "Module $name $version imported." } else { write-host "Module $name $version not imported, exiting." EXIT 1 } } else { write-host "Module $name $version is not available on disk, trying to import and install..." # If module is not imported, not available on disk, but is in online gallery then install and import if (Find-Module -Name $name -RequiredVersion $version ) { #-MinimumVersion $version write-host "Module $name $version found." Install-Module -Name $name -RequiredVersion $version -Force -Verbose:$false -Scope CurrentUser write-host "Module $name $version installed." Import-Module $name -RequiredVersion $version -DisableNameChecking -Verbose:$false write-host "Module $name $version imported." } else { # If the module is not imported, not available and not in the online gallery then abort write-host "Module $name $version not imported, not available and not in an online gallery, exiting." EXIT 1 } } } } } #EndRegion '.\Private\LoadModule.ps1' 64 #Region '.\Private\NewSRxEventObject.ps1' 0 function NewSRxEventObject { $SRxEvent = New-Object PSObject -Property @{ "Name" = $( if ([string]::isNullOrEmpty($evaluatedRules.Name)) { $($rule.Rule) } else { $evaluatedRules.Name } ); "Result" = $( if ($evaluatedRule.Success -is [bool]) { $evaluatedRule.Success } else { $false } ); "ControlFile" = $ControlFile; "RunId" = $timestamp; "FarmId" = $global:SRxEnv.FarmId; "Source" = $( if ([string]::isNullOrEmpty($xSSA.Name)) { $ENV:ComputerName } else { $xSSA.Name } ); "Category" = $( if ([string]::isNullOrEmpty($evaluatedRule.category)) { "Undefined" } else { $evaluatedRule.category } ); "Timestamp" = $(Get-Date -Format u); "Dashboard" = $(($global:SRxEnv.Dashboard.Initialized) -and $($rule.WriteToDashboard -eq "true")); "Alert" = $( if ($alert -is [bool]) { $alert } else { $false } ); "Level" = $( if ([string]::isNullOrEmpty($msg.Level)) { "Exception" } else { $msg.Level } ); "Headline" = $( if ([string]::isNullOrEmpty($msg.headline)) { $msgHeadline = "-- The test `"" + $($rule.Rule) + "`" provided an empty message headline --" } elseif($msg.headline.Length -gt 250) { $msgHeadline = $msg.headline.Substring(0,247) + "..." } else { $msgHeadline = $msg.headline } $msgHeadline ); "Details" = $( if ($msg.details -is [String]) { $msgDetails = $msg.details.split("`n").trimEnd() } else { $detailStrings = $msg.details | foreach { $_ | Out-String -stream } $i = 0; $msgDetails = $detailStrings | foreach { if (($i -eq 0) -or ($_ -ne $detailStrings[$i-1] -ne "")) { $_.trimEnd() }; $i++ } } $msgDetails ); "Data" = $msg.Data; } #return the event object... return $SRxEvent } #EndRegion '.\Private\NewSRxEventObject.ps1' 41 #Region '.\Private\ProcessRules.ps1' 0 function ProcessRules { $global:SRxEnv.SetCustomProperty("EnableProcessRules", $true) [int]$counter = 1 [int]$maxcount = $__rulesControl.Count foreach($rule in $__rulesControl) { if( -not $global:SRxEnv.EnableProcessRules) { break } if([string]::IsNullOrWhiteSpace($rule.Rule)) {continue} $start = Get-Date $ruleTitle = $($rule.Rule).replace("Rule-","") Write-SRx INFO "Executing rule $ruleTitle..." -ForegroundColor Cyan #-NoNewline if( $null -ne $global:__onRuleCallback ) { # Write-Host ("global:__onRuleCallback = $($global:__onRuleCallback.getType().Name)") $global:__onRuleCallback.Invoke( $ruleTitle, $counter++, $maxcount ) } $ruleFile = $__ruleDefinitionFiles | ? {$_.Name -eq "$($rule.Rule).ps1"} if(-not $ruleFile -or $ruleFile.Count -eq 0) { Write-SRx WARNING "Unable to find rule $($rule.Rule) to run." continue } if($ruleFile.Count -gt 1) { Write-SRx WARNING "Not running rule: $($rule.Rule)" Write-SRx WARNING "Found two or more rules with the same name, $($rule.Rule), in the .\lib\Rules\* folders." Write-SRx WARNING "All rules must have unique names." continue } # dot source the test script try { # $params = "-ThresholdsFile file.csv" if([string]::IsNullOrEmpty($Params)) { $evaluatedRules = & $ruleFile.FullName } else { Write-SRx INFO "Params found. Invoking expression..." $evaluatedRules = Invoke-Expression "& '$($ruleFile.FullName)' $Params" } foreach($evaluatedRule in $evaluatedRules) { if ($evaluatedRule -is [string]) { Write-SRx INFO "-skipped-> " -ForegroundColor DarkGray -NoNewline Write-SRx INFO $evaluatedRule continue } # if no Dashboard, don't write to lists if(-not $global:SRxEnv.Dashboard.Initialized) { $alert = $false } else { $alertLevels = @("exception", "error") if ($rule.AlertOnErrorOnly -ne "true") { $alertLevels += "warning" } #We only want to alert on warnings when... $alert = $((-not $evaluatedRule.Success) -and #the rule evaluation was un-successful and... ($rule.AlertOnFailure -eq "true") -and # "AlertOnFailure" is defined for this rule and... ($alertLevels -contains ($evaluatedRule.Message.level))) # The alert levels contains the level of the rule } $msg = $evaluatedRule.Message NewSRxEventObject } } catch { $evaluatedRules = $null #to prevent bleed over from previous test... $msgExcp = "Caught exception while evaluating $($rule.Rule)" $l = if($msgExcp.Length -gt 80) {80} else {$msgExcp.Length} Write-SRx ERROR $('=' * $l) Write-SRx ERROR $msgExcp Write-SRx ERROR "Exception: $($_.Exception.Message)" Write-SRx ERROR $('-' * $l) Write-SRx ERROR $_.InvocationInfo.PositionMessage Write-SRx ERROR $_.Exception Write-SRx ERROR $('=' * $l) $alert = $($rule.AlertOnFailure -eq "true") $msg = New-Object PSObject @{ level = "Exception"; headline = $_.Exception.Message; details = $_.InvocationInfo.PositionMessage; } NewSRxEventObject } finally { $end = Get-Date $span = New-TimeSpan $start $end Write-SRx VERBOSE "Finished evaluating rule $($rule.Rule). Time: [$($span.Hours):$($span.Minutes):$($span.Seconds).$($span.Milliseconds)]" } } } #EndRegion '.\Private\ProcessRules.ps1' 103 #Region '.\Private\SRxEnv.ps1' 0 $SRxLogLevel = [SRxLogLevel]::VERBOSE #====================================== #== Extensions to the $global:SRxEnv object == #====================================== if ($global:SRxEnv.Exists) { $global:SRxEnv | Set-SRxCustomProperty "h" $(New-Object PSObject) $global:SRxEnv.h | Set-SRxCustomProperty "description" "Utility Helper Methods" $global:SRxEnv.h | Add-Member -Force ScriptMethod -Name isNumeric -Value { param ( $value ) return $value -match "^[\d\.]+$" } $global:SRxEnv.h | Add-Member -Force ScriptMethod -Name isUnknownOrNull -Value { param ( $value ) return ( [string]::IsNullOrEmpty($value) -or ( ($value -is [string]) -and ($value -eq 'unknown') ) ) } $global:SRxEnv.h | Add-Member -Force ScriptMethod -Name isUnknownOrZero -Value { param ( $value ) return ( ([string]::IsNullOrEmpty($value)) -or ($value -eq 0) -or ( ($value -is [string]) -and (($value -eq "0") -or ($value -eq 'unknown')) )) } $global:SRxEnv.h | Add-Member -Force ScriptMethod -Name isUnknown -Value { param ( $value ) return ( ($value -is [string]) -and ($value -eq 'unknown') ) } $global:SRxEnv.h | Add-Member -Force ScriptMethod -Name GetDiscreteTime -Value { param ( [datetime]$t, $interval = 15 ) $midPoint = [math]::Round($interval/2) #Rounds a datetime to a discrete point in time (e.g. useful for charting points in time) return $t.AddMinutes( $($x = $t.Minute % $interval; if ($x -lt $midPoint) {(-1)*$x } else {$interval-$x}) ).AddSeconds( (-1)*($t.Second) ) } $global:SRxEnv.h | Add-Member -Force ScriptMethod -Name BuildHandleMappings -Value { param ( [bool]$invalidatePreviousMappings = $false ) $map = @() foreach ($name in $global:___SRxCache.SSASummaryList.Name) { $handle = "" #the assumed value if (-not $invalidateSiblingMappings) { if (($name -eq $global:SRxEnv.SSA) -and ($global:SRxEnv.Dashboard.Handle -ne $null)) { $handle = $global:SRxEnv.Dashboard.Handle } else { $mappedHandle = $($global:SRxEnv.HandleMap | Where {$_.Name -eq $name}).Handle if ($mappedHandle.count -gt 1) { #this path should not happen *(implies multiple SSAs with the same name) Write-SRx WARNING $("[`$global:SRxEnv.h.BuildHandleMappings] Invalid HandleMap: Multiple SSAs map to '" + $SSA + "' ...ignoring map") $mappedHandle = "" } if (-not [string]::isNullOrEmpty($mappedHandle)) { $handle = $mappedHandle } } } Write-SRx VERBOSE $(" --> Mapping SSA '" + $name + "', to Handle: '" + $handle + "'") $map += $( New-Object PSObject -Property @{ "Name" = $name; "Handle" = $handle } ) } $global:SRxEnv.PersistCustomProperty("HandleMap", $map) } } #EndRegion '.\Private\SRxEnv.ps1' 63 #Region '.\Public\Connect-SRxProvisioningDatabase_JSON.ps1' 0 Function Connect-SRxProvisioningDatabase_JSON { [CmdletBinding()] PARAM ( ) PROCESS { try { $Reloaded = $false # Work folder $profilesFolderPath = $global:SRxEnv.CachePath + "\Profiles" if (!(Test-Path $profilesFolderPath)) { Write-SRx VERBOSE " > Create the new folder $profilesFolderPath" New-Item -itemType Directory -Path $profilesFolderPath | Out-Null $Reloaded = $true } if( $global:SRxEnv.Tenancy.TermGroupID -ne "") { #ensure folder for term group $sgroupName = (Remove-SRxStringSpecialCharacter -String $global:SRxEnv.Tenancy.TermGroupName) $profileFolderPath = $profilesFolderPath + "\" + $sgroupName if (!(Test-Path $profileFolderPath)) { Write-SRx VERBOSE " > Create the new folder $profileFolderPath" New-Item -itemType Directory -Path $profileFolderPath | Out-Null $Reloaded = $true } else { $configDPath = $profileFolderPath + "\ProvisioningDatabase.min.json" if (!(Test-Path $configDPath)) { Save-SRxProfile -TermGroupName $global:SRxEnv.Tenancy.TermGroupName #Write-SRx VERBOSE " ? Can't find ProvisioningDatabase.min.json" #$Reloaded = $true } if ((Test-Path $configDPath)) { # Write-SRx VERBOSE " > Loading provisioning schema $configDPath ..." $fileStamp = (Get-ChildItem -path $configDPath).LastWriteTime if( $global:SRxEnv.ProvisioningDatabaseTimeStamp ) { if( $global:SRxEnv.ProvisioningDatabaseTimeStamp -ne $fileStamp) { $Reloaded = $true } } $configDB = Get-Content $configDPath -Raw -ErrorAction Stop $global:SRxDB = ConvertFrom-Json $configDB #-Depth 1024 $propertyDBValue = $SRxDB.Templates.ProvisioningTemplate.TermGroups.TermGroup.TermSets.TermSet#.Terms #Write-SRx VERBOSE " > Provisioning schema $($propertyDBValue.ID) loaded" if($global:SRxEnv.ProvisioningDatabase ) { $global:SRxEnv.psobject.members.remove("ProvisioningDatabase") } $global:SRxEnv | Add-Member -Force -MemberType "NoteProperty" -Name "ProvisioningDatabase" -Value $propertyDBValue $global:SRxEnv.SetCustomProperty( "ProvisioningDatabaseTimeStamp", $fileStamp) # Write-SRx VERBOSE " > Provisioning schema on <$($SRxDB.Templates.ProvisioningTemplate.TermGroups.TermGroup.Name)> activated" #-ForegroundColor Cyan } } } else { $Reloaded = $true } return $Reloaded } catch { return $false } } } #EndRegion '.\Public\Connect-SRxProvisioningDatabase_JSON.ps1' 71 #Region '.\Public\Connect-SRxSPOService.ps1' 0 function Connect-SRxSPOService { <# .SYNOPSIS Create a new stored credential stored on local machine .DESCRIPTION This function will save a new stored credential to a .cred file into current user's folder on local machine. .NOTES ========================================= Project : The Source Shell (SRx) ----------------------------------------- File Name : Connect-SRxSPOService.psm1 Author : Nikolay Mukhin Requires : PowerShell Version 5.1, The Source Shell (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== .INPUTS .OUTPUTS .EXAMPLE Connect-SRxSPOService #> [CmdletBinding()] param ( ) BEGIN { Write-SRx DEBUG "BEGIN Connect-SRxSPOService" } PROCESS { Write-SRx DEBUG "PROCESS - Entered Connect-SRxSPOService process" try { if( $global:SRxEnv.Tenancy.LoginAs) { #$global:SRxEnv.LoginAs) { $cred = Get-SRxStoredCredential -UserName $global:SRxEnv.Tenancy.LoginAs #$global:SRxEnv.LoginAs } else { $cred = Get-SRxStoredCredential #-Pa #currently logged-in user's pa-account } if( -not $cred) { return $true #$Setup #if( CredentialsPrompt -eq 1) { # throw "[shell] Please use New-SRxStoredCredential cmdlet to store credentials for your account" #} #else { # New-SRxStoredCredential #} #Write-SRx INFO $("[shell] Please restart shell with key -RebuildSRx to re-connected tenancy with updated credentials.") -ForegroundColor Red # -NoNewline } else { try { $tenancyURL = Convert-SRxURL_JSON -URL $global:SRxEnv.Tenancy.AdminUrl #$tenancyURL = $global:SRxEnv.Tenancy.AdminURL #"https://medline0-admin.sharepoint.com" #if($tenancyURL -eq "") { # #} $SRXEnv.Tenancy.LastConnectionSuccessful = $false $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $false if( -not $global:SRxEnv.Tenancy.MFA) { $global:SRxEnv.SetCustomProperty("PnPCredentials", $cred) Write-SRx DEBUG "Connect-SPOService on $tenancyURL" Connect-SPOService -Url $tenancyURL -Credential $cred #$connection = Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl #if( -not $connection) { # $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $false #} } else { Connect-SPOService -Url $tenancyURL #$connection = Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl } $global:SRxEnv.PnPConnections['SPO'] = $true $SRXEnv.Tenancy.LastConnectionSuccessful = $true $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $true if( $global:SRxEnv.Log.Level -eq "VERBOSE") { # Write-SRx VERBOSE $(" > Connected to tenancy (SPO Online): ") -NoNewline # Write-SRx VERBOSE ($tenancyURL) } } catch { #Write-SRx Warning $("[shell] Can't Connect-SPOService on tenancy: $($tenancyURL)") Write-SRx Warning $("[shell] "+ $_.Exception.Message) #Write-SRx Warning $("[shell] Please run New-SRxStoredCredential cmdlet to enter or update stored credentials for $($global:SRxEnv.PnPUserName)") $SRXEnv.Tenancy.LastConnectionSuccessful = $false $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $false $global:SRxEnv.PnPConnections['SPO'] = $null throw $_.Exception.Message } } } catch { #Write-SRx Warning $("[shell] Can't Connect-SPOService on tenancy: $($global:SRxEnv.Tenancy.AdminURL)") #Write-SRx Warning $($_.Exception.Message) #$global:__SRxHasInitFailure = $true $SRXEnv.Tenancy.LastConnectionSuccessful = $false $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $false } } END { Write-SRx DEBUG "END" } } #EndRegion '.\Public\Connect-SRxSPOService.ps1' 116 #Region '.\Public\Convert-SRxFromXML.ps1' 0 function Convert-SRxFromXML { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline)] [System.Xml.XmlNode]$node, #we are working through the nodes [string]$Prefix = '', #do we indicate an attribute with a prefix? $ShowDocElement = $false #Do we show the document element? ) process { #if option set, we skip the Document element if ($node.DocumentElement -and !($ShowDocElement)) { $node = $node.DocumentElement } $oHash = [ordered] @{ } # start with an ordered hashtable. #The order of elements is always significant regardless of what they are write-verbose "calling with $($node.LocalName)" if ($null -ne $node.Attributes) #if there are elements # record all the attributes first in the ordered hash { $node.Attributes | ForEach-Object { $oHash.$($Prefix + $_.FirstChild.parentNode.LocalName) = $_.FirstChild.value } } # check to see if there is a pseudo-array. (more than one # child-node with the same name that must be handled as an array) $node.ChildNodes | #we just group the names and create an empty #array for each Group-Object -Property LocalName | Where-Object { $_.count -gt 1 } | Select-Object Name | ForEach-Object{ write-verbose "pseudo-Array $($_.Name)" $oHash.($_.Name) = @() <# create an empty array for each one#> }; foreach ($child in $node.ChildNodes) { #now we look at each node in turn. write-verbose "processing the '$($child.LocalName)'" $childName = $child.LocalName if ($child -is [system.xml.xmltext]) # if it is simple XML text { write-verbose "simple xml $childname"; $oHash.$childname += $child.InnerText } # if it has a #text child we may need to cope with attributes elseif ($child.FirstChild.Name -eq '#text' -and $child.ChildNodes.Count -eq 1) { write-verbose "text"; if ($null -ne $child.Attributes) #hah, an attribute { <#we need to record the text with the #text label and preserve all the attributes #> $aHash = [ordered]@{ }; $child.Attributes | ForEach-Object { $aHash.$($_.FirstChild.parentNode.LocalName) = $_.FirstChild.value } #now we add the text with an explicit name $aHash.'#text' += $child.'#text' $oHash.$childname += $aHash } else { #phew, just a simple text attribute. $oHash.$childname += $child.FirstChild.InnerText } } elseif ($null -ne $child.'#cdata-section') # if it is a data section, a block of text that isnt parsed by the parser, # but is otherwise recognized as markup { write-verbose "cdata section"; $oHash.$childname = $child.'#cdata-section' } elseif ($child.ChildNodes.Count -gt 1 -and ($child | Get-Member -MemberType Property).Count -eq 1) { $oHash.$childname = @() foreach ($grandchild in $child.ChildNodes) { $oHash.$childname += (Convert-SRxFromXML $grandchild) } } else { # create an array as a value to the hashtable element $oHash.$childname += (Convert-SRxFromXML $child) } } $oHash } } #EndRegion '.\Public\Convert-SRxFromXML.ps1' 98 #Region '.\Public\Convert-SRxURL_JSON.ps1' 0 Function Convert-SRxURL_JSON { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$false)][string]$URL, [Parameter(Mandatory=$false)][string]$environment ) PROCESS { try { if($null -eq $URL) { return $null } if($null -eq $environment) { $environment = $global:SRxEnv.PnPEnvironment } $environmentPrefix = '' if($environment) { #$environmentConfig = ($global:SRxEnv.DefaultSettings.EcoSystem.Environments | Select-Object -ExpandProperty $environment) #$environmentPrefix = $environmentConfig.URLPrefix $environment_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value $environment $environmentPrefix = Get-SRxCustomPropertyValueByKey_JSON -termHost $environment_ -Key "ts_URLPrefix" } $masterPrefix = "" #$masterConfig = ($global:SRxEnv.DefaultSettings.EcoSystem.Environments | Select-Object -ExpandProperty "Master") $master_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value "Master" $masterPrefix = Get-SRxCustomPropertyValueByKey_JSON -termHost $master_ -Key "ts_URLPrefix" #if( $masterConfig) { # $masterPrefix = $masterConfig.URLPrefix #} $str = $URL.replace('{tenancy}', $global:SRxEnv.Tenancy.Name) #if( $global:SRxEnv.EcoSystemURLPrefix) { # $ecosystemPrefix = $global:SRxEnv.EcoSystemURLPrefix #} #else { $ecosystemPrefix = Get-SRxCustomPropertyValueByKey_JSON -Key "ts_URLPrefix" #$ecosystemPrefix = $global:SRxEnv.Tenancy.EcoSystem #} $str = $str.replace('{ecosystem}',$ecosystemPrefix) if($environmentPrefix -eq '' -or $null -eq $environmentPrefix) { $str = $str.replace('_{environment}',$environmentPrefix) } else { $str = $str.replace('{environment}',$environmentPrefix) } if($masterPrefix -eq '' -or $null -eq $masterPrefix) { $str = $str.replace('_{master}',$masterPrefix) } else { $str = $str.replace('{master}',$masterPrefix) } } catch { return $null } return $str } } #EndRegion '.\Public\Convert-SRxURL_JSON.ps1' 56 #Region '.\Public\Get-Master.ps1' 0 function Get-Master { <# .SYNOPSIS Invokes a test(s) and handles where the output event(s) get written .DESCRIPTION This cmdlet wraps the invocation chain of Invoke-ProvisioningSequence followed by: -> Export-SRxToSearchDashboard | Export-SRxToAlertsList -> and/or Write-SRxConsole .NOTES ========================================= Project : The Source Shell (SRx) ----------------------------------------- File Name : Get-Master.psm1 Author : Nikolay Mukhin Contributors: Eric Dixon Requires : PowerShell Version 5.1, The Source Shell (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== .INPUTS Control file (e.g. TestControl.csv) .OUTPUTS [ $SRxEvent(s) ] .EXAMPLE #> [CmdletBinding()] param ( [alias("All")][switch]$RunAllTests, [alias("OutNull")][switch]$NoWriteToConsole, [switch]$PassThrough, [alias("EventLog")][switch]$WriteErrorsToEventLog, [switch]$Details, [Parameter(Mandatory=$false, ParameterSetName="Get")] [string]$ControlFile, [Parameter(Mandatory=$false, ParameterSetName="Get")] [string]$Test ) BEGIN { Write-SRx DEBUG "BEGIN Get-Master" } PROCESS { Write-SRx DEBUG "PROCESS - Entered Get-Master process" if($Test) { if($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent) { $output = New-SRxReport -Test $Test -Debug } else { $output = New-SRxReport -Test $Test } } elseif($ControlFile) { $output = New-SRxReport -ControlFile $ControlFile } else { $output = New-SRxReport -ControlFile "Get-Master" } if($PassThrough) { $output } } END { Write-SRx DEBUG "END" } } #EndRegion '.\Public\Get-Master.ps1' 80 #Region '.\Public\Get-SRxConnection.ps1' 0 Function Get-SRxConnection { PARAM ( [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true)][string]$siteURL, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$false)][string]$clientId, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$false)][string]$thumbprint, [switch]$MFA, [switch]$APP, [switch]$SPO, [switch]$Force ) BEGIN { $method = "?" $connection = $null $key = Convert-SRxURL_JSON -URL $siteURL $tenant = Convert-SRxURL_JSON -URL $global:SRxEnv.Tenancy.Tenant if( -not $clientId) { $clientId = $global:SRxEnv.Tenancy.ClientId } if( -not $thumbprint) { $thumbprint = $global:SRxEnv.Tenancy.Thumbprint } try { if($SPO) { $method = "SPO" if( -not $global:SRxEnv.PnPConnections['SPO']) { Connect-SRxSPOService } return } elseif($MFA) { $method = "MFA" if($global:SRxEnv.PnPConnections.ContainsKey($key)) { $method += " --> cache" $connection = $global:SRxEnv.PnPConnections[$key] } else { $connection = Connect-PnPOnline -Url $key -Interactive -ReturnConnection -WarningAction SilentlyContinue } } elseif($APP) { #https://nonodename.com/post/termstore/ #https://www.leonarmston.com/2022/01/pnp-powershell-csom-now-works-with-sharepoint-sites-selected-permission-using-azure-ad-app/ $method = "APP" if(($global:SRxEnv.Tenancy.PnPLogin -eq "application") -or $Force) { $connection = Connect-PnPOnline -Url $key -ClientId $clientId -Tenant $tenant -Thumbprint $thumbprint -ReturnConnection } else { $method += " --> MFA" $connection = Connect-PnPOnline -Url $key -Interactive -ReturnConnection -WarningAction SilentlyContinue } } elseif($global:SRxEnv.Tenancy.PnPLogin -eq "application") { $method = "APP" $adminURL = Convert-SRxURL_JSON -URL $global:SRxEnv.Tenancy.AdminUrl if( $key -eq $adminURL) { if($global:SRxEnv.Tenancy.MFA) { $method += " --> MFA" $connection = Connect-PnPOnline -Url $key -Interactive -ReturnConnection -WarningAction SilentlyContinue } else { $method += " --> account" $connection = Connect-PnPOnline -Url $key -Credentials $global:SRxEnv.PnPCredentials -ReturnConnection } } else { $connection = Connect-PnPOnline -Url $key -ClientId $clientId -Tenant $tenant -Thumbprint $thumbprint -ReturnConnection } } elseif($global:SRxEnv.Tenancy.MFA) { $method = "MFA" $connection = Connect-PnPOnline -Url $key -Interactive -ReturnConnection -WarningAction SilentlyContinue } else { $method = "account" $connection = Connect-PnPOnline -Url $key -Credentials $global:SRxEnv.PnPCredentials -ReturnConnection } #$connection = Connect-PnPOnline -Url $siteURL -UseWebLogin -ReturnConnection -WarningAction SilentlyContinue if( -not $connection) { $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $false } else { $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $true } $global:SRxEnv.PnPConnections[$key] = $connection } catch { $SRXEnv.Tenancy.LastPnPConnectionSuccessful = $false $global:SRxEnv.PnPConnections[$key] = $null Write-SRx ERROR $(" > Failed PnPOnline ($method) to $key") throw $_.Exception.Message return $null } Write-SRx VERBOSE $(" > Connected PnPOnline ($method) to $key") return $connection } } #EndRegion '.\Public\Get-SRxConnection.ps1' 89 #Region '.\Public\Get-SRxCustomizationWriter.ps1' 0 function Get-SRxCustomizationWriter{ return [CustomizationWriter]::new() } #EndRegion '.\Public\Get-SRxCustomizationWriter.ps1' 4 #Region '.\Public\Get-SRxCustomPropertyValueByKey_JSON.ps1' 0 Function Get-SRxCustomPropertyValueByKey_JSON { [CmdletBinding()] PARAM ( #[Parameter(Mandatory=$false)]$termHost, #TermSet or Term [Parameter(ValueFromPipeline,Mandatory=$false)][pscustomobject]$termHost, [Parameter(Mandatory=$true)][string]$Key, [switch]$ToUrl, [switch]$ToBoolean ) PROCESS { try { $retVal = $null if( -not $termHost) { $termHost = $global:SRxEnv.ProvisioningDatabase } if( -not $termHost.CustomProperties) { return $retVal } if( $termHost.CustomProperties -is [array] ) { $properties = $termHost.CustomProperties| Where-Object {$_.Key -eq $Key } if ($null -ne $properties) { #return $properties[0].Value $retVal = $properties[0].Value } } else { $property = $termHost.CustomProperties.Property if( $null -ne $property) { #Write-SRx WARNING "76 $($property.Key) ?? $Key" if( $property.Key -eq $Key) { #Write-SRx ERROR "+ $($property.Value)" #return $property.Value $retVal = $property.Value } } } if( $retVal ) { if( $ToUrl ) { $environment = $global:SRxEnv.PnPEnvironment $environment_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value $environment $environmentPrefix = Get-SRxCustomPropertyValueByKey_JSON -termHost $environment_ -Key "ts_URLPrefix" $master_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value "Master" $masterPrefix = Get-SRxCustomPropertyValueByKey_JSON -termHost $master_ -Key "ts_URLPrefix" $str = $retVal.replace('{tenancy}', $global:SRxEnv.Tenancy.Name) $str = $str.replace('{site}','/') $ecosystemPrefix = Get-SRxCustomPropertyValueByKey_JSON -Key "ts_URLPrefix" $str = $str.replace('{ecosystem}',$ecosystemPrefix) if($environmentPrefix -eq '' -or $null -eq $environmentPrefix) { $str = $str.replace('_{environment}',$environmentPrefix) } else { $str = $str.replace('{environment}',$environmentPrefix) } if($masterPrefix -eq '' -or $null -eq $masterPrefix) { $str = $str.replace('_{master}',$masterPrefix) } else { $str = $str.replace('{master}',$masterPrefix) } return $str } elseif( $ToBoolean) { return [System.Convert]::ToBoolean($retVal) } else { return $retVal } } } catch { return $null } return $null } } #EndRegion '.\Public\Get-SRxCustomPropertyValueByKey_JSON.ps1' 68 #Region '.\Public\Get-SRxHostingTerm.ps1' 0 Function Get-SRxHostingTerm { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$false)][switch]$Site, [Parameter(Mandatory=$false)][switch]$Sites, [Parameter(Mandatory=$false)][switch]$Design, [Parameter(Mandatory=$false)][switch]$Environment, [Parameter(Mandatory=$false)][switch]$Profile, [Parameter(Mandatory=$false)][string]$siteID, [Parameter(Mandatory=$false)][string]$siteDesign, [Parameter(Mandatory=$false)][string]$siteEnvironment, [Parameter(Mandatory=$true)]$connection ) PROCESS { try { $termHost = $null if($Profile) { $termHost = Get-PnPTermSet -Identity $global:SRxEnv.Tenancy.TermSetID -TermGroup $global:SRxEnv.Tenancy.TermGroupName -Connection $connection if( -not $termHost) { Write-SRx VERBOSE $(" ? Profile term not found") } } elseif($Environment) { $environment_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value $siteEnvironment $termEnvironmentID = $environment_.ID $termHost = Get-PnPTerm -Identity $([GUID]$termEnvironmentID) -ErrorAction SilentlyContinue -IncludeChildTerms -Connection $connection if( -not $termHost) { Write-SRx VERBOSE $(" ? $siteEnvironment environment term not found") } } elseif($Design) { $environment_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value $siteEnvironment $termEnvironmentID = $environment_.ID $termEnvironment = Get-PnPTerm -Identity $([GUID]$termEnvironmentID) -ErrorAction SilentlyContinue -IncludeChildTerms -Connection $connection $termHost = Get-SRxTermsWithCustomProperty -termsHost $termEnvironment -customPropertyKey "ts_Design" -customPropertyValue $siteDesign -connection $connection if( -not $termHost) { Write-SRx VERBOSE $(" ? ts_Design = $siteDesign term not found") } } elseif($Site) { $termHost = Get-PnPTerm -Identity $([GUID]$siteID) -ErrorAction SilentlyContinue -IncludeChildTerms -Connection $connection if( -not $termHost) { Write-SRx VERBOSE $(" ? Site term not found") } } elseif($Sites) { $environment_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value $siteEnvironment $sites_ = Get-SRxTermByCustomProperty_JSON -termHost $environment_ -Key "ts_Sites" -Value $siteDesign $termHost = Get-PnPTerm -Identity $([GUID]$($sites_.ID)) -ErrorAction SilentlyContinue -IncludeChildTerms -Connection $connection } if( $termHost) { Get-PnPProperty -ClientObject $termHost -Property CustomProperties, Id, Name, Terms -Connection $connection | Out-Null } return $termHost } catch { } } } #EndRegion '.\Public\Get-SRxHostingTerm.ps1' 57 #Region '.\Public\Get-SRxIsConnected.ps1' 0 function Get-SRxIsConnected { $connected = $false try { $config = Get-Content $global:SRxEnv.CustomConfig -Raw $customConfig = ConvertFrom-Json $config $propName = "Tenancy" $global:SRxEnv.SetCustomProperty($propName, $($customConfig.$propName)) $provisioningDBExists = Test-SRxProvisioningDatabase_JSON if( $($global:SRxEnv.Tenancy.LastConnectionSuccessful) -and $($global:SRxEnv.Tenancy.LastPnPConnectionSuccessful) -and $provisioningDBExists) { #$spo -and $pnp ) { #-and $($global:SRxEnv.Tenancy.LastConnectionSuccessful)) { $connected = $true } } catch {} return $connected } #EndRegion '.\Public\Get-SRxIsConnected.ps1' 15 #Region '.\Public\Get-SRxRemoteCommand.ps1' 0 function Get-SRxRemoteCommand { [CmdletBinding()] PARAM( [Parameter(Mandatory=$true)][string]$name, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$false)][string]$siteTitle, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$false)][string]$environment, [Parameter(ValueFromPipeline=$true, Mandatory=$false)]$pipeline ) PROCESS { $filter = $null if( $null -ne $global:SRxEnv.FilterNodes) { $filter = $global:SRxEnv.FilterNodes.Clone()} $command = @{ ts_ControlFile = $name ts_Environment = $environment ts_Title = $siteTitle ts_IsAdmin = $false ts_NewSite = $false ts_Rebuild = $false pipeline = $pipeline #$_ filter = $filter } return $command } } #EndRegion '.\Public\Get-SRxRemoteCommand.ps1' 25 #Region '.\Public\Get-SRxStoredCredential.ps1' 0 function Get-SRxStoredCredential { <# .SYNOPSIS Retrieves user credentials stored on local machine .DESCRIPTION This cmdlet retieves sredentials saved by current on local machine. .NOTES ========================================= Project : The Source Shell (SRx) ----------------------------------------- File Name : Get-SRxStoredCredential.psm1 Author : Nikolay Mukhin Requires : PowerShell Version 5.1, The Source Shell (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== .INPUTS -List no keys assumes current user's login name -Pa "pa-" current user's login name -UserName any desired login name .OUTPUTS $Credential object .EXAMPLE $cred = Get-SRxStoredCredential -List $cred = Get-SRxStoredCredential $cred = Get-SRxStoredCredential -Pa $cred = Get-SRxStoredCredential -UserName "nmukhin@medline.com" #> [CmdletBinding(DefaultParameterSetName = 'List')] param ( [Parameter(Mandatory=$false, ParameterSetName="Get")][string]$UserName, [Parameter(Mandatory=$false, ParameterSetName="List")][switch]$List#, #[Parameter(Mandatory=$false, ParameterSetName="List")] #[switch]$Pa ) BEGIN { Write-SRx DEBUG "BEGIN Get-SRxStoredCredential" } PROCESS { Write-SRx DEBUG "PROCESS - Entered Get-SRxStoredCredential process" $KeyPath = $home + "\" + $env:computername if (!(Test-Path $KeyPath)) { try { #Write-SRx VERBOSE $("<--Creating-user's-local-store-folder-- " + $KeyPath) -ForegroundColor Yellow New-Item -ItemType Directory -Path $KeyPath -ErrorAction STOP | Out-Null } catch { Write-SRx Warning $("[shell] Can't create user's local store folder: " + $_.Exception.Message) throw $_.Exception.Message } } if ($List) { try { #Write-SRx VERBOSE $(" > Enumerating-credentials-in-user's-local-store-folder-- " + $KeyPath) #-ForegroundColor Gray $CredentialList = @(Get-ChildItem -Path $keypath -Filter *.cred -ErrorAction STOP) #foreach ($Cred in $CredentialList) { # Write-Host "Username: $($Cred.BaseName)" # } } catch { Write-SRx Warning $("[shell] Can't enumerate credentials stored in user's local store folder: " + $_.Exception.Message) #Write-Warning $_.Exception.Message } return $CredentialList } $domain = "@$($global:SRxEnv.Tenancy.Domain)" if( !($UserName)) { #if($Pa) { $UserName = $global:SRxEnv.Tenancy.LoginPrefix + "$env:UserName$domain" #@medline.com" #} #else { # $UserName = "$env:UserName$domain" #@medline.com" #} } # Write-SRx VERBOSE $(" > Using credentials for " + $Username) #-ForegroundColor Yellow if (Test-Path "$($KeyPath)\$($Username).cred") { $PwdSecureString = Get-Content "$($KeyPath)\$($Username).cred" | ConvertTo-SecureString $Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $Username, $PwdSecureString $global:SRxEnv.SetCustomProperty("PnPUserName", $Username) } else { $global:SRxEnv.SetCustomProperty("PnPUserName", $null) Write-SRx Warning $("[shell] Can't find stored credentials for $($Username) on local computer") $Credential = $null } return $Credential } END { Write-SRx DEBUG "END" } } #EndRegion '.\Public\Get-SRxStoredCredential.ps1' 112 #Region '.\Public\Get-SRxTermByCustomProperty_JSON.ps1' 0 Function Get-SRxTermByCustomProperty_JSON { [CmdletBinding()] PARAM ( [Parameter(ValueFromPipeline,Mandatory=$false)][pscustomobject]$termHost, #TermSet or Term [Parameter(ValueFromPipeline)][pscustomobject]$Thing [Parameter(Mandatory=$false)][string]$Key, [Parameter(Mandatory=$false)][string]$Value, [switch]$ID ) PROCESS { try { $result = @() if( -not $termHost) { $termHost = $global:SRxEnv.ProvisioningDatabase } $propertyValue = $null if( -not $ID) { $propertyValue = Get-SRxCustomPropertyValueByKey_JSON -termHost $termHost -Key $Key #Write-SRx WARNING "propertyValue = $propertyValue" } else { $propertyValue = $termHost.ID } if( $null -ne $propertyValue) { #Write-SRx VERBOSE "1" if( $Value ) { #Write-SRx VERBOSE "2 $Value" if( $propertyValue -eq $Value) { #Write-SRx ERROR "+ $($termHost.Name) ==" $result += $termHost } } else { #Write-SRx WARNING "+ $($termHost.Name)" $result += $termHost } } #Write-Host ($result | ft | out-string) if( $null -ne $termHost.Terms) { #Write-SRx VERBOSE "Terms: $($termHost.Terms.Count)" #if( $termHost.Terms -is [array] ) { $termHost.Terms | Resolve-SRxTermsArray_JSON | ForEach-Object { #@($termHost.Terms) | ForEach-Object { if( $null -eq $_) { #Write-SRx ERROR "124" } else { #Write-SRx VERBOSE "$($_.Name)" if( -not $ID) { $arr = Get-SRxTermByCustomProperty_JSON -termHost $_ -Key $Key -Value $Value } else { $arr = Get-SRxTermByCustomProperty_JSON -termHost $_ -Key $Key -Value $Value -ID } @($arr) | ForEach-Object { #Write-SRx WARNING "+ $($_.Name)" $result += $_ } } } } # return $result } catch { Write-SRx ERROR ($_.Exception.Message) Write-SRx VERBOSE ($_.Exception) return $null } return $result } } #EndRegion '.\Public\Get-SRxTermByCustomProperty_JSON.ps1' 66 #Region '.\Public\Get-SRxTermsWithCustomProperty.ps1' 0 Function Get-SRxTermsWithCustomProperty { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true)]$termsHost, #TermSet or Term [Parameter(Mandatory=$true)][string]$customPropertyKey, [Parameter(Mandatory=$false)]$customPropertyValue, [Parameter(Mandatory=$true)]$connection ) PROCESS { try { $result = @() Get-PnPProperty -ClientObject $termsHost -Property Terms, Name -Connection $connection | Out-Null #Write-Host "termHost = $($termsHost)" $termsHost.Terms | ForEach-Object { if($_.CustomProperties.ContainsKey($customPropertyKey)) { #Write-Host "got Key !" if($customPropertyValue) { if($_.CustomProperties[$customPropertyKey] -eq $customPropertyValue) { #Write-Host "got Value !" $result = $result + $_ } } else { $result = $result + $_ } } if($customPropertyValue) { Get-SRxTermsWithCustomProperty -termsHost $_ -customPropertyKey $customPropertyKey -customPropertyValue $customPropertyValue -connection $connection | ForEach-Object { $result = $result + $_ } } else { Get-SRxTermsWithCustomProperty -termsHost $_ -customPropertyKey $customPropertyKey -connection $connection | ForEach-Object { $result = $result + $_ } } } return $result } catch { Write-SRx ERROR ($_.Exception.Message) Write-SRx VERBOSE ($_.Exception) return $null } } } #EndRegion '.\Public\Get-SRxTermsWithCustomProperty.ps1' 45 #Region '.\Public\Get-SRxWorkFolder.ps1' 0 function Get-SRxWorkFolder { [CmdletBinding()] PARAM( [string]$folder, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true)][string]$siteDesign ) PROCESS { # Work folder if (!(Test-Path $($global:SRxEnv.MasterCachePath))) { Write-SRx VERBOSE " > Create the new folder $($global:SRxEnv.MasterCachePath)" New-Item -itemType Directory -Path $($global:SRxEnv.MasterCachePath) | Out-Null } $designFolderPath = $global:SRxEnv.MasterCachePath + "\" + $siteDesign if (!(Test-Path $designFolderPath)) { Write-SRx VERBOSE " > Create the new folder $designFolderPath" New-Item -itemType Directory -Path $designFolderPath | Out-Null } $destfolderPath = $designFolderPath + "\$folder" if (!(Test-Path $destfolderPath)) { Write-SRx VERBOSE " > Create the new folder $destfolderPath" New-Item -itemType Directory -Path $destfolderPath | Out-Null } return $destfolderPath } } #EndRegion '.\Public\Get-SRxWorkFolder.ps1' 29 #Region '.\Public\Hide-SRxWindow.ps1' 0 function Hide-SRxWindow { $null = [Win32Functions.Win32Windows]::ShowWindowAsync((Get-Process -PID $pid ).MainWindowHandle, 2) } #EndRegion '.\Public\Hide-SRxWindow.ps1' 4 #Region '.\Public\Initialize-SRxEnv.ps1' 0 Function Initialize-SRxEnv { [CmdletBinding(PositionalBinding=$true)] PARAM ( [Parameter(Mandatory=$false)][string]$RootPath, [Parameter(Mandatory=$false,ValueFromPipeline=$false)]$SourceID, #== Source ID if process started as command from GUI [Parameter(Mandatory=$false,ValueFromPipeline=$false)]$LoadModule2, [switch]$DisableLogToFile ) BEGIN { $env:PNPPOWERSHELL_UPDATECHECK = "off" } PROCESS { try { if( -not $global:SRxEnv ) { if( -not $RootPath) { $RootPath = Get-Location } Set-Location $RootPath #Write-Host "init SRxEnv" $SRxLogLevel = [SRxLogLevel]::VERBOSE #$executionContext.SessionState.Path.CurrentLocation $SRxRootPath = $rootpath#$PWD.Path #Get-Location #Write-Host "SRxRootPath = $SRxRootPath" $SRxConfigPath = "\Config\" #sub-path to the .json config files $configPath = Join-Path $(Join-Path $SRxRootPath $SRxConfigPath) "core.config.json" if (-not (Test-Path $configPath)) { Write-Host " ? - can't find $configPath" return } $config = Get-Content $configPath -Raw -Encoding UTF8 -ErrorAction Stop $global:SRxEnv = ConvertFrom-Json $config #== Create utility methods for the $global:SRxEnv object $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name UpdateShellTitle -Value { param([string]$trailingText = "") if ($global:SRxEnv.isBuiltinAdmin) { $adminPrefix = "Administrator: " } if ([String]::IsNullOrEmpty($trailingText) -and ($global:SRxEnv.Version -ne "__REPLACE_WITH_VERSION_NUMBER_NAME__")) { $trailingText = " - " + $global:SRxEnv.Version } (Get-Host).UI.RawUI.WindowTitle = $($adminPrefix + $global:SRxEnv.Title + " " + $trailingText) } #-- Set the title for this shell window $global:SRxEnv.UpdateShellTitle("(Initializing...)") $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name ResolvePath -Value { param([string]$pathStub = "") if (-not [String]::IsNullOrEmpty($pathStub)) { if ($pathStub.StartsWith("..\")) { $length = $global:SRxEnv.Paths.SRxRoot.length $root = $global:SRxEnv.Paths.SRxRoot.substring(0, $length -4) #strip /src return Join-Path $root $pathStub.SubString(3) } #strip off the "..\" elseif ($pathStub.StartsWith(".\")) { return Join-Path $global:SRxEnv.Paths.SRxRoot $pathStub.SubString(2) } #strip off the ".\" elseif ($pathStub.StartsWith($global:SRxEnv.Paths.SRxRoot)) { return $pathStub } elseif (($pathStub.StartsWith("`"$")) -or ($global:SRxEnv.Paths.$pathStub.StartsWith("$"))) { try { return $(Invoke-Expression $global:SRxEnv.Paths.$pathStub) } catch { } } elseif (Test-Path $pathStub) { return $pathStub } } #if we're still here, then throw a warning Write-SRx WARNING $("~~[`$global:SRxEnv.ResolvePath(`$path)] Unable to resolve path: " + $pathStub) } $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name CreateFileWithReadPermissions -Value { param ($filePath) if (-not [string]::IsNullOrEmpty($filePath)) { if (-not $(Test-Path $filePath)) { $file = New-Item -Path $filePath -ItemType File # set permissions for user group so any user can open/write (if Admin is the first, other non-Admin users would get locked out) $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(".\Users","FullControl","Allow") $acl = Get-Acl $file.FullName $acl.SetAccessRule($rule) Set-Acl $file.Fullname $acl } } } $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name SetCustomProperty -Value { param ([String]$propertyName, $propertyValue) #if (($propertyName.Contains(".")) -and ($(Get-Command "Set-SRxCustomProperty" -ErrorAction SilentlyContinue) -ne $null)) { # $this | Set-SRxCustomProperty $propertyName $propertyValue #} else #{ if ( $null -ne $( $this | Get-Member -Name $propertyName) ) { #if the property already exists, just set it $this.$propertyName = $propertyValue } else { #add the new member property and set it $this | Add-Member -Force -MemberType "NoteProperty" -Name $propertyName -Value $propertyValue } #} } $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name PersistCustomProperty -Value { param ([String]$propertyName, $propertyValue, [bool]$persistToFile = $true) #Write-SRx VERBOSE " > Saving 1" $this.SetCustomProperty($propertyName, $propertyValue) if ($persistToFile) { #Write-SRx VERBOSE " > Saving 2" if ($global:SRxEnv.CustomConfigIsReadOnly) { Write-SRx Warning $("~~~ The property '" + $propertyName + "' cannot be persisted (the custom config file is read only or inaccessible) - skipping...") } else { # -- add propert info to $global:SRxEnv.CustomConfig (e.g. custom.config.json) $readlockRetries = 40 $persistFailure = $false $config = $null #Write-SRx VERBOSE " > Saving 3" do { try { #Write-SRx VERBOSE " > Saving 4" Write-SRx VERBOSE $(" > Saving '" + $propertyName + "' to " + $global:SRxEnv.CustomConfig) #-ForegroundColor Yellow $config = Get-Content $($global:SRxEnv.CustomConfig) -Raw -Encoding UTF8 -ErrorAction Stop if ([string]::isNullOrEmpty($config)) { $persistFailure = $true Write-SRx Warning $("~~~ The property '" + $propertyName + "' cannot be persisted (the custom config file is null or empty) - skipping...") } else { try { $tmpSRxEnv = ConvertFrom-Json $config -ErrorAction SilentlyContinue if ($null -ne $(Get-Command "Set-SRxCustomProperty" -ErrorAction SilentlyContinue)) { $tmpSRxEnv | Set-SRxCustomProperty $propertyName $propertyValue } else { $tmpSRxEnv | Add-Member -Force -MemberType "NoteProperty" -Name $propertyName -Value $propertyValue } $jsonConfig = ConvertTo-Json $tmpSRxEnv -Depth 12 } catch { $persistFailure = $true Write-Error $(" --> Caught exception converting `$global:SRxEnv.CustomConfig to object/JSON. Check for syntax errors - skipping: " + $propertyName) } } } catch [System.IO.IOException] { if ($readlockRetries -eq 40) { Write-SRx INFO " --> Unable to read $configPath - Retrying up to 10s" -NoNewline -ForegroundColor Yellow } elseif ($($readlockRetries % 4) -eq 0) { Write-SRx INFO "." -NoNewline } Start-Sleep -Milliseconds 250 $readlockRetries-- } catch { #Write-SRx VERBOSE " > Saving 5" $persistFailure = $true Write-Error $(" --> Caught exception reading `$global:SRxEnv.CustomConfig when persisting a change - skipping: " + $propertyName) } } while (($readlockRetries -gt 0) -and ([string]::isNullOrEmpty($jsonConfig)) -and (-not $persistFailure)) #Write-SRx VERBOSE " > Saving 6" if ([string]::isNullOrEmpty($jsonConfig) -and ($readlockRetries -lt 1)) { Write-SRx Warning $("~~~ Timed out waiting for a read/write lock on `$global:SRxEnv.CustomConfig - skipping: " + $propertyName) } elseif (-not [string]::isNullOrEmpty($jsonConfig)) { try { $jsonConfig | Set-Content $($global:SRxEnv.CustomConfig) -ErrorAction Stop } catch { Write-SRx Warning $("~~~ Unable to obtain a read/write lock on `$global:SRxEnv.CustomConfig - skipping: " + $propertyName) } } } } } <# $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name LoadModule -Value { param( $name, $version, $debug2) $version = if ( $null -eq $version ) { $global:SRxEnv.Version } else { $version } $debug = if ( $null -eq $debug2 ) { $global:SRxEnv.Debug } else { $debug2 } $module = $null if ($debug) { #write-host "Module $name debug $version imported." if ($debug2) { $module = $("$PSScriptRoot\Modules\$name\$version\$name.psm1") } else { $module = $(Join-Path $global:SRxEnv.paths.SRxRoot "Modules\$name\$($global:SRxEnv.Version)\$name.psm1") } if ( -not $(Test-Path -path $module -PathType Leaf )) { $module = $null } } # If module is imported say that and do nothing if (Get-Module -Name $name -Verbose:$false ) { #write-host "Module $name $version is already imported." } else { if ($debug -and $module) { write-host "Module $name local source code version $version imported." Import-Module $module -DisableNameChecking -Verbose:$false -Force } else { # If module is not imported, but available on disk then import if( $null -ne $(Get-Module -ListAvailable -Name $name -Verbose:$false | Where-Object { $_.Name -eq $name -and $_.Version -eq [System.Version]$version })) { if ( (Import-Module -Name $name -RequiredVersion $version -PassThru -Verbose:$false -WarningAction silentlyContinue).Version -eq [System.Version]$version) { #write-host "Module $name $version imported." } else { write-host "Module $name $version not imported, exiting." EXIT 1 } } else { write-host "Module $name $version is not available on disk, trying to import and install..." # If module is not imported, not available on disk, but is in online gallery then install and import if (Find-Module -Name $name -RequiredVersion $version ) { #-MinimumVersion $version write-host "Module $name $version found." Install-Module -Name $name -RequiredVersion $version -Force -Verbose:$false -Scope CurrentUser write-host "Module $name $version installed." Import-Module $name -RequiredVersion $version -DisableNameChecking -Verbose:$false write-host "Module $name $version imported." } else { # If the module is not imported, not available and not in the online gallery then abort write-host "Module $name $version not imported, not available and not in an online gallery, exiting." EXIT 1 } } } } } #> #-- local log location path $global:SRxEnv.SetCustomProperty("LocalLogPath", $($home + "\" + $env:computername + "\Log")) if($LoginAs) { $global:SRxEnv.SetCustomProperty("LoginAs", $LoginAs) } #-- Ensure the $global:SRxEnv.Paths object is defined if ($global:SRxEnv.Paths -isNot [PSCustomObject]) { $global:SRxEnv.SetCustomProperty("Paths", $(New-Object PSObject)) } if (-not $global:SRxEnv.Paths.SRxRoot) { $global:SRxEnv.Paths | Add-Member -Force "SRxRoot" $SRxRootPath } else { $global:SRxEnv.Paths.SRxRoot = $SRxRootPath } if (-not $global:SRxEnv.Paths.Mgmt) { $global:SRxEnv.Paths | Add-Member -Force "Mgmt" $(Join-Path $SRxRootPath $SRxConfigPath) } else { $global:SRxEnv.Paths.Mgmt = $global:SRxEnv.ResolvePath($global:SRxEnv.Paths.Mgmt) } #== Configure SRx Logging #-- Ensure the $global:SRxEnv.Log object is defined if ($global:SRxEnv.Log -isNot [PSCustomObject]) { $logObj = New-Object PSObject -Property @{ "Level" = "INFO"; "ToFile" = $true #if( $DisableLogToFile ) { $false } else { $true }; "RetentionBytes" = 1000MB; "RetentionDays" = 90; } $global:SRxEnv.SetCustomProperty("Log", $logObj) } if( $DisableLogToFile ) { $global:SRxEnv.Log.ToFile = $false } #-- Set the verbosity of logging... if ([string]::IsNullOrWhiteSpace($global:SRxEnv.Log.Level)) { $global:SRxEnv.Log | Add-Member -Force "Level" $SRxLogLevel } else { $global:SRxEnv.Log.Level = $SRxLogLevel } #-- enable/disable logging to a file if ($global:SRxEnv.Log.ToFile -is [bool]) { $actualLogToFileSetting = $global:SRxEnv.Log.ToFile } else { $actualLogToFileSetting = $false $global:SRxEnv.Log | Add-Member -Force "ToFile" $true } #-- if the SRx log folder/file is not already configured or it does not exist... if ([String]::IsNullOrEmpty($global:SRxEnv.LogFile) -or (-not (Test-Path $global:SRxEnv.LogFile))) { #...then temporarily disable file logging ($EnableLogToFile = $false) until further into the initialization $global:SRxEnv.Log.ToFile = $false #if ($TrackDebugTimings) { $global:SRxEnv.DebugTimings["[shell]"].Add( @{ $(Get-Date) = "Tested path for SRx LogFile, disabled Log.ToFile..." } ) | Out-Null } } else { $global:SRxEnv.Log.ToFile = $actualLogToFileSetting #if ($TrackDebugTimings) { $global:SRxEnv.DebugTimings["[shell]"].Add( @{ $(Get-Date) = "Tested path for SRx LogFile, enabled Log.ToFile..." } ) | Out-Null } } if( $DisableLogToFile ) { $global:SRxEnv.Log.ToFile = $false } #---------------- # Write-SRx #---------------- #== Apply any overrides supplied in custom.config.json (if it exists) if ($SRxConfig -is [String]) { if ($SRxConfig.EndsWith(".config.json")) { $customConfigPath = $global:SRxEnv.ResolvePath($SRxConfig) Write-SRx INFO $("[shell] Custom config specified: ") -ForegroundColor Cyan -NoNewline Write-SRx INFO $customConfigPath if (-not (Test-Path $customConfigPath)) { Write-SRx WARNING $("...ignoring custom config file specified with the -SRxConfig param (file does not exist)") $customConfigPath = $null } } else { Write-SRx WARNING $("[shell] Invalid custom config file specified with the -SRxConfig param (specified file does not end with *.config.json)") $customConfigPath = $null } } # Write-SRx VERBOSE " > Loading: ecosystem.config.json ..." $ecosystemConfigPath = Join-Path $(Join-Path $SRxRootPath $SRxConfigPath) "ecosystem.config.json" $ecosystemDB = Get-Content $ecosystemConfigPath -Raw -Encoding UTF8 -ErrorAction Stop $global:SRxEcoSystem = ConvertFrom-Json $ecosystemDB if($global:SRxEnv.DefaultSettings ) { $global:SRxEnv.psobject.members.remove("DefaultSettings") } $global:SRxEnv | Add-Member -Force -MemberType "NoteProperty" -Name "DefaultSettings" -Value $global:SRxEcoSystem $global:SRxEnv.SetCustomProperty("CachePath", $($home + "\" + $env:computername)) $global:SRxEnv.SetCustomProperty("MasterCachePath", $($home + "\" + $env:computername + "\MasterCache")) $path = $global:SRxEnv.CachePath if (-not (Test-Path $path)) { Write-SRx VERBOSE " > Create the new folder $path" New-Item -itemType Directory -Path $path | Out-Null } if ([string]::isNullOrEmpty($customConfigPath)) { $coreConfigPath = Join-Path $global:SRxEnv.Paths.Mgmt "custom.config.json" $customConfigPath = Join-Path $global:SRxEnv.CachePath "custom.config.json" if( -not (Test-Path $customConfigPath)) { Copy-Item -Path $coreConfigPath -Destination $customConfigPath } } #-- Set this property in case any other modules want to persist any custom configuration (even if the path does not exist) $global:SRxEnv.SetCustomProperty("CustomConfig", $customConfigPath) $global:SRxEnv.SetCustomProperty("CustomConfigIsReadOnly", $UseReadOnlyConfig) if ((-not [string]::isNullOrEmpty($global:SRxEnv.CustomConfig)) -and (Test-Path $global:SRxEnv.CustomConfig)) { #Write-SRx VERBOSE $("--Loading--> " + $global:SRxEnv.CustomConfig) try { $config = Get-Content $global:SRxEnv.CustomConfig -Raw -Encoding UTF8 $customConfig = ConvertFrom-Json $config } catch { Write-SRx ERROR ("!!! Exception occurred getting content from " + $global:SRxEnv.CustomConfig) Write-SRx ERROR ($_.Exception.Message) Write-SRx VERBOSE ($_.Exception) Write-SRx WARNING $("[shell] Unable to load the custom config file; Will block any attempts to persist options here.") $global:SRxEnv.SetCustomProperty("CustomConfigIsReadOnly", $true) } #temporary helper function #function OverlaySubProperties () { # #For $customConfig.$propName, extract each child property and append/overwrite as applicable... # $options = $($customConfig.$propName | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue).Name # foreach ($configName in $options) { # Write-SRx VERBOSE $("Custom Config -> $propName." + $configName + " = " + [string]($customConfig.$propName.$configName)) -ForegroundColor Yellow # if ( $( $global:SRxEnv.$propName | Get-Member -Name $configName) -ne $null ) { # #if the property already exists, just set it # $global:SRxEnv.$propName.$configName = $customConfig.$propName.$configName # } else { # #add the new member property and set it # $global:SRxEnv.$propName | Add-Member -Force -MemberType "NoteProperty" -Name $configName -Value $customConfig.$propName.$configName # } # } #} $propertyBag = $customConfig | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue foreach ($propName in $propertyBag.Name) { # Write-SRx VERBOSE $(" > Custom Config: " + $propName) -ForegroundColor Yellow switch ($propName) { "Paths" { #OverlaySubProperties #For Paths, extract each child path name and append/overwrite as applicable... $pathNames = $($customConfig.Paths | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue).Name foreach ($pathName in $pathNames) { # Write-SRx VERBOSE $(" Custom Config -> Path : `$pathName = " + $pathName + " [" + $customConfig.Paths.$pathName + "]") -ForegroundColor Yellow if ( $null -ne $( $global:SRxEnv.Paths | Get-Member -Name $pathName) ) { #if the property already exists, just set it $global:SRxEnv.Paths.$pathName = $customConfig.Paths.$pathName } else { #add the new member property and set it $global:SRxEnv.Paths | Add-Member -Force -MemberType "NoteProperty" -Name $pathName -Value $customConfig.Paths.$pathName } } break } "Override" { #Create base object if it does not exist... if ($null -eq $global:SRxEnv.Override) { $global:SRxEnv.SetCustomProperty("Override", (New-Object PSObject)) } #OverlaySubProperties #For Override options, extract each child config and append/overwrite as applicable... $options = $($customConfig.Override | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue).Name foreach ($configName in $options) { # Write-SRx VERBOSE $(" Custom Config -> Override." + $configName + " = " + [string]($customConfig.Override.$configName)) -ForegroundColor Yellow if ( $null -ne $( $global:SRxEnv.Override | Get-Member -Name $configName) ) { #if the property already exists, just set it $global:SRxEnv.Override.$configName = $customConfig.Override.$configName } else { #add the new member property and set it $global:SRxEnv.Override | Add-Member -Force -MemberType "NoteProperty" -Name $configName -Value $customConfig.Override.$configName } } break } #"Servers" { # -- future location for next generation server objects -- #} "Log" { #For Log options, extract each child config and append/overwrite as applicable... $options = $($customConfig.Log | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue).Name if ($null -eq $global:SRxEnv.Log) { $global:SRxEnv.SetCustomProperty("Log", (New-Object PSObject)) } foreach ($configName in $options) { # Write-SRx VERBOSE $(" Custom Config -> Log." + $configName + " = " + [string]($customConfig.Log.$configName)) -ForegroundColor Yellow switch ($configName) { "ToFile" { $actualLogToFileSetting = $customConfig.Log.ToFile break } "Level" { if ([int]$([SRxLogLevel]::$($customConfig.Log.Level)) -gt [int]$([SRxLogLevel]::$($SRxLogLevel))) { $global:SRxEnv.Log.Level = [SRxLogLevel]::$($customConfig.Log.Level) } break } default { if ( $null -ne $( $global:SRxEnv.Log | Get-Member -Name $configName) ) { #if the property already exists, just set it $global:SRxEnv.Log.$configName = $customConfig.Log.$configName } else { #add the new member property and set it $global:SRxEnv.Log | Add-Member -Force -MemberType "NoteProperty" -Name $configName -Value $customConfig.Log.$configName } } } } $logObj = $null break } "HandleMap" { if ($customConfig.HandleMap -is [Hashtable]) { $global:SRxEnv.SetCustomProperty("HandleMap", @($($customConfig.$propName))) } else { $global:SRxEnv.SetCustomProperty("HandleMap", $($customConfig.$propName)) } break } default { $global:SRxEnv.SetCustomProperty($propName, $($customConfig.$propName)) } } } #if ($TrackDebugTimings) { $global:SRxEnv.DebugTimings["[shell]"].Add( @{ $(Get-Date) = "Loaded custom.config.json..." } ) | Out-Null } } elseif (-not [string]::isNullOrEmpty($global:SRxEnv.CustomConfig)) { Write-SRx VERBOSE $("--Creating--> " + $global:SRxEnv.CustomConfig) #Write-SRx WARNING $global:SRxEnv $global:SRxEnv.CreateFileWithReadPermissions( $($global:SRxEnv.CustomConfig) ) try { <# New-Object PSObject -Property @{ "Paths" = @{ "Log" = ".\var\log" }; "RequiredModules" = @(); "Log" = @{ "Level" = "INFO"; "ToFile" = $true "RetentionBytes" = 1048576000; "RetentionDays" = 90; }; "Tmp" = @{ "RetentionBytes" = 1048576000; "RetentionDays" = 8; }; "Override" = @{ "CrawlVisualizationQueryLimit" = 100; }; } | ConvertTo-Json | Set-Content $global:SRxEnv.CustomConfig -ErrorAction Stop #> Get-Content $configPath -Raw -ErrorAction Stop | Set-Content $global:SRxEnv.CustomConfig -ErrorAction Stop $logObj = $null } catch { Write-SRx Error $("~~~ Unable to obtain a read/write lock when creating `$global:SRxEnv.CustomConfig - skipping its creation...") $global:SRxEnv.SetCustomProperty("CustomConfigIsReadOnly", $true) } #if ($TrackDebugTimings) { $global:SRxEnv.DebugTimings["[shell]"].Add( @{ $(Get-Date) = "Created custom.config.json..." } ) | Out-Null } } else { Write-SRx WARNING $("[shell] `$global:SRxEnv.CustomConfig is null; Will block any attempts to persist options here.") $global:SRxEnv.SetCustomProperty("CustomConfigIsReadOnly", $true) } #== Configure SRx Logging if ($null -ne $logObj) { $global:SRxEnv.PersistCustomProperty("Log", $logObj) } #-- set the logging path if (-not $global:SRxEnv.Paths.Log) { $global:SRxEnv.Paths | Add-Member -Force "Log" $($global:SRxEnv.LocalLogPath) } else { #-- Ensure $global:SRxEnv.Paths.Log is resolved to a full path (e.g. not a relative path such as .\var\log) #$global:SRxEnv.Paths.Log = $global:SRxEnv.ResolvePath($global:SRxEnv.Paths.Log) $global:SRxEnv.Paths.Log = $global:SRxEnv.LocalLogPath } if ((-not $global:SRxEnv.LogFile) -or (-not (Test-Path $global:SRxEnv.LogFile))) { $logFolder = Join-Path $global:SRxEnv.Paths.Log "Session" if (-not (Test-Path $logFolder)) { New-Item -path $logFolder -ItemType Directory | Out-Null } if($SourceID) { $sourceTag = $SourceID.Replace("-","") $currentLogFile = Join-Path -Path $logFolder $("SRx-" + $(Get-Date -Format "yyyyMMddHHmmss") + "-" + $pid + "-tag-" + $sourceTag + ".log") $global:SRxEnv.SetCustomProperty("SourceTag", $sourceTag) } else { $currentLogFile = Join-Path -Path $logFolder $("SRx-" + $(Get-Date -Format "yyyyMMddHHmmss") + "-" + $pid + ".log") } } else { $currentLogFile = $global:SRxEnv.ResolvePath($global:SRxEnv.LogFile) } $global:SRxEnv.SetCustomProperty("LogFile", $currentLogFile) $global:SRxEnv.Log.ToFile = $actualLogToFileSetting if( $DisableLogToFile ) { $global:SRxEnv.Log.ToFile = $false } # if ($global:SRxEnv.Log.ToFile) { <# if( $global:SRxEnv.Log.Level -eq "VERBOSE") { Write-SRx INFO $("[shell] Current log file: ") -ForegroundColor DarkCyan -NoNewline Write-SRx INFO ($global:SRxEnv.LogFile) } #> # } #if ($TrackDebugTimings) { $global:SRxEnv.DebugTimings["[shell]"].Add( @{ $(Get-Date) = "Configured SRx log file..." } ) | Out-Null } #== Set the run-time properties $global:SRxEnv.SetCustomProperty("CurrentUser", $SRxCurrentUser) $global:SRxEnv.SetCustomProperty("isBuiltinAdmin", $isBuiltinAdmin) #== Update the properties for this PowerShell window (e.g. the UI) === if (-not $global:SRxEnv.UI) { $global:SRxEnv.SetCustomProperty("UI", $( New-Object PSObject -Property @{ "MinWidth" = 125; "MinHeight" = 5000; })) } if ((-not $global:SRxEnv.UI.MinWidth) -or ($global:SRxEnv.UI.MinWidth -lt 125)) { $global:SRxEnv.UI | Add-Member -Force -MemberType "NoteProperty" -Name "MinWidth" -Value 125 } if ((-not $global:SRxEnv.UI.MinHeight) -or ($global:SRxEnv.UI.MinHeight -lt 5000)) { $global:SRxEnv.UI | Add-Member -Force -MemberType "NoteProperty" -Name "MinHeight" -Value 5000 } #-- Ensure minimum width and height for this shell window if ($Host -and $Host.UI -and $Host.UI.RawUI) { $rawUI = $Host.UI.RawUI $oldSize = $rawUI.BufferSize if ($oldSize.Width -gt $global:SRxEnv.UI.MinWidth) { $global:SRxEnv.UI.MinWidth = $oldSize.Width } if ($oldSize.Height -gt $global:SRxEnv.UI.MinHeight) { $global:SRxEnv.UI.MinHeight = $oldSize.Height } $typeName = $oldSize.GetType().FullName $newSize = New-Object $typeName ($global:SRxEnv.UI.MinWidth, $global:SRxEnv.UI.MinHeight) $rawUI.BufferSize = $newSize } #== Resolve any relative or variable valued paths ..and validate existance foreach ($pathName in $($global:SRxEnv.Paths | Get-Member | Where-Object {$_.MemberType -eq "NoteProperty"}).Name) { #Ensure this path is resolved to a full path (e.g. not a relative path such as ./somePathName or $PWD/somePathName) if( $pathName -ne "Log") { $global:SRxEnv.Paths.$pathName = $global:SRxEnv.ResolvePath($global:SRxEnv.Paths.$pathName) } switch ($pathName) { "Modules" { #== "Modules" path for "Search" specific modules, "Core" helper modules (e.g. utility functionality), and any product specific modules (e.g. "SP2013", "SP2016", etc) #-- x:\<SRxProjectPath>\Modules if (-not (Test-Path $global:SRxEnv.Paths.Modules)) { Write-SRx WARNING $("[shell] Invalid path specified for Modules: " + $global:SRxEnv.Paths.Modules) $global:__SRxHasInitFailure = $true } break } "Log" { #== "Log" path for results and user output #-- x:\<current-path>\var\log if (-not (Test-Path $global:SRxEnv.Paths.Log)) { New-Item $global:SRxEnv.Paths.Log -Type Directory -ErrorAction SilentlyContinue| Out-Null #Verify that it did get created... if (-not (Test-Path $global:SRxEnv.Paths.Log)) { Write-SRx WARNING $("[shell] Unable to create the Log path: " + $global:SRxEnv.Paths.Log) $global:__SRxHasInitFailure = $true } else { Write-SRx INFO $("[shell] Results and Logging Path: " + $global:SRxEnv.Paths.Log) } } break } "Scripts" { #== "Scripts" path (e.g. useful for custom scripts) #-- x:\<SRxProjectPath>\Scripts if (-not (Test-Path $global:SRxEnv.Paths.Scripts)) { Write-SRx WARNING $("[shell] Invalid path specified for Scripts: " + $global:SRxEnv.Paths.Scripts) } else { if (-not ($Env:Path).ToLower().Contains($global:SRxEnv.Paths.Scripts.ToLower())) { #Append to the $ENV:Path (if it doesn't already exist) $ENV:Path += ";" + $global:SRxEnv.Paths.Scripts #Write-SRx VERBOSE $(" > Appending the Scripts folder (" + $global:SRxEnv.Paths.Scripts + ") to `$ENV:Path") } } break } "Tools" { #== "Tools" path (e.g. for ULSViewer) #-- x:\<SRxProjectPath>\Tools if (-not (Test-Path $global:SRxEnv.Paths.Tools)) { if ($global:SRxEnv.Paths.Tools.toLower().startsWith($global:SRxEnv.Paths.SRxRoot.toLower())) { Write-SRx VERBOSE $("[shell] Removing `$global:SRxEnv.Paths.Tools because the specified path does not exist") $global:SRxEnv.Paths.PSObject.Properties.Remove("Tools") } else { Write-SRx WARNING $("[shell] Invalid path specified for Tools: " + $global:SRxEnv.Paths.Tools) } } else { if (-not ($Env:Path).ToLower().Contains($global:SRxEnv.Paths.Tools.ToLower())) { #Append to the $ENV:Path (if it doesn't already exist) $ENV:Path += ";" + $global:SRxEnv.Paths.Tools Write-SRx INFO $("[shell] Appending the Tools folder (" + $global:SRxEnv.Paths.Tools + ") to `$ENV:Path") } } break } default { if (-not (Test-Path $global:SRxEnv.Paths.$pathName)) { #Write-SRx WARNING $("[shell] Invalid path specified for " + $pathName + ": " + $global:SRxEnv.Paths.$pathName) } } } } #Write-SRx INFO "9 ---------------------------------" $global:SRxEnv.SetCustomProperty("PnPConnections", @{}) $global:SRxEnv.PnPConnections['SPO'] = $null $connected = Get-SRxIsConnected $global:SRxEnv.SetCustomProperty("isSetup", $(-not $connected)) if($connected) { $global:SRxEnv.SetCustomProperty("isStakeHolder", $($global:SRxEnv.Tenancy.IsStakeholder)) } #final $global:SRxEnv.SetCustomProperty("Exists", $true) #Write-Host "10" #shell if($LoadModule2) { $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name LoadModule -Value $LoadModule2 } else { $global:SRxEnv | Add-Member -Force -MemberType ScriptMethod -Name LoadModule -Value $LoadModule } #Write-Host "11" } <# else { Write-Host ( $global:SRxEnv | FL | Out-String ) if( -not $global:SRxEnv.PnPConnections ) { Write-Host "init SRxEnv-1" $global:SRxEnv.SetCustomProperty("PnPConnections", @{}) Write-Host "init SRxEnv-2" $global:SRxEnv.PnPConnections['SPO'] = $null } } #> } catch { return $null } return $str } } #EndRegion '.\Public\Initialize-SRxEnv.ps1' 660 #Region '.\Public\New-SRxReport.ps1' 0 function New-SRxReport { <# .SYNOPSIS Invokes a test(s) and handles where the output event(s) get written .DESCRIPTION This cmdlet wraps the invocation chain of Test-SRx followed by: -> Export-SRxToSearchDashboard | Export-SRxToAlertsList -> and/or Write-SRxConsole .NOTES ========================================= Project : Search Health Reports (SRx) ----------------------------------------- File Name : New-SRxReport.psm1 Author : Eric Dixon Requires : PowerShell Version 5.1, Search Health Reports (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys' fees, that arise or result from the use or distribution of the Sample Code. ======================================================================================== .INPUTS Control file (e.g. TestControl.csv) .OUTPUTS [ $SRxEvent(s) ] .EXAMPLE #> [CmdletBinding()] param ( [alias("All")][switch]$RunAllTests, [alias("OutNull")][switch]$NoWriteToConsole, [switch]$PassThrough, [alias("EventLog")][switch]$WriteErrorsToEventLog, [switch]$Details ) DynamicParam { # Set the dynamic parameters' name $ParameterControlFile = 'ControlFile' $ParameterTest = 'Test' # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollectionControlFile = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionTest = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttributeControlFile = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeControlFile.Mandatory = $false $ParameterAttributeControlFile.Position = 1 $ParameterAttributeTest = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeTest.Mandatory = $false $ParameterAttributeTest.Position = 1 # Add the attributes to the attributes collection $AttributeCollectionControlFile.Add($ParameterAttributeControlFile) $AttributeCollectionTest.Add($ParameterAttributeTest) # Generate and set the ValidateSet $rulesControlFolder = $global:SRxEnv.Paths.RuleSets #Join-Path $global:SRxEnv.Paths.Config "TestControls" #$arrSet = Get-ChildItem -Path $rulesControlFolder -File "*.csv" -Recurse | Select-Object -ExpandProperty Name $arrSet = Get-ChildItem -Path $rulesControlFolder -File "*.csv" -Recurse ` | Select-Object -ExpandProperty Name ` | % {if($_.EndsWith(".csv","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} $ValidateSetAttributeControlFile = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) $rulesDefinitions = $global:SRxEnv.Paths.Rules #$arrSet2 = Get-ChildItem -Path $rulesDefinitions -File "Rule-*.ps1" -Recurse | Select-Object -ExpandProperty Name $arrSet2 = Get-ChildItem -Path $rulesDefinitions -File "Rule-*.ps1" -Recurse ` | Select-Object -ExpandProperty Name ` | % {if($_.StartsWith("Rule-","CurrentCultureIgnoreCase")){$_.Substring(5)}} ` | % {if($_.EndsWith(".ps1","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} $ValidateSetAttributeTest = New-Object System.Management.Automation.ValidateSetAttribute($arrSet2) # Add the ValidateSet to the attributes collection $AttributeCollectionControlFile.Add($ValidateSetAttributeControlFile) $AttributeCollectionTest.Add($ValidateSetAttributeTest) # Create and return the dynamic parameter $RuntimeParameterControlFile = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterControlFile, [string], $AttributeCollectionControlFile) $RuntimeParameterDictionary.Add($ParameterControlFile, $RuntimeParameterControlFile) $RuntimeParameterTest = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterTest, [string], $AttributeCollectionTest) $RuntimeParameterDictionary.Add($ParameterTest, $RuntimeParameterTest) return $RuntimeParameterDictionary } BEGIN { Write-SRx DEBUG "BEGIN New-SRxReport" $ControlFile = $PsBoundParameters[$ParameterControlFile] $Test = $PsBoundParameters[$ParameterTest] if($RunAllTests) { Write-SRx VERBOSE "Running SRx Report -RunAllTests" } elseif($Test) { Write-SRx VERBOSE "Running SRx Report with test: $Test" } elseif($ControlFile) { Write-SRx VERBOSE "Running SRx Report with control file: $ControlFile" } } PROCESS { Write-SRx DEBUG "PROCESS - Entered New-SRxReport process" if($RunAllTests) { $output = Test-SRx -RunAllTests } elseif($Test) { if($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent) { $output = Test-SRx -Name $Test -Debug } else { $output = Test-SRx -Name $Test } } elseif($ControlFile) { $output = Test-SRx -ControlFile $ControlFile } else { Write-SRx ERROR "You must supply command line parameter -Test <test name>, -ControlFile <control file name>, or -RunAllTests" return } if($global:SRxEnv.Dashboard.Initialized) { $output | Export-SRxToSearchDashboard | Export-SRxToAlertsList | Out-Null } if($global:SRxEnv.OMS.Initialized) { $output | Export-SRxToOMS | Out-Null } if(-not $NoWriteToConsole -and $output -ne $null) { $output | Write-SRxConsole -Details:$Details } if($WriteErrorsToEventLog) { foreach($o in $output) { $message = $null if($o.Level -eq "Warning") { $message = $o.Name +":"+ $o.Headline +"("+ $o.RunId +")" $eventId = 21121 $eventType = [System.Diagnostics.EventLogEntryType]::Warning } if($o.Level -eq "Error") { $message = $o.Name +":"+ $o.Headline +"("+ $o.RunId +")" $eventId = 21122 $eventType = [System.Diagnostics.EventLogEntryType]::Error } if($output.Level -eq "Exception") { $message = $o.Name +":"+ $o.Headline +"("+ $o.RunId +")" $eventType = [System.Diagnostics.EventLogEntryType]::Error $eventId = 21123 } if(-not [string]::IsNullOrEmpty($message)) { try { Write-EventLog -Source "SharePoint Search Health Reports Dashboard" -LogName Application -EntryType $eventType -Message $message -EventId $eventId Write-SRx INFO "Wrote test output to event log." } catch { Write-SRx ERROR "Failed writing test result to Event Log" } } } } if($PassThrough) { $output } } END { Write-SRx DEBUG "END" } } #EndRegion '.\Public\New-SRxReport.ps1' 225 #Region '.\Public\New-SRxStoredCredential.ps1' 0 function New-SRxStoredCredential { <# .SYNOPSIS Create a new stored credential stored on local machine .DESCRIPTION This function will save a new stored credential to a .cred file into current user's folder on local machine. .NOTES ========================================= Project : The Source Shell (SRx) ----------------------------------------- File Name : New-SRxStoredCredential.psm1 Author : Nikolay Mukhin Requires : PowerShell Version 5.1, The Source Shell (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== .INPUTS .OUTPUTS $Credential object .EXAMPLE New-SRxStoredCredential #> [CmdletBinding()] param ( ) BEGIN { Write-SRx DEBUG "BEGIN New-SRxStoredCredential" } PROCESS { Write-SRx DEBUG "PROCESS - Entered New-SRxStoredCredential process" $KeyPath = $home + "\" + $env:computername if (!(Test-Path $KeyPath)) { try { New-Item -ItemType Directory -Path $KeyPath -ErrorAction STOP | Out-Null } catch { Write-SRx Warning $("[shell] Can't create user's local store folder: " + $_.Exception.Message) throw $_.Exception.Message } } try { $Credential = Get-Credential -Message "Enter a user name and password" if( -not [string]::isNullOrEmpty($Credential.Username) ) { $Credential.Password | ConvertFrom-SecureString | Out-File "$($KeyPath)\$($Credential.Username).cred" -Force # Return a PSCredential object (with no password) so the caller knows what credential username was entered for future recalls New-Object -TypeName System.Management.Automation.PSCredential($Credential.Username,(new-object System.Security.SecureString)) } } catch {} } END { Write-SRx DEBUG "END" } } #EndRegion '.\Public\New-SRxStoredCredential.ps1' 65 #Region '.\Public\Read-SRxPipeline.ps1' 0 Function Read-SRxPipeline { $path = $global:SRxEnv.SchedulerPath $pipeline = $null try { if (!(Test-Path $path)) { Write-SRx VERBOSE "Create the new folder $path" New-Item -itemType Directory -Path $path | Out-Null New-Item -itemType File -Path $path -Name "Pipeline.json" -Value '{}' | Out-Null } $schedulerFolderPath = $path #+ "\" + "Scheduler" <# if (!(Test-Path $schedulerFolderPath)) { Write-SRx VERBOSE "Create the new folder $schedulerFolderPath" New-Item -itemType Directory -Path $schedulerFolderPath | Out-Null New-Item -itemType File -Path $schedulerFolderPath -Name "Pipeline.json" -Value '{}' | Out-Null } #> #Set-Content -Path $schedulerPipeline -Value '{}' $schedulerPipeline = $schedulerFolderPath + "\Pipeline.json" $pipeline = Get-Content $($schedulerPipeline) -Raw -ErrorAction Stop if ([string]::isNullOrEmpty($pipeline)) { Write-SRx Warning $("~~~ The property cannot be persisted (the custom pipeline file is null or empty) - skipping...") } else { } } catch { $pipeline = $null } return $pipeline } #EndRegion '.\Public\Read-SRxPipeline.ps1' 36 #Region '.\Public\Read-SRxPipelineValue.ps1' 0 Function Read-SRxPipelineValue { $pipeline = Read-SRxPipeline if ([string]::isNullOrEmpty($pipeline)) { #Write-SRx Warning $("empty pipeline - skipping...") return $null } $tmp = ConvertFrom-Json $pipeline -ErrorAction SilentlyContinue Set-SRxPipeline -init $($tmp.Pipeline) return $tmp.Pipeline } #EndRegion '.\Public\Read-SRxPipelineValue.ps1' 11 #Region '.\Public\Remove-SRxStringSpecialCharacter.ps1' 0 function Remove-SRxStringSpecialCharacter { <# .SYNOPSIS This function will remove the special character from a string. .DESCRIPTION This function will remove the special character from a string. I'm using Unicode Regular Expressions with the following categories \p{L} : any kind of letter from any language. \p{Nd} : a digit zero through nine in any script except ideographic http://www.regular-expressions.info/unicode.html http://unicode.org/reports/tr18/ .PARAMETER String Specifies the String on which the special character will be removed .PARAMETER SpecialCharacterToKeep Specifies the special character to keep in the output .EXAMPLE Remove-SRxStringSpecialCharacter -String "^&*@wow*(&(*&@" wow .EXAMPLE Remove-SRxStringSpecialCharacter -String "wow#@!`~)(\|?/}{-_=+*" wow .EXAMPLE Remove-SRxStringSpecialCharacter -String "wow#@!`~)(\|?/}{-_=+*" -SpecialCharacterToKeep "*","_","-" wow-_* .NOTES Francois-Xavier Cat @lazywinadmin lazywinadmin.com github.com/lazywinadmin #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [Alias('Text')] [System.String[]]$String, [Alias("Keep")] #[ValidateNotNullOrEmpty()] [String[]]$SpecialCharacterToKeep ) PROCESS { try { IF ($PSBoundParameters["SpecialCharacterToKeep"]) { $Regex = "[^\p{L}\p{Nd}" Foreach ($Character in $SpecialCharacterToKeep) { IF ($Character -eq "-") { $Regex += "-" } else { $Regex += [Regex]::Escape($Character) } #$Regex += "/$character" } $Regex += "]+" } #IF($PSBoundParameters["SpecialCharacterToKeep"]) ELSE { $Regex = "[^\p{L}\p{Nd}]+" } FOREACH ($Str in $string) { Write-Verbose -Message "Original String: $Str" $Str -replace $regex, "" } } catch { $PSCmdlet.ThrowTerminatingError($_) } } #PROCESS } #EndRegion '.\Public\Remove-SRxStringSpecialCharacter.ps1' 79 #Region '.\Public\Resolve-SRxPropertiesArray_JSON.ps1' 0 function Resolve-SRxPropertiesArray_JSON { [CmdletBinding()] param( [Parameter(ValueFromPipeline)][pscustomobject]$Thing ) process { if($null -eq $Thing) { #$null @() } elseif($($Thing.Psobject.Properties.value.count) -eq 0) { @() } elseif( $Thing.Property) { @($($Thing.Property)) } else { $Thing } } } #EndRegion '.\Public\Resolve-SRxPropertiesArray_JSON.ps1' 16 #Region '.\Public\Resolve-SRxTermsArray_JSON.ps1' 0 function Resolve-SRxTermsArray_JSON { [CmdletBinding()] param( [Parameter(ValueFromPipeline)][pscustomobject]$Thing ) process { if($null -eq $Thing) { $null } elseif($($Thing.Psobject.Properties.value.count) -eq 0) { @() } elseif( $Thing.Term) { @($($Thing.Term)) } else { $Thing } } } #EndRegion '.\Public\Resolve-SRxTermsArray_JSON.ps1' 13 #Region '.\Public\Save-SRxConfig.ps1' 0 function Save-SRxConfig() { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true)]$propertyName, [Parameter(Mandatory=$true)]$propertyValue ) BEGIN { } PROCESS { $readlockRetries = 40 $persistFailure = $false $config = $null #Write-SRx VERBOSE " > Saving 3" do { try { #Write-SRx VERBOSE " > Saving 4" Write-SRx VERBOSE $(" > Saving '" + $propertyName + "' to " + $global:SRxEnv.CustomConfig) #-ForegroundColor Yellow $config = Get-Content $($global:SRxEnv.CustomConfig) -Raw -Encoding UTF8 -ErrorAction Stop if ([string]::isNullOrEmpty($config)) { $persistFailure = $true Write-SRx Warning $("~~~ The property '" + $propertyName + "' cannot be persisted (the custom config file is null or empty) - skipping...") } else { try { $tmpSRxEnv = ConvertFrom-Json $config -ErrorAction SilentlyContinue if ($null -ne $(Get-Command "Set-SRxCustomProperty" -ErrorAction SilentlyContinue)) { $tmpSRxEnv | Set-SRxCustomProperty $propertyName $propertyValue } else { $tmpSRxEnv | Add-Member -Force -MemberType "NoteProperty" -Name $propertyName -Value $propertyValue } $jsonConfig = ConvertTo-Json $tmpSRxEnv -Depth 12 } catch { $persistFailure = $true Write-Error $(" --> Caught exception converting `$global:SRxEnv.CustomConfig to object/JSON. Check for syntax errors - skipping: " + $propertyName) } } } catch [System.IO.IOException] { if ($readlockRetries -eq 40) { Write-SRx INFO " --> Unable to read $configPath - Retrying up to 10s" -NoNewline -ForegroundColor Yellow } elseif ($($readlockRetries % 4) -eq 0) { Write-SRx INFO "." -NoNewline } Start-Sleep -Milliseconds 250 $readlockRetries-- } catch { #Write-SRx VERBOSE " > Saving 5" $persistFailure = $true Write-Error $(" --> Caught exception reading `$global:SRxEnv.CustomConfig when persisting a change - skipping: " + $propertyName) } } while (($readlockRetries -gt 0) -and ([string]::isNullOrEmpty($jsonConfig)) -and (-not $persistFailure)) #Write-SRx VERBOSE " > Saving 6" if ([string]::isNullOrEmpty($jsonConfig) -and ($readlockRetries -lt 1)) { Write-SRx Warning $("~~~ Timed out waiting for a read/write lock on `$global:SRxEnv.CustomConfig - skipping: " + $propertyName) } elseif (-not [string]::isNullOrEmpty($jsonConfig)) { try { $jsonConfig | Set-Content $($global:SRxEnv.CustomConfig) -ErrorAction Stop } catch { Write-SRx Warning $("~~~ Unable to obtain a read/write lock on `$global:SRxEnv.CustomConfig - skipping: " + $propertyName) } } } } #EndRegion '.\Public\Save-SRxConfig.ps1' 60 #Region '.\Public\Save-SRxProfile.ps1' 0 Function Save-SRxProfile { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true)]$TermGroupName ) BEGIN { } PROCESS { try { Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl -SPO | Out-Null $connection = Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl $count = 13 $counter = 0 $Activity = "Exporting TermGroup $TermGroupName ..." $status = "" #Write-Host ($termGroup | Format-List | Out-String) Write-SRx VERBOSE "" #Write-SRx WARNING " Exporting TermGroup $TermGroupName ..." #$($global:SRxEnv.Tenancy.TermGroupName)" $termGroup = Get-PnPTermGroup -Identity $TermGroupName -ErrorAction SilentlyContinue -Connection $connection #$TermGroupID $global:SRxEnv.Tenancy.TermGroupID = $termGroup.Id # Work folder $profilesFolderPath = $global:SRxEnv.CachePath + "\Profiles" if (!(Test-Path $profilesFolderPath)) { Write-SRx VERBOSE " > Create the new folder $profilesFolderPath" New-Item -itemType Directory -Path $profilesFolderPath | Out-Null } if( $global:SRxEnv.Tenancy.TermGroupID -ne "") { #ensure folder for term group $sgroupName = (Remove-SRxStringSpecialCharacter -String $global:SRxEnv.Tenancy.TermGroupName) $profileFolderPath = $profilesFolderPath + "\" + $sgroupName if (!(Test-Path $profileFolderPath)) { Write-SRx VERBOSE " > Create the new folder $profileFolderPath" New-Item -itemType Directory -Path $profileFolderPath | Out-Null } #saving copy of custom.config.json #$customConfigPath = Join-Path $global:SRxEnv.Paths.Mgmt "custom.config.json" $customConfigPath = Join-Path $global:SRxEnv.CachePath "custom.config.json" $backupConfigPath = $profileFolderPath + "\custom.config.json" $status = "Saving custom.config.json to $backupConfigPath ..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" Copy-Item -Path $customConfigPath -Destination $backupConfigPath #export taxonomy group to xml file $backupTermGroupPath = $profileFolderPath + "\termGroup.xml" $status = "Exporting Taxonomy Group to $backupTermGroupPath ..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" if ((Test-Path $backupTermGroupPath)) { Remove-Item $backupTermGroupPath -Confirm:$false } #Write-SRx VERBOSE " > termGroupID = $($termGroup.Id)" $termGroup | Export-PnPTermGroupToXml -Out $backupTermGroupPath -ErrorAction:Stop -FullTemplate -Force -Connection $connection if ((Test-Path $backupTermGroupPath)) { #filtering group xml to extract only provisioning schema termset into separate xml file $backupTermSetPath = $profileFolderPath + "\termSet.xml" $status = "Saving provisioning schema to $backupTermSetPath..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" $siteXML = [xml](Get-Content -Path $backupTermGroupPath -Raw -Encoding UTF8) $termSets = $siteXML.Provisioning.Templates.ProvisioningTemplate.TermGroups.TermGroup.TermSets if( $null -ne $termSets) { ($termSets.ChildNodes | Where-Object { ($($global:SRxEnv.Tenancy.TermSetID) -ne $_.ID) }) | ForEach-Object { $termSets.RemoveChild($_) | Out-Null } } $siteXML.Save($backupTermSetPath) $status = "Saving provisioning schema to json..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" [xml[]] (Get-Content -Raw -Encoding UTF8 -Path $backupTermSetPath) | Convert-SRxFromXML | ConvertTo-Json -Depth 50 | Format-SRxJson -Indentation 1 | Set-Content $($profileFolderPath + "\ProvisioningDatabase.json") $status = "Saving provisioning schema to minified json..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" [xml[]] (Get-Content -Raw -Encoding UTF8 -Path $backupTermSetPath) | Convert-SRxFromXML | ConvertTo-Json -Depth 50 | Format-SRxJson -Minify | Set-Content $($profileFolderPath + "\ProvisioningDatabase.min.json") #filtering group xml to extract everything,but provisioning schema termset into separate xml file $backupTermSetPath = $profileFolderPath + "\termSetFiltered.xml" $status = "Saving filtered group to $backupTermSetPath..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" $siteXML = [xml](Get-Content -Path $backupTermGroupPath -Raw -Encoding UTF8) $termSets = $siteXML.Provisioning.Templates.ProvisioningTemplate.TermGroups.TermGroup.TermSets if( $null -ne $termSets) { ($termSets.ChildNodes | Where-Object { ($($global:SRxEnv.Tenancy.TermSetID) -eq $_.ID) }) | ForEach-Object { $termSets.RemoveChild($_) | Out-Null } } $siteXML.Save($backupTermSetPath) $status = "Saving filtered group to json..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" [xml[]] (Get-Content -Raw -Encoding UTF8 -Path $backupTermSetPath) | Convert-SRxFromXML | ConvertTo-Json -Depth 50 | Format-SRxJson -Indentation 1 | Set-Content $($profileFolderPath + "\the_source_term_group.json") $status = "Saving filtered group to minified json..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" [xml[]] (Get-Content -Raw -Encoding UTF8 -Path $backupTermSetPath) | Convert-SRxFromXML | ConvertTo-Json -Depth 50 | Format-SRxJson -Minify | Set-Content $($profileFolderPath + "\the_source_term_group.min.json") $status = "Activating provisioning schema..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE "" Write-SRx VERBOSE " > $status" Connect-SRxProvisioningDatabase_JSON $isStakeholder = Test-SRxIsStakeHolder $global:SRxEnv.SetCustomProperty("isStakeHolder", $isStakeholder) $status = "isStakeHolder = $isStakeholder" Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" $global:SRxEnv.Tenancy.IsStakeholder = $isStakeholder $status = "Saving Tenancy..." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" #$global:SRxEnv.PersistCustomProperty("Tenancy", $global:SRxEnv.Tenancy) Save-SRxConfig -propertyName "Tenancy" -propertyValue $global:SRxEnv.Tenancy $status = "Completed." Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) Write-SRx VERBOSE " > $status" } else { Write-SRx ERROR " > Can't export $TermGroupName ID = $($termGroup.Id)" return "exception" } if($Activity) { Write-Progress -Activity $Activity -Status "Ready" -Completed } } } catch { if($Activity) { Write-Progress -Activity $Activity -Status "Ready" -Completed } Write-SRx ERROR ($_.Exception.Message) Write-SRx VERBOSE ($_.Exception) Write-SRx INFO ("Hit enter to continue...") #Read-Host return "exception" } return 0 #success } END { if($Activity) { Write-Progress -Activity $Activity -Status "Ready" -Completed } #Disconnect-PnPOnline -Connection $connection } } #EndRegion '.\Public\Save-SRxProfile.ps1' 180 #Region '.\Public\Set-SRxCommand.ps1' 0 Function Set-SRxCommand { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$false)]$command ) BEGIN { } PROCESS { try { if( $command ) { $global:SRxEnv.SetCustomProperty("Command", $true) $hashtable = @{} $hashtable["ts_Title"] = $command.ts_Title $hashtable["ts_ControlFile"] = $command.ts_ControlFile $hashtable["ts_IsAdmin"] = [System.Convert]::ToBoolean($command.ts_IsAdmin) $hashtable["ts_NewSite"] = [System.Convert]::ToBoolean($command.ts_NewSite) $hashtable["ts_Rebuild"] = [System.Convert]::ToBoolean($command.ts_Rebuild) $hashtable["ts_Environment"] = $command.ts_Environment $sopobject = [pscustomobject]$hashtable $global:SRxEnv.SetCustomProperty("SOP", $sopobject) $pipeline = @() foreach( $site in @($command.pipeline)) { if( -not [string]::IsNullOrEmpty($site.siteDesign) ) { $design_name = $site.siteDesign $master_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value "Master" $master_design_ = Get-SRxTermByCustomProperty_JSON -termHost $master_ -Key "ts_Design" -Value $design_name $master_site_ = Get-SRxTermByCustomProperty_JSON -termHost $master_design_ -Key "ts_Site" -Value $design_name $masterUrl = Get-SRxCustomPropertyValueByKey_JSON -termHost $master_site_ -Key "ts_URL" -ToUrl $site.masterUrl = $masterUrl $site.HubURL = Convert-SRxURL_JSON -URL $site.HubURL -environment $site.environment } if( -not [string]::IsNullOrEmpty($site.siteTitle) ) { $site.siteName = Remove-SRxStringSpecialCharacter -String $site.siteTitle } $pipeline += $site } $global:SRxEnv.SetCustomProperty("Pipeline", $pipeline) if($null -ne $global:SRxEnv.SOP) { $global:SRxEnv.SetCustomProperty("PnPEnvironment", $global:SRxEnv.SOP.ts_Environment) $global:SRxEnv.SetCustomProperty("PnPRebuild", [boolean]$global:SRxEnv.SOP.ts_Rebuild) $global:SRxEnv.SetCustomProperty("PnPNewSite", [boolean]$global:SRxEnv.SOP.ts_NewSite) } } } catch { Write-SRx ERROR " > Set-SRxCommand error:" Write-SRx ERROR ($_.Exception.Message) Write-SRx VERBOSE ($_.Exception) return "exception" } return 0 #success } END { } } #EndRegion '.\Public\Set-SRxCommand.ps1' 63 #Region '.\Public\Set-SRxCustomProperty.ps1' 0 function Set-SRxCustomProperty { <# .SYNOPSIS Internal helper for adding or refreshing a property to an existing object .DESCRIPTION .EXAMPLE $targetObject | Set-SRxCustomProperty "_CustomPropertyName" $propertyValue #> [CmdletBinding()] param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)]$object, [parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)][String]$propertyName, [parameter(Mandatory=$false,ValueFromPipeline=$false,Position=1)]$propertyValue ) if ($propertyName.Contains(".")) { Write-SRx VERBOSE ("[Set-SRxCustomProperty] `$propertyName contains '.' (" + $propertyName + ")") if ($propertyName.Contains("..") -or $propertyName.EndsWith(".")) { Write-SRx WARNING ("[Set-SRxCustomProperty] Skipping Invalid Property Name: " + $propertyName) return } else { $firstPos = $propertyName.indexOf(".") $parent = $propertyName.Substring(0, $firstPos) $child = $propertyName.Substring($firstPos + 1) Write-SRx DEBUG (" --> [Set-SRxCustomProperty] `$propertyName contains tokens ( p: " + $parent + " , c: " + $child +" )") if (($null -ne $object.$parent) -and (($object.$parent -is [PSObject]) -or ($object.$parent -is [Hashtable]))) { Write-SRx DEBUG (" --> [Set-SRxCustomProperty] Recursing... ( p: " + $parent + " , c: " + $child +" )") $object.$parent | Set-SRxCustomProperty $child $propertyValue } } } else { if ($object -is [Hashtable]) { $object.$propertyName = $propertyValue } elseif ($object -is [PSObject]) { if ( $null -ne $( $object | Get-Member -Name $propertyName) ) { #if the property already exists, just set it $object.$propertyName = $propertyValue } else { #add the new member property and set it $object | Add-Member -Force -MemberType "NoteProperty" -Name $propertyName -Value $propertyValue } } } } #EndRegion '.\Public\Set-SRxCustomProperty.ps1' 49 #Region '.\Public\Set-SRxPipeline.ps1' 0 function Set-SRxPipeline { PARAM ( [switch]$Print, [Parameter(Mandatory=$false)]$init, #[hashtable[]]$Init, [Parameter(Mandatory=$false)]$update, #[hashtable[]]$Update [Parameter(Mandatory=$false)]$ID ) BEGIN { } PROCESS { if($Print) { Write-SRx WARNING "PIPELINE=$($global:SRxEnv.Pipeline)" $pipelineObject = $global:SRxEnv.Pipeline[0] Write-SRx WARNING "PIPELINE OBJECT=$($pipelineObject)" $hashtable = @{} foreach( $property in $pipelineObject.psobject.properties.name ) { $hashtable[$property] = $pipelineObject.$property } Write-SRx WARNING "HASHTABLE=$($hashtable | Out-String)" return } if($null -ne $init) { $global:SRxEnv.SetCustomProperty("Pipeline", $init) } elseif( $null -ne $update) { if($null -ne $global:SRxEnv.Pipeline) { $pipe = [System.Collections.ArrayList]@() #$id = 0 foreach( $pipelineObject in $global:SRxEnv.Pipeline ) { #Write-SRx VERBOSE "Update pipeline id = $($pipelineObject.id)" $hashtable = @{} foreach( $property in $pipelineObject.psobject.properties.name ) { $hashtable[$property] = $pipelineObject.$property #Write-SRx VERBOSE "property = $property Value = $($hashtable[$property])" } #Write-SRx VERBOSE "Pipeline object id = $( $hashtable['id'])" #Write-SRx VERBOSE "Input object id = $( $update['id'])" if( $ID) { if($hashtable[$ID] -eq $update[$ID]) { foreach ($key in $update.Keys) { #Write-SRx VERBOSE "Replacing values..." if($key -ne $ID) { #Write-SRx VERBOSE "Replace property = $key Old Value = $($hashtable[$key]) New Value = $($update[$key])" $hashtable[$key] = $update[$key] } } } } else { if($hashtable['id'] -eq $update['id']) { foreach ($key in $update.Keys) { #Write-SRx VERBOSE "Replacing values..." if($key -ne 'id') { #Write-SRx VERBOSE "Replace property = $key Old Value = $($hashtable[$key]) New Value = $($update[$key])" $hashtable[$key] = $update[$key] } } } } #$hashtable['id'] = $id++ $site = [pscustomobject]$hashtable $pipe.Add($site) | Out-Null } #Write-SRx ERROR "Pipe.Count = $($pipe.Count)" $pipeline = $pipe.ToArray() $global:SRxEnv.SetCustomProperty("Pipeline", $pipeline) } else { return 3 #error } } else { $pipe = [System.Collections.ArrayList]@() $site = [PSCustomObject]@{ id = 0 environment = $null siteDesign = $null siteOwner = $null termID = $null siteTitle = $null siteName = $null siteURL = $null HubURL = $null } $pipe.Add($site) | Out-Null $pipeline = $pipe.ToArray() $global:SRxEnv.SetCustomProperty("Pipeline", $pipeline) } } END { } } #EndRegion '.\Public\Set-SRxPipeline.ps1' 95 #Region '.\Public\Set-SRxTermDesign.ps1' 0 Function Set-SRxTermDesign { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true)][string]$siteDesign, [Parameter(Mandatory=$false)][string]$title, [Parameter(Mandatory=$false)][string]$siteEnvironment, [Parameter(Mandatory=$true)]$connection ) PROCESS { try { $siteTitle = $siteDesign if( $title ) { $siteTitle = $title } $termDesign = Get-SRxHostingTerm -Design -siteDesign $siteDesign -siteEnvironment $siteEnvironment -Connection $connection if( -not $termDesign ) { # environment $environment_ = Get-SRxTermByCustomProperty_JSON -Key "ts_Environment" -Value $siteEnvironment $termEnvironmentID = $environment_.ID #Write-SRx WARNING "termEnvironmentID = $termEnvironmentID" $termEnvironment = Get-PnPTerm -Identity $([GUID]$termEnvironmentID) -ErrorAction SilentlyContinue -IncludeChildTerms -Connection $connection # design $termDesign = Get-SRxTermsWithCustomProperty -termsHost $termEnvironment -customPropertyKey "ts_Design" -customPropertyValue $siteDesign -connection $connection if(-not $termDesign) { Write-SRx VERBOSE " > Creating new $siteTitle term folder ..." # designs $termDesigns = Get-SRxTermsWithCustomProperty -termsHost $termEnvironment -customPropertyKey "ts_Designs" -customPropertyValue $siteEnvironment -connection $connection if(-not $termDesigns) { $customProperties = @{} $customProperties.add("ts_Designs", $siteEnvironment) $termDesigns = Add-PnPTermToTerm -ParentTerm $termEnvironment.Id -Name "Designs" -CustomProperties $customProperties -Connection $connection Start-Sleep -Seconds 3 } $customProperties = @{} $customProperties.add("ts_Design", $siteDesign) $termDesign = Add-PnPTermToTerm -ParentTerm $termDesigns.Id -Name $siteTitle -CustomProperties $customProperties -Connection $connection Start-Sleep -Seconds 3 } } return $termDesign } catch { Write-SRx ERROR ($_.Exception.Message) Write-SRx VERBOSE ($_.Exception) return $null } } } #EndRegion '.\Public\Set-SRxTermDesign.ps1' 51 #Region '.\Public\Show-SRxNotImplementedWarning.ps1' 0 function Show-SRxNotImplementedWarning { Write-Host Write-Host $("-" * 51) -ForegroundColor Yellow -BackgroundColor Yellow Write-Host $(" OPERATION IS NOT IMPLEMENTED ") -ForegroundColor Black -BackgroundColor Yellow Write-Host $("-" * 51) -ForegroundColor Yellow -BackgroundColor Yellow Write-Host return 0 } #EndRegion '.\Public\Show-SRxNotImplementedWarning.ps1' 9 #Region '.\Public\Show-SRxProductionWarning.ps1' 0 function Show-SRxProductionWarning { if(($global:SRxEnv.PnPEnvironment -eq "Production") -or ($global:SRxEnv.PnPEnvironment -eq "Release") -or ($global:SRxEnv.PnPEnvironment -eq "Master")) { Write-Host Write-Host $("-" * 51) -ForegroundColor DarkRed -BackgroundColor DarkRed if($global:SRxEnv.PnPEnvironment -eq "Master") { Write-Host $(" OPERATION ON MASTER DESIGN ") -ForegroundColor White -BackgroundColor DarkRed } else { Write-Host $(" OPERATION ON PRODUCTION LANDSCAPE ") -ForegroundColor White -BackgroundColor DarkRed } Write-Host $("-" * 51) -ForegroundColor DarkRed -BackgroundColor DarkRed } } #EndRegion '.\Public\Show-SRxProductionWarning.ps1' 14 #Region '.\Public\Show-SRxShell.ps1' 0 Function Show-SRxShell { PARAM ( [Parameter(Mandatory=$false)][switch]$Clear ) BEGIN { if( -not $(Get-Alias -Name ecosystem -ErrorAction SilentlyContinue)) { New-Alias -Name ecosystem -Value _EcoSystem -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name log -ErrorAction SilentlyContinue)) { New-Alias -Name log -Value _Log -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name cache -ErrorAction SilentlyContinue)) { New-Alias -Name cache -Value _Cache -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name task -ErrorAction SilentlyContinue)) { New-Alias -Name task -Value _Task -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name shell -ErrorAction SilentlyContinue)) { New-Alias -Name shell -Value _Shell -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name menu -ErrorAction SilentlyContinue)) { New-Alias -Name menu -Value Start-SOP -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name systray -ErrorAction SilentlyContinue)) { New-Alias -Name systray -Value Start-SysTray -Force -Scope Global -Option Constant } #if( -not $(Get-Alias -Name gui -ErrorAction SilentlyContinue)) { # New-Alias -Name gui -Value _Gui -Force -Scope Global -Option Constant #} if( -not $(Get-Alias -Name docs -ErrorAction SilentlyContinue)) { New-Alias -Name docs -Value _Docs -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name pipeline -ErrorAction SilentlyContinue)) { New-Alias -Name pipeline -Value _Pipeline -Force -Scope Global -Option Constant } } PROCESS { try { Write-Host Write-Host $("-" * 53) -ForegroundColor Black -BackgroundColor DarkCyan Write-Host $(" Provisioning Shell ") -ForegroundColor White -BackgroundColor DarkCyan Write-Host $(" Commands: menu | pipeline | log | cache | ecosystem ") -ForegroundColor Black -BackgroundColor DarkCyan Write-Host $(" Cmdlets: Start-Rule, Start-Ruleset, Get-Master ") -ForegroundColor Black -BackgroundColor DarkCyan Write-Host $("-" * 53) -ForegroundColor Black -BackgroundColor DarkCyan } catch { Write-Host "error" } } } #EndRegion '.\Public\Show-SRxShell.ps1' 52 #Region '.\Public\Show-SRxSOPTitle.ps1' 0 function Show-SRxSOPTitle { PARAM ( [Parameter(Mandatory=$false)][switch]$Clear ) PROCESS { if( $global:SRxEnv.SOP) { # Clear console try { if( $Clear -eq $true) { Clear-Host } $title = $global:SRxEnv.SOP.ts_Title $even = "" if( $($title.Length) % 2 -eq 0) { $even = " " } $width = 51 $side = ($width - $title.Length) / 2 Write-Host Write-Host $("-" * $width) -ForegroundColor Black -BackgroundColor DarkCyan Write-Host $(" " * $side) -ForegroundColor White -BackgroundColor DarkCyan -NoNewline Write-Host $($title + $even) -ForegroundColor White -BackgroundColor DarkCyan -NoNewline Write-Host $(" " * $side) -ForegroundColor White -BackgroundColor DarkCyan -NoNewline Write-Host Write-Host $("-" * $width) -ForegroundColor Black -BackgroundColor DarkCyan Write-Host } catch {} } } } #EndRegion '.\Public\Show-SRxSOPTitle.ps1' 31 #Region '.\Public\Show-SRxWindow.ps1' 0 function Show-SRxWindow { $null = [Win32Functions.Win32Windows]::ShowWindowAsync((Get-Process -PID $pid ).MainWindowHandle, 10) } #EndRegion '.\Public\Show-SRxWindow.ps1' 4 #Region '.\Public\Start-Rule.ps1' 0 function Start-Rule { <# .SYNOPSIS Invokes a test(s) and handles where the output event(s) get written .DESCRIPTION This cmdlet wraps the invocation chain of Test-SRx followed by: -> Export-SRxToSearchDashboard | Export-SRxToAlertsList -> and/or Write-SRxConsole .NOTES ========================================= Project : Search Health Reports (SRx) ----------------------------------------- File Name : Start-Rule.psm1 Author : Eric Dixon Requires : PowerShell Version 5.1, Search Health Reports (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys' fees, that arise or result from the use or distribution of the Sample Code. ======================================================================================== .INPUTS Control file (e.g. TestControl.csv) .OUTPUTS [ $SRxEvent(s) ] .EXAMPLE #> [CmdletBinding()] param ( [alias("All")][switch]$RunAllTests, [alias("OutNull")][switch]$NoWriteToConsole, [switch]$PassThrough, [alias("EventLog")][switch]$WriteErrorsToEventLog, [switch]$Details, [Parameter(Mandatory=$false)][string]$environment, [Parameter(Mandatory=$false)][boolean]$isnewsite, [Parameter(Mandatory=$false)][boolean]$isrebuild, [Parameter(Mandatory=$false)][boolean]$isadmin ) DynamicParam { # Set the dynamic parameters' name $ParameterControlFile = 'ControlFile' $ParameterTest = 'Test' # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollectionControlFile = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionTest = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttributeControlFile = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeControlFile.Mandatory = $false $ParameterAttributeControlFile.Position = 1 $ParameterAttributeTest = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeTest.Mandatory = $false $ParameterAttributeTest.Position = 1 # Add the attributes to the attributes collection $AttributeCollectionControlFile.Add($ParameterAttributeControlFile) $AttributeCollectionTest.Add($ParameterAttributeTest) # Generate and set the ValidateSet $rulesControlFolder =$global:SRxEnv.Paths.RuleSets # Join-Path $global:SRxEnv.Paths.Config "TestControls" #$arrSet = Get-ChildItem -Path $rulesControlFolder -File "*.csv" -Recurse | Select-Object -ExpandProperty Name $arrSet = Get-ChildItem -Path $rulesControlFolder -File "*.csv" -Recurse ` | Select-Object -ExpandProperty Name ` | % {if($_.EndsWith(".csv","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} $ValidateSetAttributeControlFile = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) $rulesDefinitions = $global:SRxEnv.Paths.Rules #$arrSet2 = Get-ChildItem -Path $rulesDefinitions -File "Rule-*.ps1" -Recurse | Select-Object -ExpandProperty Name $arrSet2 = Get-ChildItem -Path $rulesDefinitions -File "Rule-*.ps1" -Recurse ` | Select-Object -ExpandProperty Name ` | % {if($_.StartsWith("Rule-","CurrentCultureIgnoreCase")){$_.Substring(5)}} ` | % {if($_.EndsWith(".ps1","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} $ValidateSetAttributeTest = New-Object System.Management.Automation.ValidateSetAttribute($arrSet2) # Add the ValidateSet to the attributes collection $AttributeCollectionControlFile.Add($ValidateSetAttributeControlFile) $AttributeCollectionTest.Add($ValidateSetAttributeTest) # Create and return the dynamic parameter $RuntimeParameterControlFile = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterControlFile, [string], $AttributeCollectionControlFile) $RuntimeParameterDictionary.Add($ParameterControlFile, $RuntimeParameterControlFile) $RuntimeParameterTest = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterTest, [string], $AttributeCollectionTest) $RuntimeParameterDictionary.Add($ParameterTest, $RuntimeParameterTest) return $RuntimeParameterDictionary } BEGIN { Write-SRx DEBUG "BEGIN Start-Rule" if($environment) { $global:SRxEnv.SetCustomProperty("PnPEnvironment", $environment) } if($isnewsite) { $global:SRxEnv.SetCustomProperty("PnPNewSite", $([boolean]$isnewsite)) } if($isrebuild) { $global:SRxEnv.SetCustomProperty("PnPRebuild", $([boolean]$isrebuild)) } $ControlFile = $PsBoundParameters[$ParameterControlFile] $Test = $PsBoundParameters[$ParameterTest] if($RunAllTests) { Write-SRx VERBOSE "Running SRx Report -RunAllTests" } elseif($Test) { Write-SRx VERBOSE "Running SRx Report with test: $Test" } elseif($ControlFile) { Write-SRx VERBOSE "Running SRx Report with control file: $ControlFile" } } PROCESS { Write-SRx DEBUG "PROCESS - Entered Start-Rule process" if($RunAllTests) { $output = Test-SRx -RunAllTests } elseif($Test) { if($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent) { $output = Test-SRx -Name $Test -Debug } else { $output = Test-SRx -Name $Test } } elseif($ControlFile) { $output = Test-SRx -ControlFile $ControlFile } else { Write-SRx ERROR "You must supply command line parameter -Test <test name>, -ControlFile <control file name>, or -RunAllTests" return } if($global:SRxEnv.Dashboard.Initialized) { $output | Export-SRxToSearchDashboard | Export-SRxToAlertsList | Out-Null } if($global:SRxEnv.OMS.Initialized) { $output | Export-SRxToOMS | Out-Null } if(-not $NoWriteToConsole -and $output -ne $null) { $output | Write-SRxConsole -Details:$Details } if($WriteErrorsToEventLog) { foreach($o in $output) { $message = $null if($o.Level -eq "Warning") { $message = $o.Name +":"+ $o.Headline +"("+ $o.RunId +")" $eventId = 21121 $eventType = [System.Diagnostics.EventLogEntryType]::Warning } if($o.Level -eq "Error") { $message = $o.Name +":"+ $o.Headline +"("+ $o.RunId +")" $eventId = 21122 $eventType = [System.Diagnostics.EventLogEntryType]::Error } if($output.Level -eq "Exception") { $message = $o.Name +":"+ $o.Headline +"("+ $o.RunId +")" $eventType = [System.Diagnostics.EventLogEntryType]::Error $eventId = 21123 } if(-not [string]::IsNullOrEmpty($message)) { try { Write-EventLog -Source "SharePoint Search Health Reports Dashboard" -LogName Application -EntryType $eventType -Message $message -EventId $eventId Write-SRx INFO "Wrote test output to event log." } catch { Write-SRx ERROR "Failed writing test result to Event Log" } } } } if($PassThrough) { $output } } END { Write-SRx DEBUG "END" } } #EndRegion '.\Public\Start-Rule.ps1' 233 #Region '.\Public\Start-Ruleset.ps1' 0 function Start-Ruleset { <# .SYNOPSIS Evaluates test definition file and generates a corresponding SRxEvent .DESCRIPTION Can specify a control file as a parameter, which defines a specific set of tests to perform where each test specified in the control generates an SRxEvent .NOTES ========================================= Project : Search Health Reports (SRx) ----------------------------------------- File Name : Start-Ruleset.psm1 Author : Eric Dixon Requires : PowerShell Version 5.1, Search Health Reports (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys' fees, that arise or result from the use or distribution of the Sample Code. ======================================================================================== .INPUTS Control file (e.g. TestControl.csv) .OUTPUTS [ $SRxEvent(s) ] .EXAMPLE Start-Ruleset -ControlFile TestControl.csv #> [CmdletBinding()] param ( [Parameter(ParameterSetName='DefaultSet')] [alias("All")][switch]$RunAllTests, [Parameter(ParameterSetName='DefaultSet')] $Params=$null, [Parameter(ParameterSetName='DefaultSet')] [switch]$WhatIf=$false ) DynamicParam { # Set the dynamic parameters' name $ParameterControlFile = 'ControlFile' $ParameterTest = 'Name' # Generate the parameter values (e.g. control and test file names) for validation $__rulesControlFolder = $global:SRxEnv.Paths.RuleSets #Join-Path $global:SRxEnv.Paths.Config "TestControls" $__ruleControlFiles = Get-ChildItem -Path $__rulesControlFolder -File "*.csv" -Recurse $__ruleDefinitionsFolder = $global:SRxEnv.Paths.Rules $__ruleDefinitionFiles = Get-ChildItem -Path $__ruleDefinitionsFolder -File "Rule-*.ps1" -Recurse # Create the collection of attributes $AttributeCollectionControlFile = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionTest = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttributeControlFile = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeControlFile.Mandatory = $false $ParameterAttributeControlFile.Position = 1 $ParameterAttributeControlFile.ParameterSetName = 'ControlFileSet' $ParameterAttributeTest = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeTest.Mandatory = $false $ParameterAttributeTest.Position = 1 $ParameterAttributeTest.ParameterSetName = 'TestNameSet' # Add the control file and test parameters to the attribute collections $AttributeCollectionControlFile.Add($ParameterAttributeControlFile) $ValidateSetAttributeControlFile = New-Object System.Management.Automation.ValidateSetAttribute( $( $__ruleControlFiles | Select-Object -ExpandProperty Name | % {if($_.EndsWith(".csv","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} ) ) $AttributeCollectionControlFile.Add($ValidateSetAttributeControlFile) $AttributeCollectionTest.Add($ParameterAttributeTest) $ValidateSetAttributeTest = New-Object System.Management.Automation.ValidateSetAttribute( $( $__ruleDefinitionFiles | Select-Object -ExpandProperty Name | % {if($_.StartsWith("Rule-","CurrentCultureIgnoreCase")){$_.Substring(5)}} | % {if($_.EndsWith(".ps1","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} ) ) $AttributeCollectionTest.Add($ValidateSetAttributeTest) # Create and return the dynamic parameter $RuntimeParameterControlFile = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterControlFile, [string], $AttributeCollectionControlFile) $RuntimeParameterTest = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterTest, [string], $AttributeCollectionTest) # Create and return the dynamic parameter dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $RuntimeParameterDictionary.Add($ParameterControlFile, $RuntimeParameterControlFile) $RuntimeParameterDictionary.Add($ParameterTest, $RuntimeParameterTest) # Add "Helpful Features" for Rules and Control Files # -ListControlFiles $ParameterTestListControls = 'ListControlFiles' $ParameterAttributeTestListControls = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeTestListControls.Mandatory = $false $ParameterAttributeTestListControls.Position = 2 $ParameterAttributeTestListControls.ParameterSetName = 'TestNameSet' $AttributeCollectionTestListControls = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionTestListControls.Add($ParameterAttributeTestListControls) $RuntimeParameterTestListControls = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterTestListControls, [switch], $AttributeCollectionTestListControls) $RuntimeParameterDictionary.Add($ParameterTestListControls, $RuntimeParameterTestListControls) # -ListTests $ParameterControlFileListTests = 'ListTests' $ParameterAttributeControlFileListTests = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeControlFileListTests.Mandatory = $false $ParameterAttributeControlFileListTests.Position = 2 $ParameterAttributeControlFileListTests.ParameterSetName = 'ControlFileSet' $AttributeCollectionControlFileListTests = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionControlFileListTests.Add($ParameterAttributeControlFileListTests) $RuntimeParameterControlFileListTests = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterControlFileListTests, [switch], $AttributeCollectionControlFileListTests) $RuntimeParameterDictionary.Add($ParameterControlFileListTests, $RuntimeParameterControlFileListTests) return $RuntimeParameterDictionary } BEGIN { $startAll = Get-Date Write-SRx DEBUG "BEGIN Start-Ruleset" $ControlFile = $PsBoundParameters[$ParameterControlFile] $Test = $PsBoundParameters[$ParameterTest] if($RunAllTests) { $__rulesControl = @() $__ruleDefinitionFiles = $__ruleDefinitionFiles | ? {($_.fullname -notLike "*\Example\*") -and ($_.fullname -notLike "*\InDev\*")} foreach($f in $__ruleDefinitionFiles) { $o = New-Object PSObject -Property @{ "Rule" = $([System.IO.Path]::GetFileNameWithoutExtension($f)); "WriteToDashboard" = "true" ; "AlertOnFailure" = "true" } $__rulesControl += $o } $ControlFile = "RunAllTests" } elseif($Test) { $TestListControls = $PsBoundParameters[$ParameterTestListControls] $__rulesControl = @() if(-not $Test.EndsWith(".ps1", "CurrentCultureIgnoreCase")) { $Test += ".ps1" } if(-not $Test.StartsWith("Rule-", "CurrentCultureIgnoreCase")) { $Test = "Rule-" + $Test } $o = New-Object PSObject -Property @{ "Rule" = $([System.IO.Path]::GetFileNameWithoutExtension($Test)); "WriteToDashboard" = "true" ; "AlertOnFailure" = "true" } $__rulesControl += $o $ControlFile = $Test } elseif($ControlFile) { $ControlsListTests = $PsBoundParameters[$ParameterControlFileListTests] if(-not $ControlFile.EndsWith(".csv", "CurrentCultureIgnoreCase")) { $ControlFile += ".csv" } if(Test-Path $(Join-Path $(Join-Path $__rulesControlFolder "Core") $ControlFile)) { $__rulesControl = Import-Csv $(Join-Path $(Join-Path $__rulesControlFolder "Core") $ControlFile) } elseif(Test-Path $(Join-Path $(Join-Path $__rulesControlFolder "Premier") $ControlFile)) { $__rulesControl = Import-Csv $(Join-Path $(Join-Path $__rulesControlFolder "Premier") $ControlFile) } } else { Write-SRx ERROR "You must supply command line parameter -Test <test name>, -ControlFile <control file name>, or -RunAllTests" return } # timestamp for RunId $timestamp = $(Get-Date -f "yyyyMMddHHmmss") } PROCESS { Write-SRx DEBUG "PROCESS - Entered Start-Ruleset PROCESS" if($TestListControls ){ ListControls } elseif($ControlsListTests){ ListTests } else { ProcessRules } } END { $endAll = Get-Date $spanAll = New-TimeSpan $startAll $endAll Write-SRx INFO "Rule(s) finished in Time: [$($spanAll.Hours):$($spanAll.Minutes):$($spanAll.Seconds).$($spanAll.Milliseconds)]" -ForegroundColor Yellow Write-SRx DEBUG "END" } } #EndRegion '.\Public\Start-Ruleset.ps1' 215 #Region '.\Public\Start-SOP.ps1' 0 function Start-SOP { <# .SYNOPSIS Invokes a test(s) and handles where the output event(s) get written .DESCRIPTION This cmdlet wraps the invocation chain of Invoke-ProvisioningSequence followed by: -> Export-SRxToSearchDashboard | Export-SRxToAlertsList -> and/or Write-SRxConsole .NOTES ========================================= Project : The Source Shell (SRx) ----------------------------------------- File Name : Start-SOP.psm1 Author : Nikolay Mukhin Requires : PowerShell Version 5.1, The Source Shell (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== .INPUTS Control file (e.g. TestControl.csv) .OUTPUTS [ $SRxEvent(s) ] .EXAMPLE #> [CmdletBinding()] param ( [alias("All")][switch]$RunAllTests, [alias("OutNull")][switch]$NoWriteToConsole, [switch]$PassThrough, #[switch]$Production, #[switch]$Rebuild, [alias("EventLog")][switch]$WriteErrorsToEventLog, [switch]$Details, [Parameter(Mandatory=$false, ParameterSetName="Get")] [string]$ControlFile, [Parameter(Mandatory=$false, ParameterSetName="Get")] [string]$Test ) BEGIN { Write-SRx DEBUG "BEGIN Start-SOP" } PROCESS { Write-SRx DEBUG "PROCESS - Entered Start-SOP process" $global:SRxEnv.SetCustomProperty("PnPEnvironment", "Test") $global:SRxEnv.SetCustomProperty("PnPNewSite", $false) $global:SRxEnv.SetCustomProperty("PnPRebuild", $false) $global:SRxEnv.SetCustomProperty("SOP", $null) if($Test) { if($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent) { $output = New-SRxReport -Test $Test -Debug } else { $output = New-SRxReport -Test $Test } } elseif($ControlFile) { Set-SRxPipeline $output = New-SRxReport -ControlFile $ControlFile } else { $shell = $false $exit = $false $SOP = "Operations" $group = "System Utilities" $member = "Update Provisioning Schema in local cache" do { Set-SRxPipeline $sopobject = $global:SRxEnv.DefaultSettings.EcoSystem.$SOP.$group.$member $global:SRxEnv.SetCustomProperty("SOP", $sopobject) $global:SRxEnv.SOP.ts_Title = "Provisioning Operations" $ProgressPreference = "SilentlyContinue" $output = Test-SRx -ControlFile "Start-SOP" $ProgressPreference = "Continue" if($null -ne $global:SRxEnv.SOP) { if($global:SRxEnv.SOP -eq "Exit") { $exit = $true } elseif( $global:SRxEnv.SOP.ts_ControlFile -eq "Exit") { $exit = $true } else { $global:SRxEnv.SetCustomProperty("PnPEnvironment", $global:SRxEnv.SOP.ts_Environment) $global:SRxEnv.SetCustomProperty("PnPRebuild", [boolean]$global:SRxEnv.SOP.ts_Rebuild) $global:SRxEnv.SetCustomProperty("PnPNewSite", [boolean]$global:SRxEnv.SOP.ts_NewSite) Show-SRxSOPTitle -Clear | Out-Null if( $global:SRxEnv.SOP.ts_IsAdmin -and (-not $global:SRxEnv.isStakeHolder)) { $output = New-SRxReport -ControlFile "Invoke-NotImplemented" } else { $output = New-SRxReport -ControlFile $global:SRxEnv.SOP.ts_ControlFile } } } if( -not $exit) { if( $global:SRxEnv.SOP.ts_ControlFile -ne "Format-EcoSystem") { $decision = $Host.UI.PromptForChoice("------------------------------------------------------", '', @('&Main Menu'; '&Shell'), 0) Write-Host if( $decision -eq 1) { $shell = $true $exit = $true } } } } while(-not $exit) if( -not $shell) { Clear-Host } } if($PassThrough) { $output } } END { $global:SRxEnv.SetCustomProperty("PnPEnvironment", "Test") $global:SRxEnv.SetCustomProperty("PnPNewSite", $false) $global:SRxEnv.SetCustomProperty("PnPRebuild", $false) $global:SRxEnv.SetCustomProperty("SOP", $null) Start-SRxShell Write-SRx DEBUG "END" } } #EndRegion '.\Public\Start-SOP.ps1' 143 #Region '.\Public\Start-SRxShell.ps1' 0 Function Start-SRxShell { [CmdletBinding(PositionalBinding=$true)] PARAM ( [Parameter(Mandatory=$false)][string]$RootPath, [parameter(Mandatory=$false,ValueFromPipeline=$false)][string]$ControlFile, [switch]$Setup, #== Use this to open Setup Menu [Parameter(Mandatory=$false)][switch]$isJob ) PROCESS { try { if( -not $global:SRxEnv ) { Initialize-SRxEnv -RootPath $RootPath if( -not $global:SRxEnv ) { return } } $global:SRxEnv.LoadModule("Microsoft.Online.SharePoint.PowerShell",$global:SRxEnv.Modules."Microsoft.Online.SharePoint.PowerShell",$false) #$global:SRxEnv.LoadModule("PnP.Powershell","1.8.0",$false) $global:SRxEnv.LoadModule("PnP.Powershell",$global:SRxEnv.Modules."PnP.Powershell",$false) $global:SRxEnv.SetCustomProperty("PnPEnvironment", "Test") $global:SRxEnv.SetCustomProperty("PnPNewSite", $false) $global:SRxEnv.SetCustomProperty("PnPRebuild", $false) #Set-SRxPipeline $global:SRxEnv.SetCustomProperty("MasterDesign", "Department") Read-SRxPipelineValue | Out-Null $global:SRxEnv.SetCustomProperty("SOP", $null) if( -not $global:SRxEnv.Tenancy.LastConnectionSuccessful) { New-SRxReport -Test Format-EcoSystem | Out-Null } else { Get-SRxConnection -SPO -siteUrl $global:SRxEnv.Tenancy.AdminUrl | Out-Null if( -not $global:SRxEnv.Tenancy.LastConnectionSuccessful) { New-SRxReport -Test Format-EcoSystem | Out-Null } } Connect-SRxProvisioningDatabase_JSON | Out-Null $global:SRxEnv.SetCustomProperty("isStakeHolder", $($global:SRxEnv.Tenancy.IsStakeholder)) Set-Item Env:\PNPPOWERSHELL_UPDATECHECK "off" $global:SRxEnv.UpdateShellTitle() if($Setup) { #----------------------------------------------- # Format windows shortcuts #----------------------------------------------- #----------------------------- # auto-startup for systray #----------------------------- $programs = @{ "Provisioning Explorer" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "Startup", 7, ' -NoProfile -noexit -WindowStyle hidden -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #minimized } $startMenu = $($home + "\AppData\Roaming\Microsoft\Windows\Start Menu\Programs") Format-SRxFileShortcut -programs $programs -startMenu $startMenu #----------------------------- # start menu #----------------------------- $programs = @{ "Provisioning Shell" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "SharePoint", 1, ' -NoProfile -noexit -ExecutionPolicy Bypass -Command ".\shell.ps1"') #normal "Provisioning Explorer" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "SharePoint", 7, ' -NoProfile -noexit -WindowStyle hidden -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #minimized #"Debug" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "SharePoint", 1, ' -NoProfile -noexit -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #normal } $startMenu = "$($home)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs" Format-SRxFileShortcut -programs $programs -startMenu $startMenu #----------------------------- # provisioning folder #----------------------------- $programs = @{ "shell" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", $null, 1, ' -NoProfile -noexit -ExecutionPolicy Bypass -Command ".\shell.ps1"') #normal "explorer" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", $null, 7, ' -NoProfile -noexit -WindowStyle hidden -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #minimized "debug" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", $null, 1, ' -NoProfile -noexit -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #normal } $str = $SRxEnv.Paths.SRxRoot $workingdirectory = $str.substring(0, $str.lastindexof('\')) #$startMenu = "$($home)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs" Format-SRxFileShortcut -programs $programs -startMenu $workingdirectory #----------------------------- # start setup #----------------------------- New-SRxReport -Test Format-EcoSystem | Out-Null if($global:SRxEnv.isSetup) { #close window and terminate process $window.Close() Stop-Process $pid } } elseif($ControlFile -eq "Start-Command") { Test-SRx -ControlFile $ControlFile | Out-Null return $null } else { if($ControlFile) { Test-SRx -ControlFile $ControlFile | Out-Null } # Write-SRx VERBOSE $("[shell] Configuring commands: config, log, cache, task, menu") -ForegroundColor DarkCyan <# if( -not $(Get-Alias -Name ecosystem -ErrorAction SilentlyContinue)) { New-Alias -Name ecosystem -Value _EcoSystem -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name log -ErrorAction SilentlyContinue)) { New-Alias -Name log -Value _Log -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name cache -ErrorAction SilentlyContinue)) { New-Alias -Name cache -Value _Cache -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name task -ErrorAction SilentlyContinue)) { New-Alias -Name task -Value _Task -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name shell -ErrorAction SilentlyContinue)) { New-Alias -Name shell -Value _Shell -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name menu -ErrorAction SilentlyContinue)) { New-Alias -Name menu -Value Start-SOP -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name systray -ErrorAction SilentlyContinue)) { New-Alias -Name systray -Value Start-SysTray -Force -Scope Global -Option Constant } #if( -not $(Get-Alias -Name gui -ErrorAction SilentlyContinue)) { # New-Alias -Name gui -Value _Gui -Force -Scope Global -Option Constant #} if( -not $(Get-Alias -Name docs -ErrorAction SilentlyContinue)) { New-Alias -Name docs -Value _Docs -Force -Scope Global -Option Constant } if( -not $(Get-Alias -Name pipeline -ErrorAction SilentlyContinue)) { New-Alias -Name pipeline -Value _Pipeline -Force -Scope Global -Option Constant } #> if( -not $global:__SRxHasInitFailure ) { if( $global:SRxEnv.Log.Level -ne "VERBOSE") { Clear-Host } } if( -not $isJob) { Show-SRxShell } } } catch { return $null } return $null } } #EndRegion '.\Public\Start-SRxShell.ps1' 141 #Region '.\Public\Start-SRxSysTray.ps1' 0 Add-Type -assembly System.Windows.Forms Add-Type -assembly System.Data Add-Type -assembly System.Drawing Add-Type -assembly System.Design function Start-SRxSysTray { <# .SYNOPSIS Invokes a test(s) and handles where the output event(s) get written .DESCRIPTION This cmdlet wraps the invocation chain of Invoke-ProvisioningSequence followed by: -> Export-SRxToSearchDashboard | Export-SRxToAlertsList -> and/or Write-SRxConsole .NOTES ========================================= Project : The Source Shell (SRx) ----------------------------------------- File Name : Start-SysTray.psm1 Author : Nikolay Mukhin Requires : PowerShell Version 5.1, The Source Shell (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== .INPUTS Control file (e.g. TestControl.csv) .OUTPUTS [ $SRxEvent(s) ] .EXAMPLE #> [CmdletBinding(PositionalBinding=$true)] PARAM ( [Parameter(Mandatory=$false)][string]$RootPath, [switch]$PassThrough ) BEGIN { #Write-Host "BEGIN Start-SysTray" if( -not $global:SRxEnv ) { Initialize-SRxEnv -RootPath $RootPath } if( $global:SRxEnv.Exists ) { $global:SRxEnv.LoadModule("SRxSysTrayHost",$null,$null) #----------------------------- # auto-startup for systray #----------------------------- $programs = @{ "Provisioning Explorer" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "Startup", 7, ' -NoProfile -noexit -WindowStyle hidden -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #minimized } $startMenu = $($home + "\AppData\Roaming\Microsoft\Windows\Start Menu\Programs") Format-SRxFileShortcut -programs $programs -startMenu $startMenu #----------------------------- # start menu #----------------------------- $programs = @{ "Provisioning Shell" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "SharePoint", 1, ' -NoProfile -noexit -ExecutionPolicy Bypass -Command ".\shell.ps1"') #normal "Provisioning Explorer" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "SharePoint", 7, ' -NoProfile -noexit -WindowStyle hidden -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #minimized #"Debug" = @("%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe", "SharePoint", 1, ' -NoProfile -noexit -ExecutionPolicy Bypass -Command ".\shell.ps1 -SysTray"') #normal } $startMenu = "$($home)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs" Format-SRxFileShortcut -programs $programs -startMenu $startMenu } } PROCESS { try { if( -not $global:SRxEnv.Exists ) { return } $global:SRxEnv.UpdateShellTitle() Write-SRx DEBUG "PROCESS - Entered Start-SysTray process" # 'dirty' implementation for singleton process when use -SysTray argument $activeProcessId = Get-WmiObject win32_process -Filter "commandline like '%-SysTray%'" | Select-Object -Property processid ($activeProcessId | Where-Object { ($($pid) -ne $_.processid) }) | ForEach-Object { Stop-Process $($_.processid) | Out-Null } # Force garbage collection just to start slightly lower RAM usage. [System.GC]::Collect() $f = New-SysTrayHost $f.Run2() } catch { Write-Host "--- Start-SysTray ---" Write-Host ($_.Exception.Message) Write-Host ($_.Exception) Read-Host } } END { #Write-Host "END Start-SysTray" #Read-Host if( $global:SRxEnv.Exists ) { Stop-Process $pid } #Write-Host "END Start-SysTray" } } #EndRegion '.\Public\Start-SRxSysTray.ps1' 104 #Region '.\Public\Test-SRx.ps1' 0 function Test-SRx { <# .SYNOPSIS Evaluates test definition file and generates a corresponding SRxEvent .DESCRIPTION Can specify a control file as a parameter, which defines a specific set of tests to perform where each test specified in the control generates an SRxEvent .NOTES ========================================= Project : Search Health Reports (SRx) ----------------------------------------- File Name : Test-SRx.psm1 Author : Eric Dixon Requires : PowerShell Version 5.1, Search Health Reports (SRx), Microsoft.SharePoint.PowerShell ======================================================================================== This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys' fees, that arise or result from the use or distribution of the Sample Code. ======================================================================================== .INPUTS Control file (e.g. TestControl.csv) .OUTPUTS [ $SRxEvent(s) ] .EXAMPLE Test-SRx -ControlFile TestControl.csv #> [CmdletBinding()] param ( [Parameter(ParameterSetName='DefaultSet')] [alias("All")][switch]$RunAllTests, [Parameter(ParameterSetName='DefaultSet')] $Params=$null, [Parameter(ParameterSetName='DefaultSet')] [switch]$WhatIf=$false ) DynamicParam { # Set the dynamic parameters' name $ParameterControlFile = 'ControlFile' $ParameterTest = 'Name' # Generate the parameter values (e.g. control and test file names) for validation $__rulesControlFolder = $global:SRxEnv.Paths.RuleSets #Join-Path $global:SRxEnv.Paths.Config "TestControls" $__ruleControlFiles = Get-ChildItem -Path $__rulesControlFolder -File "*.csv" -Recurse $__ruleDefinitionsFolder = $global:SRxEnv.Paths.Rules $__ruleDefinitionFiles = Get-ChildItem -Path $__ruleDefinitionsFolder -File "Rule-*.ps1" -Recurse # Create the collection of attributes $AttributeCollectionControlFile = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionTest = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttributeControlFile = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeControlFile.Mandatory = $false $ParameterAttributeControlFile.Position = 1 $ParameterAttributeControlFile.ParameterSetName = 'ControlFileSet' $ParameterAttributeTest = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeTest.Mandatory = $false $ParameterAttributeTest.Position = 1 $ParameterAttributeTest.ParameterSetName = 'TestNameSet' # Add the control file and test parameters to the attribute collections $AttributeCollectionControlFile.Add($ParameterAttributeControlFile) $ValidateSetAttributeControlFile = New-Object System.Management.Automation.ValidateSetAttribute( $( $__ruleControlFiles | Select-Object -ExpandProperty Name | % {if($_.EndsWith(".csv","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} ) ) $AttributeCollectionControlFile.Add($ValidateSetAttributeControlFile) $AttributeCollectionTest.Add($ParameterAttributeTest) $ValidateSetAttributeTest = New-Object System.Management.Automation.ValidateSetAttribute( $( $__ruleDefinitionFiles | Select-Object -ExpandProperty Name | % {if($_.StartsWith("Rule-","CurrentCultureIgnoreCase")){$_.Substring(5)}} | % {if($_.EndsWith(".ps1","CurrentCultureIgnoreCase")){$_.Substring(0,$_.Length-4)}} ) ) $AttributeCollectionTest.Add($ValidateSetAttributeTest) # Create and return the dynamic parameter $RuntimeParameterControlFile = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterControlFile, [string], $AttributeCollectionControlFile) $RuntimeParameterTest = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterTest, [string], $AttributeCollectionTest) # Create and return the dynamic parameter dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $RuntimeParameterDictionary.Add($ParameterControlFile, $RuntimeParameterControlFile) $RuntimeParameterDictionary.Add($ParameterTest, $RuntimeParameterTest) # Add "Helpful Features" for Rules and Control Files # -ListControlFiles $ParameterTestListControls = 'ListControlFiles' $ParameterAttributeTestListControls = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeTestListControls.Mandatory = $false $ParameterAttributeTestListControls.Position = 2 $ParameterAttributeTestListControls.ParameterSetName = 'TestNameSet' $AttributeCollectionTestListControls = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionTestListControls.Add($ParameterAttributeTestListControls) $RuntimeParameterTestListControls = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterTestListControls, [switch], $AttributeCollectionTestListControls) $RuntimeParameterDictionary.Add($ParameterTestListControls, $RuntimeParameterTestListControls) # -ListTests $ParameterControlFileListTests = 'ListTests' $ParameterAttributeControlFileListTests = New-Object System.Management.Automation.ParameterAttribute $ParameterAttributeControlFileListTests.Mandatory = $false $ParameterAttributeControlFileListTests.Position = 2 $ParameterAttributeControlFileListTests.ParameterSetName = 'ControlFileSet' $AttributeCollectionControlFileListTests = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollectionControlFileListTests.Add($ParameterAttributeControlFileListTests) $RuntimeParameterControlFileListTests = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterControlFileListTests, [switch], $AttributeCollectionControlFileListTests) $RuntimeParameterDictionary.Add($ParameterControlFileListTests, $RuntimeParameterControlFileListTests) return $RuntimeParameterDictionary } BEGIN { $startAll = Get-Date Write-SRx DEBUG "BEGIN Test-SRx" $ControlFile = $PsBoundParameters[$ParameterControlFile] $Test = $PsBoundParameters[$ParameterTest] if($RunAllTests) { $__rulesControl = @() $__ruleDefinitionFiles = $__ruleDefinitionFiles | ? {($_.fullname -notLike "*\Example\*") -and ($_.fullname -notLike "*\InDev\*")} foreach($f in $__ruleDefinitionFiles) { $o = New-Object PSObject -Property @{ "Rule" = $([System.IO.Path]::GetFileNameWithoutExtension($f)); "WriteToDashboard" = "true" ; "AlertOnFailure" = "true" } $__rulesControl += $o } $ControlFile = "RunAllTests" } elseif($Test) { $TestListControls = $PsBoundParameters[$ParameterTestListControls] $__rulesControl = @() if(-not $Test.EndsWith(".ps1", "CurrentCultureIgnoreCase")) { $Test += ".ps1" } if(-not $Test.StartsWith("Rule-", "CurrentCultureIgnoreCase")) { $Test = "Rule-" + $Test } $o = New-Object PSObject -Property @{ "Rule" = $([System.IO.Path]::GetFileNameWithoutExtension($Test)); "WriteToDashboard" = "true" ; "AlertOnFailure" = "true" } $__rulesControl += $o $ControlFile = $Test } elseif($ControlFile) { $ControlsListTests = $PsBoundParameters[$ParameterControlFileListTests] if(-not $ControlFile.EndsWith(".csv", "CurrentCultureIgnoreCase")) { $ControlFile += ".csv" } if(Test-Path $(Join-Path $(Join-Path $__rulesControlFolder "Core") $ControlFile)) { $__rulesControl = Import-Csv $(Join-Path $(Join-Path $__rulesControlFolder "Core") $ControlFile) } elseif(Test-Path $(Join-Path $(Join-Path $__rulesControlFolder "Premier") $ControlFile)) { $__rulesControl = Import-Csv $(Join-Path $(Join-Path $__rulesControlFolder "Premier") $ControlFile) } } else { Write-SRx ERROR "You must supply command line parameter -Test <test name>, -ControlFile <control file name>, or -RunAllTests" return } # timestamp for RunId $timestamp = $(Get-Date -f "yyyyMMddHHmmss") } PROCESS { Write-SRx DEBUG "PROCESS - Entered Test-SRx PROCESS" if($TestListControls ){ ListControls } elseif($ControlsListTests){ ListTests } else { ProcessRules } } END { $endAll = Get-Date $spanAll = New-TimeSpan $startAll $endAll Write-SRx INFO "Rule(s) finished in Time: [$($spanAll.Hours):$($spanAll.Minutes):$($spanAll.Seconds).$($spanAll.Milliseconds)]" -ForegroundColor Yellow Write-SRx DEBUG "END" } } #EndRegion '.\Public\Test-SRx.ps1' 215 #Region '.\Public\Test-SRxAllowedForRecycleBinDeletion.ps1' 0 Function Test-SRxAllowedForRecycleBinDeletion { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true)][string]$siteURL, [Parameter(Mandatory=$true)][string]$environment ) PROCESS { try { #---------------------------------------------------------------------------------------- # Protection for recycle bin deletion for any sites except dev, test and release # URL structure https://{tenancy}.sharepoint.com/sites/{ecosystem}_{environment)_sitename #----------------------------------------------------------------------------------------- $enableDeleteRecycleBin = $false # 1. Any site in production environment can't be deleted from recycle bin $enableDeleteRecycleBin = $($environment -ne "Production") if($enableDeleteRecycleBin) { # 2. URL must start with {ecosystem} prefix $ecosystemPrefix = Get-SRxCustomPropertyValueByKey_JSON -Key "ts_URLPrefix" $enableDeleteRecycleBin = $siteURL.Contains("sites/$($ecosystemPrefix)_") if($enableDeleteRecycleBin) { # 3. URL must include one of {environment} substrings listed below: $environmentURL = @('_test_', '_release_', '_development_') $enableDeleteRecycleBin = ($null -ne ($environmentURL | Where-Object { $siteURL -match $_ })) } } Write-SRx VERBOSE " > Site allowance for deletion in RecycleBin: $enableDeleteRecycleBin" } catch { Write-SRx ERROR ($_.Exception.Message) Write-SRx VERBOSE ($_.Exception) return $false } return $enableDeleteRecycleBin } } #EndRegion '.\Public\Test-SRxAllowedForRecycleBinDeletion.ps1' 36 #Region '.\Public\Test-SRxIsStakeHolder.ps1' 0 Function Test-SRxIsStakeHolder { PROCESS { try { $retVal = $false #---------------------------------------------------------------------------------------- # Test to enforce protection for secitive operations #----------------------------------------------------------------------------------------- $connection = Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl $termSet = Get-PnPTermSet -Identity $global:SRxEnv.Tenancy.TermSetID -TermGroup $global:SRxEnv.Tenancy.TermGroupName -Connection $connection Get-PnPProperty -ClientObject $termSet -Property Stakeholders -Connection $connection | Out-Null $searcher = [adsisearcher]"(samaccountname=$env:USERNAME)" $account = $searcher.FindOne().Properties.mail Write-SRx VERBOSE "Stakeholders: $($termSet.Stakeholders)" Write-SRx VERBOSE "account: $account" # $samlAccount = $("i:0#.f|membership|$account").ToLower() # $retVal = $( $samlAccount -in $($termSet.Stakeholders)) #$retVal = $( $account -in $($termSet.Stakeholders)) $retVal = $( $(@($termSet.Stakeholders -like "*$account").Count) -ne 0) Write-SRx VERBOSE " > Stakeholder permission for user $account : $retVal" } catch { #Write-SRx ERROR ($_.Exception.Message) #Write-SRx VERBOSE ($_.Exception) $retVal = $false Write-SRx VERBOSE " > Stakeholder permission for user $account : $retVal" return $false } return $retVal } } #EndRegion '.\Public\Test-SRxIsStakeHolder.ps1' 35 #Region '.\Public\Test-SRxProvisioningDatabase_JSON.ps1' 0 Function Test-SRxProvisioningDatabase_JSON { $retVal = $false if( $global:SRxEnv.Tenancy.TermGroupID -ne "") { $profilesFolderPath = $global:SRxEnv.CachePath + "\Profiles" $sgroupName = (Remove-SRxStringSpecialCharacter -String $global:SRxEnv.Tenancy.TermGroupName) $profileFolderPath = $profilesFolderPath + "\" + $sgroupName $configDPath = $profileFolderPath + "\ProvisioningDatabase.min.json" if ((Test-Path $configDPath)) { $retVal = $true } } return $retVal } #EndRegion '.\Public\Test-SRxProvisioningDatabase_JSON.ps1' 12 #Region '.\Public\Update-SRxTermCustomProperty.ps1' 0 Function Update-SRxTermCustomProperty { [CmdletBinding()] PARAM ( [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$false)]$termID, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true)][string]$Key, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true)]$Value, [switch]$termSet ) BEGIN { $connection = Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl } PROCESS { try { #$customProperties = @{} #$customProperties.add($Key, $Value) #Set-PnPTerm -Identity $([GUID]$termID) -CustomProperties $customProperties -Connection $connection #Start-Sleep -Seconds 3 #return if( -not $termSet) { $termSite = Get-PnPTerm -Identity $([GUID]$termID) -IncludeChildTerms -Connection $connection } else { $termSite = Get-PnPTermSet -Identity $global:SRxEnv.Tenancy.TermSetID -TermGroup $global:SRxEnv.Tenancy.TermGroupName -Connection $connection } if($termSite) { Get-PnPProperty -ClientObject $termSite -Property CustomProperties, Name -Connection $connection | Out-Null $termSite.SetCustomProperty($Key, $Value) #$status = "Updating $environment/Designs/$termDesign/Sites/$siteTitle ..." #Write-Progress -Activity $Activity -Status $status -PercentComplete $(($counter++ / $count) * 100) #Write-SRx VERBOSE " > $status" Invoke-PnPQuery -RetryCount 5 -Connection $connection Start-Sleep -Seconds 3 } #Write-SRx VERBOSE " > Updated." } catch { Write-SRx ERROR ("Update-SRxTermCustomProperty: $($_.Exception.Message)") Write-SRx VERBOSE ($_.Exception) return "exception" } return 0 } END { #if( $connection) { Disconnect-PnPOnline -Connection $connection } } } #EndRegion '.\Public\Update-SRxTermCustomProperty.ps1' 43 #Region '.\Public\Write-SRx.ps1' 0 $global:__SRxUseSimpleTemplate = $false function Write-SRx { <# .SYNOPSIS Custom logging module .DESCRIPTION Compares the logging level of the current Write-SRx invocation with the $global:SRxEnv.Log.Level to assess if this should be logged or not. Additionally, can log to a common log file based on the current setting for $global:SRxEnv.Log.ToFile All errors are automatically logged to: $global:SRxEnv.Paths.Log "errors.log" .NOTES ========================================= Project : Search Health Reports (SRx) ----------------------------------------- File Name : Write-SRx.psm1 Author : Eric Dixon Requires : PowerShell Version 5.1, Search Health Reports (SRx) ======================================================================================== This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys' fees, that arise or result from the use or distribution of the Sample Code. ======================================================================================== .EXAMPLE Write-SRx VERBOSE "some message here..." -ForegroundColor Cyan -NoNewline #> [CmdletBinding()] param ( [parameter(Mandatory=$false)] #[ValidateSet([SRxLogLevel]::SILENT, [SRxLogLevel]::INFO, [SRxLogLevel]::VERBOSE, [SRxLogLevel]::WARNING, [SRxLogLevel]::ERROR, [SRxLogLevel]::DEBUG)] [SRxLogLevel]$Level=[SRxLogLevel]::INFO, [parameter(Mandatory=$false,ValueFromPipeline=$true)] [string]$Message, [string]$Product="", [string]$Category="", [string]$Thread="", [parameter(Mandatory=$false)] [System.ConsoleColor]$ForegroundColor=(get-host).ui.rawui.ForegroundColor, [System.ConsoleColor]$BackgroundColor=(get-host).ui.rawui.BackgroundColor, [switch]$NoNewline=$false ) BEGIN { #if($global:SRxEnv.SRxProgressBar) { # $global:SRxEnv.SetCustomProperty("SRxProgressBar", $false) # Write-Host #} if($ForegroundColor -lt 0) { $ForegroundColor = [System.ConsoleColor]::White } if($BackgroundColor -lt 0) { $BackgroundColor = [System.ConsoleColor]::DarkBlue } if($global:__SRxUseSimpleTemplate) { $msgTempl = "{0}" } else { $now = Get-Date -Format "MM/dd/yyyy HH:mm:ss" $msgTempl = "$now`t`t$Thread`t$Product`t$Category`t`t$Level`t{0}" } $global:__SRxUseSimpleTemplate = $NoNewline } PROCESS { $logToFile_ONLY = $false if([int]$Level -gt [int]$([SRxLogLevel]::$($global:SRxEnv.Log.Level))) { #return $logToFile_ONLY = $true } # this handles Write-SRx with no parameters. Just writes the time stamp and nothing to log file. if([string]::IsNullOrEmpty($Message)) { Write-Host return } foreach($msg in $Message) { if( -not $logToFile_ONLY) { switch($Level) { ERROR { Show-SRxWindow Write-Host -ForegroundColor Red $msg } WARNING {Write-Host -ForegroundColor Yellow $msg} INFO {Write-Host $msg -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor -NoNewline:$NoNewline} VERBOSE {Write-Host $msg -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor -NoNewline:$NoNewline} DEBUG {Write-Host $msg -ForegroundColor Gray} Default {return} # bail on invalid log levels } } $m = $msgTempl -f $msg if($global:SRxEnv.Log.ToFile) { Add-Content $global:SRxEnv.LogFile $m } if([int]$Level -eq [int]$([SRxLogLevel]::ERROR)) { $errorFile = Join-Path $global:SRxEnv.Paths.Log "errors.log" $global:SRxEnv.CreateFileWithReadPermissions($errorFile) Add-Content $errorFile $m } } } END { } } #EndRegion '.\Public\Write-SRx.ps1' 138 #Region '.\Public\Write-SRxConsole.ps1' 0 function Write-SRxConsole { [CmdletBinding()] param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)] [object[]]$data, [switch]$Details ) BEGIN { [System.Collections.ArrayList]$dataList = @() } PROCESS { foreach($d in $data) { $dataList.Add($d) | Out-Null Write-SRx DEBUG "PROCESS[$d]" } } END { Write-SRx DEBUG " --> [formatting data for output" if ($global:SRxEnv.Log.Level -eq "Debug") { $dataList | Format-List | Out-String -Stream } else { $dataList | Format-Table -AutoSize @{l='Rule'; e={$_.Name.Substring(5)}}, Level, Headline | Out-String -Stream | ColorByLevel -IncludeDetails:$Details } } } #EndRegion '.\Public\Write-SRxConsole.ps1' 36 #Region '.\Public\Write-SRxMenu.ps1' 0 function Write-SRxMenu { <# .SYNOPSIS Outputs a command-line menu which can be navigated using the keyboard. .DESCRIPTION Outputs a command-line menu which can be navigated using the keyboard. * Automatically creates multiple pages if the entries cannot fit on-screen. * Supports nested menus using a combination of hashtables and arrays. * No entry / page limitations (apart from device performance). * Sort entries using the -Sort parameter. * -MultiSelect: Use space to check a selected entry, all checked entries will be invoked / returned upon confirmation. * Jump to the top / bottom of the page using the "Home" and "End" keys. * "Scrolling" list effect by automatically switching pages when reaching the top/bottom. * Nested menu indicator next to entries. * Remembers parent menus: Opening three levels of nested menus means you have to press "Esc" three times. Controls Description -------- ----------- Up Previous entry Down Next entry Left / PageUp Previous page Right / PageDown Next page Home Jump to top End Jump to bottom Space Check selection (-MultiSelect only) Enter Confirm selection Esc / Backspace Exit / Previous menu .EXAMPLE PS C:\>$menuReturn = Write-SRxMenu -Title 'Menu Title' -Entries @('Menu Option 1', 'Menu Option 2', 'Menu Option 3', 'Menu Option 4') Output: Menu Title Menu Option 1 Menu Option 2 Menu Option 3 Menu Option 4 .EXAMPLE PS C:\>$menuReturn = Write-SRxMenu -Title 'AppxPackages' -Entries (Get-AppxPackage).Name -Sort This example uses Write-SRxMenu to sort and list app packages (Windows Store/Modern Apps) that are installed for the current profile. .EXAMPLE PS C:\>$menuReturn = Write-SRxMenu -Title 'Advanced Menu' -Sort -Entries @{ 'Command Entry' = '(Get-AppxPackage).Name' 'Invoke Entry' = '@(Get-AppxPackage).Name' 'Hashtable Entry' = @{ 'Array Entry' = "@('Menu Option 1', 'Menu Option 2', 'Menu Option 3', 'Menu Option 4')" } } This example includes all possible entry types: Command Entry Invoke without opening as nested menu (does not contain any prefixes) Invoke Entry Invoke and open as nested menu (contains the "@" prefix) Hashtable Entry Opened as a nested menu Array Entry Opened as a nested menu .NOTES Write-SRxMenu by QuietusPlus (inspired by "Simple Textbased Powershell Menu" [Michael Albert]) .LINK https://quietusplus.github.io/Write-Menu .LINK https://github.com/QuietusPlus/Write-Menu #> [CmdletBinding()] <# Parameters #> param( # Array or hashtable containing the menu entries [Parameter(Mandatory=$true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [Alias('InputObject')] $Entries, # Title shown at the top of the menu. [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('Name')] [string] $Title, # Sort entries before they are displayed. [Parameter()] [switch] $Sort, # Select multiple menu entries using space, each selected entry will then get invoked (this will disable nested menu's). [Parameter()] [switch] $MultiSelect ) <# Configuration #> # Entry prefix, suffix and padding $script:cfgPrefix = ' ' $script:cfgPadding = 2 $script:cfgSuffix = ' ' $script:cfgNested = ' >' # Minimum page width $script:cfgWidth = 30 # Hide cursor [System.Console]::CursorVisible = $false # Save initial colours $script:colorForeground = [System.Console]::ForegroundColor $script:colorBackground = [System.Console]::BackgroundColor <# Checks #> # Check if entries has been passed if ($Entries -like $null) { Write-Error "Missing -Entries parameter!" return } # Check if host is console if ($host.Name -ne 'ConsoleHost') { Write-Error "[$($host.Name)] Cannot run inside current host, please use a console window instead!" return } <# Set-Color #> function Set-Color ([switch]$Inverted) { switch ($Inverted) { $true { [System.Console]::ForegroundColor = $colorBackground [System.Console]::BackgroundColor = $colorForeground } Default { [System.Console]::ForegroundColor = $colorForeground [System.Console]::BackgroundColor = $colorBackground } } } <# Get-Menu #> function Get-Menu ($script:inputEntries) { # Clear console Clear-Host #Write-Host( $inputEntries | Format-List | Out-String) #Write-Host( "Type = " + $inputEntries.GetType().Name) #Read-Host # Check if -Title has been provided, if so set window title, otherwise set default. if ($Title -notlike $null) { #$host.UI.RawUI.WindowTitle = $Title $script:menuTitle = "$Title" } else { $script:menuTitle = 'Menu' } # Set menu height $script:pageSize = ($host.UI.RawUI.WindowSize.Height - 5) if($global:SRxEnv.SOP) { $script:pageSize = ($host.UI.RawUI.WindowSize.Height - 10) } # Convert entries to object $script:menuEntries = @() switch ($inputEntries.GetType().Name) { 'String' { # Set total entries $script:menuEntryTotal = 1 # Create object $script:menuEntries = New-Object PSObject -Property @{ Command = '' Name = $inputEntries Selected = $false onConfirm = 'Name' }; break } 'Object[]' { # Get total entries $script:menuEntryTotal = $inputEntries.Length # Loop through array foreach ($i in 0..$($menuEntryTotal - 1)) { #$tempName = $($inputEntries.Keys)[$i] $tempName = $($inputEntries)[$i] $tempCommand = '' $tempAction = 'Name' #Write-Host( "(inputEntries)[i].GetType().Name = " + $($inputEntries)[$i].GetType().Name) # Check if command contains nested menu if ($($inputEntries)[$i].GetType().Name -eq 'PSCustomObject') { $tempName = $($inputEntries)[$i].Title $tempCommand = $($inputEntries)[$i].Menus $tempAction = 'Hashtable' #} elseif ($tempCommand.Substring(0,1) -eq '@') { # $tempAction = 'Invoke' } elseif ($($inputEntries)[$i].GetType().Name -eq 'String') { $tempName = $($inputEntries)[$i] $tempCommand = '' $tempAction = 'Name' } else { $tempCommand = '' #$tempName #$tempAction = 'Command' $tempAction = 'Name' } # Create object $script:menuEntries += New-Object PSObject -Property @{ #Command = '' #Name = $($inputEntries)[$i] #Selected = $false #onConfirm = 'Name' Name = $tempName Command = $tempCommand Selected = $false onConfirm = $tempAction }; $i++ }; #Write-Host "menuEntries" #Write-Host ($script:menuEntries | Format-List | Out-String ) #Read-Host break } 'Hashtable' { # Get total entries $script:menuEntryTotal = $inputEntries.Count # Loop through hashtable foreach ($i in 0..($menuEntryTotal - 1)) { # Check if hashtable contains a single entry, copy values directly if true if ($menuEntryTotal -eq 1) { $tempName = $($inputEntries.Keys) $tempCommand = $($inputEntries.Values) } else { $tempName = $($inputEntries.Keys)[$i] $tempCommand = $($inputEntries.Values)[$i] } # Check if command contains nested menu if ($tempCommand.GetType().Name -eq 'Hashtable') { $tempAction = 'Hashtable' } elseif ($tempCommand.Substring(0,1) -eq '@') { $tempAction = 'Invoke' } else { $tempAction = 'Command' } # Create object $script:menuEntries += New-Object PSObject -Property @{ Name = $tempName Command = $tempCommand Selected = $false onConfirm = $tempAction }; $i++ }; break } Default { Write-Error "Type `"$($inputEntries.GetType().Name)`" not supported, please use an array or hashtable." exit } } # Sort entries if ($Sort -eq $true) { $script:menuEntries = $menuEntries | Sort-Object -Property Name } # Get longest entry $script:entryWidth = ($menuEntries.Name | Measure-Object -Maximum -Property Length).Maximum # Widen if -MultiSelect is enabled if ($MultiSelect) { $script:entryWidth += 4 } # Set minimum entry width if ($entryWidth -lt $cfgWidth) { $script:entryWidth = $cfgWidth } # Set page width $script:pageWidth = $cfgPrefix.Length + $cfgPadding + $entryWidth + $cfgPadding + $cfgSuffix.Length # Set current + total pages $script:pageCurrent = 0 $script:pageTotal = [math]::Ceiling((($menuEntryTotal - $pageSize) / $pageSize)) # Insert new line #[System.Console]::WriteLine("") if($global:SRxEnv.SOP) { Show-SRxSOPTitle } else { [System.Console]::WriteLine("") } # Save title line location + write title $script:lineTitle = [System.Console]::CursorTop [System.Console]::WriteLine(" $menuTitle" + "`n") # Save first entry line location $script:lineTop = [System.Console]::CursorTop } <# Get-Page #> function Get-Page { # Update header if multiple pages if ($pageTotal -ne 0) { Update-Header } # Clear entries for ($i = 0; $i -le $pageSize; $i++) { # Overwrite each entry with whitespace [System.Console]::WriteLine("".PadRight($pageWidth) + ' ') } # Move cursor to first entry [System.Console]::CursorTop = $lineTop # Get index of first entry $script:pageEntryFirst = ($pageSize * $pageCurrent) # Get amount of entries for last page + fully populated page if ($pageCurrent -eq $pageTotal) { $script:pageEntryTotal = ($menuEntryTotal - ($pageSize * $pageTotal)) } else { $script:pageEntryTotal = $pageSize } # Set position within console $script:lineSelected = 0 # Write all page entries for ($i = 0; $i -le ($pageEntryTotal - 1); $i++) { Write-Entry $i } } <# Write-Entry #> function Write-Entry ([int16]$Index, [switch]$Update) { # Check if entry should be highlighted switch ($Update) { $true { $lineHighlight = $false; break } Default { $lineHighlight = ($Index -eq $lineSelected) } } # Page entry name $pageEntry = $menuEntries[($pageEntryFirst + $Index)].Name # Prefix checkbox if -MultiSelect is enabled if ($MultiSelect) { switch ($menuEntries[($pageEntryFirst + $Index)].Selected) { $true { $pageEntry = "[X] $pageEntry"; break } Default { $pageEntry = "[ ] $pageEntry" } } } # Full width highlight + Nested menu indicator switch ($menuEntries[($pageEntryFirst + $Index)].onConfirm -in 'Hashtable', 'Invoke') { $true { $pageEntry = "$pageEntry".PadRight($entryWidth) + "$cfgNested"; break } Default { $pageEntry = "$pageEntry".PadRight($entryWidth + $cfgNested.Length) } } # Write new line and add whitespace without inverted colours [System.Console]::Write("`r" + $cfgPrefix) # Invert colours if selected if ($lineHighlight) { Set-Color -Inverted } # Write page entry [System.Console]::Write("".PadLeft($cfgPadding) + $pageEntry + "".PadRight($cfgPadding)) # Restore colours if selected if ($lineHighlight) { Set-Color } # Entry suffix [System.Console]::Write($cfgSuffix + "`n") } <# Update-Entry #> function Update-Entry ([int16]$Index) { # Reset current entry [System.Console]::CursorTop = ($lineTop + $lineSelected) Write-Entry $lineSelected -Update # Write updated entry $script:lineSelected = $Index [System.Console]::CursorTop = ($lineTop + $Index) Write-Entry $lineSelected # Move cursor to first entry on page [System.Console]::CursorTop = $lineTop } <# Update-Header #> function Update-Header { # Set corrected page numbers $pCurrent = ($pageCurrent + 1) $pTotal = ($pageTotal + 1) # Calculate offset $pOffset = ($pTotal.ToString()).Length # Build string, use offset and padding to right align current page number $script:pageNumber = "{0,-$pOffset}{1,0}" -f "$("$pCurrent".PadLeft($pOffset))","/$pTotal" # Move cursor to title [System.Console]::CursorTop = $lineTitle # Move cursor to the right [System.Console]::CursorLeft = ($pageWidth - ($pOffset * 2) - 1) # Write page indicator [System.Console]::WriteLine("$pageNumber") } <# Initialisation #> # Get menu Get-Menu $Entries # Get page Get-Page # Declare hashtable for nested entries $menuNested = [ordered]@{} <# User Input #> # Loop through user input until valid key has been pressed do { $inputLoop = $true # Move cursor to first entry and beginning of line [System.Console]::CursorTop = $lineTop [System.Console]::Write("`r") # Get pressed key $menuInput = [System.Console]::ReadKey($false) # Define selected entry $entrySelected = $menuEntries[($pageEntryFirst + $lineSelected)] # Check if key has function attached to it switch ($menuInput.Key) { # Exit / Return { $_ -in 'Escape', 'Backspace' } { # Return to parent if current menu is nested if ($menuNested.Count -ne 0) { $pageCurrent = 0 $Title = $($menuNested.GetEnumerator())[$menuNested.Count - 1].Name Get-Menu $($menuNested.GetEnumerator())[$menuNested.Count - 1].Value Get-Page $menuNested.RemoveAt($menuNested.Count - 1) | Out-Null # Otherwise exit and return $null } else { Clear-Host $inputLoop = $false [System.Console]::CursorVisible = $true return $null }; break } # Next entry 'DownArrow' { if ($lineSelected -lt ($pageEntryTotal - 1)) { # Check if entry isn't last on page Update-Entry ($lineSelected + 1) } elseif ($pageCurrent -ne $pageTotal) { # Switch if not on last page $pageCurrent++ Get-Page }; break } # Previous entry 'UpArrow' { if ($lineSelected -gt 0) { # Check if entry isn't first on page Update-Entry ($lineSelected - 1) } elseif ($pageCurrent -ne 0) { # Switch if not on first page $pageCurrent-- Get-Page Update-Entry ($pageEntryTotal - 1) }; break } # Select top entry 'Home' { if ($lineSelected -ne 0) { # Check if top entry isn't already selected Update-Entry 0 } elseif ($pageCurrent -ne 0) { # Switch if not on first page $pageCurrent-- Get-Page Update-Entry ($pageEntryTotal - 1) }; break } # Select bottom entry 'End' { if ($lineSelected -ne ($pageEntryTotal - 1)) { # Check if bottom entry isn't already selected Update-Entry ($pageEntryTotal - 1) } elseif ($pageCurrent -ne $pageTotal) { # Switch if not on last page $pageCurrent++ Get-Page }; break } # Next page { $_ -in 'RightArrow','PageDown' } { if ($pageCurrent -lt $pageTotal) { # Check if already on last page $pageCurrent++ Get-Page }; break } # Previous page { $_ -in 'LeftArrow','PageUp' } { # Check if already on first page if ($pageCurrent -gt 0) { $pageCurrent-- Get-Page }; break } # Select/check entry if -MultiSelect is enabled 'Spacebar' { if ($MultiSelect) { switch ($entrySelected.Selected) { $true { $entrySelected.Selected = $false } $false { $entrySelected.Selected = $true } } Update-Entry ($lineSelected) }; break } # Select all if -MultiSelect has been enabled 'Insert' { if ($MultiSelect) { $menuEntries | ForEach-Object { $_.Selected = $true } Get-Page }; break } # Select none if -MultiSelect has been enabled 'Delete' { if ($MultiSelect) { $menuEntries | ForEach-Object { $_.Selected = $false } Get-Page }; break } # Confirm selection 'Enter' { # Check if -MultiSelect has been enabled if ($MultiSelect) { Clear-Host # Process checked/selected entries $menuEntries | ForEach-Object { # Entry contains command, invoke it if (($_.Selected) -and ($_.Command -notlike $null) -and ($entrySelected.Command.GetType().Name -ne 'Hashtable')) { Invoke-Expression -Command $_.Command # Return name, entry does not contain command } elseif ($_.Selected) { return $_.Name } } # Exit and re-enable cursor $inputLoop = $false [System.Console]::CursorVisible = $true break } # Use onConfirm to process entry #write-host (" onConfirm =" + $entrySelected.onConfirm) #read-host switch ($entrySelected.onConfirm) { # Return hashtable as nested menu 'Hashtable' { $menuNested.$Title = $inputEntries $Title = $entrySelected.Name Get-Menu $entrySelected.Command Get-Page break } # Invoke attached command and return as nested menu 'Invoke' { $menuNested.$Title = $inputEntries $Title = $entrySelected.Name Get-Menu $(Invoke-Expression -Command $entrySelected.Command.Substring(1)) Get-Page break } # Invoke attached command and exit 'Command' { Clear-Host Invoke-Expression -Command $entrySelected.Command $inputLoop = $false [System.Console]::CursorVisible = $true break } # Return name and exit 'Name' { Clear-Host return $entrySelected.Name $inputLoop = $false [System.Console]::CursorVisible = $true } } } } } while ($inputLoop) } #EndRegion '.\Public\Write-SRxMenu.ps1' 641 |