Write-NTSPLog
Hi Everyone,
OK, so this is the first function I’ve uploaded in a while, I’ve been quite busy at work but I had to share this one as it’s very useful, and could be very useful to a lot of people. So here goes. :/
The function is fully commented and contains a comprehensive help section so you should be able to understand what it dose without reading this explanation.
This information can then be used in something like PowerBI to utilise it in a dashboard, i’m planning to make a guide so stay tuned!
Synopsis
So the basics is it’s a logging function that adds items to 2 SharePoint lists on the connected site.
One of them being an Overview of the process it’s logging the other having each event individually stated.
Detailed Overview
The function appends(or creates) 2 SharePoint lists, one is used as an Overview of the running processes (be it scripts or Workflow or anything running a series of commands) with information specified in the function parameters and the other a log of each action taken.
The “LogList” and “OverviewList” list are linked together by the Overview’s “ID” field generated when it’s created, passed by the -Passthru switch, this is then in turn referenced using the -ID parameter that’s populated in the “OverviewReferenceId” field, the function populates a new list item with the values of any specified parameters, most of which are optional.
These parameters are;
“OverviewList“,”LogList“,”ProcessName“, “Step“, “CurrentStatus“, “Result“, “ProcessOwner“, “ProcessObjectCount“, “ProcessObjectCurrentItem“, “TargetObject“, “CommandRun“, “OverviewReferenceId“, “RunBy“, “HostSystem“, “ConnectedWithAccount“, “Errors“,”Notes“,”Passthru“,”SkipCheck“,”PreBuildLists“,”ID”
So the first command in a process would look something like;
1 2 3 |
$LoggingID = Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -Passthru -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus Starting -Result Success -ProcessOwner "IT Team" ` -Confirm:$false -Step "Launching Script" |
and each item in the process you’d like to log would look something like;
1 2 3 4 |
Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) -CurrentStatus InProgress ` -Result Success -ProcessOwner "IT Team" -ID $LoggingID ` -CommandRun "Ran : 'Connect-MsolService -Credential $Cred -ErrorAction Stop'" -TargetObject "MSOL Service"` -SkipCheck -Confirm:$false -Step "Remote Connection" |
or
1 2 3 4 |
Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Success -ProcessOwner "IT Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($ADGroupMembersUPN).Count) -ProcessObjectCurrentItem $AddingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUserLicense -UserPrincipalName $($ADUser.UserPrincipalName) -AddLicenses $($LicenseImport.AccountSkuId) -ErrorAction Stop'" -Step "Adding User" |
Notice that the first command stores the return value from using the -Passthru switch to a variable and then the following commands uses that variable in the -ID parameter, this essentially updates the OverviewList list item of that value with the values in the parameters stopping another OverviewList item.
There are a few Switch parameters;
“Passthru“,”SkipCheck“,”PreBuildLists”
1 |
-Passthru |
This returns the OverviewList list ID number for use in subsequent uses of the function in the script.
1 |
-SkipCheck |
This is useful to specify after the initial run on the function as it skips the checking portion of the function.
1 |
-PreBuildLists |
You can call this anywhere as it just builds the list in the connected site without adding any list items. (if you just want to see how the function creates the list or to pre-create them.
Default Parameters
SupportsShouldProcess is set to True and ConfirmImpact is set to High, this is why I use -Confirm:$false in the examples, this is is just a precaution as the function creates SharePoint lists in the connected site.
Required Paramters
“OverviewList“,”LogList” are both required and reference the lists your logging will go in to.
Parameter Info
“ProcessObjectCount“, “ProcessObjectCurrentItem” require an [System.Int32] (a number) and are optional.
“Passthru“,”SkipCheck“,”PreBuildLists” are Switch Parameters and optional
The rest require [System.String] (Text) input and are optional
Output
Default output of the command is [System.Void] or if -PassThru is specified [System.String]
Other Information
I use a function in the scrip called ConvertTo-CapitalSplit this is something i created just to Split By the Capital when converting the internal listed names to the displaynames.
Requirements
This function uses and requires the module SharePointPnPPowerShellOnline
on systems running Powershell 4.0 and above can run the following commend to install it;
1 |
Install-Module -Name SharePointPnPPowerShellOnline |
or instructions can be found at HERE
The function itself requires a minimum of PowerShell Version 3.0
You need to make sure that yo’ve handled the SharePoint connection yourself prior to calling the command.
Usually you’ll use something like;
1 |
Connect-PnPOnline -Url 'https://REDACTED.sharepoint.com/sites/MySHAREPOINTSite/' -Credentials $Creds |
$Creds being the created elsewhere in the script.
Function Functionality
So most of this is covered above but i’ll try and give you some example of how to utilise this function.
This is something i created to manage our O365 E5 licensing, as you can see the function is used in several steps and gives me details on whats happening in the script.
I’ve redacted all sensitive information so if you’d like to plagiarise the script (:p I don’t mind) you’d have to adjust it to your own needs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
<# .NOTES O365 E5 EMS .NOTES Created by Nigel Tatschner .DESCRIPTION This script managed the adding and removing of users from office 365 licences It does this by looping through users in Active Directory Security groups, checking all O365 users for the applicable license and then adding or removing them. #> # Global Vars $ScriptPath = Split-Path -Path $PSCommandPath $AddingLoggingCount = 0 $RemovingLoggingCount = 0 # Module Import Import-Module -Name ActiveDirectory, MSOnline, MSOnlineExtended Import-Module $(Join-Path -Path $ScriptPath -ChildPath "Write-NTSPLog.ps1") $licerror = $false $Errorcount = 0 $removalcount = 0 # Credential Declaration $O365UserName = 'REDACTED' $O365Password = ConvertTo-SecureString (Get-Content -Path REDACTED) -AsPlainText -Force $Creds = New-Object System.Management.Automation.PSCredential $O365UserName, $O365Password # Error Log Connect-PnPOnline -Url 'https://REDACTED.sharepoint.com/sites/REDACTED/' -Credentials $Creds $LoggingID = Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -Passthru -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus Starting -Result Success -ProcessOwner "CIMS Team" ` -Confirm:$false -Step "Launching Script" # Connecting to O365 try { Connect-MsolService -Credential $Creds -ErrorAction Stop Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) -CurrentStatus InProgress ` -Result Success -ProcessOwner "CIMS Team" -ID $LoggingID ` -CommandRun "Ran : 'Connect-MsolService -Credential $Cred -ErrorAction Stop'" -TargetObject "MSOL Service"` -SkipCheck -Confirm:$false -Step "Remote Connection" } catch { Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) -CurrentStatus Completed ` -Result Failure -ProcessOwner "CIMS Team" -ID $LoggingID ` -CommandRun "Ran : 'Connect-MsolService -Credential $Cred -ErrorAction Stop'" -TargetObject "MSOL Service"` -SkipCheck -Confirm:$false -Step "Remote Connection" exit } # Get O365 Users $O365AllUsers = Get-MsolUser -All $O365AllUsers1 = $O365AllUsers | Where-Object -Property UserPrincipalName -like "*@REDACTED.org.uk" $O365AllUsers2 = $O365AllUsers | Where-Object -Property UserPrincipalName -Like "*@REDACTED.com" $O365AllUsers3 = $O365AllUsers | Where-Object -Property UserPrincipalName -Like "*@REDACTED.com" $O365AllUsers4 = $O365AllUsers | Where-Object -Property UserPrincipalName -Like "*@REDACTED.org.uk" $O365AllUsers = $O365AllUsers1 + $O365AllUsers2 + $O365AllUsers3 + $O365AllUsers4 # Get ADGroups $ADGroupMembers = Get-ADGroupMember -Identity "REDACTED" $ADGroupMembersUPN = @() foreach ($a in $ADGroupMembers) { $ADGroupMembersUPN += Get-ADUser -Identity $a.sAMAccountName } # Create License objects $LicenseImport = Get-MsolAccountSku | Where-Object -Property SkuPartNumber -Like "*EMSPREMIUM" $UsageLocation = "GB" # Loop through users and add any needed licenses foreach ($User in $ADGroupMembers) { $AddingLoggingCount++ $ADUser = Get-ADUser -Identity $User.sAMAccountName -ErrorAction Stop If ($O365AllUsers | Where-Object UserPrincipalName -eq $ADUser.UserPrincipalName) { $O365User = Get-MsolUser -UserPrincipalName $ADUser.UserPrincipalName -ErrorAction Stop If (($O365User.isLicensed -eq $false) -or ($O365User.UsageLocation -ne "GB")) { Try { Write-Warning "Setting user $($ADUser.UserPrincipalName) Usage Location to $UsageLocation" Set-MsolUser -UserPrincipalName $ADUser.UserPrincipalName -UsageLocation $UsageLocation -ErrorAction Stop Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Success -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($ADGroupMembersUPN).Count) -ProcessObjectCurrentItem $AddingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUser -UserPrincipalName $($ADUser.UserPrincipalName) -UsageLocation $($UsageLocation) -ErrorAction Stop'" -Step "Setting Region" } Catch { $Errorcount++ Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Failure -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($ADGroupMembersUPN).Count) -ProcessObjectCurrentItem $AddingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUser -UserPrincipalName $($ADUser.UserPrincipalName) -UsageLocation $($UsageLocation) -ErrorAction Stop'" -Errors $($_.Exception.Message) -Step "Setting Region" } } Else { Write-Warning "Usage region already set for user $($ADUser.UserPrincipalName)" } if (-not ($O365User.licenses | Where-Object AccountSkuId -eq $LicenseImport.AccountSkuId)) { if ($LicenseImport.ActiveUnits - $LicenseImport.ConsumedUnits -ge 1) { try { Write-Warning "Adding the user $($ADUser.UserPrincipalName) to E5 EMS" Set-MsolUserLicense -UserPrincipalName $ADUser.UserPrincipalName -AddLicenses $LicenseImport.AccountSkuId -ErrorAction Stop Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Success -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($ADGroupMembersUPN).Count) -ProcessObjectCurrentItem $AddingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUserLicense -UserPrincipalName $($ADUser.UserPrincipalName) -AddLicenses $($LicenseImport.AccountSkuId) -ErrorAction Stop'" -Step "Adding User" } catch { $Errorcount++ Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Failure -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($ADGroupMembersUPN).Count) -ProcessObjectCurrentItem $AddingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUserLicense -UserPrincipalName $($ADUser.UserPrincipalName) -AddLicenses $($LicenseImport.AccountSkuId) -ErrorAction Stop'" -Errors $($_.Exception.Message) -Step "Adding User" } } else { $Errorcount++ Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Failure -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($ADGroupMembersUPN).Count) -ProcessObjectCurrentItem $AddingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUserLicense -UserPrincipalName $($ADUser.UserPrincipalName) -AddLicenses $($LicenseImport.AccountSkuId) -ErrorAction Stop'" ` -Errors $("Not enough licences, Error : " + $_.Exception.Message) -Step "Adding User" $Licerror = $true } } Else { Write-Warning "EMS E5 already set for user $($ADUser.UserPrincipalName)" } } } # Loop through users and remove any unneeded licenses foreach ($User in $O365AllUsers) { $RemovingLoggingCount++ # E5 EMS if ($User.licenses | Where-Object AccountSkuId -EQ $LicenseImport.AccountSkuId) { if ($User.UserPrincipalName -match "'") { $Patteren = $User.UserPrincipalName -replace "'", '*' } else { $Patteren = $User.UserPrincipalName } $ADUser = Get-ADUser -Filter "UserPrincipalName -Like '$Patteren'" foreach ($b in $ADGroupMembersUPN) { if (-not ($User.UserPrincipalName -eq $($b.UserPrincipalName))) { $Remove = $true } Else { $Remove = $false break } } if ($Remove) { try { Write-Warning "Removing the user $($ADUser.UserPrincipalName) from E5 EMS" Set-MsolUserLicense -UserPrincipalName $ADUser.UserPrincipalName -RemoveLicenses $LicenseImport.AccountSkuId Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Success -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($O365AllUsers).Count) -ProcessObjectCurrentItem $RemovingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUserLicense -UserPrincipalName $($ADUser.UserPrincipalName) -RemoveLicenses $($LicenseImport.AccountSkuId)'" -Step "Removing User" $removalcount++ } catch { $Errorcount++ Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus InProgress -Result Failure -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -ProcessObjectCount $(($O365AllUsers).Count) -ProcessObjectCurrentItem $RemovingLoggingCount -TargetObject $($ADUser.UserPrincipalName) ` -CommandRun "Ran command : 'Set-MsolUserLicense -UserPrincipalName $($ADUser.UserPrincipalName) -RemoveLicenses $($LicenseImport.AccountSkuId)'" ` -Errors "User $($User.DisplayName) Errored : $($_.Exception.Message)" -Step "Removing User" } } Else { Write-Warning "E5 EMS Removal not needed for user $($User.UserPrincipalName)" } } } Write-NTSPLog -OverviewList "Automation Overview" -LogList "Automation Logs" -ProcessName $((Get-PSCallStack)[0].Command) ` -CurrentStatus Completed -Result Success -ProcessOwner "CIMS Team" -ID $LoggingID -SkipCheck -Confirm:$false ` -CommandRun "Script exiting gracefully" -ProcessObjectCount "" -ProcessObjectCurrentItem "" ` -Step "Ending" -Errors "Total Errors/Warnings : $($ErrorCount)" -Notes "Total Group Members : $(($ADGroupMembersUPN).Count); Total O365 users : $(($O365AllUsers).Count)" |
The actual function
So here it is, I hope you find it useful, i’ll try and update as suggestion come in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 |
<# .SYNOPSIS Logs the specified information to SharePoint lists .DESCRIPTION This function writes the supplied parameter values to 2 SharePoint lists, one being an overview of the script or automation this function is used in, the other a log for each action. If the lists don't exist, the function can create them and if they exist but are missing some fields it will add them. Overview List; The Fields(Columns) in the Overview list have the following passed from the parameters: "ProcessName", "CurrentStatus", "Result", "ProcessOwner", "ProcessObjectCount", "ProcessObjectCurrentItem" In addition, "RunBy", "HostSystem" are added automatically. This is where you can see each process the function used in and its current status. The initial run of this function in a process should have the -PassThru switch used and captured (In a variable), this will allow you to reference the integer further down the logging using the -ID parameter so that the items added to the Logs list match up with the Overview list. Log List; The Fields(Columns) in the Log list have the following passed from the parameters; "ProcessName", "CurrentStatus", "Result", "ProcessOwner", "ProcessObjectCount", "ProcessObjectCurrentItem", "TargetObject", "CommandRun", "OverviewReferenceId" In addition, "RunBy", "HostSystem" are added automatically. For each list a default view is created however remember that there is a limit of what you can view in a list on screen, make sure to create some kind of reporting or live PowerBI dashboard to display the date in a meaningful way. .PARAMETER OverviewList Enter the name of overview list, will create a new one if none exists in the current connection .PARAMETER LogList Enter the name of log list, will create a new one if none exists in the current connection .PARAMETER Passthru This outputs an object with the ID of the Overview List item .PARAMETER SkipCheck Specify this switch to skip checking if the Lists and Fields exist, should speed up process slightly. .PARAMETER PreBuildLists Builds named lists if missing and missing fields without adding or amending the lists .PARAMETER ProcessName Name of the Script or function this function is used inside of, can be left blank and 'Get-PSCallStack' will be used. .PARAMETER CurrentStatus The state of the current process .PARAMETER Result The result of the command or activity .PARAMETER ProcessOwner Who created the process this function logs .PARAMETER ProcessObjectCount Count of the items processing .PARAMETER ProcessObjectCurrentItem The current item in the process .PARAMETER TargetObject Target of the command i.e. user .PARAMETER CommandRun What Command is run .PARAMETER Id Only pass to this parameter after the first instance of the function in your script as it will act as a hook as to what item in the Overview List the Log List item belongs too, this will allow you to reference the 2 in some reporting. i.e. PowerBI. .PARAMETER Errors Any errors generated can be added here .PARAMETER Step Current Step in the Process .PARAMETER Notes Any notes for this item .EXAMPLE This function is to be used in other automation processes to log their output to SharePoint Lists. Small example of how this would look; # My Script That does things $OverViewLoggingList = Write-NTSPLog -OverviewList "My Logging Overview" -LogList "My Logging Logs" -PassThru ` -ProcessName "Process Named Doing Things" -CurrentStatus Starting -Result Success -ProcessOwner "Me" ` -TargetObject "A List of Users" -CommandRun "Starting Process" $AllTheUsers = Get-AllTheUsers -AllOfThem $Counting = 0 foreach ($a in $AllTheUsers) { $Counting++ Try { Awesome-PowerShellCommand -ImAParameter $a -ErrorAction 'Stop' Write-NTSPLog -OverviewList "My Logging Overview" -LogList "My Logging Logs" ` -ProcessName "Process Named Doing Things" -CurrentStatus InProgress -Result Success -ProcessOwner "Me" ` -TargetObject $a -CommandRun "I ran this one : 'Awesome-PowerShellCommand -ImAParameter $a -ErrorAction 'Stop''" ` -ProcessObjectCount $AllTheUsers -ProcessObjectCurrentItem $Counting -ID $OverViewLoggingList } Catch { Write-NTSPLog -OverviewList "My Logging Overview" -LogList "My Logging Logs" ` -ProcessName "Process Named Doing Things" -CurrentStatus InProgress -Result Failure -ProcessOwner "Me" ` -TargetObject $a -CommandRun "I ran this one : 'Awesome-PowerShellCommand -ImAParameter $a -ErrorAction 'Stop''" ` -ProcessObjectCount $AllTheUsers -ProcessObjectCurrentItem $Counting -ID $OverViewLoggingList -Errors $_.Exception.Message } .OUTPUTS void, System.String #> #Requires -Module SharePointPnPPowerShellOnline #Requires -Version 3.0 function Write-NTSPLog { [CmdletBinding(DefaultParameterSetName = 'Build', ConfirmImpact = 'High', SupportsShouldProcess = $true)] [OutputType([System.String], ParameterSetName = 'Build')] [OutputType([void], ParameterSetName = 'Skip')] [OutputType([System.String])] param ( [Parameter(Mandatory = $true, Position = 0, HelpMessage = 'Enter the name of overview list, will create a new one if none exists in the current connection')] [ValidateNotNullOrEmpty()] [System.String]$OverviewList, [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [System.String]$LogList, [Parameter(ParameterSetName = 'Build')] [switch]$Passthru, [Parameter(ParameterSetName = 'Skip')] [switch]$SkipCheck, [Parameter(ParameterSetName = 'Build')] [switch]$PreBuildLists, [System.String]$ProcessName, [System.String]$Step, [ValidateSet('Starting', 'InProgress', 'Completed')] [System.String]$CurrentStatus, [ValidateSet('Failure', 'Warning', 'Success-Warning', 'Success')] [System.String]$Result, [System.String]$ProcessOwner, [int32]$ProcessObjectCount, [int32]$ProcessObjectCurrentItem, [System.String]$TargetObject, [System.String]$CommandRun, [int64]$ID, [System.String]$Errors, [System.String]$Notes ) BEGIN { # Embedded Functions Start #region Modules function ConvertTo-CapitalSplit { param ( [Parameter(Mandatory = $true)] [System.String]$InputString ) $OutputString = @() foreach ($a in ($InputString.ToCharArray())) { if ([char]::IsUpper($a)) { $OutputString += " " + $a } else { $OutputString += $a } } return ($OutputString -Join '').Trim() } #endregion # Embedded Functions End # Global Vars Start #region # List of internal names for the fields(Columns) in SharePoint, when adding more make sure to CAPITALIZE the start of a new word to have it split to the displayname $OverviewFieldsInternalName = "ProcessName", "Step", "CurrentStatus", "Result", "ProcessOwner", "ProcessObjectCount", "ProcessObjectCurrentItem", "RunBy", "HostSystem", "ConnectedWithAccount", "Errors", "Notes" # List of internal names for the fields(Columns) in SharePoint, when adding more make sure to CAPITALIZE the start of a new word to have it split to the displayname $LogFieldsInternalName = "ProcessName", "Step", "CurrentStatus", "Result", "ProcessOwner", "ProcessObjectCount", "ProcessObjectCurrentItem", "TargetObject", "CommandRun", "OverviewReferenceId", "RunBy", "HostSystem", "ConnectedWithAccount", "Errors", "Notes" # List of Bound Parameters it's ignoring. # Overview List $OverviewNotChecking = "LocalLogPath", "SkipCheck", "ID", "PassThru", "ErrorAction", "WarningAction", "Verbose", "ErrorVariable", "WarningVariable", "OutVariable", "OutBuffer", "Debug", "Confirm", "LogList", "OverviewList", "TargetObject", "CommandRun" # Log List $LogNotChecking = "LocalLogPath", "SkipCheck", "ID", "PassThru", "ErrorAction", "WarningAction", "Verbose", "ErrorVariable", "WarningVariable", "OutVariable", "OutBuffer", "Debug", "Confirm", "LogList", "OverviewList" #endregion # Global Vars End # Module Import Start #region try { Import-Module SharePointPnPPowerShellOnline -ErrorAction Stop -Verbose:$false -DisableNameChecking } catch { Write-Error -Message "Could not import required module SharePointPnPPowerShellOnline, try installing and running again." break } #endregion #Module Import End #region Lists if ($SkipCheck -eq $false) { # Overview List Start #region # This generates the status of the running script and and privides a unique ID to correlate between Overview list and logging list items # Overview variables $ConnectedSite = Get-PnpSite | Select-Object -ExpandProperty url $OverviewFields = @() foreach ($a in $OverviewFieldsInternalName) { $OverviewFields += @{ 'InternalName' = $a; 'DisplayName' = (ConvertTo-CapitalSplit -InputString $a) } } # Adding From Default Field Items $OverviewFields += @{ 'InternalName' = "_DCDateModified"; 'DisplayName' = "Date Modified" } # Looking for referenced Overview list try { Write-Verbose "Testing if $($OverviewList) exists" $Global:OverviewListObject = Get-PnPList -Identity $OverviewList -throwExceptionIfListNotFound -ErrorAction Stop Write-Verbose "Overview List $($OverviewList) found, moving on.." Write-Verbose "Checking if required fields are present.." # Checking existing overview list fields - start # Gets all the fields in the view $OverviewListFields = Get-PnPField -List $OverviewListObject.Title # Compares fields currently in the view with the ones we've specifed $OverviewCompare = Compare-Object -ReferenceObject $OverviewListFields.internalname -DifferenceObject $OverviewFieldsInternalName | where SideIndicator -EQ '=>' if (([System.String]::IsNullOrEmpty($OverviewCompare) -eq $false) -or ($OverviewCompare.count -ge 0)) { Write-Verbose "Found missing Fields" foreach ($a in $OverviewCompare.inputobject) { Write-Verbose "Missing field $($a)" if ($PSCmdlet.ShouldProcess("Adding missing Field '$($a)'")) { try { Write-Verbose "Adding missing Field $($a)" $null = Add-PnPField -List $OverviewListObject.Title -InternalName $a -DisplayName $(ConvertTo-CapitalSplit -InputString $a) -Type Text -AddToDefaultView:$true -ErrorAction Stop } catch { Write-Error -Message 'Failed to add missing Field, breaking..' break } } } } else { Write-Verbose "All Fields present" } Write-Verbose "Pre-flight checks complete" # Checking existing overview list fields - end } catch [InvalidOperationException] { Write-Verbose "Failed to connect, breaking.." break } catch { Write-Verbose "Could not find Overview list : $($OverviewList)" try { Write-Verbose "Creating List : $($OverviewList)" if ($PSCmdlet.ShouldProcess("Create new Overview List '$($OverviewList)'`nin the site $($ConnectedSite)")) { $null = New-PnPList -Title $OverviewList -Template 100 -ErrorAction 'Stop' $Global:OverviewListObject = Get-PnPList -Identity $OverviewList -ErrorAction Stop } else { Break } Write-Verbose "Overview List $($OverviewList) created.." } catch { Write-Verbose "Failed to create Overview list : $($OverviewList), breaking.." Write-Error -Message $($_.Exception.Message) break } # Creating Fields and setting default values # Default Field Value = Required try { # Modifying default value Write-Verbose "Setting default field 'Title' to not required" # Setting default field Title to not required. $null = Set-PnPField -List $OverviewListObject.Title -Identity "Title" -Values @{ "Required" = $false } } Catch { Write-Error -Message 'Failed to set the dafault value of "Title", breaking..' break } # Adding Required Fields foreach ($Field in $OverviewFields) { try { $null = Add-PnPField -List $OverviewListObject.Title @Field -Type Text -ErrorAction Stop } Catch { Write-Error -Message "Failed to add Overview List Field $($Field.InternalName), breaking.." break } } # Creating default view try { # Creating View Write-Verbose "Creating Default View" $OverviewFieldsInternalName += "_DCDateModified" $null = Add-PnPView -List $OverviewListObject.Title -Title $($OverviewListObject.Title + " Default View") -SetAsDefault -Fields $OverviewFieldsInternalName -RowLimit 5000 -ErrorAction Stop } Catch { Write-Error -Message $($_.Exception.Message) Write-Error -Message 'Failed to set Overview List default Field values, breaking..' break } } #endregion # Overview List End # Log List Start #region # Log List variables $LogFields = @() foreach ($a in $LogFieldsInternalName) { $LogFields += @{ 'InternalName' = $a; 'DisplayName' = (ConvertTo-CapitalSplit -InputString $a) } } # Adding From Default Field Items $LogFields += @{ 'InternalName' = "_DCDateModified"; 'DisplayName' = "Date Modified" } # Looking for referenced Log list try { Write-Verbose "Testing if $($LogList) exists" $Global:LogListObject = Get-PnPList -Identity $LogList -throwExceptionIfListNotFound -ErrorAction Stop Write-Verbose "Log List $($LogList) found, moving on.." Write-Verbose "Checking if required fields are present.." # Checking existing Log list fields - start # Gets all the fields in the view $LogListFields = Get-PnPField -List $LogListObject.Title # Compares fields currently in the view with the ones we've specifed $LogCompare = Compare-Object -ReferenceObject $LogListFields.internalname -DifferenceObject $LogFieldsInternalName | where SideIndicator -EQ '=>' if (([System.String]::IsNullOrEmpty($LogCompare) -eq $false) -or ($LogCompare.count -ge 0)) { Write-Verbose "Found missing Fields" foreach ($a in $LogCompare.inputobject) { Write-Verbose "Missing field $($a)" if ($PSCmdlet.ShouldProcess("Adding missing Field '$($a)'")) { try { Write-Verbose "Adding missing Field $($a)" $null = Add-PnPField -List $LogListObject.Title -InternalName $a -DisplayName $(ConvertTo-CapitalSplit -InputString $a) -Type Text -AddToDefaultView:$true -ErrorAction Stop } catch { Write-Error -Message 'Failed to add missing Field, breaking..' break } } } } else { Write-Verbose "All Fields present" } Write-Verbose "Pre-flight checks complete" # Checking existing Log list fields - end } catch [InvalidOperationException] { Write-Verbose "Failed to connect, breaking.." break } catch { Write-Verbose "Could not find Log list : $($LogList)" try { Write-Verbose "Creating List : $($LogList)" if ($PSCmdlet.ShouldProcess("Create new Log List '$($LogList)'`nin the site $($ConnectedSite)")) { $MagicFix = New-PnPList -Title $LogList -Template 100 -ErrorAction 'Stop' | Out-Null $Global:LogListObject = Get-PnPList -Identity $LogList -ErrorAction Stop } else { Break } Write-Verbose "Log List $($LogList) created.." } catch { Write-Verbose "Failed to create Overview list : $($LogList), breaking.." Write-Error -Message $($_.Exception.Message) break } # Creating Fields and setting default values # Default Field Value = Required try { # Modifying default value Write-Verbose "Setting default field 'Title' to not required" # Setting default field Title to not required. $null = Set-PnPField -List $LogListObject.Title -Identity "Title" -Values @{ "Required" = $false } } Catch { Write-Error -Message $($_.Exception.Message) Write-Error -Message 'Failed to set the dafault value of "Title", breaking..' break } # Adding Required Fields foreach ($Field in $LogFields) { try { $null = Add-PnPField -List $LogListObject.Title @Field -Type Text -ErrorAction Stop } Catch { Write-Error -Message "Failed to add Overview List Field $($Field.InternalName), breaking.." break } } # Creating default view try { # Creating View Write-Verbose "Creating Default View" $LogFieldsInternalName += "_DCDateModified" $null = Add-PnPView -List $LogListObject.Title -Title $($LogListObject.Title + " Default View") -SetAsDefault -Fields $LogFieldsInternalName -RowLimit 5000 -ErrorAction Stop Write-Verbose "Default View Created" } Catch { Write-Error -Message $($_.Exception.Message) Write-Error -Message 'Failed to set Log List default Field values, breaking..' break } } #endregion # Log List - End Write-Verbose "All pre-checks complete" } #endregion } PROCESS { if ($PreBuildLists -eq $false) { # Building output to each list - Start $CreationTimeStamp = Get-Date $ConnectionMadeBy = (Get-PnPConnection).PSCredential.UserName # Overview List - Start # Get non blank parameters that are not part of not checking list $OverviewBoundParamsCompare = Compare-Object -ReferenceObject $($PSBoundParameters.Keys) -DifferenceObject $OverviewNotChecking | where SideIndicator -EQ '<=' # Adding the selected parameters in to an HashTable $OverviewOutputItems = New-Object System.Collections.Hashtable foreach ($a in $OverviewBoundParamsCompare) { $item = @{ $($a.InputObject) = $PSBoundParameters.Item($a.InputObject) } $OverviewOutputItems += $item } # Adding Default static Field items $OverviewOutputItems += @{ "ConnectedWithAccount" = $ConnectionMadeBy; "RunBy" = $env:USERNAME; "HostSystem" = $env:COMPUTERNAME; "_DCDateModified" = $CreationTimeStamp } # Finally adding the paramter values to the Overview SharePoint List if (($PSBoundParameters.Keys -contains "ID") -eq $false) { try { Write-Verbose "Adding/Updating the Overview List" $Global:IDOutput = Add-PnPListItem -List $PSBoundParameters.Item("OverviewList") -Values $OverviewOutputItems -ErrorAction Stop if ([System.String]::IsNullOrEmpty($IDOutput)) { Write-Error -Message "List Item not created, have you build the Lists?`nRemove -SkipCheck and try again, this will build the list for you." -Category InvalidOperation break } Write-Verbose "Added to the Overview List successfully" if ($Passthru) { $IDOutput | select -ExpandProperty id } } catch { Write-Error -Message "$($_.Exception.Message)" } } else { try { Write-Verbose "Adding/Updating the Overview List" $OverviewListCheck = Set-PnPListItem -Identity $PSBoundParameters.Item("ID") -List $PSBoundParameters.Item("OverviewList") -Values $OverviewOutputItems -SystemUpdate -ErrorAction Stop if ([System.String]::IsNullOrEmpty($IDOutput)) { Write-Error -Message "List Item not created, have you build the Lists?`nRemove -SkipCheck and try again, this will build the list for you." -Category InvalidOperation break } Write-Verbose "Added to the Overview List successfully" if ($Passthru) { $IDOutput | select -ExpandProperty id } } catch { Write-Error -Message "$($_.Exception.Message)" break } } # Overview List -End # Log List - Start # Get none blank parameters that are not part of not checking list $LogBoundParamsCompare = Compare-Object -ReferenceObject $($PSBoundParameters.Keys) -DifferenceObject $LogNotChecking | where SideIndicator -EQ '<=' # Adding the selected parameters in to an Hashtable $LogOutputItems = New-Object System.Collections.Hashtable foreach ($a in $LogBoundParamsCompare) { $item = @{ $($a.InputObject) = $PSBoundParameters.Item($a.InputObject) } $LogOutputItems += $item } # Adding Default static Field items $LogOutputItems += @{ "ConnectedWithAccount" = $ConnectionMadeBy; "RunBy" = $env:USERNAME; "HostSystem" = $env:COMPUTERNAME; "_DCDateModified" = $CreationTimeStamp } # Finally adding the paramter values to the Log SharePoint List if (($PSBoundParameters.Keys -contains "ID") -eq $false) { try { Write-Verbose "Adding to the Log List" $LogOutputItems += @{ "OverviewReferenceId" = $IDOutput.ID } $LogListCheck = Add-PnPListItem -List $PSBoundParameters.Item("LogList") -Values $LogOutputItems -ErrorAction Stop if ([System.String]::IsNullOrEmpty($LogListCheck)) { Write-Error -Message "List Item not created, have you build the Lists?`nRemove -SkipCheck and try again, this will build the list for you." -Category InvalidOperation break } Write-Verbose "Added to the Log List successfully" } catch { Write-Error -Message "$($_.Exception.Message)" } } else { try { Write-Verbose "Adding to the Log List" $LogOutputItems += @{ "OverviewReferenceId" = $($PSBoundParameters.Item("ID")) } $LogListCheck = Add-PnPListItem -List $PSBoundParameters.Item("LogList") -Values $LogOutputItems -ErrorAction Stop if ([System.String]::IsNullOrEmpty($LogListCheck)) { Write-Error -Message "List Item not created, have you build the Lists?`nRemove -SkipCheck and try again, this will build the list for you." -Category InvalidOperation break } Write-Verbose "Added to the Log List successfully" } catch { Write-Error -Message "$($_.Exception.Message)" break } } #Log List - End # Building output to each list - End } } END { } } |
Credit
SharePoint related queries – James Beckenham – Senior SharePoint Specialist