├── VersionDelete.csv ├── README.md ├── Adjectives.txt ├── Nouns.txt ├── CreateLotsofTeams.ps1 ├── users.csv ├── DeleteOldVersions.ps1 ├── createusers.ps1 ├── ExpandOneLiner.psm1.txt ├── SearchAll.psm1 ├── addclientcreds.psm1 ├── Logfiles.psm1 ├── ChatGPT.psm1 ├── reindexusersv2.psm1 ├── TKM365Commands.psm1 ├── Search demonstration creation Module.psm1 ├── TKAzureExtensionProperty.psm1 └── TKOnPremCommands.psm1 /VersionDelete.csv: -------------------------------------------------------------------------------- 1 | FileName 2 | /Shared Documents/Building materials licences to budget for Storytelling.docx 3 | /Shared Documents/Boot fairs with Graphic design.docx 4 | /sites/blah/Shared Documents/Building materials licences to budget for Storytelling.docx 5 | /sites/blah/Shared Documents/Boot fairs with Graphic design.docx 6 | https://m365x995492.sharepoint.com/sites/blah/Shared Documents/Building materials licences to budget for Storytelling.docx 7 | https://m365x995492.sharepoint.com/sites/blah/Shared Documents/Boot fairs with Graphic design.docx 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell 2 | This repo contains PowerShell scripts, snippets, and functions I talk about on [my blog](https://www.toddklindt.com/blog). Since I'll be updating these over time, they may differ slightly from the blog post they're from. 3 | 4 | Read them over before you run them, who knows what crazy things I've done in them. Most of the scripts here will need some tweaking before they run in your environment. If you have any questions leave an issue or send me a [tweet](https://twitter.com/toddklindt) and I'll see what I can do. 5 | 6 | There are two modules, TKM365Commands.psm1 and TKOnPremCommands.psm1. To use them save the PSM1 file locally (or clone this repo), then run this command: 7 | ```PowerShell 8 | Import-Module PathToFile\TKM365Commands.psm1 -verbose 9 | ``` 10 | Using the -verbose switch will list out the commands in the module. Alternately you can type this: 11 | ```PowerShell 12 | Get-Command -Module TKM365Commands 13 | ``` 14 | 15 | If you have any suggestions open an issue and I'll check it out. 16 | 17 | tk 18 | -------------------------------------------------------------------------------- /Adjectives.txt: -------------------------------------------------------------------------------- 1 | attractive 2 | agreeable 3 | angry 4 | big 5 | bald 6 | ambitious 7 | bewildered 8 | colossal 9 | beautiful 10 | brave 11 | clumsy 12 | fat 13 | chubby 14 | calm 15 | defeated 16 | gigantic 17 | clean 18 | delightful 19 | embarrassed 20 | great 21 | dazzling 22 | eager 23 | fierce 24 | huge 25 | drab 26 | faithful 27 | grumpy 28 | immense 29 | elegant 30 | gentle 31 | helpless 32 | large 33 | fancy 34 | happy 35 | itch 36 | little 37 | fit 38 | jolly 39 | jealous 40 | mammoth 41 | flabby 42 | kind 43 | lazy 44 | massive 45 | glamorous 46 | lively 47 | mysterious 48 | microscopic 49 | miniature 50 | gorgeous 51 | nice 52 | nervous 53 | petite 54 | puny 55 | scrawny 56 | handsome 57 | obedient 58 | obnoxious 59 | magnificent 60 | polite 61 | panicky 62 | muscular 63 | proud 64 | pitiful 65 | short 66 | plain 67 | silly 68 | repulsive 69 | small 70 | plump 71 | thankful 72 | scary 73 | tall 74 | scruffy 75 | victorious 76 | thoughtless 77 | teeny 78 | shapely 79 | witty 80 | uptight 81 | tiny 82 | skinny 83 | stocky 84 | unkempt 85 | unsightly 86 | wonderful 87 | zealous 88 | worried -------------------------------------------------------------------------------- /Nouns.txt: -------------------------------------------------------------------------------- 1 | advantage 2 | advertise 3 | advice 4 | agenda 5 | apology 6 | authorization 7 | bill 8 | brand 9 | budget 10 | change 11 | commission 12 | comparison 13 | competition 14 | competitor 15 | confirmation 16 | costs 17 | creditor 18 | customer 19 | deadline 20 | debt 21 | debtor 22 | decision 23 | decrease 24 | deficit 25 | delivery 26 | department 27 | description 28 | difference 29 | disadvantage 30 | distribution 31 | employee 32 | employer 33 | enquiry 34 | environment 35 | equipment 36 | estimate 37 | experience 38 | explanation 39 | facilities 40 | factory 41 | fall 42 | feedback 43 | goal 44 | goods 45 | growth 46 | guarantee 47 | improvement 48 | increase 49 | industry 50 | instructions 51 | interest 52 | inventory 53 | invoice 54 | knowledge 55 | limit 56 | loss 57 | margin 58 | market 59 | message 60 | mistake 61 | objective 62 | offer 63 | opinion 64 | option 65 | order 66 | output 67 | payment 68 | penalty 69 | permission 70 | possibility 71 | preparation 72 | price 73 | product 74 | production 75 | profit 76 | promotion 77 | purchase 78 | reduction 79 | refund 80 | reminder 81 | repairs 82 | report 83 | responsibility 84 | result 85 | retailer 86 | rise 87 | risk 88 | salary 89 | sales 90 | schedule 91 | share 92 | signature 93 | stock 94 | success 95 | suggestion 96 | supply 97 | support 98 | target 99 | transport 100 | turnover -------------------------------------------------------------------------------- /CreateLotsofTeams.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | break 3 | # for blog post https://www.toddklindt.com/blog/Lists/Posts/Post.aspx?ID=884 4 | # Install the PowerShell module. Do this from an Admin PowerShell shell 5 | # PnP.PowerShell version 1.6.0 has a bug that prevents the Teams from getting created. Use an older or newer version. 6 | Install-Module PnP.PowerShell -MaximumVersion 1.5.0 7 | Import-Module PnP.PowerShell -MaximumVersion 1.5.0 8 | or 9 | Install-Module PnP.PowerShell -MinimumVersion 1.6.17-nightly -AllowClobber -AllowPrerelease -SkipPublisherCheck 10 | Import-Module PnP.PowerShell -MinimumVersion 1.6.17 11 | 12 | # Register the Azure App it needs. You can delete this after you're finished creating the Teams 13 | Register-PnPManagementShellAccess 14 | #> 15 | # Connect to the Admin site. Put in your real tenant name 16 | Connect-PnPOnline -Url https://CONTOSO-admin.sharepoint.com -Interactive 17 | 18 | # import the files with nouns and adjectives 19 | $Nouns = Get-Content .\nouns.txt 20 | $Adjectives = Get-Content .\adjectives.txt 21 | 22 | # Number of Teams to create 23 | $NumberOfTeams = 3 24 | $Index = 1 25 | 26 | while ($Index -le $NumberOfTeams) { 27 | # Generate Random stuff 28 | $TeamNoun = $Nouns | Get-Random 29 | $TeamAdjective = $Adjectives | Get-Random 30 | $TeamNumber = Get-Random -Maximum 100 31 | $TeamDisplayName = "$TeamAdjective $TeamNoun $TeamNumber" 32 | Write-Host "$Index - $TeamDisplayName" 33 | New-PnPTeamsTeam -DisplayName $TeamDisplayName -MailNickName $($TeamDisplayName.Replace(" ","")) -Description $TeamDisplayName -Visibility Public -AllowGiphy $true 34 | $Index++ 35 | } 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /users.csv: -------------------------------------------------------------------------------- 1 | FirstName,LastName,SamAccountName,Title,Manager 2 | SharePoint,Install Account,sp_install,Service Account, 3 | SharePoint,Farm Account,sp_farm,Service Account, 4 | SharePoint,Web App Account,sp_webapps,Service Account, 5 | SharePoint,Service App Account,sp_serviceapps,Service Account, 6 | SharePoint,Profile Account,sp_profile,Service Account, 7 | SharePoint,Search Content Crawl,sp_content,Service Account, 8 | SharePoint,Super User,sp_superuser,Service Account, 9 | SharePoint,Super Reader,sp_superreader,Service Account, 10 | SharePoint,Workflow,sp_workflow,Service Account, 11 | SharePoint,Office Web Apps,sp_owa,Service Account, 12 | SQL,Install Account,sqlinstall,Service Account, 13 | SQL,Service Account,sqluser,Service Account, 14 | Bret,Piatt,bret,Head Honcho, 15 | Jeff,Deverter,jeff,Head Honcho,bret 16 | Nicola,Young,nicola,Sales lead,jeff 17 | Shane,Young,shane,Who knows,jeff 18 | Todd,Klindt,todd,SharePoint Consultant,jeff 19 | John,Ross,john,SharePoint Consultant,shane 20 | Randy,Drisgill,randy,Branding guy,nicola 21 | Laura,Rogers,laura,Telemetry Boss,nicola 22 | Chris,Caravjal,chris,SharePoint Consultant,nicola 23 | Ryan,Keller,ryan,SharePoint Consultant,randy 24 | Tyson,Young,tyson,,shane 25 | Pugsly,Young,pugsly,Janitor,tyson 26 | Grant,Young,grant,,nicola 27 | Luke,Young,luke,,nicola 28 | Jill,Klindt,jill,,todd 29 | Lily,Klindt,lily,,todd 30 | Penny,Klindt,penny,,todd 31 | Owen,Klindt,Owen,,todd 32 | Ben,Ross,ben,,john 33 | Julia,Ross,julia,,john 34 | Vanessa,Ross,vanessa,,john 35 | Jackie,Drisgill,jackie,,randy 36 | Chris,Rogers,chrisr,,laura 37 | Kristin,Rogers,kristin,,laura 38 | Charlotte,Rogers,charlotte,,laura 39 | Frito,Drisgill,frito,Cat,jackie 40 | Brittany,Keller,brittany,,ryan 41 | Colby,Keller,colby,Dog,ryan 42 | Daphne,Keller,daphne,Dog,ryan 43 | Phil,Jirsa,phil,SharePoint Consultant,nicola 44 | Javier,Barrera,javi,SharePoint Engineer,randy 45 | Brett,Woodall,brett,Stinky Programmer,randy 46 | Jeff,Taylor,scuba,Who Knows,todd -------------------------------------------------------------------------------- /DeleteOldVersions.ps1: -------------------------------------------------------------------------------- 1 | # Uncomment to see Verbose statements, set to "SilentyContinue" to hide them 2 | # $VerbosePreference = "Continue" 3 | 4 | # Replace with your own Connect-PnPOnline statement 5 | $SiteUrl = "https://m365x995492.sharepoint.com/sites/blah" 6 | Connect-PnPOnline -Url $SiteUrl -Credentials AlexW 7 | 8 | # Replace with the path to your own CSV file 9 | $FileList = Import-Csv .\VersionDelete.csv 10 | Write-Verbose "Found $($FileList.count) files in CSV file" 11 | 12 | # Replace with the number of versions you want to keep 13 | $VersionsToKeep = 5 14 | Write-Verbose "Keeping $VersionsToKeep of each file" 15 | 16 | foreach($File in $FileList) { 17 | # Remove site from Filename if it's there 18 | $Filename = $File.FileName.Replace($SiteUrl,"") 19 | Write-Verbose "Getting version for file $Filename" 20 | 21 | # Get the versions of each file 22 | $FileVersions = Get-PnPFileVersion -Url $Filename 23 | Write-Verbose "Found $($FileVersions.Count) versions" 24 | if ($FileVersions.Count -gt $VersionsToKeep) { # See if there are more than we want to keep 25 | 26 | # Pick the ones we want to remove 27 | $DeleteVersionList = ($FileVersions[0..$($FileVersions.Count - $VersionsToKeep)]) 28 | Write-Verbose "More than $VersionsToKeep versions. Deleting $($DeleteVersionList.count)" 29 | 30 | foreach($VersionToDelete in $DeleteVersionList) { 31 | Write-Verbose "Removing $($VersionToDelete.VersionLabel)" 32 | # Remove the versions 33 | #Remove-PnPFileVersion -Url $Filename -Identity $VersionToDelete.Id -Force 34 | $Output = [PSCustomObject]@{ 35 | PSTypeName = 'TKDeletedFileVersion' 36 | Filename = $Filename 37 | DeletedVersion = $($VersionToDelete.VersionLabel) 38 | } 39 | # Output the output 40 | $Output 41 | } 42 | } else { 43 | Write-Verbose "$Filename only had $($FileVersions.Count). Skipping..." 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /createusers.ps1: -------------------------------------------------------------------------------- 1 | # Script to create Active Directory accounts 2 | # v2.1 2/25/2019 3 | # Todd Klindt 4 | # http://www.toddklindt.com 5 | # Blog post, Yay!!! 6 | # Add the Active Directory bits and not complain if they're already there 7 | Import-Module ActiveDirectory -ErrorAction SilentlyContinue 8 | 9 | # set default password 10 | # change pass@word1 to whatever you want the account passwords to be 11 | $defpassword = (ConvertTo-SecureString "pass@word1" -AsPlainText -force) 12 | 13 | # Get domain DNS suffix 14 | $dnsroot = '@' + (Get-ADDomain).dnsroot 15 | 16 | # Import the file with the users. You can change the filename to reflect your file 17 | $users = Import-Csv .\users.csv 18 | 19 | foreach ($user in $users) { 20 | if ($user.manager -eq "") # In case it's a service account or a boss 21 | { 22 | try { 23 | New-ADUser -SamAccountName $user.SamAccountName -Name ($user.FirstName + " " + $user.LastName) ` 24 | -DisplayName ($user.FirstName + " " + $user.LastName) -GivenName $user.FirstName -Surname $user.LastName ` 25 | -EmailAddress ($user.SamAccountName + $dnsroot) -UserPrincipalName ($user.SamAccountName + $dnsroot) ` 26 | -Title $user.title -Enabled $true -ChangePasswordAtLogon $false -PasswordNeverExpires $true ` 27 | -AccountPassword $defpassword -PassThru ` 28 | } 29 | catch [System.Object] 30 | { 31 | Write-Output "Could not create user $($user.SamAccountName), $_" 32 | } 33 | } 34 | else 35 | { 36 | try { 37 | New-ADUser -SamAccountName $user.SamAccountName -Name ($user.FirstName + " " + $user.LastName) ` 38 | -DisplayName ($user.FirstName + " " + $user.LastName) -GivenName $user.FirstName -Surname $user.LastName ` 39 | -EmailAddress ($user.SamAccountName + $dnsroot) -UserPrincipalName ($user.SamAccountName + $dnsroot) ` 40 | -Title $user.title -manager $user.manager ` 41 | -Enabled $true -ChangePasswordAtLogon $false -PasswordNeverExpires $true ` 42 | -AccountPassword $defpassword -PassThru ` 43 | } 44 | catch [System.Object] 45 | { 46 | Write-Output "Could not create user $($user.SamAccountName), $_" 47 | } 48 | } 49 | # Put picture part here. 50 | $filename = "$($user.SamAccountName).jpg" 51 | Write-Output $filename 52 | 53 | if (test-path -path $filename) 54 | { 55 | Write-Output "Found picture for $($user.SamAccountName)" 56 | 57 | $photo = [byte[]](Get-Content $filename -Encoding byte) 58 | Set-ADUser $($user.SamAccountName) -Replace @{thumbnailPhoto=$photo} 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /ExpandOneLiner.psm1.txt: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Expands a PowerShell one-liner into a properly formatted multi-line script. 4 | 5 | .DESCRIPTION 6 | The Expand-OneLiner function is designed to take a PowerShell one-liner as input and return a properly formatted multi-line script. 7 | It expands common aliases, adjusts indentation for better readability, and handles various special characters to ensure the output is clear and easy to understand. 8 | 9 | .PARAMETER OneLiner 10 | The PowerShell one-liner that you want to expand. IMPORTANT: Ensure to surround the input with single quotes `'` to ensure that any `$` characters in the one-liner are passed correctly to the function. If you use double quotes `"`, PowerShell will attempt to evaluate any `$` characters as variables, which can lead to unexpected results. 11 | 12 | .EXAMPLE 13 | $formattedCode = Expand-OneLiner -OneLiner 'ls -r -fi *.lis | sort @{expression={$_.Name}},@{expression={$_.LastWriteTime};Descending=$true} | select Directory, Name, lastwritetime | Group-Object Name | %{$_.Group | Select -first 1}' 14 | Write-Output $formattedCode 15 | 16 | This example takes a one-liner that lists files, sorts them, and groups them by name. The Expand-OneLiner function will return a formatted multi-line script with expanded aliases and proper indentation. 17 | 18 | .EXAMPLE 19 | $formattedCode = Expand-OneLiner -OneLiner 'Get-Process | ?{$_.CPU -gt 10} | select Name, CPU' 20 | Write-Output $formattedCode 21 | 22 | In this example, the one-liner retrieves processes where the CPU usage is greater than 10. Notice how the `$` character inside the script block `{}` is correctly passed to the function because the entire one-liner is surrounded by single quotes `'`. 23 | #> 24 | 25 | function Expand-OneLiner { 26 | param ( 27 | [Parameter(Mandatory=$true)] 28 | [string]$OneLiner 29 | ) 30 | 31 | # Get all aliases from the current session 32 | $aliases = Get-Alias | 33 | Sort-Object { $_.Name.Length } -Descending | 34 | ForEach-Object { 35 | @{ 36 | Alias = [regex]::Escape($_.Name) # Escape special characters 37 | Definition = $_.Definition 38 | } 39 | } 40 | 41 | # Expand other aliases in the one-liner 42 | foreach ($alias in $aliases) { 43 | # Ensure we're matching whole words by using the \b word boundary in the regex 44 | # and not preceded by a '-' or '.' and not followed by a '-' 45 | $OneLiner = $OneLiner -replace "(? 27 | param( 28 | [Parameter(Mandatory = $true)] 29 | [string]$query, 30 | [Parameter(Mandatory = $false)] 31 | [switch]$ShowProgress 32 | ) 33 | 34 | # Check if we're connected to a SharePoint site 35 | $connection = Get-PnPConnection 36 | if ($null -eq $connection) { 37 | Write-Error "Not connected to a SharePoint site. Use Connect-PnPOnline to connect." 38 | return 39 | } 40 | 41 | # Initial variables 42 | $startRow = 0 43 | $pageSize = 500 # number of results per query 44 | 45 | do { 46 | # Perform query 47 | Write-Verbose "Getting results starting at row $startRow" 48 | $results = Submit-PnPSearchQuery -Query $query -StartRow $startRow 49 | 50 | # Show total number of results on first run 51 | if ($ShowProgress -and $startRow -eq 0) { 52 | Write-Host "Total results: $($results.TotalRows)" 53 | } 54 | 55 | # For each result, create a PSCustomObject with the desired properties 56 | foreach ($resultRow in $results.ResultRows) { 57 | [PSCustomObject]@{ 58 | Title = $resultRow.Title 59 | FileExtension = $resultRow.FileExtension 60 | Size = $resultRow.Size 61 | Description = $resultRow.Description 62 | Path = $resultRow.Path 63 | OriginalPath = $resultRow.OriginalPath 64 | ParentLink = $resultRow.ParentLink 65 | SPWebUrl = $resultRow.SPWebUrl 66 | SiteName = $resultRow.SiteName 67 | IsDocument = $resultRow.IsDocument 68 | } 69 | } 70 | 71 | # Show the number of results 72 | if($ShowProgress) { 73 | Write-Progress -Activity "Getting results" -Status "Getting results" -PercentComplete (($startRow / $results.TotalRows) * 100) 74 | } 75 | Write-Verbose "Found $($results.RowCount) results" 76 | 77 | # Increment the StartRow 78 | $startRow += $pageSize 79 | 80 | # Show the current StartRow 81 | Write-Verbose "Getting results starting at row $startRow" 82 | } 83 | while ($startRow -lt $results.TotalRows) # continue querying while we're below the total number of results 84 | 85 | } 86 | -------------------------------------------------------------------------------- /addclientcreds.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This function stores SharePoint Online credentials for a given tenant using the PnP.PowerShell module. 4 | Blog post at https://www.toddklindt.com/blog/Lists/Posts/Post.aspx?ID=900 5 | 6 | .DESCRIPTION 7 | The Add-ClientCredential function securely stores credentials for various SharePoint Online URLs. 8 | If credentials for a given URL already exist, the function will display the associated username and 9 | prompt the user to confirm whether they want to replace the existing credentials. 10 | 11 | .PARAMETER TenantName 12 | The name of the SharePoint Online tenant. It can be in various formats like 'contoso', 'contoso.sharepoint.com', 13 | 'contoso.onmicrosoft.com', 'https://contoso.sharepoint.com', or 'http://contoso.sharepoint.com'. 14 | 15 | .PARAMETER UserName 16 | The username for the SharePoint Online credentials. 17 | 18 | .PARAMETER Password 19 | The password for the SharePoint Online credentials, as a SecureString. 20 | 21 | .PARAMETER TestCredential 22 | A switch parameter that, when specified, will test the credentials after they are stored. 23 | 24 | .EXAMPLE 25 | Add-ClientCredential -TenantName "contoso" -UserName "user@contoso.com" -Password (ConvertTo-SecureString "YourPassword" -AsPlainText -Force) -TestCredential 26 | 27 | .EXAMPLE 28 | Add-ClientCredential 29 | #> 30 | 31 | function Add-ClientCredential { 32 | [CmdletBinding()] 33 | param ( 34 | [Parameter(Mandatory = $false)] 35 | [string] $TenantName, 36 | 37 | [Parameter(Mandatory = $false)] 38 | [string] $UserName, 39 | 40 | [Parameter(Mandatory = $false)] 41 | [SecureString] $Password, 42 | 43 | [Parameter(Mandatory = $false)] 44 | [switch] $TestCredential 45 | ) 46 | 47 | # Check if PnP.PowerShell module is installed 48 | $pnpModule = Get-Module -ListAvailable -Name PnP.PowerShell -ErrorAction SilentlyContinue 49 | if ($null -eq $pnpModule) { 50 | Write-Warning "The PnP.PowerShell module is not installed." 51 | Write-Output "To install the PnP.PowerShell module, run the following command:" 52 | Write-Output "Install-Module -Name PnP.PowerShell -Scope CurrentUser -Force -SkipPublisherCheck" 53 | return 54 | } 55 | 56 | if (-not $TenantName) { 57 | $TenantName = Read-Host "Please enter the Tenant Name" 58 | } 59 | 60 | # Normalize the tenant name to extract the base tenant name 61 | $TenantName = $TenantName -replace 'https://|http://|\.sharepoint\.com|\.onmicrosoft\.com', '' 62 | 63 | if (-not $UserName) { 64 | $UserName = Read-Host "Please enter the User Name" 65 | } 66 | 67 | if (-not $Password) { 68 | $Password = Read-Host "Please enter the Password" -AsSecureString 69 | } 70 | 71 | $urls = @( 72 | "https://$TenantName.sharepoint.com", 73 | "https://$TenantName.sharepoint.com/", 74 | "https://$TenantName-admin.sharepoint.com" 75 | ) 76 | 77 | foreach ($url in $urls) { 78 | $existingCredential = Get-PnPStoredCredential -Name $url -ErrorAction SilentlyContinue 79 | 80 | if ($null -ne $existingCredential) { 81 | Write-Output "Existing credential found for $url with username: $($existingCredential.UserName)" 82 | $replace = Read-Host "Do you want to replace it? (Y/N)" 83 | if ($replace -eq 'Y' -or $replace -eq 'y') { 84 | $replace = $true 85 | } else { 86 | $replace = $false 87 | } 88 | 89 | if (-not $replace) { 90 | Write-Output "Skipping credential for $url." 91 | continue 92 | } 93 | } 94 | 95 | Add-PnPStoredCredential -Name $url -Username $UserName -Password $Password 96 | Write-Output "Credential for $url has been added." 97 | 98 | if ($TestCredential) { 99 | Connect-PnPOnline -Url $url -Credentials (Get-PnPStoredCredential -Name $url) 100 | if (Get-PnPContext) { 101 | Write-Output "Successfully connected to $url with stored credentials." 102 | } else { 103 | Write-Warning "Failed to connect to $url with stored credentials." 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Logfiles.psm1: -------------------------------------------------------------------------------- 1 | function Format-ShareGateLogFile { 2 | <# 3 | .SYNOPSIS 4 | Formats a ShareGate log file in Excel format. 5 | 6 | .DESCRIPTION 7 | This function opens a ShareGate log file in Excel format and formats it for readability. It adds a table to the first worksheet, formats the first column as "Date-Time", calculates the duration of the log file, and formats the duration as "[h]:mm:ss" in the last row. The function saves the changes to the Excel file and closes it. From the blog post at https://www.toddklindt.com/blog/Lists/Posts/Post.aspx?ID=895 8 | 9 | .PARAMETER Path 10 | The path to the Excel file to format. This parameter is mandatory. 11 | 12 | .PARAMETER Open 13 | Pass the Open parameter if you want the file to open up in Excel automatically after it has been formatted. 14 | 15 | .EXAMPLE 16 | PS C:\> Format-ShareGateLogFile -Path "C:\path\to\ShareGateLogFile.xlsx" 17 | Formats the Excel file located at "C:\path\to\ShareGateLogFile.xlsx" for readability. 18 | 19 | .EXAMPLE 20 | PS C:\> Format-ShareGateLogFile -Path "C:\path\to\ShareGateLogFile.xlsx" -Open 21 | Formats the Excel file located at "C:\path\to\ShareGateLogFile.xlsx" for readability and opens the Excel file automatically after it has been formatted. 22 | 23 | .EXAMPLE 24 | PS C:\> Format-ShareGateLogFile -Path "C:\path\to\ShareGateLogFile.xlsx" -HideColumns -Open 25 | Formats the Excel file located at "C:\path\to\ShareGateLogFile.xlsx" for readability, hides the specified columns (E-U, W-AR, AT-BA), and opens the Excel file automatically after it has been formatted. 26 | 27 | .EXAMPLE 28 | PS C:\> Get-ChildItem -Path "C:\path\to\folder" -Filter "*.xlsx" | Format-ShareGateLogFile -HideColumns -Open 29 | This will get all the Excel (.xlsx) files in the specified folder, and for each one, it will be formatted for readability, with specified columns (E-U, W-AR, AT-BA) hidden, and the Excel file opened automatically after it has been formatted. 30 | 31 | #> 32 | 33 | [CmdletBinding()] 34 | param( 35 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 36 | [ValidateScript({Test-Path $_ -PathType Leaf})] 37 | [string]$Path, 38 | [switch]$HideColumns, 39 | [switch]$Open 40 | ) 41 | 42 | Begin { 43 | # Verify that ImportExcel module is installed 44 | if (-not(Get-Module -Name ImportExcel -ListAvailable)) { 45 | throw "ImportExcel module is not installed. Please install the module and try again." 46 | } 47 | } 48 | 49 | Process { 50 | 51 | # Open Excel file 52 | Write-Verbose "Processing $Path..." 53 | try { 54 | $excel = Open-ExcelPackage $Path 55 | $ws = $excel.Workbook.Worksheets[1] 56 | } 57 | catch { 58 | throw "Unable to open $Path. Please make sure the file is not open in another program and try again." 59 | } 60 | 61 | # Test to see it has already been worked on 62 | if ($ws.Tables.Count-ne 0) { 63 | Write-Host "The file $Path already has a Table. Skipping" 64 | Return 65 | } else { 66 | Write-Verbose "Formattting $Path..." 67 | } 68 | # Add table and format columns 69 | Add-ExcelTable -Range $ws.Cells[$($ws.Dimension.Address)] -TableName Table1 -TableStyle Medium2 70 | Set-ExcelColumn -Worksheet $ws -Column 1 -NumberFormat 'Date-Time' 71 | 72 | # Set format for last row 73 | $NumRows = $excel.Workbook.Worksheets.Dimension.Rows 74 | Set-Format -Range $excel.Workbook.Worksheets[1].Cells["A$($NumRows + 2):AA$($NumRows + 2)"] -NumberFormat '[h]:mm:ss' 75 | 76 | # Calculate duration and add formula to last row 77 | $cell = $ws.Cells["A$($NumRows + 2):A$($NumRows + 2)"] 78 | $cell.Formula = "=A2-A$($NumRows)" 79 | 80 | if ($HideColumns) { 81 | Write-Verbose "Hiding Columns" 82 | 83 | # Define the columns 84 | $ColumnsToHideList = 5..21 + 23..44 + 46..53 # Columns E-U, W-AR, AT-BA 85 | 86 | # Iterate through the columns and set the 'Hidden' property to $true 87 | foreach ($i in $ColumnsToHideList) { 88 | $ws.Column($i).Hidden = $true 89 | } 90 | 91 | } 92 | # Close Excel file 93 | if ($Open) { 94 | Write-Verbose "Opening $Path" 95 | Close-ExcelPackage $excel -Show 96 | } else { 97 | Write-Verbose "Not opening $Path" 98 | Close-ExcelPackage $excel 99 | } 100 | 101 | } 102 | 103 | End { 104 | # Nothing needed here 105 | } 106 | } -------------------------------------------------------------------------------- /ChatGPT.psm1: -------------------------------------------------------------------------------- 1 | function Format-ChatGPTConversation { 2 | <# 3 | .SYNOPSIS 4 | Formats ChatGPT conversations from a JSON file. 5 | 6 | .DESCRIPTION 7 | The Format-ChatGPTConversation function takes a JSON file containing ChatGPT conversations and formats the conversations into a structured output. It extracts relevant information such as conversation title, ID, create time, author, and content. System messages and messages without an author are skipped. 8 | 9 | To get the conversations.json file, go to https://chat.openai.com/. In the lower left corner click the three dots by your name and click Settings. In Settings, click the Data controls tab. There is an "Export" button in there. Clicking that will download a zip file. One of the files in that zip file is conversations.json. 10 | 11 | .PARAMETER filename 12 | Specifies the path to the JSON file containing the ChatGPT conversations. 13 | 14 | .EXAMPLE 15 | Format-ChatGPTConversation -filename "conversations.json" 16 | 17 | This example formats the ChatGPT conversations from the "conversations.json" file and displays the formatted output in the console. 18 | 19 | .EXAMPLE 20 | Format-ChatGPTConversation -filename "conversations.json" | Out-File -Filepath "formatted_conversations.txt" 21 | 22 | This example formats the ChatGPT conversations from the "conversations.json" file and saves the formatted output to a text file named "formatted_conversations.txt". 23 | 24 | .EXAMPLE 25 | Get-ChildItem -Path "conversations.json" | Format-ChatGPTConversation 26 | 27 | This example retrieves all the JSON files in the current directory with the "*.json" pattern and passes them through the pipeline to Format-ChatGPTConversation. The function formats the ChatGPT conversations from each JSON file and displays the formatted output in the console. 28 | 29 | .EXAMPLE 30 | Format-ChatGPTConversation -filename conversations.json | Group-Object -Property Title | Select-Object name, count 31 | 32 | This example gives you a list of each conversations and how many parts it has. 33 | 34 | .EXAMPLE 35 | Format-ChatGPTConversation -filename conversations.json | Where-Object { $_.title -eq "PowerShell Function Advice" } | select author, content | Format-List | more.com 36 | 37 | This gets only the messages in the "PowerShell Function Advice" conversation and displays them in a list format only given you the author and the content of the message. The more.com command is used to page the output. 38 | 39 | #> 40 | param ( 41 | [cmdletbinding()] 42 | [OutputType('TKchatGPT')] 43 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] 44 | [ValidateScript({ (Test-Path $_) -and ($_.EndsWith('.json')) })] 45 | [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) 46 | $path = $fakeBoundParameter[$parameterName] 47 | $files = Get-ChildItem -Path $path -Filter '*.json' -Name 48 | 49 | if ($files) { 50 | $files | Where-Object { $_ -like "$wordToComplete*" } 51 | } 52 | })] 53 | [string]$filename 54 | ) 55 | 56 | Begin { 57 | # No explicit Begin block needed for this function 58 | } 59 | 60 | Process { 61 | # Resolve $filename to full path 62 | $filename = Resolve-Path -Path $filename 63 | $conversationFile = Get-Content -Raw -Path $filename | ConvertFrom-Json 64 | Write-Verbose "Found $($conversationFile.Count) ChatGPT Conversations" 65 | 66 | foreach ($conversation in $conversationFile) { 67 | $title = $conversation.title 68 | $id = $conversation.id 69 | 70 | Write-Verbose "Processing Conversation: $title" 71 | foreach ($mapping in $conversation.mapping.PSObject.Properties) { 72 | $object = $mapping.Value 73 | 74 | $create_time_unix = $object.message.create_time 75 | $create_time_epoch = [DateTimeOffset]::FromUnixTimeSeconds($create_time_unix) 76 | $create_time = $create_time_epoch.LocalDateTime.ToString("MMMM dd, yyyy h:mm:ss tt") 77 | 78 | # Skip system messages or where the author is null 79 | $author = $object.message.author.role 80 | if ($author -eq "system" -or [string]::IsNullOrEmpty($author)) { 81 | continue 82 | } 83 | 84 | $content_parts = $object.message.content.parts 85 | $content = if ($content_parts) { $content_parts[0] } else { $null } 86 | 87 | [PSCustomObject]@{ 88 | PSTypeName = "TKchatGPT" 89 | title = $title 90 | id = $id 91 | create_time = $create_time 92 | author = $author 93 | content = $content 94 | } 95 | } 96 | } 97 | } 98 | 99 | End { 100 | # No explicit End block needed for this function 101 | } 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /reindexusersv2.psm1: -------------------------------------------------------------------------------- 1 | # Re-index SPO user profiles script 2 | # Author: Mikael Svenson - @mikaelsvenson 3 | # Blog: http://techmikael.com 4 | 5 | function Request-PnPReindexUserProfile { 6 | <# 7 | .SYNOPSIS 8 | Script to trigger re-indexing of all user profiles 9 | 10 | .Description 11 | If you perform search schema mappings after profiles exist you have to update the last modified time on a profile for it to be re-indexed. 12 | This script ensures all profiles are updated with a new time stamp. Once the import job completes allow 4-24h for profiles to be updated in search. 13 | 14 | If used in automation replace Connect-PnPOnline with somethine which works for you. 15 | 16 | A temp file will be created on the file system where you execute the command. That temp file will also be uploaded to the "Shared Documents" library in the site you pass in the -url parameter. 17 | 18 | .Parameter url 19 | The site you will use to host the import file. Can be any site you have write access to. DO NOT use the admin site. 20 | 21 | .Example 22 | Request-PnPReindexUserProfile -url https://contoso.sharepoint.com 23 | 24 | #> 25 | [CmdletBinding()] 26 | param ( 27 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)][string]$url 28 | ) 29 | 30 | begin { 31 | 32 | } 33 | 34 | process { 35 | # In case they didn't heed our warning and tried to use the Admin site 36 | $url = $url.Replace("-admin.",".") 37 | 38 | # Need the current location so we know where to save the temp file 39 | $tempPath = Get-Location 40 | 41 | # Make sure they have the PnP.PowerShell module installed 42 | $hasPnP = (Get-Module PnP.PowerShell -ListAvailable).Length 43 | if ($hasPnP -eq 0) { 44 | Write-Output "This script requires PnP PowerShell, please install it" 45 | Write-Output "Install-Module PnP.PowerShell" 46 | return 47 | } 48 | 49 | # Replace connection method as needed 50 | try { 51 | Connect-PnPOnline -Url $url -Interactive 52 | } 53 | catch { 54 | Write-Error "Could not connect to $url" 55 | Write-Error $_ 56 | return 57 | } 58 | 59 | Write-Output "Retrieving all user profiles" 60 | try { 61 | $ProfileList = Submit-PnPSearchQuery -Query '-AccountName:spofrm -AccountName:spoapp -AccountName:app@sharepoint -AccountName:spocrawler -AccountName:spocrwl -PreferredName:"Foreign Principal"' -SourceId "b09a7990-05ea-4af9-81ef-edfab16c4e31" -SelectProperties "aadobjectid", "department", "write" -All -TrimDuplicates:$false -RelevantResults -ErrorAction Stop 62 | } 63 | catch { 64 | Write-Error "Error with Submit-PnPSearchQuery" 65 | Write-Error "Please check connection and permissions to $url and try again" 66 | Write-Error $_ 67 | return 68 | } 69 | 70 | # Put the template file together 71 | $fragmentTemplate = "{{""IdName"": ""{0}"",""Department"": ""{1}""}}"; 72 | $accountFragments = @(); 73 | 74 | foreach ($Profile in $ProfileList) { 75 | $aadId = $Profile.aadobjectid + "" 76 | $dept = $Profile.department + "" 77 | if(-not [string]::IsNullOrWhiteSpace($aadId) -and $aadId -ne "00000000-0000-0000-0000-000000000000") { 78 | $accountFragments += [string]::Format($fragmentTemplate,$aadId,$dept) 79 | } 80 | } 81 | 82 | Write-Output "Found $($accountFragments.Count) profiles" 83 | $json = "{""value"":[" + ($accountFragments -join ',') + "]}" 84 | 85 | $propertyMap = @{} 86 | $propertyMap.Add("Department", "Department") 87 | 88 | $filename = "upa-batch-trigger"; 89 | $web = Get-PnPWeb 90 | $RootFolder = $web.GetFolderByServerRelativeUrl("/"); 91 | 92 | # Cleanup 93 | $FileList = $RootFolder.Files 94 | $FolderList = $RootFolder.Folders 95 | Get-PnPProperty -ClientObject $RootFolder -Property Files,Folders 96 | 97 | foreach ($File in $FileList) { 98 | if($File.Name -like "*$filename*") { 99 | Write-Output "Remove old import file" 100 | $File.DeleteObject() 101 | } 102 | } 103 | 104 | foreach ($Folder in $FolderList) { 105 | if($Folder.Name -like "*$filename*") { 106 | Write-Output "Remove old import status folder" 107 | $Folder.DeleteObject() 108 | } 109 | } 110 | 111 | Invoke-PnPQuery 112 | # End cleanup 113 | 114 | Set-Content -Path "$tempPath\$filename.txt" -value $json 115 | 116 | Write-Output "Kicking off import job - Please be patient and allow for 4-24h before profiles are updates in search.`n`nDo NOT re-run because you are impatient!" 117 | try { 118 | $job = New-PnPUPABulkImportJob -UserProfilePropertyMapping $propertyMap -IdType CloudId -IdProperty "IdName" -Folder "Shared Documents" -Path "$tempPath\$filename.txt" 119 | } 120 | catch { 121 | Write-Error "Could not upload $tempPath\$($filename.txt) to $url" 122 | Write-Error $_ 123 | return 124 | } 125 | 126 | Remove-Item -Path "$tempPath\$filename.txt" 127 | 128 | if(-not [string]::IsNullOrWhiteSpace($job)) { 129 | Write-Output "You can check the status of your job with: Get-PnPUPABulkImportStatus -JobId $($job.JobId)" 130 | $job 131 | } 132 | 133 | } 134 | 135 | end { 136 | 137 | } 138 | } 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /TKM365Commands.psm1: -------------------------------------------------------------------------------- 1 | function Get-TKPnPGraphURI { 2 | <# 3 | .Synopsis 4 | Get information from the Graph API 5 | .DESCRIPTION 6 | Get information from the Graph API. This function requires the PnP.PowerShell module be installed and you are already connected with Connect-PnPOnline. Find Graph endpoints with the Graph Explorer at https://developer.microsoft.com/en-us/graph/graph-explorer From a blog post at https://www.toddklindt.com/blog Code based on the sample at https://pnp.github.io/script-samples/graph-call-graph/README.html 7 | v1.0 - 4/15/22 8 | .EXAMPLE 9 | Get-TKPnPGraphURI -uri https://graph.microsoft.com/v1.0/me/ 10 | .EXAMPLE 11 | Get-TKPnPGraphURI -uri https://graph.microsoft.com/beta/me/transitiveMemberOf/microsoft.graph.group?$count=true | select displayName, visibility 12 | .EXAMPLE 13 | Get-TKPnPGraphURI -uri https://graph.microsoft.com/beta/users/$count 14 | The command will automatically set ConsistencyLevel = Eventual in the headers if it sees $count or $search in the URI. Alternately you can use the -ConsistencyLevel parameter to set it manually. 15 | #> 16 | [CmdletBinding()] 17 | param ( 18 | [Parameter(Mandatory=$true,HelpMessage = "URI of the Graph API Endpoint, e.g. https://graph.microsoft.com/v1.0/me/")] 19 | [ValidateNotNullOrEmpty()] 20 | [ValidatePattern("^http")] 21 | [uri]$uri, 22 | [ValidateSet("BoundedStaleness", "ConsistentPrefix", "Eventual","Session","Strong")]$ConsistencyLevel 23 | ) 24 | 25 | begin { 26 | try { 27 | # Make sure we're connected 28 | Write-Verbose "Checking for PnP Connection..." 29 | Get-PnPConnection | Out-Null 30 | } 31 | catch { 32 | throw "No Connection Found. Please connect with Connect-PnPOnline" 33 | } 34 | 35 | try { 36 | Write-Verbose "Getting PnP Access Token..." 37 | $token = Get-PnPGraphAccessToken 38 | } 39 | catch { 40 | $_ 41 | throw "Was unable to get a Graph Access Token" 42 | } 43 | # Terms that require ConsistencyLevel = eventual 44 | $TermsList = @('$count','$search') 45 | } 46 | 47 | process { 48 | try { 49 | Write-Verbose "Getting Me..." 50 | # Set the default headers 51 | $headers = @{"Authorization"="Bearer $($token)"} 52 | 53 | if ($ConsistencyLevel) { 54 | # If the user passed a ConsistencyLevel parameter, use that 55 | Write-Verbose "Setting ConsistencyLevel to $ConsistencyLevel" 56 | $headers = @{'ConsistencyLevel' = $ConsistencyLevel;"Authorization"="Bearer $($token)"} 57 | } else { 58 | # If not, see if we see one in the URI 59 | foreach ($term in $TermsList) { 60 | Write-Verbose $term 61 | if ($uri -like "*$($term)*") { 62 | Write-Verbose "Found term $($term) in URI. Adding ConsistencyLevel to Header" 63 | $headers = @{'ConsistencyLevel' = "eventual";"Authorization"="Bearer $($token)"} 64 | } 65 | 66 | } 67 | } 68 | 69 | $me = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -ContentType "application/json" 70 | } 71 | catch { 72 | $_ 73 | throw "Error found" 74 | } 75 | 76 | if($null -eq $me.value) { 77 | Write-Verbose "Collection Returned" 78 | $me 79 | } else { 80 | Write-Verbose "Single Object returned" 81 | $($me.value) 82 | } 83 | } 84 | end { 85 | 86 | } 87 | } 88 | 89 | function Get-TKPnPCurrentUser { 90 | [CmdletBinding()] 91 | param ( 92 | [Parameter(HelpMessage = "Use the Graph API Endpoint https://graph.microsoft.com/v1.0/me/ to get the Current User's information instead of the SharePoint context")] 93 | [switch]$UseGraph, 94 | [Parameter(HelpMessage = "Use the Beat /beta/ Graph Endpoint instead of the /v1.0/ Endpoint. Used with -UseGraph parameter")] 95 | [switch]$UseBetaEndpoint 96 | ) 97 | 98 | begin { 99 | 100 | } 101 | 102 | process { 103 | if ($UseGraph) { 104 | Write-Verbose "Using Graph endpoint" 105 | if ($UseBetaEndPoint) { 106 | Write-Verbose "Using Beta Endpoit" 107 | Get-TKPnPGraphURI -uri https://graph.microsoft.com/beta/me/ 108 | } else { 109 | Write-Verbose "Using v1.0 Endpoit" 110 | Get-TKPnPGraphURI -uri https://graph.microsoft.com/v1.0/me/ 111 | } 112 | 113 | } else { 114 | Write-Verbose "Using SharePoint Context" 115 | try { 116 | $ctx = Get-PnPContext -ErrorAction Stop 117 | } 118 | catch { 119 | $_ 120 | return 121 | } 122 | 123 | $ctx.Load($ctx.Web.CurrentUser) 124 | $ctx.ExecuteQuery() 125 | $CurrentUser = $ctx.Web.CurrentUser 126 | 127 | [PSCustomObject]@{ 128 | PSTypeName = 'TKPnPCurrentUser' 129 | ID = $CurrentUser.Id 130 | Title = $CurrentUser.Title 131 | LoginName = $CurrentUser.LoginName 132 | Email = $CurrentUser.Email 133 | } 134 | } 135 | 136 | } 137 | 138 | end { 139 | 140 | } 141 | } -------------------------------------------------------------------------------- /Search demonstration creation Module.psm1: -------------------------------------------------------------------------------- 1 | # For blog post https://www.toddklindt.com/blog/Lists/Posts/Post.aspx?ID=899 2 | # 7/13/2023 3 | function Add-AttorneyFiles { 4 | <# 5 | .SYNOPSIS 6 | This function creates attorney files and case folders in a SharePoint directory in Microsoft 365. 7 | 8 | .DESCRIPTION 9 | The Add-AttorneyFiles function creates a specified number of attorney files and case folders in a SharePoint directory. 10 | It can create static files, and has options to create only closed cases or only client cases. 11 | The name of the static file can be specified, and defaults to "readme.txt". 12 | 13 | .PARAMETER AttorneyCount 14 | The number of attorney files to create. This parameter is mandatory. 15 | 16 | .PARAMETER CaseCount 17 | The number of case folders to create for each attorney. This parameter is mandatory. 18 | 19 | .PARAMETER CreateStaticFile 20 | If this switch is present, a static file will be created in each case folder. 21 | 22 | .PARAMETER OnlyClosedCases 23 | If this switch is present, only closed case folders will be created. 24 | 25 | .PARAMETER OnlyClientCases 26 | If this switch is present, only client case folders will be created. 27 | 28 | .PARAMETER StaticFileName 29 | The name of the static file to create. Defaults to "readme.txt". 30 | 31 | .EXAMPLE 32 | Add-AttorneyFiles -AttorneyCount 10 -CaseCount 5 -CreateStaticFile 33 | 34 | This will create 10 attorney files, each with 5 case folders. A static file named "readme.txt" will be created in each case folder. 35 | 36 | .EXAMPLE 37 | Add-AttorneyFiles -AttorneyCount 5 -CaseCount 3 -OnlyClosedCases 38 | 39 | This will create 5 attorney files, each with 3 closed case folders. No static file will be created. 40 | 41 | .EXAMPLE 42 | Add-AttorneyFiles -AttorneyCount 7 -CaseCount 4 -CreateStaticFile -StaticFileName "myfile.txt" 43 | 44 | This will create 7 attorney files, each with 4 case folders. A static file named "myfile.txt" will be created in each case folder. 45 | #> 46 | Param( 47 | [Parameter(Mandatory=$true)] 48 | [int]$AttorneyCount, 49 | 50 | [Parameter(Mandatory=$true)] 51 | [int]$CaseCount, 52 | 53 | [Parameter(Mandatory=$false)] 54 | [switch]$CreateStaticFile, 55 | 56 | [Parameter(Mandatory=$false)] 57 | [switch]$OnlyClosedCases, 58 | 59 | [Parameter(Mandatory=$false)] 60 | [switch]$OnlyClientCases, 61 | 62 | [Parameter(Mandatory=$false)] 63 | [string]$StaticFileName = "readme.txt" 64 | ) 65 | 66 | # Check if we're connected to a SharePoint site 67 | $connection = Get-PnPConnection 68 | if ($null -eq $connection) { 69 | Write-Error "Not connected to a SharePoint site. Use Connect-PnPOnline to connect." 70 | return 71 | } 72 | 73 | $firstNames = @("John","Jane","James","Jill","Jack","Jenny","Jeff","Jasmine","Jeremy","Joan","Jacob","Julia","Joseph","Joyce","Jerry","Janet","Judith","Jose","Jean","Jocelyn", "Anna", "Brian", "Catherine", "David", "Emma", "Frank", "Grace", "Henry", "Irene", "Kyle", "Laura", "Michael", "Nina", "Oscar", "Paula", "Quincy", "Rachel", "Sam", "Tina", "Ursula", "Victor", "Wendy", "Xavier", "Yvonne", "Zach","Olivia","Ava","Sophia","Isabella","Liam","Alexander","Eric","Erik","Jakob","Mark","Marc","Gabriele","Vittoria") 74 | $lastNames = @("Smith", "Johnson", "Williams", "Jones", "Brown", "Davis", "Miller", "Wilson", "Moore", "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson","Jones","Edwards","Sullivan","Rodriguez", "Johnson","Martinez","Gregory","Burke","Lopez","Campbell","Mullin","Park") 75 | 76 | $AttorneyList = @() 77 | while ($AttorneyList.Count -lt $AttorneyCount) { 78 | Write-Host "AttorneyList Count $($AttorneyList.Count) of $AttorneyCount" 79 | $potentialName = "$($lastNames | Get-Random), $($firstNames | Get-Random)" 80 | if ($potentialName -notin $AttorneyList) { 81 | write-host "Adding $potentialName" 82 | $AttorneyList += $potentialName 83 | } 84 | } 85 | 86 | $words = (New-Object Net.WebClient).DownloadString("http://svnweb.freebsd.org/csrg/share/dict/words?view=co&content-type=text/plain").Split("`n") | Where-Object { $_ -and ($_ -cne $_.ToUpper()) } 87 | 88 | foreach ($attorney in $AttorneyList) { 89 | Write-Verbose "Shared Documents/AttorneyFiles/$attorney" 90 | $attorneyFolderPath = Resolve-PnPFolder -SiteRelativePath "Shared Documents/AttorneyFiles/$attorney" 91 | 92 | $clientFilesFolderPath = $null 93 | if(!$OnlyClosedCases) { 94 | Write-Verbose "$($attorneyFolderPath.ServerRelativeUrl)/Client Files" 95 | $clientFilesFolderPath = Resolve-PnPFolder -SiteRelativePath "/Shared Documents/AttorneyFiles/$attorney/Client Files" 96 | Write-Verbose "Client Files Folder Path: $($clientFilesFolderPath.ServerRelativeUrl)" 97 | 98 | } 99 | 100 | $closedCasesFolderPath = $null 101 | if(!$OnlyClientCases) { 102 | Write-Verbose "$($attorneyFolderPath.ServerRelativeUrl)/Closed Cases" 103 | $closedCasesFolderPath = Resolve-PnPFolder -SiteRelativePath "/Shared Documents/AttorneyFiles/$attorney/Closed Cases" 104 | Write-Verbose "Closed Cases Folder Path: $($closedCasesFolderPath.ServerRelativeUrl)" 105 | 106 | } 107 | 108 | for ($i=0; $i -lt $CaseCount; $i++) { 109 | write-host "Case Count: $i of $CaseCount to $($attorney)" 110 | $randomWords = for ($j=0; $j -lt 200; $j++) { 111 | $words | Get-Random 112 | } 113 | 114 | if($CreateStaticFile) { 115 | $randomWords -join ' ' | Set-Content -Path $StaticFileName 116 | } 117 | 118 | $caseName = "$($lastNames | Get-Random), $($firstNames | Get-Random) - $(Get-Random -Minimum 100000 -Maximum 999999)" 119 | 120 | if ((Get-Random -Minimum 0 -Maximum 2) -eq 0 -and !$OnlyClientCases) { 121 | Write-Verbose "Closed Case" 122 | Add-PnPFolder -Folder $closedCasesFolderPath -Name $caseName 123 | if($CreateStaticFile) { 124 | Add-PnPFile -Path $StaticFileName -Folder "Shared Documents/AttorneyFiles/$($attorney)/Closed Cases/$($caseName)" 125 | } 126 | } elseif(!$OnlyClosedCases) { 127 | Write-Verbose "Client File" 128 | Add-PnPFolder -Folder $clientFilesFolderPath -Name $caseName 129 | if($CreateStaticFile) { 130 | Add-PnPFile -Path $StaticFileName -Folder "Shared Documents/AttorneyFiles/$($attorney)/Client Files/$($caseName)" 131 | } 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /TKAzureExtensionProperty.psm1: -------------------------------------------------------------------------------- 1 | function Get-TKAzureApplicationRegistration { 2 | <# 3 | .Synopsis 4 | Lists the Application Registrations in the tenant. 5 | .DESCRIPTION 6 | Lists the Application Registrations in the tenant. This function requires the PnP.PowerShell module. It also requires you to connect to your tenant with Connect-PnPOnline before you run it. It uses the https://graph.microsoft.com/v1.0/applications Graph Endpoint. 7 | .EXAMPLE 8 | Get-TKAzureApplicationRegistration 9 | 10 | Lists all the Application Registrations 11 | 12 | .EXAMPLE 13 | Get-TKAzureApplicationRegistration | Format-List 14 | 15 | Lists the Application Registrations and shows their id and appId properties. 16 | #> 17 | [CmdletBinding()] 18 | param ( 19 | 20 | ) 21 | 22 | begin { 23 | 24 | } 25 | 26 | process { 27 | $AppRegList = (Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/applications).value 28 | Write-Verbose "Getting App Reg List" 29 | foreach ($AppReg in $AppRegList) { 30 | Write-Verbose "Getting $($AppReg.displayName) " 31 | [PSCustomObject]@{ 32 | PSTypeName = 'TKAzureApplicationRegistration' 33 | displayName = $AppReg.displayName 34 | id = $AppReg.id 35 | appId =$AppReg.appId 36 | } 37 | } 38 | } 39 | 40 | end { 41 | 42 | } 43 | } 44 | 45 | function Get-TKAzureApplicationRegistrationExtensionProperty { 46 | <# 47 | .Synopsis 48 | Lists the Extended Properties for a given Azure App Reg 49 | .DESCRIPTION 50 | Lists the Extended Properties for a given Azure App Reg. This function requires the PnP.PowerShell module. It also requires you to connect to your tenant with Connect-PnPOnline before you run it. It uses the https://graph.microsoft.com/v1.0/applications Graph Endpoint. 51 | .EXAMPLE 52 | Get-TKAzureApplicationRegistration | Format-List 53 | 54 | displayName : ToddsExtensionAttributes 55 | id : 058e677c-ff88-439f-9f7f-0bf864af90bf 56 | appId : f930f377-6804-4868-b4d9-9e74b4a3031c 57 | 58 | Get-TKAzureApplicationRegistrationExtensionProperty -id 058e677c-ff88-439f-9f7f-0bf864af90bf 59 | 60 | name appDisplayName dataType targetObjects 61 | ---- -------------- -------- ------------- 62 | extension_f930f37768044868b4d99e74b4a3031c_Test02withOBJs String {User} 63 | extension_f930f37768044868b4d99e74b4a3031c_Test01 String {User} 64 | 65 | Lists all the Extension Properties for the App Reg called ToddsExtensionAttributes 66 | 67 | #> 68 | [CmdletBinding()] 69 | param ( 70 | # id Property of the Application Registration 71 | [Parameter(Mandatory=$true)]$id # The id of the App Registration 72 | ) 73 | 74 | begin { 75 | 76 | } 77 | 78 | process { 79 | $ExtensionPropertyList = (Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/applications/$id/extensionProperties).value 80 | 81 | foreach ($ExtensionProperty in $ExtensionPropertyList) { 82 | [PSCustomObject]@{ 83 | PSTypeName = 'TKAzureApplicationRegistrationExtensionProperty' 84 | name = $ExtensionProperty.name 85 | appDisplayName = $ExtensionProperty.appDisplayName 86 | dataType = $ExtensionProperty.dataType 87 | targetObjects = $ExtensionProperty.targetObjects 88 | } 89 | } 90 | 91 | 92 | } 93 | 94 | end { 95 | 96 | } 97 | } 98 | 99 | function Add-TKAzureApplicationRegistrationExtensionProperty { 100 | <# 101 | .Synopsis 102 | Adds an Extension Property to an Application Registration. 103 | .DESCRIPTION 104 | Adds an Extension Property to an Application Registration. This function requires the PnP.PowerShell module. It also requires you to connect to your tenant with Connect-PnPOnline before you run it. It uses the https://graph.microsoft.com/v1.0/applications/$id/extensionProperties Graph Endpoint. 105 | .EXAMPLE 106 | $id = (Get-TKAzureApplicationRegistration | Where-Object -Property displayName -EQ -Value "ToddsExtensionAttributes").id 107 | Add-TKAzureApplicationRegistrationExtensionProperty -Name "TestExtensionProperty" -DataType String -TargetObjects User -id $id 108 | 109 | Adds an Extension Property called "TestExtionsionProperty" with the data type of String to the App Reg whose name is "ToddsExtensionAttributes" 110 | 111 | #> 112 | [CmdletBinding()] 113 | param ( 114 | # The name of the Extension Property you want to create 115 | [Parameter(Mandatory,Position=0)][string]$Name, 116 | # The type of content you want to store in the property 117 | [Parameter(Mandatory,Position=1)][ValidateSet("Binary", "Boolean", "DateTime","Integer","LargeInteger","String")][string]$DataType, 118 | # The class of Azure AD Object want to be able to assign this property to 119 | [Parameter(Mandatory,Position=2)][ValidateSet("User", "group", "device","Integer","LargeInteger","String")][string]$TargetObjects, 120 | # The id (not appId) of the Azure Application Registration you want to use to store this property 121 | [Parameter(Mandatory,Position=2)][string]$id 122 | ) 123 | 124 | begin { 125 | 126 | } 127 | 128 | process { 129 | # DataTypes are Binary, Boolean, DateTime,Integer, LargeInteger, and String, https://learn.microsoft.com/en-us/previous-versions/azure/ad/graph/howto/azure-ad-graph-api-directory-schema-extensions 130 | # TargetObject types, https://learn.microsoft.com/en-us/graph/api/resources/directoryobject?view=graph-rest-1.0 131 | $ContentObject = [pscustomobject]@{ 132 | name = $Name 133 | dataType = $DataType 134 | targetObjects = $TargetObjects 135 | } 136 | $Content = $ContentObject | ConvertTo-Json 137 | 138 | Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/applications/$id/extensionProperties -Method Post -Content $Content 139 | 140 | } 141 | 142 | end { 143 | 144 | } 145 | } 146 | 147 | function Set-TKAzureADUserExtenstionProperty { 148 | [CmdletBinding()] 149 | param ( 150 | [Parameter(Mandatory,Position=0)][string]$UPN, 151 | [Parameter(Mandatory,Position=1)][string]$ExtensionProperty, 152 | [Parameter(Mandatory,Position=2)]$Value 153 | ) 154 | 155 | begin { 156 | 157 | } 158 | 159 | process { 160 | $UserList = (Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/users).value 161 | 162 | $User = $UserList | Where-Object -Property UserPrincipalName -EQ -Value $UPN 163 | # Check to see if User was found 164 | 165 | $UserID = $User.id 166 | 167 | $ContentObject = [pscustomobject]@{ 168 | $ExtensionProperty = $Value 169 | } 170 | [string]$Content = $ContentObject | ConvertTo-Json 171 | #$Content = '{"extension_f930f37768044868b4d99e74b4a3031c_Attorney": "true"}' 172 | Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/users/$UserID -Method Patch -Content $Content 173 | 174 | } 175 | 176 | end { 177 | 178 | } 179 | } 180 | 181 | function Remove-TKAzureADUserExtenstionProperty { 182 | [CmdletBinding()] 183 | param ( 184 | [Parameter(Mandatory,Position=0)][string]$UPN, 185 | [Parameter(Mandatory,Position=1)][string]$ExtensionProperty 186 | ) 187 | 188 | begin { 189 | 190 | } 191 | 192 | process { 193 | $UserList = (Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/users).value 194 | 195 | $User = $UserList | Where-Object -Property UserPrincipalName -EQ -Value $UPN 196 | # Check to see if User was found 197 | 198 | $UserID = $User.id 199 | 200 | $ContentObject = [pscustomobject]@{ 201 | $ExtensionProperty = $null 202 | } 203 | [string]$Content = $ContentObject | ConvertTo-Json 204 | Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/users/$UserID -Method Patch -Content $Content 205 | 206 | } 207 | 208 | end { 209 | 210 | } 211 | } 212 | 213 | function Get-TKAzureADUserExtenstionProperty { 214 | <# 215 | .Synopsis 216 | Returns all of the Extension Properties defined for a user. 217 | .DESCRIPTION 218 | Returns all of the Extension Properties defined for a user. This function requires the PnP.PowerShell module. It also requires you to connect to your tenant with Connect-PnPOnline before you run it. It uses the https://graph.microsoft.com/beta/users/ Graph Endpoint. 219 | .EXAMPLE 220 | Get-TKAzureADUserExtenstionProperty -UPN GradyA@1kgvf.onmicrosoft.com 221 | 222 | Name Value 223 | ---- ----- 224 | extension_f930f37768044868b4d99e74b4a3031c_Affirmation Please work 225 | extension_f930f37768044868b4d99e74b4a3031c_HireDate 2022-10-06T00:00:00Z 226 | 227 | #> 228 | [CmdletBinding()] 229 | param ( 230 | [Parameter(Mandatory,Position=0)][string]$UPN 231 | ) 232 | 233 | begin { 234 | 235 | } 236 | 237 | process { 238 | $UserList = (Invoke-PnPGraphMethod -Url https://graph.microsoft.com/v1.0/users).value 239 | 240 | $User = $UserList | Where-Object -Property UserPrincipalName -EQ -Value $UPN 241 | # Check to see if User was found 242 | 243 | $UserID = $User.id 244 | 245 | $UserProperties = Invoke-PnPGraphMethod -Url https://graph.microsoft.com/beta/users/$UserID 246 | $ExtensionPropertyList = $UserProperties.psobject.Properties.Where({$_.Name -like "extension*"}) 247 | 248 | foreach ($ExtensionProperty in $ExtensionPropertyList) { 249 | [PSCustomObject]@{ 250 | PSTypeName = 'TKAzureADUserExtenstionProperty' 251 | Name = $ExtensionProperty.Name 252 | Value = $ExtensionProperty.Value 253 | } 254 | } 255 | } 256 | 257 | end { 258 | 259 | } 260 | } -------------------------------------------------------------------------------- /TKOnPremCommands.psm1: -------------------------------------------------------------------------------- 1 | # Some on-prem goodies. 2 | # Originally published at https://github.com/ToddKlindt/PowerShell 3 | # Also check out https://www.toddklindt.com 4 | 5 | function Get-TKSPServiceAccount { 6 | <# 7 | .Synopsis 8 | Gets all the accounts used for services by on-prem SharePoint Server. 9 | .DESCRIPTION 10 | Gets all the accounts used for services by on-prem SharePoint Server. Must be run on a SharePoint server by a local admin in an admin PowerShell window. Most of the functional code came from https://info.summit7.us/blog/retrieve-all-sharepoint-service-accounts-with-powershell. I prettied it up some, but all the props go to Michael Wilke. 11 | .EXAMPLE 12 | Get-TKSPServiceAccount 13 | .EXAMPLE 14 | Get-TKSPServiceAccount | Sort-Object ServiceName 15 | 16 | Gets all of the service accounts and sorts them by service name. 17 | .EXAMPLE 18 | Get-TKSPServiceAccount | Export-Csv -Path .\output.csv -NoTypeInformation 19 | 20 | Gets all of the service accounts in the farm. It outputs the results into a CSV file called output.csv in the current directory. 21 | 22 | .EXAMPLE 23 | Get-TKSPServiceAccount | ConvertTo-Csv -NoTypeInformation | Tee-Object -File .\output.csv | ConvertFrom-Csv 24 | 25 | Gets all of the service accounts in the farm. It outputs the results into a CSV file called output.csv in the current directory and displays it on the screen at the same time. 26 | 27 | #> 28 | 29 | ##Requires -RunAsAdministrator 30 | [CmdletBinding()] 31 | [OutputType('TKSPSeviceAccount')] 32 | param ( 33 | 34 | ) 35 | 36 | begin { 37 | 38 | } 39 | 40 | process { 41 | # Add the SharePoint snapin if it's not already here 42 | try { 43 | Add-PSSnapin Microsoft.SharePoint.PowerShell 44 | } 45 | catch { 46 | Write-Error "Could not add Microsoft.SharePoint.PowerShell snapin" 47 | return 48 | } 49 | 50 | #Get all accounts registered as managed accounts 51 | Write-Verbose "Getting Managed Accounts" 52 | $temp = Get-SPManagedAccount 53 | foreach ($item in $temp) { 54 | write-verbose $item.Username 55 | $TempItem = [PSCustomObject]@{ 56 | PSTypeName = 'TKSPServiceAccount' 57 | ServiceName = 'Managed Account' 58 | UserName = $item.Username 59 | } 60 | $TempItem 61 | } 62 | 63 | #Get Application Pool Accounts 64 | Write-Verbose "Getting SharePoint Application Pool Accounts" 65 | # First get web application app pool accounts 66 | $temp = Get-SPWebApplication -IncludeCentralAdministration | Select-Object -expand applicationpool | Select-Object name , username 67 | 68 | # Then add the Service app app pool accounts 69 | $temp += $(Get-SPServiceApplicationPool | Select-Object name, @{Name = "Username"; Expression = { $_.ProcessAccountName } }) 70 | 71 | foreach ($item in $temp) { 72 | Write-Verbose "$($item.name) - $($item.Username)" 73 | $TempItem = [PSCustomObject]@{ 74 | PSTypeName = 'TKSPServiceAccount' 75 | ServiceName = $item.name 76 | UserName = $item.Username 77 | } 78 | $TempItem 79 | } 80 | 81 | #Get all accounts running service applications 82 | Write-Verbose "Getting SharePoint Service Application Accounts" 83 | $temp = Get-SPServiceApplication | Select-Object DisplayName, applicationpool -expand applicationpool -EA 0 | Select-Object -Unique 84 | foreach ($item in $temp) { 85 | Write-Verbose "$($item.DisplayName) - $($item.ProcessAccountName)" 86 | $TempItem = [PSCustomObject]@{ 87 | PSTypeName = 'TKSPServiceAccount' 88 | ServiceName = $item.DisplayName 89 | UserName = $item.ProcessAccountName 90 | } 91 | $TempItem 92 | } 93 | 94 | #Get User Profile sync account 95 | Write-Verbose "Getting SharePoint User Profile Sync Account" 96 | $caWebApp = [Microsoft.SharePoint.Administration.SPAdministrationWebApplication]::Local 97 | $configManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager( $(Get-SPServiceContext $caWebApp.Sites[0].Url)) 98 | $temp = $configManager | Select-Object -expand connectionmanager | Select-Object AccountUserName 99 | foreach ($item in $temp) { 100 | Write-Verbose $item.AccountUsername 101 | $TempItem = [PSCustomObject]@{ 102 | PSTypeName = 'TKSPServiceAccount' 103 | ServiceName = 'User Profile Sync Account' 104 | UserName = $item.AccountUsername 105 | } 106 | $TempItem 107 | } 108 | 109 | $temp = Get-SPServiceInstance | Select-Object -expand service | ForEach-Object { if ( $_.ProcessIdentity -and $_.ProcessIdentity.GetType() -eq "String") { Select-Object -InputObject $_ -Property TypeName, @{Name = "UserName"; Expression = { $_.ProcessIdentity } } } elseif ($_.TypeName -eq "SharePoint Server Search") { Select-Object -InputObject $_ -Property TypeName, @{Name = "UserName"; Expression = { $_.ProcessIdentity } } } elseif ( $_.ProcessIdentity ) { Select-Object -InputObject $_ -Property TypeName, @{Name = "UserName"; Expression = { $_.ProcessIdentity.UserName } } } } 110 | 111 | foreach ($item in $temp) { 112 | Write-Verbose "$($item.TypeName) - $($item.UserName)" 113 | $TempItem = [PSCustomObject]@{ 114 | PSTypeName = 'TKSPServiceAccount' 115 | ServiceName = $item.TypeName 116 | UserName = $item.Username 117 | } 118 | $TempItem 119 | } 120 | 121 | #Get Services accounts 122 | Write-Verbose "Getting Accounts Running SharePoint Services" 123 | $temp = Get-WmiObject -Query "select * from win32_service where name LIKE 'SP%v4'" | Select-Object name, startname -Unique 124 | foreach ($item in $temp) { 125 | Write-Verbose "$($item.name) - $($item.Startname)" 126 | $TempItem = [PSCustomObject]@{ 127 | PSTypeName = 'TKSPServiceAccount' 128 | ServiceName = $item.name 129 | UserName = $item.Startname 130 | } 131 | $TempItem 132 | } 133 | 134 | $temp = Get-WmiObject -Query "select * from win32_service where name LIKE '%15'" | Select-Object name, startname -Unique 135 | foreach ($item in $temp) { 136 | Write-Verbose "$($item.name) - $($item.Startname)" 137 | $TempItem = [PSCustomObject]@{ 138 | PSTypeName = 'TKSPServiceAccount' 139 | ServiceName = $item.name 140 | UserName = $item.Startname 141 | } 142 | $TempItem 143 | } 144 | 145 | $temp = Get-WmiObject -Query "select * from win32_service where name LIKE 'FIM%'" | Select-Object name, startname 146 | foreach ($item in $temp) { 147 | Write-Verbose "$($item.name) - $($item.Startname)" 148 | $TempItem = [PSCustomObject]@{ 149 | PSTypeName = 'TKSPServiceAccount' 150 | ServiceName = $item.name 151 | UserName = $item.Startname 152 | } 153 | $TempItem 154 | } 155 | 156 | #Get Object Cache accounts 157 | Write-Verbose "Getting SharePoint Object Cache Accounts" 158 | $temp = Get-SPWebApplication | ForEach-Object { $_.Properties["portalsuperuseraccount"] } 159 | if (-not [string]::IsNullOrWhiteSpace($temp)) { 160 | foreach ($item in $temp) { 161 | Write-Verbose "portalsuperuseraccount - $($item)" 162 | $TempItem = [PSCustomObject]@{ 163 | PSTypeName = 'TKSPServiceAccount' 164 | ServiceName = 'portalsuperuseraccount' 165 | UserName = $item 166 | } 167 | } 168 | } 169 | 170 | $temp = Get-SPWebApplication | ForEach-Object { $_.Properties["portalsuperreaderaccount"] } 171 | if (-not [string]::IsNullOrWhiteSpace($temp)) { 172 | foreach ($item in $temp) { 173 | Write-Verbose "portalsuperreaderaccount - $($item)" 174 | $TempItem = [PSCustomObject]@{ 175 | PSTypeName = 'TKSPServiceAccount' 176 | ServiceName = 'portalsuperreaderaccount' 177 | UserName = $item 178 | } 179 | $TempItem 180 | } 181 | } 182 | 183 | #Get default Search crawler account 184 | Write-Verbose "Getting SharePoint Search Crawler Account(s)" 185 | $temp = New-Object Microsoft.Office.Server.Search.Administration.content $(Get-SPEnterpriseSearchServiceApplication) | Select-Object DefaultGatheringAccount 186 | foreach ($item in $temp) { 187 | Write-Verbose $item.DefaultGatheringAccount 188 | $TempItem = [PSCustomObject]@{ 189 | PSTypeName = 'TKSPServiceAccount' 190 | ServiceName = 'Default SharePoint Search Crawler Account' 191 | UserName = $item.DefaultGatheringAccount 192 | } 193 | $TempItem 194 | } 195 | #Get all search crawler accounts from crawl rules 196 | $rules = Get-SPEnterpriseSearchCrawlRule -SearchApplication (Get-SPEnterpriseSearchServiceApplication) 197 | foreach ($rule in $rules) { 198 | Write-Verbose $item.AccountName 199 | $TempItem = [PSCustomObject]@{ 200 | PSTypeName = 'TKSPServiceAccount' 201 | ServiceName = 'SharePoint Search Crawler Account' 202 | UserName = $item.AccountName 203 | } 204 | $TempItem 205 | } 206 | 207 | #Get Unattended Accounts 208 | Write-Verbose "Getting Unattended Service Application ID Account(s)" 209 | $UnattendedAccounts = @() 210 | 211 | try { 212 | if (Get-SPVisioServiceApplication) { 213 | $svcapp = Get-SPServiceApplication | Where-Object { $_.TypeName -like "*Visio*" } 214 | $Visio = ($svcapp | Get-SPVisioExternalData).UnattendedServiceAccountApplicationID 215 | $TempItem = [PSCustomObject]@{ 216 | PSTypeName = 'TKSPServiceAccount' 217 | ServiceName = 'Viso Unattended ID Account' 218 | UserName = $Visio 219 | } 220 | $TempItem 221 | $UnattendedAccounts += $Visio 222 | } 223 | } 224 | catch { 225 | # no action needed 226 | } 227 | 228 | try { 229 | if (Get-SPExcelServiceApplication) { 230 | $Excel = (Get-SPExcelServiceApplication).UnattendedAccountApplicationID 231 | $TempItem = [PSCustomObject]@{ 232 | PSTypeName = 'TKSPServiceAccount' 233 | ServiceName = 'Excel Unattended ID Account' 234 | UserName = $Excel 235 | } 236 | $TempItem 237 | $UnattendedAccounts += $Excel 238 | } 239 | } 240 | catch { 241 | # no action needed 242 | } 243 | 244 | try { 245 | [void](Get-Command -name Get-SPPerformancePointServiceApplication -ErrorAction Stop) 246 | if (Get-SPPerformancePointServiceApplication) { 247 | $PerformancePoint = (Get-SPPerformancePointSecureDataValues -ServiceApplication $svcApp.Id).DataSourceUnattendedServiceAccount 248 | $TempItem = [PSCustomObject]@{ 249 | PSTypeName = 'TKSPServiceAccount' 250 | ServiceName = 'Performance Point Unattended ID Account' 251 | UserName = $PerformancePoint 252 | } 253 | $TempItem 254 | $UnattendedAccounts += $PerformancePoint 255 | } 256 | } 257 | catch { 258 | # no action needed 259 | } 260 | 261 | try { 262 | if (Get-PowerPivotServiceApplication) { 263 | $PowerPivot = (Get-PowerPivotServiceApplication).UnattendedAccount 264 | $TempItem = [PSCustomObject]@{ 265 | PSTypeName = 'TKSPServiceAccount' 266 | ServiceName = 'Power Pivot Unattended ID Account' 267 | UserName = $PowerPivot 268 | } 269 | $TempItem 270 | $UnattendedAccounts += $PowerPivot 271 | } 272 | } 273 | catch { 274 | # no action needed 275 | } 276 | 277 | $serviceCntx = Get-SPServiceContext -Site (Get-SPWebApplication -includecentraladministration | Where-Object { $_.IsAdministrationWebApplication } | Select-Object -ExpandProperty Url) 278 | $sssProvider = New-Object Microsoft.Office.SecureStoreService.Server.SecureStoreProvider 279 | $sssProvider.Context = $serviceCntx 280 | $marshal = [System.Runtime.InteropServices.Marshal] 281 | 282 | try { 283 | 284 | $applications = $sssProvider.GetTargetApplications() 285 | 286 | foreach ($application in $applications | Where-Object { $UnattendedAccounts -contains $_.Name }) { 287 | $sssCreds = $sssProvider.GetCredentials($application.Name) 288 | foreach ($sssCred in $sssCreds | Where-Object { $_.CredentialType -eq "WindowsUserName" -or $_.CredentialType -eq "UserName" }) { 289 | # Pretty sure this doesn't work. Need to create some Secure Store creds and test 290 | $ptr = $marshal::SecureStringToBSTR($sssCred.Credential) 291 | $str = $marshal::PtrToStringBSTR($ptr) 292 | $str + " (" + $application.Name + ")" 293 | if (-not [string]::IsNullOrWhiteSpace($str)) { 294 | $TempItem = [PSCustomObject]@{ 295 | PSTypeName = 'TKSPServiceAccount' 296 | ServiceName = 'Secure Store Account' 297 | UserName = $str 298 | } 299 | $TempItem 300 | } 301 | 302 | } 303 | } 304 | 305 | } 306 | catch { 307 | # no action needed 308 | } 309 | 310 | #Get All Farm administrators 311 | Write-Verbose "Getting Farm Administrators Group" 312 | $FarmAdministrators = Get-SPWebApplication -IncludeCentralAdministration | Where-Object IsAdministrationWebApplication | Select-Object -Expand Sites | Where-Object ServerRelativeUrl -eq "/" | Get-SPWeb | Select-Object -Expand SiteGroups | Where-Object Name -eq "Farm Administrators" | Select-Object -expand Users -Unique 313 | 314 | foreach ($FarmAdmin in $FarmAdministrators) { 315 | $TempItem = [PSCustomObject]@{ 316 | PSTypeName = 'TKSPServiceAccount' 317 | ServiceName = 'Farm Administrator' 318 | UserName = $FarmAdmin.UserLogin 319 | } 320 | $TempItem 321 | } 322 | } 323 | 324 | end { 325 | 326 | } 327 | } --------------------------------------------------------------------------------