From 25b459a1b21c952fff35ab009a4b38790c86f2f8 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Mon, 6 Mar 2017 16:32:17 -0500 Subject: [PATCH 01/65] A bunch of new scripts. --- AD/Get-DisabledUser.ps1 | 43 +++++ AD/Reset-ADLastPasswordSetTime.ps1 | 5 + Convert-RobocopyExitCode.ps1 | 22 +++ ConvertFrom-Base64Image.ps1 | 3 + ConvertTo-Base64Image.ps1 | 5 + ConvertTo-Icon.ps1 | 4 + Exchange/Get-AllMailboxSizes.ps1 | 25 +++ Exchange/Set-MSOMailboxAuditing.ps1 | 31 ++++ File Management/Archive-DuplicateFile.ps1 | 64 ++++--- File Management/Get-DirectoryLargerThan.ps1 | 64 +++++++ File Management/Get-DuplicateFile.ps1 | 34 ++++ File Management/Get-FilesModifyDate.ps1 | 19 ++ File Management/Move-EmptyFolder.ps1 | 10 ++ File Management/Move-GFRClientFiles.ps1 | 91 ++++++++++ .../Get-BitcoinPriceChart.ps1 | 0 Fun/Get-FeaturedBrew.ps1 | 14 ++ Fun/Get-Weather.ps1 | 139 +++++++++++++++ Fun/Get-XMas.ps1 | 69 ++++++++ .../Send-CatFactMessage.ps1 | 128 +++++++------- Write-Type.ps1 => Fun/Write-Type.ps1 | 96 +++++----- GUI/Create-MsgBoxWithImage.ps1 | 21 +++ GUI/Get-Directory.ps1 | 14 ++ GUI/Get-File.ps1 | 112 ++++++++++++ GUI/New-MsgBoxWithImage.ps1 | 21 +++ GUI/Prompt.ps1 | 22 +++ GUI/Read-FolderBrowserDialog.ps1 | 5 + GUI/Read-YesOrNo.ps1 | 7 + Get-Certificate.ps1 | 116 +++++++++++++ Get-MD5Checksum.ps1 | 10 ++ Get-Weather.ps1 | 110 ------------ Install-Choco.ps1 | 1 + Install-PoshSSH.ps1 | 2 + Install-PowerShellGet.ps1 | 3 + MSO/Copy-SPOList.ps1 | 66 +++++++ MSO/Get-DisabledLicensedUser.ps1 | 2 + MSO/Get-MSOLUsersWithAlias.ps1 | 14 ++ MSO/Set-License.ps1 | 95 ++++++++++ MapDrivesUtility/Invoke-MapNetworkDrive.ps1 | 164 ++++++++++++++++++ MapDrivesUtility/Map Network Drives.lnk | Bin 0 -> 1921 bytes MapDrivesUtility/Offices.csv | 4 + MapDrivesUtility/README.txt | 14 ++ MapDrivesUtility/Start-MapDrivesUtility.ps1 | 162 +++++++++++++++++ MapDrivesUtility/Translations.csv | 3 + MapDrivesUtility/invisible.vbs | 1 + MapDrivesUtility/launch.bat | 1 + Munki/Create-MunkiRepo.ps1 | 39 +++++ Munki/Get-MunkiRepoIP.ps1 | 22 +++ Munki/Set-Repo | 45 +++++ Munki/Sync-MunkiRepos.ps1 | 86 +++++++++ Munki/installmunki | 55 ++++++ Munki/softwareupdate.sh | 24 +++ Munki/updatemunkirepo | 28 +++ New-EncryptedPasswordFile.ps1 | 23 +++ Office/Enable-OutlookQuarantineFolder.ps1 | 38 ++++ Registry/Check-DotNetVersion.ps1 | 6 + Registry/Disable-UAC.ps1 | 3 + Registry/Suspend-SS.ps1 | 54 ++++++ Registry/Suspend-SSWithAuth.ps1 | 53 ++++++ Set-ScriptRoot.ps1 | 6 + Test-IsAdmin.ps1 | 8 + Test-IsIse.ps1 | 2 + Windows Updates/Get-InstalledUpdates.ps1 | 20 +++ Windows Updates/Get-MicrosoftUpdates.ps1 | Bin 0 -> 1042 bytes Windows/Get-ComputerNetInfo.ps1 | 31 ++++ Windows/Get-NTPTimeOffset.ps1 | 80 +++++++++ Windows/Get-NetSessionInfoString.ps1 | 13 ++ Windows/Test-Is64BitOs.ps1 | 8 + Windows/Test-WebPorts.ps1 | 6 + Write-CMTraceLog.ps1 | 32 ++++ 69 files changed, 2275 insertions(+), 243 deletions(-) create mode 100644 AD/Get-DisabledUser.ps1 create mode 100644 AD/Reset-ADLastPasswordSetTime.ps1 create mode 100644 Convert-RobocopyExitCode.ps1 create mode 100644 ConvertFrom-Base64Image.ps1 create mode 100644 ConvertTo-Base64Image.ps1 create mode 100644 ConvertTo-Icon.ps1 create mode 100644 Exchange/Get-AllMailboxSizes.ps1 create mode 100644 Exchange/Set-MSOMailboxAuditing.ps1 create mode 100644 File Management/Get-DirectoryLargerThan.ps1 create mode 100644 File Management/Get-DuplicateFile.ps1 create mode 100644 File Management/Get-FilesModifyDate.ps1 create mode 100644 File Management/Move-EmptyFolder.ps1 create mode 100644 File Management/Move-GFRClientFiles.ps1 rename Get-BitcoinPrice.ps1 => Fun/Get-BitcoinPriceChart.ps1 (100%) create mode 100644 Fun/Get-FeaturedBrew.ps1 create mode 100644 Fun/Get-Weather.ps1 create mode 100644 Fun/Get-XMas.ps1 rename Send-CatFactMessage.ps1 => Fun/Send-CatFactMessage.ps1 (97%) mode change 100755 => 100644 rename Write-Type.ps1 => Fun/Write-Type.ps1 (96%) create mode 100644 GUI/Create-MsgBoxWithImage.ps1 create mode 100644 GUI/Get-Directory.ps1 create mode 100644 GUI/Get-File.ps1 create mode 100644 GUI/New-MsgBoxWithImage.ps1 create mode 100644 GUI/Prompt.ps1 create mode 100644 GUI/Read-FolderBrowserDialog.ps1 create mode 100644 GUI/Read-YesOrNo.ps1 create mode 100644 Get-Certificate.ps1 create mode 100644 Get-MD5Checksum.ps1 delete mode 100644 Get-Weather.ps1 create mode 100644 Install-Choco.ps1 create mode 100644 Install-PoshSSH.ps1 create mode 100644 Install-PowerShellGet.ps1 create mode 100644 MSO/Copy-SPOList.ps1 create mode 100644 MSO/Get-DisabledLicensedUser.ps1 create mode 100644 MSO/Get-MSOLUsersWithAlias.ps1 create mode 100644 MSO/Set-License.ps1 create mode 100644 MapDrivesUtility/Invoke-MapNetworkDrive.ps1 create mode 100644 MapDrivesUtility/Map Network Drives.lnk create mode 100644 MapDrivesUtility/Offices.csv create mode 100644 MapDrivesUtility/README.txt create mode 100644 MapDrivesUtility/Start-MapDrivesUtility.ps1 create mode 100644 MapDrivesUtility/Translations.csv create mode 100644 MapDrivesUtility/invisible.vbs create mode 100644 MapDrivesUtility/launch.bat create mode 100644 Munki/Create-MunkiRepo.ps1 create mode 100644 Munki/Get-MunkiRepoIP.ps1 create mode 100644 Munki/Set-Repo create mode 100644 Munki/Sync-MunkiRepos.ps1 create mode 100644 Munki/installmunki create mode 100644 Munki/softwareupdate.sh create mode 100644 Munki/updatemunkirepo create mode 100644 New-EncryptedPasswordFile.ps1 create mode 100644 Office/Enable-OutlookQuarantineFolder.ps1 create mode 100644 Registry/Check-DotNetVersion.ps1 create mode 100644 Registry/Disable-UAC.ps1 create mode 100644 Registry/Suspend-SS.ps1 create mode 100644 Registry/Suspend-SSWithAuth.ps1 create mode 100644 Set-ScriptRoot.ps1 create mode 100644 Test-IsAdmin.ps1 create mode 100644 Test-IsIse.ps1 create mode 100644 Windows Updates/Get-InstalledUpdates.ps1 create mode 100644 Windows Updates/Get-MicrosoftUpdates.ps1 create mode 100644 Windows/Get-ComputerNetInfo.ps1 create mode 100644 Windows/Get-NTPTimeOffset.ps1 create mode 100644 Windows/Get-NetSessionInfoString.ps1 create mode 100644 Windows/Test-Is64BitOs.ps1 create mode 100644 Windows/Test-WebPorts.ps1 create mode 100644 Write-CMTraceLog.ps1 diff --git a/AD/Get-DisabledUser.ps1 b/AD/Get-DisabledUser.ps1 new file mode 100644 index 0000000..2990c1d --- /dev/null +++ b/AD/Get-DisabledUser.ps1 @@ -0,0 +1,43 @@ +#Requires –Version 3 + +function Get-DisabledUser { + param ( + [Parameter(Mandatory = $true)] + [string]$UsersFilePath, + + [Parameter(Mandatory = $false)] + [ValidateSet('Email', 'FullName', 'UserID')] + [string]$ListType = 'Email' + ) + + begin { + Import-Module ActiveDirectory + + $Users = Get-Content -Path $UsersFilePath + $ADUsers = Get-ADUser -SearchBase 'OU=Users,DC=company,DC=LOCAL' -Filter * + $Results = @() + } + + process { + foreach ($User in $Users) { + switch ($ListType) { + 'Email' { $SearchProperty = 'UserPrincipalName' } + 'FullName' { $SearchProperty = 'Name' } + 'UserID' { $SearchProperty = 'SamAccountName' } + } + + $FoundUser = $ADUsers | Where-Object { $_.$SearchProperty -eq $User } + + $Results += New-Object -TypeName PSObject -Property @{ + SearchProperty = $User + UserPrincipalName = $FoundUser.UserPrincipalName + Name = $FoundUser.Name + SamAccountName = $FoundUser.SamAccountName + Enabled = if ($FoundUser.Enabled) { $true } else { $false } + } + } + $Results | Sort-Object Enabled | Format-Table -AutoSize + } +} + +Get-DisabledUser -UsersFilePath C:\TestUsers.txt -ListType Email \ No newline at end of file diff --git a/AD/Reset-ADLastPasswordSetTime.ps1 b/AD/Reset-ADLastPasswordSetTime.ps1 new file mode 100644 index 0000000..816fba6 --- /dev/null +++ b/AD/Reset-ADLastPasswordSetTime.ps1 @@ -0,0 +1,5 @@ +$creds = Get-Credential + +$Me = Get-ADUser -Filter * | Where-Object -Property SamAccountName -Like userid* +$Me | Set-ADUser -ChangePasswordAtLogon $true -Credential $creds +$Me | Set-ADUser -ChangePasswordAtLogon $false -Credential $creds \ No newline at end of file diff --git a/Convert-RobocopyExitCode.ps1 b/Convert-RobocopyExitCode.ps1 new file mode 100644 index 0000000..ec2a794 --- /dev/null +++ b/Convert-RobocopyExitCode.ps1 @@ -0,0 +1,22 @@ +function Convert-RobocopyExitCode ($ExitCode) { + switch ($ExitCode) { + 16 {'***FATAL ERROR***'} + 15 {'OKCOPY + FAIL + MISMATCHES + XTRA'} + 14 {'FAIL + MISMATCHES + XTRA'} + 13 {'OKCOPY + FAIL + MISMATCHES'} + 12 {'FAIL + MISMATCHES'} + 11 {'OKCOPY + FAIL + XTRA'} + 10 {'FAIL + XTRA'} + 9 {'OKCOPY + FAIL'} + 8 {'FAIL'} + 7 {'OKCOPY + MISMATCHES + XTRA'} + 6 {'MISMATCHES + XTRA'} + 5 {'OKCOPY + MISMATCHES'} + 4 {'MISMATCHES'} + 3 {'OKCOPY + XTRA'} + 2 {'XTRA'} + 1 {'OKCOPY'} + 0 {'No Change'} + default {'Unknown'} + } +} \ No newline at end of file diff --git a/ConvertFrom-Base64Image.ps1 b/ConvertFrom-Base64Image.ps1 new file mode 100644 index 0000000..83daa76 --- /dev/null +++ b/ConvertFrom-Base64Image.ps1 @@ -0,0 +1,3 @@ +$Base64Icon = '' +$IconStream = [System.Convert]::FromBase64String($Base64Icon) +$IconBMP = [System.Drawing.Image]::FromStream($IconStream) \ No newline at end of file diff --git a/ConvertTo-Base64Image.ps1 b/ConvertTo-Base64Image.ps1 new file mode 100644 index 0000000..1706d70 --- /dev/null +++ b/ConvertTo-Base64Image.ps1 @@ -0,0 +1,5 @@ +[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null +$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog +$OpenFileDialog.ShowDialog() | Out-Null +$Image = Get-Item $OpenFileDialog.FileName +[System.Convert]::ToBase64String((Get-Content $Image -Encoding Byte)) >> .\EncodedImage.txt \ No newline at end of file diff --git a/ConvertTo-Icon.ps1 b/ConvertTo-Icon.ps1 new file mode 100644 index 0000000..f234164 --- /dev/null +++ b/ConvertTo-Icon.ps1 @@ -0,0 +1,4 @@ +$IconBMP = [System.Drawing.Image]::FromStream($IconStream) +$IconBMP.MakeTransparent() +$Hicon = $IconBMP.GetHicon() +$IconBMP = [System.Drawing.Icon]::FromHandle($Hicon) \ No newline at end of file diff --git a/Exchange/Get-AllMailboxSizes.ps1 b/Exchange/Get-AllMailboxSizes.ps1 new file mode 100644 index 0000000..60454be --- /dev/null +++ b/Exchange/Get-AllMailboxSizes.ps1 @@ -0,0 +1,25 @@ +$Creds = Get-Credential + +# If you get an Access Denied message but you're a member of Exchange Online Admins, +# make sure you don't have MFA enabled +$ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $Creds -Authentication Basic -AllowRedirection + +Import-PSSession $ExchangeOnlineSession + +$LogDirectory = (New-Item -ItemType Directory "$PSScriptRoot\Logs" -Force).FullName +$Date = (Get-Date).ToString('yyyyMMdd-HHmm') +$CsvPath = "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.csv" + +Get-Mailbox -ResultSize Unlimited | + Get-MailboxStatistics | + Select-Object DisplayName, StorageLimitStatus, ItemCount, ` + @{ + name = "TotalItemSize (MB)" + expression = { [math]::Round( ` + ($_.TotalItemSize.ToString().Split("(")[1].Split(" ")[0].Replace(",","")/1MB),2) + } + } | + Sort-Object "TotalItemSize (MB)" -Descending | + Export-CSV $CsvPath -NoTypeInformation \ No newline at end of file diff --git a/Exchange/Set-MSOMailboxAuditing.ps1 b/Exchange/Set-MSOMailboxAuditing.ps1 new file mode 100644 index 0000000..9574822 --- /dev/null +++ b/Exchange/Set-MSOMailboxAuditing.ps1 @@ -0,0 +1,31 @@ +# Connect to Exchange Online +Import-Module ..\OneDrive\OneDrive.psm1 +$Creds = Get-OneDriveCredential +$ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $Creds -Authentication Basic -AllowRedirection +Import-PSSession $ExchangeOnlineSession + +# Audit options +$AuditOwnerOptions = @( + 'Create', 'HardDelete', 'MailboxLogin', 'Move', 'MoveToDeletedItems', + 'SoftDelete', 'Update' +) + +$AuditAdminOptions = @( + 'Copy', 'Create', 'FolderBind', 'HardDelete', 'MessageBind', 'Move', + 'MoveToDeletedItems', 'SendAs', 'SendOnBehalf', 'SoftDelete', 'Update' +) + +$AuditDelegateOptions = @( + 'Create', 'FolderBind', 'HardDelete', 'Move', 'MoveToDeletedItems', + 'SendAs', 'SendOnBehalf', 'SoftDelete', 'Update' +) + +# Get all users and enable auditing options for their mailboxes +Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox | + Set-Mailbox -AuditEnabled $true -AuditOwner $AuditOwnerOptions ` + -AuditAdmin $AuditAdminOptions -AuditDelegate $AuditDelegateOptions + +# Disconnect from Exchange Online +Remove-PSSession $ExchangeOnlineSession \ No newline at end of file diff --git a/File Management/Archive-DuplicateFile.ps1 b/File Management/Archive-DuplicateFile.ps1 index ab4f33f..f22bec6 100644 --- a/File Management/Archive-DuplicateFile.ps1 +++ b/File Management/Archive-DuplicateFile.ps1 @@ -31,46 +31,68 @@ function Archive-DuplicateFile { Write-Verbose "Getting all files within [$Dir]..." Get-ChildItem -Path $Dir -File -Recurse -Exclude $DupeDir -Force -ErrorAction Continue | Where-Object { + # Don't search for duplicates within the directory we're archiving to $_.FullName -notlike "$DupeDir\*" } | ForEach-Object { + Write-Host $_.FullName + # Check if the file name exists in our list if ($AllItems.ContainsKey($_.Name)) { + + # Create an object with details about the item we're about to archive $ArchiveFile = New-Object -TypeName psobject -Property @{ Name = $_.Name - Path = $_.FullName + OriginalPath = $_.FullName + ArchivePath = '' LastWriteTime = $_.LastWriteTime + Error = '' } - $CurrLastWriteTime = $AllItems.($_.Name)[0] - $CurrFullName = $AllItems.($_.Name)[1] + $PreviousLastWriteTime = $AllItems.($ArchiveFile.Name)[0] + $PreviousFullName = $AllItems.($ArchiveFile.Name)[1] # If this file is newer than what was previously found, archive old and add this to list - if ($_.LastWriteTime -gt $CurrLastWriteTime) { - Write-Verbose "[$($_.Name)] found in list already, this one is newer, archiving older copy..." + if ($ArchiveFile.LastWriteTime -gt $PreviousLastWriteTime) { + Write-Verbose "[$($ArchiveFile.Name)] found already, this one is newer, archiving [$PreviousFullName]..." - If ($PSCmdlet.ShouldProcess($CurrFullName, 'Archive Item')) { - # Archive the old item and log it - $ArchiveFile.LastWriteTime = $CurrLastWriteTime - $ArchiveFile.Path = $CurrFullName - $ArchiveFile | Export-Csv -Path $Log -Append -NoTypeInformation - Move-Item -Path $CurrFullName -Destination $DupeDir - } + if ($PSCmdlet.ShouldProcess($PreviousFullName, 'Archive Item')) { + # Archive the old item and add the new item to the list + $ArchiveFile.ArchivePath = $PreviousFullName.Replace($Dir, $DupeDir) + try { + New-Item -Path $ArchiveFile.ArchivePath.TrimEnd($_.Name) -ItemType Directory -Force + Move-Item -Path $PreviousFullName -Destination $ArchiveFile.ArchivePath -Force + } catch { + $ArchiveFile.Error = $_.Exception.Message + } + $ArchiveFile.LastWriteTime = $PreviousLastWriteTime + $ArchiveFile.OriginalPath = $PreviousFullName - # Add our new item to the list - $CurrLastWriteTime = $_.LastWriteTime - $CurrFullName = $_.FullName + # Update what we have in our list + $AllItems.($ArchiveFile.Name)[0] = $PreviousLastWriteTime + $AllItems.($ArchiveFile.Name)[1] = $PreviousFullName + } # If this file is older than what was previously found, archive it } else { - Write-Verbose "[$($_.Name)] found in list already, this one has older date, archiving..." - If ($PSCmdlet.ShouldProcess($_.FullName, 'Archive Item')) { - # Archive this item and log it - $ArchiveFile | Export-Csv -Path $Log -Append -NoTypeInformation - Move-Item -Path $_.FullName -Destination $DupeDir + Write-Verbose "[$($ArchiveFile.Name)] found already, this one is older, archiving [$($_.FullName)]..." + + if ($PSCmdlet.ShouldProcess($_.FullName, 'Archive Item')) { + # Archive this item + $ArchiveFile.ArchivePath = $_.FullName.Replace($Dir, $DupeDir) + try { + New-Item -Path $ArchiveFile.ArchivePath.TrimEnd($_.Name) -ItemType Directory -Force + Move-Item -Path $_.FullName -Destination $ArchiveFile.ArchivePath -Force + } catch { + $ArchiveFile.Error = $_.Exception.Message + } } } + + # Log it + $ArchiveFile | Export-Csv -Path $Log -Append -NoTypeInformation + } else { - Write-Verbose "Adding [$($_.Name)] to list..." + # Item is unique so add it to the list $AllItems.Add($_.Name, @($_.LastWriteTime, $_.FullName)) } } diff --git a/File Management/Get-DirectoryLargerThan.ps1 b/File Management/Get-DirectoryLargerThan.ps1 new file mode 100644 index 0000000..148d42c --- /dev/null +++ b/File Management/Get-DirectoryLargerThan.ps1 @@ -0,0 +1,64 @@ +begin { + $RootDirectory = '\\server\users' + [long] $Script:Threshold = 5GB + + function Format-Size { + param ( + [Parameter(Mandatory = $true)] + [long] $Size + ) + + if ($Threshold -gt 1GB) { + "{0:N2}" -f ($Size / 1GB) + ' GB' + } else { + "{0:N2}" -f ($Size) + ' MB' + } + } + + function Measure-Content { + param ( + [Parameter(Mandatory = $true)] + [string] $DirectoryPath + ) + + $Contents = Get-ChildItem $DirectoryPath -Recurse -Force -ErrorAction Continue | Where-Object { $_.PSIsContainer -eq $false } + [long] ($Contents | Measure-Object -Property Length -Sum | Select-Object Sum).Sum + } + + + function Measure-Directory { + param ( + [Parameter(Mandatory = $true)] + [string] $RootDirectory + ) + + Write-Output "Measuring children of $RootDirectory`n`n" + + $LargeDirectories = @() + + Get-ChildItem $RootDirectory | Where-Object { $_.PSIsContainer -eq $true } | ForEach-Object { + Write-Output "Measuring $_" + + [long] $Size = Measure-Content $_.FullName + + if ($Size -gt $Threshold) { + $LargeDirectories += New-Object psobject -Property @{ + Size = Format-Size $Size + Path = $_.FullName + Directory = $_.Name + } + } + } + + $Script:LargeDirectories + } +} + +process { + Start-Transcript ".\logs\DirectoryLargerThan$(Format-Size $Threshold)-$(Get-Date -Format yyyy-MM-dd-HHmm).txt" + Measure-Directory $RootDirectory + Write-Output "`n`n`n`nThere are $($Results.Count) directories larger than $(Format-Size $Threshold)`n" + $LargeDirectories | Format-Table -AutoSize +} + +end { Stop-Transcript } \ No newline at end of file diff --git a/File Management/Get-DuplicateFile.ps1 b/File Management/Get-DuplicateFile.ps1 new file mode 100644 index 0000000..c5c21c0 --- /dev/null +++ b/File Management/Get-DuplicateFile.ps1 @@ -0,0 +1,34 @@ +function Get-DuplicateFile { + [CmdletBinding(SupportsShouldProcess = $true)] + Param ( + [Parameter( + Mandatory = $false, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateScript({ + if (Test-Path -Path $_ -PathType Container) { + $true + } else { + throw "[$_] is not a valid directory." + $false + } + })] + [string[]]$Directory = '.' + ) + + begin { + $AllItems = @() + } + + process { + foreach ($Dir in $Directory) { + Write-Verbose "Getting all files within [$Dir]..." + $AllItems += Get-ChildItem -Path $Dir -File -Recurse -Force -ErrorAction Continue + } + + $AllItems | Group-Object -Property Name | Where-Object { $_.Count -gt 1 } | ForEach-Object { + $_.Group | ForEach-Object { $_ } + } + } +} \ No newline at end of file diff --git a/File Management/Get-FilesModifyDate.ps1 b/File Management/Get-FilesModifyDate.ps1 new file mode 100644 index 0000000..292ecad --- /dev/null +++ b/File Management/Get-FilesModifyDate.ps1 @@ -0,0 +1,19 @@ +# Get the path this script is running from +$ScriptPath = Split-Path $MyInvocation.MyCommand.Path -Parent + +# Array to store our info in +$Output = @() + +# Servers we're checking +$Servers = @('server01', 'server02') + +# Path we're checking +$Path = 'ShareName' + +# Loop through each server +ForEach ($Server in $Servers) { + $Output += Get-ChildItem "\\$Server\$Path" -Recurse | Where { $_.Extension -eq '.bak' } +} + +# Create csv at script root with output +$Output | select FullName, LastWriteTime | Export-Csv "$ScriptPath\Backups.csv" -NoTypeInformation \ No newline at end of file diff --git a/File Management/Move-EmptyFolder.ps1 b/File Management/Move-EmptyFolder.ps1 new file mode 100644 index 0000000..2f86881 --- /dev/null +++ b/File Management/Move-EmptyFolder.ps1 @@ -0,0 +1,10 @@ +$Folders = Get-ChildItem '\\server\users' + +$EmptyDir = New-Item -ItemType Directory '.\empty' -Force + +foreach ($Folder in $Folders) { + if ((Get-ChildItem $Folder.FullName).Count -eq 0) { + Write-Host "moving: $Folder" + Move-Item $Folder.FullName $EmptyDir + } +} \ No newline at end of file diff --git a/File Management/Move-GFRClientFiles.ps1 b/File Management/Move-GFRClientFiles.ps1 new file mode 100644 index 0000000..1a3a908 --- /dev/null +++ b/File Management/Move-GFRClientFiles.ps1 @@ -0,0 +1,91 @@ +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [Parameter(Mandatory = $true)] + [ValidateScript({ + # Validate the path + if (Test-Path -Path $_) { + # Validate the directory contains files + if ((Get-ChildItem -Path $_ -File).Count) { + $true + } else { + throw "Docs directory is empty" + } + } else { + throw "Invalid path given: $_" + } + })] + [string] $DocsPath, + + [Parameter(Mandatory = $true)] + [ValidateScript({ + # Validate the path + if (Test-Path -Path $_) { + # Validate the file has the .xml extension + if ((Get-Item -Path $_).Extension -eq '.xml') { + $true + } else { + throw "HitList given does not have the '.xml' extension" + } + } else { + throw "Invalid path given: $_" + } + })] + [string] $HitListPath, + + [Parameter(Mandatory = $false)] + [switch] $Copy +) + +begin { + Write-Verbose "Gathering files within $DocsPath..." + $Docs = Get-ChildItem -Path $DocsPath + $DocsCount = $Docs.Count + Write-Verbose "$DocsCount files found." + + Write-Verbose "Importing HitList from $HitListPath (this may take a while depending on the size of the file)..." + $HitList = [xml] (Get-Content -Path $HitListPath) + $HitListCount = $HitList.dcs.dc.Count + Write-Verbose "$HitListCount entries found." + + $HitListDictionary = @{} +} + +process { + $Counter = 1 + foreach ($Doc in $HitList.dcs.dc) { + $ClientName = $Doc.i1.'#cdata-section' + $FileName = "$($Doc.doc_name).$($Doc.tp)" + + Write-Progress -Activity "Processing HitList..." -Status "($Counter / $HitListCount)" ` + -PercentComplete ($Counter / $HitListCount * 100) -CurrentOperation "$ClientName - $FileName" + + $HitListDictionary.Add($FileName, $ClientName) + $Counter++ + } + + if ($Copy) { Write-Verbose "Script run in 'Copy' mode - source files will remain intact." } + + $Counter = 1 + foreach ($File in $Docs) { + try { + $Client = $HitListDictionary.($File.Name) + $ClientDirectory = "$DocsPath\$Client" + + Write-Progress -Activity "Processing Docs..." -Status "($Counter / $DocsCount)" ` + -PercentComplete ($Counter / $DocsCount * 100) -CurrentOperation $File.FullName + + if (-not (Test-Path -Path $ClientDirectory)) { + New-Item -Path $ClientDirectory -ItemType Directory + } + if ($Copy) { + Copy-Item -Path $File.FullName -Destination $ClientDirectory + } else { + Move-Item -Path $File.FullName -Destination $ClientDirectory + } + } catch { + Write-Error -Message "Error processing $($File.FullName): $($_.Exception.Message)" + } finally { + $Counter++ + } + } +} \ No newline at end of file diff --git a/Get-BitcoinPrice.ps1 b/Fun/Get-BitcoinPriceChart.ps1 similarity index 100% rename from Get-BitcoinPrice.ps1 rename to Fun/Get-BitcoinPriceChart.ps1 diff --git a/Fun/Get-FeaturedBrew.ps1 b/Fun/Get-FeaturedBrew.ps1 new file mode 100644 index 0000000..41c04b5 --- /dev/null +++ b/Fun/Get-FeaturedBrew.ps1 @@ -0,0 +1,14 @@ +# http://www.brewerydb.com/developers/docs +$BreweryDB = 'http://api.brewerydb.com/v2' +$APIKey = 'cd4f34c5b35a1a2c4c76dcad8c5253bc' +$Request = 'features' + +$Result = Invoke-RestMethod -Method Get -Uri "$BreweryDB/$Request/?key=$APIKey" -ContentType 'application/json' +$Result.data | Select-Object Brewery, Beer | ForEach-Object { + New-Object -TypeName psobject -Property @{ + BreweryName = $_.Brewery.Name + #BreweryDescription = $_.Brewery.Description + BeerName = $_.Beer.Name + #BeerDescription = $_.Beer.Description + } +} | Out-GridView diff --git a/Fun/Get-Weather.ps1 b/Fun/Get-Weather.ps1 new file mode 100644 index 0000000..72a5a31 --- /dev/null +++ b/Fun/Get-Weather.ps1 @@ -0,0 +1,139 @@ +#Requires -Version 3 + +$UtilityName = 'Archaic Weather Gathering Utility - Nick Rodriguez' + +# Default values +$City = 'Asheville' +$Country = 'United States' +$ZipCode = '28803' +$Days = '6' + +function Get-Weather { + param( + [Parameter(Mandatory = $true)] + [string]$City, + + [Parameter(Mandatory = $true)] + [string]$Country + ) + + $WebService = New-WebServiceProxy -Uri 'http://www.webservicex.net/globalweather.asmx?WSDL' + return ([xml]$WebService.GetWeather($City, $Country)).CurrentWeather +} + +function Get-Forecast { + param( + [Parameter(Mandatory = $true)] + [string]$ZipCode, + + [Parameter(Mandatory = $true)] + [int]$Days + ) + + $URI = 'http://www.weather.gov/forecasts/xml/DWMLgen/wsdl/ndfdXML.wsdl' + $Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy + $LatLonList = ([xml]$Proxy.LatLonListZipCode($ZipCode)).dwml.LatLonlist -split ',' + $Lat = $LatLonList[0] + $Lon = $LatLonList[1] + $Date = Get-Date -UFormat %Y-%m-%d + $Format = "Item24hourly" + [xml]$Weather = $Proxy.NDFDgenByDay($Lat, $Lon, $Date, $Days, 'e', $Format) + + + $Forecast = for ($Day = 0; $Day -le $Days - 1; $Day ++) { + New-Object PSObject -Property @{ + Date = ((Get-Date).AddDays($i)).ToString("MM/dd/yyyy") ; + MaxTemp = $Weather.dwml.data.parameters.temperature[0].Value[$Day] ; + MinTemp = $Weather.dwml.data.parameters.temperature[1].Value[$Day] ; + Summary = $Weather.dwml.data.parameters.weather."weather-conditions"[$Day]."Weather-summary" + } + } + + return $Forecast | Format-Table -Property Date, MaxTemp, MinTemp, Summary -AutoSize +} + +function Load-MenuSystem { + [int]$MenuLevel1 = 0 + [int]$MenuLevel2 = 0 + [boolean]$xValidSelection = $false + + while ($MenuLevel1 -lt 1 -or $MenuLevel1 -gt 3) { + Clear-Host + + # Present the Menu Options + Write-Host "`n`t$UtilityName`n" -ForegroundColor Magenta + Write-Host "`t`tPlease select an option`n" -ForegroundColor Cyan + Write-Host "`t`t`t1. Current Weather" -ForegroundColor Cyan + Write-Host "`t`t`t2. Forecast" -ForegroundColor Cyan + Write-Host "`t`t`t3. Exit`n" -ForegroundColor Cyan + # Retrieve the response from the user + [int]$MenuLevel1 = Read-Host "`t`tEnter Menu Option Number" + if ( $MenuLevel1 -lt 1 -or $MenuLevel1 -gt 3 ) { + Write-Host "`tPlease select one of the options available.`n" -ForegroundColor Red + Start-Sleep -Seconds 1 + } + } + + switch ($MenuLevel1){ # User has selected a valid entry.. load next menu + 1 { + while ($MenuLevel2 -lt 1 -or $MenuLevel2 -gt 4) { + Clear-Host + + # Present the Menu Options + Write-Host "`n`t$UtilityName`n" -ForegroundColor Magenta + Write-Host "`t`tCurrent weather for $City, $Country`n" -ForegroundColor Cyan + Write-Host "`t`t`t1. Refresh" -ForegroundColor Cyan + Write-Host "`t`t`t2. Change City" -ForegroundColor Cyan + Write-Host "`t`t`t3. Change Country" -ForegroundColor Cyan + Write-Host "`t`t`t4. Go to Main Menu`n" -ForegroundColor Cyan + [int]$MenuLevel2 = Read-Host "`t`tEnter Menu Option Number" + if( $MenuLevel2 -lt 1 -or $MenuLevel2 -gt 4 ) { + Write-Host "`tPlease select one of the options available.`n" -ForegroundColor Red + Start-Sleep -Seconds 1 + } + } + switch ($MenuLevel2) { + 1 { + Get-Weather $City $Country + pause + } + 2 { $City = Read-Host "`n`tEnter a city name" } + 3 { $Country = Read-Host "`n`tEnter a country name" } + default { break } + } + } + + 2 { + while ($MenuLevel2 -lt 1 -or $MenuLevel2 -gt 4) { + Clear-Host + + # Present the Menu Options + Write-Host "`n`t$UtilityName`n" -Fore Magenta + Write-Host "`t`t$Days day forecast for area code $ZipCode`n" -Fore Cyan + Write-Host "`t`t`t1. Refresh" -Fore Cyan + Write-Host "`t`t`t2. Change Zip Code" -Fore Cyan + Write-Host "`t`t`t3. Change Number of Days" -Fore Cyan + Write-Host "`t`t`t4. Go to Main Menu`n" -Fore Cyan + [int]$MenuLevel2 = Read-Host "`t`tEnter Menu Option Number" + } + if( $MenuLevel2 -lt 1 -or $MenuLevel2 -gt 4 ){ + Write-Host "`tPlease select one of the options available.`n" -Fore Red; Start-Sleep -Seconds 1 + } + Switch ($MenuLevel2) { + 1 { + Get-Forecast $ZipCode $Days + pause + } + 2 { $ZipCode = Read-Host "`n`tEnter a zip code" } + 3 { $Days = Read-Host "`n`tEnter the number of days to forecast" } + default { break } + } + } + + default { exit } + } + + Load-MenuSystem +} + +Load-MenuSystem \ No newline at end of file diff --git a/Fun/Get-XMas.ps1 b/Fun/Get-XMas.ps1 new file mode 100644 index 0000000..5301113 --- /dev/null +++ b/Fun/Get-XMas.ps1 @@ -0,0 +1,69 @@ +# inspired by: +# http://forums.microsoft.com/TechNet-FR/ShowPost.aspx?PostID=2555221&SiteID=45 +$notes = @' + 4A4 4A4 2A4 4A4 4A4 2A4 4A4 4C4 4F3 8G3 1A4 + 4Bb4 4Bb4 4Bb4 8Bb4 4Bb4 4A4 4A4 8A4 8A4 4A4 4G3 4G3 4A4 2G3 2C4 + 4A4 4A4 2A4 4A4 4A4 2A4 4A4 4C4 4F3 4G3 1A4 4Bb4 4Bb4 4Bb4 4Bb4 + 4Bb4 4A4 4A4 8A4 8A4 4C4 4C4 4Bb4 4G3 1F3 4C3 4A4 4G3 4F3 2C3 8C3 8C3 + 4C3 4A4 4G3 4F3 1D3 4D3 4Bb4 4A4 4G3 1E3 4C4 4C4 4Bb4 4G3 + 1A4 4C3 4A4 4G3 4F3 1C3 4C3 4A4 4G3 4F3 1D3 + 4D3 4Bb3 4A4 4G3 4C4 4C4 4C4 8C4 8C4 4D4 4C4 4Bb4 4G3 4F3 2C4 4A4 4A4 2A4 + 4A4 4A4 2A4 4A4 4C4 4C3 8G3 1A4 4Bb4 4Bb4 4Bb4 8Bb4 4Bb4 4A4 4A4 8A4 8A4 + 4A4 4G3 4G3 4A4 2G3 2C4 4A4 4A4 2A4 4A4 4A4 2A4 4A4 4C4 4F3 8G3 + 1A4 4Bb4 4Bb4 4Bb4 4Bb4 4Bb4 4A4 4A4 8A4 8A4 4C4 4C4 4Bb4 4G3 1F3 +'@ + +# Note is given by fn=f0 * (a)^n +# a is the twelth root of 2 +# n is the number of half steps from f0, positive or negative +# f0 used here is A4 at 440 Hz + +$StandardDuration = 1000 +$f0 = 440 +$a = [math]::pow(2,(1/12)) # Twelth root of 2 + +function Get-Frequency([string]$note) +{ + # n is the number of half steps from the fixed note + $null = $note -match '([A-G#]{1,2})(\d+)' + $octave = ([int] $matches[2]) - 4; + $n = $octave * 12 + ( Get-HalfSteps $matches[1] ); + $f0 * [math]::Pow($a, $n); +} + +function Get-HalfSteps([string]$note) +{ + switch($note) + { + 'A' { 0 } + 'A#' { 1 } + 'Bb' { 1 } + 'B' { 2 } + 'C' { 3 } + 'C#' { 4 } + 'Db' { 4 } + 'D' { 5 } + 'D#' { 6 } + 'Eb' { 6 } + 'E' { 7 } + 'F' { 8 } + 'F#' { 9 } + 'Gb' { 9 } + 'G' { 10 } + 'G#' { 11 } + 'Ab' { 11 } + } +} + +$notes.Split(' ') | ForEach-Object { + + if ($_ -match '(\d)(.+)') + { + $duration = $StandardDuration / ([int]$matches[1]) + $playNote = $matches[2] + $freq = Get-Frequency $playNote + + [console]::Beep( $freq, $duration) + Start-Sleep -Milliseconds 50 + } +} \ No newline at end of file diff --git a/Send-CatFactMessage.ps1 b/Fun/Send-CatFactMessage.ps1 old mode 100755 new mode 100644 similarity index 97% rename from Send-CatFactMessage.ps1 rename to Fun/Send-CatFactMessage.ps1 index 59406ac..55cb81a --- a/Send-CatFactMessage.ps1 +++ b/Fun/Send-CatFactMessage.ps1 @@ -1,34 +1,34 @@ -function Send-CatFactMessage { - <# - .SYNOPSIS - Send a cat fact to users on a computer. - .DESCRIPTION - Send a random cat fact to any number of computers and all users or a specific user. Supports credential passing. - .EXAMPLE - Send-CatFactMessage -PlayAudio - Sends cat fact message to all users on localhost and outputs fact through speakers. - .EXAMPLE - Get-ADComputer -Filter * | Send-CatFactMessage -UserName JDoe -Credential (Get-Credential) - Send cat fact to jDoe on all AD computers. Prompt user for credentials to run command with. - .EXAMPLE - Send-CatFactMessage -ComputerName pc1, pc2, pc3 - Send cat fact to all users on provided computer names. - .PARAMETER ComputerName - The computer name to execute against. Default is local computer. - .PARAMETER UserName - The name the user to display the message to. Default is all users. - .PARAMETER PlayAudio - Use Windows Speech Synthesizer to output the fact using text to speech. - .PARAMETER Credential - The credential object to execute the command with. - #> - +function Send-CatFactMessage { + <# + .SYNOPSIS + Send a cat fact to users on a computer. + .DESCRIPTION + Send a random cat fact to any number of computers and all users or a specific user. Supports credential passing. + .EXAMPLE + Send-CatFactMessage -PlayAudio + Sends cat fact message to all users on localhost and outputs fact through speakers. + .EXAMPLE + Get-ADComputer -Filter * | Send-CatFactMessage -UserName JDoe -Credential (Get-Credential) + Send cat fact to jDoe on all AD computers. Prompt user for credentials to run command with. + .EXAMPLE + Send-CatFactMessage -ComputerName pc1, pc2, pc3 + Send cat fact to all users on provided computer names. + .PARAMETER ComputerName + The computer name to execute against. Default is local computer. + .PARAMETER UserName + The name the user to display the message to. Default is all users. + .PARAMETER PlayAudio + Use Windows Speech Synthesizer to output the fact using text to speech. + .PARAMETER Credential + The credential object to execute the command with. + #> + [CmdletBinding(SupportsShouldProcess = $true)] param ( - [Parameter( - Mandatory = $false, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true + [Parameter( + Mandatory = $false, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true )] [string[]]$ComputerName = $env:COMPUTERNAME, @@ -41,39 +41,39 @@ [Parameter(Mandatory = $false)] [PSCredential]$Credential ) - - $CatFact = (ConvertFrom-Json (Invoke-WebRequest -Uri 'http://catfacts-api.appspot.com/api/facts')).facts - - if ($pscmdlet.ShouldProcess("User: $UserName, Computer: $ComputerName", "Send cat fact, $CatFact")) { - $ScriptBlock = { - param ( - [string]$UserName, - - [string]$CatFact, - - [bool]$PlayAudio = $false - ) - - msg $UserName $CatFact - - if ($PlayAudio) { - Add-Type -AssemblyName System.Speech - $SpeechSynth = New-Object System.Speech.Synthesis.SpeechSynthesizer - $SpeechSynth.Speak($CatFact) - } - } - - if ($Credential) { - Write-Verbose "Sending cat fact using credential $($Credential.UserName)" - - Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock ` - -ArgumentList $UserName, $CatFact, $PlayAudio -AsJob -Credential $Credential - } else { - Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock ` - -ArgumentList $UserName, $CatFact, $PlayAudio -AsJob - } - - Get-Job | Wait-Job | Receive-Job - Get-Job | Remove-Job - } + + $CatFact = (ConvertFrom-Json (Invoke-WebRequest -Uri 'http://catfacts-api.appspot.com/api/facts')).facts + + if ($pscmdlet.ShouldProcess("User: $UserName, Computer: $ComputerName", "Send cat fact, $CatFact")) { + $ScriptBlock = { + param ( + [string]$UserName, + + [string]$CatFact, + + [bool]$PlayAudio = $false + ) + + msg $UserName $CatFact + + if ($PlayAudio) { + Add-Type -AssemblyName System.Speech + $SpeechSynth = New-Object System.Speech.Synthesis.SpeechSynthesizer + $SpeechSynth.Speak($CatFact) + } + } + + if ($Credential) { + Write-Verbose "Sending cat fact using credential $($Credential.UserName)" + + Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock ` + -ArgumentList $UserName, $CatFact, $PlayAudio -AsJob -Credential $Credential + } else { + Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock ` + -ArgumentList $UserName, $CatFact, $PlayAudio -AsJob + } + + Get-Job | Wait-Job | Receive-Job + Get-Job | Remove-Job + } } \ No newline at end of file diff --git a/Write-Type.ps1 b/Fun/Write-Type.ps1 similarity index 96% rename from Write-Type.ps1 rename to Fun/Write-Type.ps1 index 667f84b..509376a 100644 --- a/Write-Type.ps1 +++ b/Fun/Write-Type.ps1 @@ -1,49 +1,49 @@ -function Write-Type { - <# - .Synopsis - Make Write-Host text appear as if it is being typed - - .DESCRIPTION - Input text and if desired specify the write speed (25-500 milliseconds) and foreground color for the text - - .EXAMPLE - Write-Typewriter 'Hello world!' - - .EXAMPLE - Write-Typewriter 'Hello world!' 250 - - .EXAMPLE - Write-Typewriter -Text '2 spooky 4 me!' -TypeSpeed 400 -ForegroundColor 'Red' - - .NOTES - v1.1 - 2016-04-04 - Nick Rodriguez - -Changed name - -Changed TypeSpeed range - -Added ForegroundColor param - -Changed sleep to not use method after seeing it slow performance with Measure-Command - -Changed code formatting to my liking - - v1.0 - 2016-01-25 - Nathan Kasco (http://poshcode.org/6193) - #> - - [CmdletBinding()] - [OutputType([string])] - - param ( - [Parameter(Mandatory = $true, Position = 0)] - [string] $Text, - - [Parameter(Mandatory = $false, Position = 1)] - [ValidateRange(25, 500)] - [int] $TypeSpeed = 125, - - [Parameter(Mandatory = $false, Position = 2)] - [string] $ForegroundColor = 'White' - ) - - # Pause after typing each letter - $Text.GetEnumerator() | ForEach-Object { - Write-Host $_ -NoNewline -ForegroundColor $ForegroundColor - Start-Sleep -Milliseconds $TypeSpeed - } +function Write-Type { + <# + .Synopsis + Make Write-Host text appear as if it is being typed + + .DESCRIPTION + Input text and if desired specify the write speed (25-500 milliseconds) and foreground color for the text + + .EXAMPLE + Write-Typewriter 'Hello world!' + + .EXAMPLE + Write-Typewriter 'Hello world!' 250 + + .EXAMPLE + Write-Typewriter -Text '2 spooky 4 me!' -TypeSpeed 400 -ForegroundColor 'Red' + + .NOTES + v1.1 - 2016-04-04 - Nick Rodriguez + -Changed name + -Changed TypeSpeed range + -Added ForegroundColor param + -Changed sleep to not use method after seeing it slow performance with Measure-Command + -Changed code formatting to my liking + + v1.0 - 2016-01-25 - Nathan Kasco (http://poshcode.org/6193) + #> + + [CmdletBinding()] + [OutputType([string])] + + param ( + [Parameter(Mandatory = $true, Position = 0)] + [string] $Text, + + [Parameter(Mandatory = $false, Position = 1)] + [ValidateRange(25, 500)] + [int] $TypeSpeed = 125, + + [Parameter(Mandatory = $false, Position = 2)] + [string] $ForegroundColor = 'White' + ) + + # Pause after typing each letter + $Text.GetEnumerator() | ForEach-Object { + Write-Host $_ -NoNewline -ForegroundColor $ForegroundColor + Start-Sleep -Milliseconds $TypeSpeed + } } \ No newline at end of file diff --git a/GUI/Create-MsgBoxWithImage.ps1 b/GUI/Create-MsgBoxWithImage.ps1 new file mode 100644 index 0000000..bb6f336 --- /dev/null +++ b/GUI/Create-MsgBoxWithImage.ps1 @@ -0,0 +1,21 @@ +Add-Type -AssemblyName System.Windows.Forms + +# Get the Base64 string of an image from here https://www.base64-image.de/ and paste below +$Base64Icon = '' +$IconStream = [System.IO.MemoryStream][System.Convert]::FromBase64String($Base64Icon) +$IconBMP = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($IconStream) + +$form = New-Object System.Windows.Forms.Form +$form.Text = 'Test Title' +$form.Size = '640, 480' +$form.FormBorderStyle='FixedToolWindow' +$form.StartPosition='CenterScreen' + +$image=New-Object Windows.Forms.PictureBox +$image.Size='256,256' +$image.Image = $IconBMP +$image.Location='50,50' +$form.controls.add($image) + +$form.Add_Shown({$form.Activate()}) +[void]$form.ShowDialog() \ No newline at end of file diff --git a/GUI/Get-Directory.ps1 b/GUI/Get-Directory.ps1 new file mode 100644 index 0000000..644fb0f --- /dev/null +++ b/GUI/Get-Directory.ps1 @@ -0,0 +1,14 @@ +function Get-Directory { + [CmdletBinding()] + [OutputType([psobject])] + param() + + [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null + $OpenDirectoryDialog = New-Object Windows.Forms.FolderBrowserDialog + $OpenDirectoryDialog.ShowDialog() | Out-Null + try { + Get-Item $OpenDirectoryDialog.SelectedPath + } catch { + Write-Warning 'Open Directory Dialog was closed or cancelled without selecting a Directory' + } +} \ No newline at end of file diff --git a/GUI/Get-File.ps1 b/GUI/Get-File.ps1 new file mode 100644 index 0000000..9d6af55 --- /dev/null +++ b/GUI/Get-File.ps1 @@ -0,0 +1,112 @@ +Function Get-File { + <# + .SYNOPSIS + Prompt user to select a file. + + .DESCRIPTION + + + .PARAMETER TypeName + The type of file you're prompting for. This appears in the Open File Dialog and is only used to help the user. + + .PARAMETER TypeExtension + The extension you're prompting for (e.g. "exe") + + .PARAMETER MultipleExtensions + Filter by multiple extensions. Comma separated list. + + .PARAMETER MultipleFiles + Use this to allow the user to select multiple files. + + .PARAMETER InitialDirectory + Directory the Open File Dialog will start from. + + .PARAMETER Title + Title that will appear in the Title Bar of the Open File Dialog. + + .INPUTS + None. You cannot pipe input to this function. + + .OUTPUTS + System.IO.FileSystemInfo + + .EXAMPLE + Get-File + # Prompts the user to select a file of any type + + .EXAMPLE + Get-File -TypeName 'Setup File' -TypeExtension 'msi' -InitialDirectory 'C:\Temp\Downloads' + # Prompts the user to select an msi file and begin the prompt in the C:\Temp\Downloads directory + + .EXAMPLE + Get-File -TypeName 'Log File' -MultipleExtensions 'log', 'txt' -MultipleFiles + # Prompts the user to select one or more txt or log file + + .NOTES + Created by Nick Rodriguez + + Version 1.0 - 2/26/16 + + #> + [CmdletBinding(DefaultParameterSetName = 'SingleExtension')] + [OutputType([psobject[]])] + param ( + [Parameter(Mandatory=$false, ParameterSetName = 'SingleExtension')] + [string] + $TypeName = 'All Files (*.*)', + + [Parameter(Mandatory=$false, ParameterSetName = 'SingleExtension')] + [string] + $TypeExtension = '*', + + [Parameter(Mandatory=$false, ParameterSetName = 'MultipleExtensions')] + [string[]] + $MultipleExtensions, + + [Parameter(Mandatory=$false)] + [switch] + $MultipleFiles, + + [Parameter(Mandatory=$false)] + [ValidateScript({ + if (-not (Test-Path $_ )) { + throw "The path [$_] was not found." + } else { $true } + })] + [string[]] + $InitialDirectory = $PSScriptRoot, + + [Parameter(Mandatory=$false)] + [string] + $Title = 'Select a file' + ) + + [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null + + $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog + $OpenFileDialog.Title = $Title + $OpenFileDialog.InitialDirectory = $InitialDirectory + + if ($PSCmdlet.ParameterSetName -eq 'MultipleExtensions' ) { + foreach ($Extension in $MultipleExtensions) { + $TypeExtensionName += "*.$Extension, " + $TypeExtensionFilter += "*.$Extension; " + } + $TypeExtensionName = $TypeExtensionName.TrimEnd(', ') + $TypeExtension = $TypeExtension.TrimEnd('; ') + $OpenFileDialog.Filter = "$TypeName ($TypeExtensionName)| $TypeExtensionFilter" + } else { + $OpenFileDialog.Filter = "$TypeName (*.$TypeExtension)| *.$TypeExtension" + } + + $OpenFileDialog.ShowHelp = $true + $OpenFileDialog.ShowDialog() | Out-Null + + try { + if ($MultipleFiles) { + foreach ($FileName in $OpenFileDialog.FileNames) { Get-Item $FileName } + } else { + Get-Item $OpenFileDialog.FileName + } + } catch { } # User closed the window or hit Cancel, return nothing +} \ No newline at end of file diff --git a/GUI/New-MsgBoxWithImage.ps1 b/GUI/New-MsgBoxWithImage.ps1 new file mode 100644 index 0000000..bb6f336 --- /dev/null +++ b/GUI/New-MsgBoxWithImage.ps1 @@ -0,0 +1,21 @@ +Add-Type -AssemblyName System.Windows.Forms + +# Get the Base64 string of an image from here https://www.base64-image.de/ and paste below +$Base64Icon = '' +$IconStream = [System.IO.MemoryStream][System.Convert]::FromBase64String($Base64Icon) +$IconBMP = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($IconStream) + +$form = New-Object System.Windows.Forms.Form +$form.Text = 'Test Title' +$form.Size = '640, 480' +$form.FormBorderStyle='FixedToolWindow' +$form.StartPosition='CenterScreen' + +$image=New-Object Windows.Forms.PictureBox +$image.Size='256,256' +$image.Image = $IconBMP +$image.Location='50,50' +$form.controls.add($image) + +$form.Add_Shown({$form.Activate()}) +[void]$form.ShowDialog() \ No newline at end of file diff --git a/GUI/Prompt.ps1 b/GUI/Prompt.ps1 new file mode 100644 index 0000000..8d59dbe --- /dev/null +++ b/GUI/Prompt.ps1 @@ -0,0 +1,22 @@ +# Give the user an interface to run options +Function Prompt { + $Title = "Title" + $Message = "What do you want to do?" + $Get = New-Object System.Management.Automation.Host.ChoiceDescription "&Get", "Description." + $Start = New-Object System.Management.Automation.Host.ChoiceDescription "&Start", "Description." + $Kill = New-Object System.Management.Automation.Host.ChoiceDescription "&Kill", "Description." + $Exit = New-Object System.Management.Automation.Host.ChoiceDescription "&Exit", "Exits this utility." + $Options = [System.Management.Automation.Host.ChoiceDescription[]]($Get, $Start, $Kill, $Exit) + $Result = $Host.UI.PromptForChoice($Title, $Message, $Options, 0) + + Switch ($Result) { + 0 {'Getting services...'; break} + 1 {'Starting services...'; break} + 2 {'Stopping Services...'; break} + 3 {'Exiting...'; exit} + } + + Prompt +} + +Prompt \ No newline at end of file diff --git a/GUI/Read-FolderBrowserDialog.ps1 b/GUI/Read-FolderBrowserDialog.ps1 new file mode 100644 index 0000000..71f62e8 --- /dev/null +++ b/GUI/Read-FolderBrowserDialog.ps1 @@ -0,0 +1,5 @@ +function Read-FolderBrowserDialog { + $ShellApp = New-Object -ComObject Shell.Application + $Directory = $ShellApp.BrowseForFolder(0, 'Select a directory', 0, 'C:\') + if ($Directory) { return $Directory.Self.Path } else { return '' } +} \ No newline at end of file diff --git a/GUI/Read-YesOrNo.ps1 b/GUI/Read-YesOrNo.ps1 new file mode 100644 index 0000000..99d6e24 --- /dev/null +++ b/GUI/Read-YesOrNo.ps1 @@ -0,0 +1,7 @@ +function Read-YesOrNo { + param ([string] $Message) + + $Prompt = Read-Host $Message + while ('yes', 'no' -notcontains $Prompt) { $Prompt = Read-Host "Please enter either 'yes' or 'no'" } + if ($Prompt -eq 'yes') { $true } else { $false } +} \ No newline at end of file diff --git a/Get-Certificate.ps1 b/Get-Certificate.ps1 new file mode 100644 index 0000000..2dac1d4 --- /dev/null +++ b/Get-Certificate.ps1 @@ -0,0 +1,116 @@ +<# + .NOTES + =========================================================================== + Created with: SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.126 + Created on: 7/27/2016 11:37 AM + Created by: Nick Rodriguez + Filename: Get-Certificate.ps1 + =========================================================================== + .DESCRIPTION + Get certificates installed on local or remote computers + .PARAMETER Name + Computer name or IP to search. Can be an array or a piped variable. + .PARAMETER Subject + Certificate subject name to search for. By default all subjects will be returned. Wildcards supported (e.g. "*DHG*"). + .PARAMETER DaysLeft + Days left before a certificate expires. To return all certs that expire within 30 days, set this to 30. + .PARAMETER NotExpired + Use this switch to only return certificates that have not expired. +#> + +function Get-Certificate { + [cmdletbinding()] + param ( + [Parameter( + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [Alias('ComputerName', 'Computer')] + [string[]]$Name = 'localhost', + + [PSCredential]$Credential, + + [string]$Subject = '*' + ) + + DynamicParam { + # Set the dynamic parameters' name + $ParameterName = 'StorePath' + + # Create the dictionary + $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + + # Create the collection of attributes + $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] + + # Create and set the parameters' attributes + $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute + $ParameterAttribute.Mandatory = $true + $ParameterAttribute.Position = 2 + + # Add the attributes to the attributes collection + $AttributeCollection.Add($ParameterAttribute) + + # Generate and set the ValidateSet + $arrSet = Get-ChildItem -Path 'Cert:' | ForEach-Object { + $Parent = $_.Location + Get-ChildItem -Path "Cert:\$Parent" | ForEach-Object { + Write-Output "Cert:\$Parent\$($_.Name)" + } + } + + $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) + + # Add the ValidateSet to the attributes collection + $AttributeCollection.Add($ValidateSetAttribute) + + # Create and return the dynamic parameter + $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) + $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) + return $RuntimeParameterDictionary + } + + begin { + $StorePath = $PsBoundParameters[$ParameterName] + + $ScriptBlock = { + [cmdletbinding()] + param ( + [string]$StorePath, + + [string]$Subject = '*' + ) + + # Get all certs in specified store filtering by subject if provided + Get-ChildItem -Path $StorePath | Where-Object { + $_.Subject -like $Subject + } | ForEach-Object { + $ExtensionsFormatted = try { $_.Extensions.Format(1) } catch { } + $Template = try { ($_.Extensions.Format(0) -replace "(.+)?=(.+)\((.+)?", '$2')[0] } catch { } + $Days = (New-TimeSpan -End $_.NotAfter).Days + + $Cert = $_.psobject.Copy() + $Cert | Add-Member -Name ExtensionsFormatted -MemberType NoteProperty -Value $ExtensionsFormatted + $Cert | Add-Member -Name DaysUntilExpired -MemberType NoteProperty -Value $Days + $Cert | Add-Member -Name Template -MemberType NoteProperty -Value $Template + + Write-Output $Cert + } + } + } + + process { + if ($Credential) { + Write-Verbose 'Credentials provided' + Invoke-Command -ComputerName $Name -ScriptBlock $ScriptBlock -ArgumentList $StorePath, $Subject -Credential $Credential + } else { + Write-Verbose 'Credentials not provided' + Invoke-Command -ComputerName $Name -ScriptBlock $ScriptBlock -ArgumentList $StorePath, $Subject + } + } +} + +$Computers = @('server01', 'server03') + +$Computers | Get-Certificate -Credential $Creds -StorePath Cert:\LocalMachine\AuthRoot | + Select-Object FriendlyName, DaysUntilExpired, PSComputerName, Template, ExtensionsFormatted | Format-Table -AutoSize \ No newline at end of file diff --git a/Get-MD5Checksum.ps1 b/Get-MD5Checksum.ps1 new file mode 100644 index 0000000..20024d1 --- /dev/null +++ b/Get-MD5Checksum.ps1 @@ -0,0 +1,10 @@ +# If the content is a string: +$someString = "Hello World!" +$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider +$utf8 = new-object -TypeName System.Text.UTF8Encoding +$hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($someString))) + +# If the content is a file: +$someFilePath = "C:\foo.txt" +$md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider +$hash = [System.BitConverter]::ToString($md5.ComputeHash([System.IO.File]::ReadAllBytes($someFilePath))) \ No newline at end of file diff --git a/Get-Weather.ps1 b/Get-Weather.ps1 deleted file mode 100644 index 0faa0a4..0000000 --- a/Get-Weather.ps1 +++ /dev/null @@ -1,110 +0,0 @@ -$UtilityName = 'Archaic Weather Gathering Utility - Nick Rodriguez' - -# Default values -$City = 'Asheville' -$Country = 'United States' -$ZipCode = '28803' -$Days = '6' - -Function Get-Weather { - param([string]$City, [string]$Country) - $webservice = New-WebServiceProxy -Uri 'http://www.webservicex.net/globalweather.asmx?WSDL' - $CurrentWeather = ([xml]$webservice.GetWeather($City, $Country)).CurrentWeather - return $CurrentWeather -} - -Function Get-Forecast { - Param([string]$ZipCode, [int]$Days) - $URI = 'http://www.weather.gov/forecasts/xml/DWMLgen/wsdl/ndfdXML.wsdl' - $Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy - [xml]$latlon=$proxy.LatLonListZipCode($ZipCode) - $Forecast = foreach($l in $latlon) { - $a = $l.dwml.latlonlist -split "," - $lat = $a[0] - $lon = $a[1] - $sDate = get-date -UFormat %Y-%m-%d - $format = "Item24hourly" - [xml]$weather = $Proxy.NDFDgenByDay($lat,$lon,$sDate,$Days,'e',$format) - For($i = 0 ; $i -le $Days -1 ; $i ++) { - New-Object psObject -Property @{ - "Date" = ((Get-Date).addDays($i)).tostring("MM/dd/yyyy") ; - "maxTemp" = $weather.dwml.data.parameters.temperature[0].value[$i] ; - "minTemp" = $weather.dwml.data.parameters.temperature[1].value[$i] ; - "Summary" = $weather.dwml.data.parameters.weather."weather-conditions"[$i]."Weather-summary" - } - } - } - - return $Forecast | Format-Table -Property date, maxTemp, minTemp, Summary -AutoSize -} - -Function LoadMenuSystem { - [INT]$MenuLevel1=0 - [INT]$xMenuLevel2=0 - [BOOLEAN]$xValidSelection=$false - while ( $MenuLevel1 -lt 1 -or $MenuLevel1 -gt 3 ) { - CLS - #… Present the Menu Options - Write-Host "`n`t$UtilityName`n" -ForegroundColor Magenta - Write-Host "`t`tPlease select an option`n" -Fore Cyan - Write-Host "`t`t`t1. Current Weather" -Fore Cyan - Write-Host "`t`t`t2. Forecast" -Fore Cyan - Write-Host "`t`t`t3. Exit`n" -Fore Cyan - #… Retrieve the response from the user - [int]$MenuLevel1 = Read-Host "`t`tEnter Menu Option Number" - if( $MenuLevel1 -lt 1 -or $MenuLevel1 -gt 3 ) { - Write-Host "`tPlease select one of the options available.`n" -Fore Red; Start-Sleep -Seconds 1 - } - } - Switch ($MenuLevel1){ #… User has selected a valid entry.. load next menu - 1 { - while ( $xMenuLevel2 -lt 1 -or $xMenuLevel2 -gt 4 ) { - CLS - # Present the Menu Options - Write-Host "`n`t$UtilityName`n" -Fore Magenta - Write-Host "`t`tCurrent weather for $City, $Country`n" -Fore Cyan - Write-Host "`t`t`t1. Refresh" -Fore Cyan - Write-Host "`t`t`t2. Change City" -Fore Cyan - Write-Host "`t`t`t3. Change Country" -Fore Cyan - Write-Host "`t`t`t4. Go to Main Menu`n" -Fore Cyan - [int]$xMenuLevel2 = Read-Host "`t`tEnter Menu Option Number" - if( $xMenuLevel2 -lt 1 -or $xMenuLevel2 -gt 4 ) { - Write-Host "`tPlease select one of the options available.`n" -Fore Red; Start-Sleep -Seconds 1 - } - } - Switch ($xMenuLevel2) { - 1{ Get-Weather $City $Country; pause } - 2{ $City = Read-Host "`n`tEnter a city name" } - 3{ $Country = Read-Host "`n`tEnter a country name" } - default { break} - } - } - 2 { - while ( $xMenuLevel2 -lt 1 -or $xMenuLevel2 -gt 4 ) { - CLS - # Present the Menu Options - Write-Host "`n`t$UtilityName`n" -Fore Magenta - Write-Host "`t`t$Days day forecast for area code $ZipCode`n" -Fore Cyan - Write-Host "`t`t`t1. Refresh" -Fore Cyan - Write-Host "`t`t`t2. Change Zip Code" -Fore Cyan - Write-Host "`t`t`t3. Change Number of Days" -Fore Cyan - Write-Host "`t`t`t4. Go to Main Menu`n" -Fore Cyan - [int]$xMenuLevel2 = Read-Host "`t`tEnter Menu Option Number" - } - if( $xMenuLevel2 -lt 1 -or $xMenuLevel2 -gt 4 ){ - Write-Host "`tPlease select one of the options available.`n" -Fore Red; Start-Sleep -Seconds 1 - } - Switch ($xMenuLevel2) { - 1{ Get-Forecast $ZipCode $Days; pause } - 2{ $ZipCode = Read-Host "`n`tEnter a zip code" } - 3{ $Days = Read-Host "`n`tEnter the number of days to forecast" } - default { break} - } - } - default { exit } - } - - LoadMenuSystem -} - -LoadMenuSystem \ No newline at end of file diff --git a/Install-Choco.ps1 b/Install-Choco.ps1 new file mode 100644 index 0000000..2a294ce --- /dev/null +++ b/Install-Choco.ps1 @@ -0,0 +1 @@ +iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex \ No newline at end of file diff --git a/Install-PoshSSH.ps1 b/Install-PoshSSH.ps1 new file mode 100644 index 0000000..7362d42 --- /dev/null +++ b/Install-PoshSSH.ps1 @@ -0,0 +1,2 @@ +#iex (New-Object Net.WebClient).DownloadString("https://gist.github.com/darkoperator/6152630/raw/c67de4f7cd780ba367cccbc2593f38d18ce6df89/instposhsshdev") +Install-Module -Name Posh-SSH \ No newline at end of file diff --git a/Install-PowerShellGet.ps1 b/Install-PowerShellGet.ps1 new file mode 100644 index 0000000..68eca4b --- /dev/null +++ b/Install-PowerShellGet.ps1 @@ -0,0 +1,3 @@ +# Install PowerShellGet from this link - https://www.microsoft.com/en-us/download/details.aspx?id=51451 +Install-PackageProvider NuGet -Force +Import-PackageProvider NuGet -Force \ No newline at end of file diff --git a/MSO/Copy-SPOList.ps1 b/MSO/Copy-SPOList.ps1 new file mode 100644 index 0000000..8453e30 --- /dev/null +++ b/MSO/Copy-SPOList.ps1 @@ -0,0 +1,66 @@ +function Copy-SPOList { + [cmdletbinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$SiteUrl, + + [Parameter(Mandatory = $true)] + [string]$SourceListName, + + [Parameter(Mandatory = $true)] + [string]$DestinationListName + ) + + Import-Module .\OneDrive.psm1 + Import-SharePointClientComponents + + $Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl) + $Creds = Get-Credential + $SPOCreds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Creds.UserName, $Creds.Password) + $Context.Credentials = $SPOCreds + + $SourceList = $Context.Web.Lists.GetByTitle($SourceListName) + $DestinationList = $Context.Web.Lists.GetByTitle($DestinationListName) + $ItemsToCopy = $SourceList.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery()) + $Fields = $SourceList.Fields + + Write-Progress -Activity "Gathering all items from $SourceListName..." + $Context.Load($ItemsToCopy) + $Context.Load($SourceList) + $Context.Load($DestinationList) + $Context.Load($Fields) + $Context.ExecuteQuery() + + $Count = $ItemsToCopy.Count + $Counter = 0 + + foreach ($Item in $ItemsToCopy) { + $Counter++ + + $SPOItem = New-Object -TypeName psobject + $SPOItem | Add-Member -Name ID -MemberType NoteProperty -Value $Item.ID + + Write-Progress -Activity "Copying items from $SourceListName to $DestinationListName" ` + -Status "Item $Counter of $Count - $($SPOItem.ID)" -PercentComplete (($Counter / $Count) * 100) + + $ListItemInfo = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation + $NewItem = $DestinationList.AddItem($ListItemInfo) + + foreach ($Field in $Fields) { + if ( + (-Not ($Field.ReadOnlyField)) -and + (-Not ($Field.Hidden)) -and + ($Field.InternalName -ne "Attachments") -and + ($Field.InternalName -ne "ContentType") + ) { + $SPOItem | Add-Member -Name $Field.InternalName -MemberType NoteProperty -Value $Item[$Field.InternalName] + $NewItem[$Field.InternalName] = $Item[$Field.InternalName] + $NewItem.update() + } + } + + $SPOItem + + $Context.ExecuteQuery() + } +} \ No newline at end of file diff --git a/MSO/Get-DisabledLicensedUser.ps1 b/MSO/Get-DisabledLicensedUser.ps1 new file mode 100644 index 0000000..08f7f87 --- /dev/null +++ b/MSO/Get-DisabledLicensedUser.ps1 @@ -0,0 +1,2 @@ +Connect-MsolService +Get-MsolUser -All -EnabledFilter DisabledOnly | Where-Object { $_.IsLicensed } \ No newline at end of file diff --git a/MSO/Get-MSOLUsersWithAlias.ps1 b/MSO/Get-MSOLUsersWithAlias.ps1 new file mode 100644 index 0000000..451faf9 --- /dev/null +++ b/MSO/Get-MSOLUsersWithAlias.ps1 @@ -0,0 +1,14 @@ +$Creds = Get-Credential +$PSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ ` + -Credential $Creds -Authentication Basic –AllowRedirection + +Import-PSSession $PSSession + +$Results = Get-Mailbox | Select-Object DisplayName, @{ + Name = 'EmailAddresses' + Expression = { ($_.EmailAddresses | Where-Object { $_ -LIKE "SMTP:*" }).TrimStart('SMTP:') } +} + +$Results | Format-Table -AutoSize + +#$Results | Export-Csv c:\temp\UsersWithAlias.csv -NoTypeInformation \ No newline at end of file diff --git a/MSO/Set-License.ps1 b/MSO/Set-License.ps1 new file mode 100644 index 0000000..c2f0ee8 --- /dev/null +++ b/MSO/Set-License.ps1 @@ -0,0 +1,95 @@ +<# +.SYNOPSIS + Grant MSO license to users + +.DESCRIPTION + +.PARAMETER xx + +.EXAMPLE + +.NOTES + Created by Nick Rodriguez + +#> + +begin { + # https://support.microsoft.com/en-us/kb/3108269 + + # Connect to MS Online + Connect-MsolService + + function Get-LicenseSkuId { + param ([string] $LicenseName) + + # Get the license name + $LicenseObj = Get-MsolAccountSku | Where-Object { + $_.SkuPartNumber -eq $LicenseName -or + $_.AccountSkuId -eq $LicenseName + } + $LicenseObj.AccountSkuId + } + + function Set-License { + [CmdletBinding(DefaultParameterSetName = 'SpecificUser')] + param ( + [Parameter(Mandatory=$true)] + [ValidateSet('All','SpecificUser','Department')] + [string] $UserSet, + + [Parameter(Mandatory=$true)] + [string] $LicenseSkuId, + + [Parameter(Mandatory=$false, ParameterSetName = 'Group')] + [ValidateSet('All','EnabledOnly','DisabledOnly')] + [string] $FilterIsLicensed, + + [Parameter(Mandatory=$false, ParameterSetName = 'SpecificUser')] + [string] $Email, + + [Parameter(Mandatory=$false, ParameterSetName = 'Group')] + [string] $Department + ) + + switch ($UserSet) { + # Give license to all users + All { $Users = Get-MSOLUser -All -EnabledFilter $FilterIsLicensed } + + # Get specific user based on email + SpecificUser { $Users = Get-MsolUser -UserPrincipalName $Email } + + # Get specific user group based on department + Department { $Users = Get-MsolUser -Department $Department -EnabledFilter $FilterIsLicensed } + } + + # Grant license to given users + $Users | Set-MsolUserLicense -AddLicenses $LicenseSkuId -Verbose + + # Verify license was granted + $Users | Select UserPrincipalName, Licenses | ForEach-Object { + Write-Host "$($_.UserPrincipalName) - " -NoNewline + + if ($_.Licenses.AccountSkuId -contains $LicenseSkuId) { + Write-Host $true -ForegroundColor Green + } else { + Write-Host $false -ForegroundColor Red + } + } + } +} + +process { + $License = Get-LicenseSkuId -LicenseName 'enterprisewithscal' + + # Add users from file + #Get-Content 'C:\users.txt' | ForEach-Object { Set-License -UserSet SpecificUser -LicenseSkuId $License -Email $_ } + + # Add single user + #Set-License -UserSet SpecificUser -LicenseSkuId $License -Email 'me@company.com' + + # Add department + #Set-License -UserSet Department -LicenseSkuId $License -Department 'IT' -FilterIsLicensed EnabledOnly + + # All enabled users + Set-License -UserSet All -LicenseSkuId $License -FilterIsLicensed EnabledOnly +} \ No newline at end of file diff --git a/MapDrivesUtility/Invoke-MapNetworkDrive.ps1 b/MapDrivesUtility/Invoke-MapNetworkDrive.ps1 new file mode 100644 index 0000000..c17a17e --- /dev/null +++ b/MapDrivesUtility/Invoke-MapNetworkDrive.ps1 @@ -0,0 +1,164 @@ +begin { + # Set $DebugMode to $true to log every map occurrence or $false to disable + $DebugMode = $false + + # Destination for error and debug logs to be copied to + $CentralLogPath = '\\server01\DriveMapError' + + # Enable debug mode if user's name is in debug user list + if ((Get-Content "$CentralLogPath\DebugUsers.txt") -contains (whoami)) { $DebugMode = $true } + + # Error flag - set to false when error occurs + $ErrorFree = $true + + Start-Transcript -Path "$env:TEMP\MapNetworkDrive.log" -Force + Write-Host "Map Network Drives v2.0" + + Add-Type -AssemblyName System.Windows.Forms + + $Network = New-Object -ComObject WScript.Network + + function Get-UserGroups { + [cmdletbinding()] + param() + + $Groups = [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups + + foreach ($Group in $Groups) { + $GroupSID = $Group.Value + $GroupName = New-Object System.Security.Principal.SecurityIdentifier($GroupSID) + $GroupDisplayName = $GroupName.Translate([System.Security.Principal.NTAccount]) + $GroupDisplayName.Value + } + } +} + +process { + # Import the csv's with our mapping rules + $Offices = Import-Csv -Path "\\server01\MapDrivesUtility\Offices.csv" + $Translations = Import-Csv -Path "\\server01\MapDrivesUtility\Translations.csv" + + # Get all groups the user is a member of that match a translated group + $GroupsFound = Get-UserGroups + $UserGroups = foreach ($GroupName in $GroupsFound) { + $Translations | ForEach-Object { if ($_.GroupName -eq $GroupName) { $GroupName } } + } + + Write-Host "$env:USERNAME is a member of the following groups: $($GroupsFound -join ', ')" + Write-Host "The following groups match a map drives translation group: [$($UserGroups -join '], [')]" + + Write-Host "Checking location criteria..." + $Locations = ($Translations | Where-Object { + $Translation = $_ + $Location = $_.Location + Write-Host "$Location..." + + # Group filter + ($Translation.GroupName -eq '' -or + ($Translation.GroupName -split ',' | ForEach-Object { + if ($UserGroups -contains $_) { + Write-Host "+++$_ is in a member group: [$($Translation.GroupName)]" + return $true + } else { + Write-Host "---not in a member group: [$($Translation.GroupName)]" + } + })) -and + + # NOT group filter + ($Translation.NotGroupName -eq '' -or + ($Translation.NotGroupName -split ',' | ForEach-Object { + if ($UserGroups -contains $_) { + Write-Host "---$_ is a member of a NOT group: [$($Translation.NotGroupName)]" + return $false + } else { + Write-Host "+++not a member of a NOT group: [$($Translation.NotGroupName)]" + $true + } + })) -and + + # UserName filter + ($Translation.UserName -eq '' -or + ($Translation.UserName -split ',' | ForEach-Object { + if ((whoami) -eq $_) { + Write-Host "+++$_ is a member user: [$($Translation.UserName)]" + return $true + } else { + Write-Host "---$(whoami) is not a member user: [$($Translation.UserName)]" + } + })) -and + + # ComputerName filter + ($Translation.ComputerName -eq '' -or + ($Translation.ComputerName -split ',' | ForEach-Object { + if ((hostname) -eq $_) { + Write-Host "+++$_ is a member computer: [$($Translation.ComputerName)]" + return $true + } else { + Write-Host "---$(hostname) is not a member computer: [$($Translation.ComputerName)]" + } + })) + }).Location + Write-Host "Mapping the following locations: [$($Locations -join '], [')]" + + # Map the drive mappings the user is a member of + foreach ($Location in $Locations) { + $Offices | Where-Object { + $_.Type -ne 'Local' -and + $_.Location -contains $Location + } | ForEach-Object { + $Location = $_.Location + $DriveLetter = "$($_.DriveLetter)`:" + $DrivePath = $_.DrivePath + + # Remove any old drive mapping + try { + Write-Host "Removing old mapping for drive for $DriveLetter..." -NoNewline + $Network.RemoveNetworkDrive($DriveLetter, $true) + Write-Host "success!" -ForegroundColor Green + } catch { + if ($_.Exception.Message -like '*This network connection does not exist.*') { + Write-Host $_.Exception.Message -ForegroundColor Yellow + } else { + Write-Host "error:`n$($_.Exception.Message)" -ForegroundColor Red + $ErrorFree = $false + } + } + + # Wait a moment before continuing + Start-Sleep -Seconds 1 + + # Map new drive + try { + Write-Host "Mapping drive for $Location ($DriveLetter to $DrivePath)..." -NoNewline + $Network.MapNetworkDrive($DriveLetter, $DrivePath) + Write-Host "success!" -ForegroundColor Green + } catch { + Write-Host "error:`n$($_.Exception.Message)" -ForegroundColor Red + $ErrorFree = $false + } + } + } +} + +end { + Write-Host "Currently mapped drives:" + $New = $true + $Network = New-Object -ComObject WScript.Network + $Network.EnumNetworkDrives() | ForEach-Object { + if ($New) { + Write-Host "$_ = " -NoNewline + $New = $false + } else { + Write-Host $_ + $New = $true + } + } + + Stop-Transcript + + if (-not $ErrorFree -or $DebugMode) { + if (-not $ErrorFree) { $Prefix = 'Error-' } + $Date = (Get-Date).ToString('yyyyMMdd-HHmm') + Copy-Item -Path "$env:TEMP\MapNetworkDrive.log" -Destination "$CentralLogPath\$Prefix$env:COMPUTERNAME-$env:USERNAME-$Date.log" + } +} \ No newline at end of file diff --git a/MapDrivesUtility/Map Network Drives.lnk b/MapDrivesUtility/Map Network Drives.lnk new file mode 100644 index 0000000000000000000000000000000000000000..3940de588f8a4caa7b8b42b3e6a3e235416ba598 GIT binary patch literal 1921 zcma)7ZERCj7=Bt^94Ojl1LtDoZgB>Jl-B+rOffAjV>Rms6d0I0Wos{6S!tJhOS}04 ze+Wk7*N8!vfMgmz{2E9Q_rowDi~legOEidznxGgB*@!VQVF91__Jejp*tvP$bMAZ2 zeZSvxTLD;VtfatclcM<=Dxq*4nYjK~+t24LGiU6wVl($Gx${NpvD9E;_?OVQ@@G@r z_QCl|EO9vAdkD+CO=|Jb&BaDI&6l_Q+6f!l;>lPlYru^;xEqtBQ!Q^eWXH}kmp&E) z@4xw}x!N&(7Hxp;XbQrX%spw z@1+Jib@UNH9kHHxn2_&eb`dfn#Dm0LL}$bMRvYI~#1}Vuz~JU%rxv_bOO2xjdDJ`5 z@<^)xi-39?yXV7;CQ1*WgZ?P+nVx0nk-@hO^e~PDk!D0|&1_{z4Wg5LjaFs2lCud) z*uiW@(M@hA-_oc@W0@uEnCTSxjnrt&$dTCGg4Jl{BRR9PE)smMF_`xOLoCis^XeX- zsviw|U-``Fnto^Usbh@eNBHvC+fVLe)yY^&I3wYR;r(sysCPKD`%?VJmtBL&D)R4@ z143@(GrE#%IqbOFI5N4W&UWakb<67q{`$0@{J8`c5>j6=dX+V)vzXI@rly0ZE}mHa zl6TK#-{6JCdp{9Pl4n#4DI0E#S-w`)STq}pI$df~OD9rYsU!mNsGc%XThmHVOJ`Gh z4_bw<7@D5Zbj9fI&@}~C1}@if1O7L4n-cJZee3)?S=DO3fQ2uR0^YWnaYLe_%mDN^ zyqHgQX`;*1tT2cQ!e-Id;svJS844NjZ`2jh8C<=7r_;X5!&-l?sJFrx@K+e3ch>>Y zdr`WlWO!aXS1hq0%O=fmrdVn{)KqFop>T5TBW*$lh8j8lL`X|9e?!^enYzo?_TY%m ze*Ea#!4cWpLNM$@n>fqpjV{$!I6AV+yE5kc(`UWaWA`iz?4S4O4^wCUzOwdY$asG2 z_o{v8|2RI~{KfR0_N(d99LnEKX;L#+9H%aL@#>l*|BhE5G`2Rx$&ZO9shK52uA@qw zyIq(SEMf}P8C9mTmn2XL2C9olVzG!f$G4=nDqIiw#ln>fr_k23i#af#-#kFTE#%h) zRUfuZScJ(A%SZ~u`1kXvx$G,Local,,Do not remove! +"City1, USA",Local,F,\\city1server01\Netvol1 +"City2, USA",Local,G,\\city2server01\Netvol1 \ No newline at end of file diff --git a/MapDrivesUtility/README.txt b/MapDrivesUtility/README.txt new file mode 100644 index 0000000..1148020 --- /dev/null +++ b/MapDrivesUtility/README.txt @@ -0,0 +1,14 @@ +Offices.csv +This file contains all office drive mapping entries. One unique entry is "", the script treats this entry special in that when selected it removes all currently mapped network drives. Here is a description of each column in the file: +Location - friendly name of the location of the drive mapping as it will appear in the drive mapping utility. +Type - 3 acceptable keywords here, Local is used for mappings that should only apply when using Map Drives Utility, Citrix is used for mappings that should only apply when the utility is invoked from a Citrix GPO, Both is used for mappings that should apply in both local and Citrix sessions. +DriveLetter - the drive letter to use in the mapping. +DrivePath - the network path to use in the mapping. + +Translations.csv +This file is used only in the context of a Citrix GPO. It uses the groups that the user is a member, groups they aren't a member of, the name of the user, and the computer the user is logged into, to get the name of a location that matches a location in the Offices.csv file and map those drives. The user must meet the criteria of all columns. Each column supports comma separated entries. Here is a description of each column in the file: +Location - location name to use in the Offices.csv file. +GroupName - name of the group the user must be a member of. +NotGroupName - name of the group the user must not be a member of. +UserName - the user must have this name. +ComputerName - the user must be logged into this computer. \ No newline at end of file diff --git a/MapDrivesUtility/Start-MapDrivesUtility.ps1 b/MapDrivesUtility/Start-MapDrivesUtility.ps1 new file mode 100644 index 0000000..095144a --- /dev/null +++ b/MapDrivesUtility/Start-MapDrivesUtility.ps1 @@ -0,0 +1,162 @@ +Add-Type -AssemblyName System.Windows.Forms + +$Network = New-Object -ComObject WScript.Network + +function Show-Error { + param([string]$Message) + + [System.Windows.Forms.MessageBox]::Show( + "$Message`n`nClick OK to exit", + 'Map Drives Utility Error', + [System.Windows.Forms.MessageBoxButtons]::OK, + [System.Windows.Forms.MessageBoxIcon]::Hand + ) | Out-Null +} + +try { + $Offices = Import-Csv "\\server01\MapDrivesUtility\Offices.csv" +} catch { + Show-Error 'You must be in an office or connected via VPN for the Map Network Drives utility to launch' + exit +} + +# Get the large logo from a base64 string +$Base64Logo = '' +$IconStream = [System.IO.MemoryStream][System.Convert]::FromBase64String($Base64Icon) +$IconBMP = [System.Drawing.Image]::FromStream($IconStream) +$Hicon = $IconBMP.GetHicon() +$IconBMP = [System.Drawing.Icon]::FromHandle($Hicon) + + +function Get-MappedDrives { + $MappedDrives = @{} + $Letter = '' + + $Network.EnumNetworkDrives() | ForEach-Object { + if ($Letter -ne '') { + $MappedDrives.Add($Letter, $_) + $Letter = '' + } else { + $Letter = $_ + } + } + + $MappedDrives +} + +function Invoke-MapNetworkDrives { + param([psobject[]]$Mappings) + + foreach ($Drive in $Mappings) { + $DriveLetter = "$($Drive.DriveLetter)`:" + $DrivePath = $Drive.DrivePath + + Write-Host "$DriveLetter to $DrivePath" + + # Map drive + try { + # Check if drive letter is already mapped + if (-not (Test-Path -Path $DriveLetter -ErrorAction Stop)) { + Write-Verbose "$DriveLetter was not mapped, mapping" + + # Map new drive if it's not in use + $Network.MapNetworkDrive($DriveLetter, $DrivePath) + } elseif ($DrivePath -ne (Get-MappedDrives).$DriveLetter) { + Write-Verbose "$DrivePath doesn't match, remapping" + + # Remove mapped drive + $Network.RemoveNetworkDrive($DriveLetter, $true) + + # Map new drive + $Network.MapNetworkDrive($DriveLetter, $DrivePath) + } else { + Write-Verbose "$DriveLetter is already mapped to $DrivePath" + # Already mapped + } + } catch { + Show-Error -Message "There was a problem mapping $DriveLetter to [$DrivePath]`n`n$($_.Exception.Message)" + } + } +} + +function Remove-NetworkDrives { + try { + $MappedDrives = Get-MappedDrives + + # Remove each mapped drive + foreach ($DriveLetter in $MappedDrives.Keys) { + $Network.RemoveNetworkDrive($DriveLetter, $true) + } + } catch { + Show-Error -Message "There was a problem removing all mapped drives`n`n$($_.Exception.Message)" + } +} + +$Form = New-Object System.Windows.Forms.Form +$Form.Text = 'Map Network Drives' +$Form.Icon = $IconBMP +$Form.SizeGripStyle = 'Hide' +$Form.FormBorderStyle = 'FixedSingle' +$Form.MaximizeBox = $false +$Form.Width = 300 +$Form.Height = 260 +$Form.BackColor = "#ffffff" + +$Logo = New-Object System.Windows.Forms.PictureBox +$Logo.Image = $LogoBMP +$Logo.Location = New-Object System.Drawing.Point(30, 0) +$logo.AutoSize = $true +$Form.Controls.Add($Logo) + +$OfficeList = New-Object System.Windows.Forms.ComboBox +$OfficeList.DropDownStyle = 'DropDownList' +$OfficeList.Items.Insert(0, 'Please select an Office') +$OfficeList.SelectedIndex = 0 +$OfficeList.Width = 200 +$OfficeList.Location = New-Object System.Drawing.Point(50, 130) +$Offices | Where-Object { $_.Type -ne 'Citrix' } | Select-Object Location -Unique | + ForEach-Object { $OfficeList.Items.Add($_.Location) | Out-Null } +$OfficeList.Add_SelectionChangeCommitted({ + if ($OfficeList.Items[0] -eq 'Please select an Office' -and + $OfficeList.SelectedItem -ne 'Please select an Office') { + $OfficeList.Items.RemoveAt(0) + } +}) +$Form.Controls.Add($OfficeList) + +$ConnectingLabel = New-Object System.Windows.Forms.Label +$ConnectingLabel.Visible = $false +$ConnectingLabel.Text = 'Connecting...' +$ConnectingLabel.Width = 250 +$ConnectingLabel.Height = 20 +$ConnectingLabel.Location = New-Object System.Drawing.Point(110, 200) +$ConnectingLabel.Font = "Microsoft Sans Serif, 8" +$Form.Controls.Add($ConnectingLabel) + +$ConnectButton = New-Object System.Windows.Forms.Button +$ConnectButton.AutoSize = $true +$ConnectButton.UseVisualStyleBackColor = $true +$ConnectButton.Location = New-Object System.Drawing.Point(60, 160) +$ConnectButton.Text = 'Connect Network Drives' +$ConnectButton.Font = 'Microsoft Sans Serif, 11' +$ConnectButton.Add_Click({ + if ($OfficeList.SelectedItem -eq '') { + Remove-NetworkDrives + } elseif ($OfficeList.SelectedItem -ne 'Please select an Office') { + # Show the connecting label + $ConnectingLabel.Visible = $true + + # Get the selected office's drive mappings + $Mappings = $Offices | Where-Object { $_.Location -eq $OfficeList.SelectedItem } + + # Launch selected product + Invoke-MapNetworkDrives $Mappings + + # Exit + $Form.Dispose() + } +}) +$Form.Controls.Add($ConnectButton) + +[void]$Form.ShowDialog() +$Form.Dispose() \ No newline at end of file diff --git a/MapDrivesUtility/Translations.csv b/MapDrivesUtility/Translations.csv new file mode 100644 index 0000000..e22a8de --- /dev/null +++ b/MapDrivesUtility/Translations.csv @@ -0,0 +1,3 @@ +Location,GroupName,NotGroupName,UserName,ComputerName +"City1, USA",company\City1 Users,,, +"City2, USA",company\City2 Users,,, \ No newline at end of file diff --git a/MapDrivesUtility/invisible.vbs b/MapDrivesUtility/invisible.vbs new file mode 100644 index 0000000..fb7cb76 --- /dev/null +++ b/MapDrivesUtility/invisible.vbs @@ -0,0 +1 @@ +CreateObject("Wscript.Shell").Run """" & WScript.Arguments(0) & """", 0, False \ No newline at end of file diff --git a/MapDrivesUtility/launch.bat b/MapDrivesUtility/launch.bat new file mode 100644 index 0000000..f83f395 --- /dev/null +++ b/MapDrivesUtility/launch.bat @@ -0,0 +1 @@ +PowerShell -WindowStyle Hidden -NoProfile -File Start-MapDrivesUtility.ps1 \ No newline at end of file diff --git a/Munki/Create-MunkiRepo.ps1 b/Munki/Create-MunkiRepo.ps1 new file mode 100644 index 0000000..afe8191 --- /dev/null +++ b/Munki/Create-MunkiRepo.ps1 @@ -0,0 +1,39 @@ +$Script = { + # Remove the port 80 binding on "Default Web Site" because we'll need that for our Munki repo + Try { + $Binding = Get-WebBinding -Port 80 -Name "Default Web Site" + Write-Verbose "Port 80 binding found on Default Web Site - removing..." + $Binding | Remove-WebBinding + } Catch { + Write-Verbose "Port 80 binding not found on Default Web Site - moving on..." + } + + # Check if the Munki repo already exists + $MunkiExists = $false + Get-Website -Name "Munki" | ForEach-Object { + If ($_.Name -eq "Munki") { $MunkiExists = $true } + } + + # Create the Munki repo if it doesn't exist + If ($MunkiExists) { + Write-Verbose "Munki repo already exists - moving on..." + } else { + Write-Verbose "Munki repo doesn't exist - creating..." + + # Create site directory + New-Item -ItemType Directory "R:\repo" -Force + + # Create the new Munki website + New-Website -Name "Munki" -PhysicalPath "R:\" + + # Set MIME types + Add-WebConfigurationProperty -PSPath IIS:\Sites\Munki -Filter system.webServer/staticContent -Name '.' -Value ` + @{ fileExtension = '.'; mimeType = 'application/octet-stream' }, # Accept all extensions + @{ fileExtension = '*'; mimeType = 'application/octet-stream' } # Accept all files + + # Allow Directory Browsing + Set-WebConfigurationProperty -PSPath IIS:\sites\Munki -Filter system.webServer/directoryBrowse -Name enabled -Value 'true' + } +} + +Invoke-Command -ComputerName server01 -ScriptBlock $Script \ No newline at end of file diff --git a/Munki/Get-MunkiRepoIP.ps1 b/Munki/Get-MunkiRepoIP.ps1 new file mode 100644 index 0000000..57afcb2 --- /dev/null +++ b/Munki/Get-MunkiRepoIP.ps1 @@ -0,0 +1,22 @@ +# List of servers with Munki repos setup +$Servers = (Get-ADGroupMember -Identity MunkiRepos).Name + +$Creds = Get-Credential + +foreach ($Computer in $Servers) { + if(Test-Connection -ComputerName $Computer -Count 1 -ea 0) { + try { + $Networks = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $Computer -EA Stop -Credential $Creds | ? {$_.IPEnabled} + } catch { + Write-Warning "Error occurred while querying $Computer." + Continue + } + foreach ($Network in $Networks) { + $IPAddress = $Network.IpAddress[0] + $OutputObj = New-Object -Type PSObject + $OutputObj | Add-Member -MemberType NoteProperty -Name Repo -Value $Computer.ToUpper() + $OutputObj | Add-Member -MemberType NoteProperty -Name IPAddress -Value $IPAddress + $OutputObj + } + } + } \ No newline at end of file diff --git a/Munki/Set-Repo b/Munki/Set-Repo new file mode 100644 index 0000000..92b807e --- /dev/null +++ b/Munki/Set-Repo @@ -0,0 +1,45 @@ +#!/bin/bash + +# Get our log file, or create one +Log="/Users/Shared/Munki/MunkiRepo.log" +if [ ! -f $Log ]; then + mkdir -p /Users/Shared/Munki/ + touch $Log +fi + +# Get the active network interface +ActiveIFace=`route get google.com | awk '/interface/ {print $2}'` +echo "`date`: The active interface is $ActiveIFace" >> $Log + +# Get the current IP address +CurrentIP=`ifconfig $ActiveIFace | awk '/inet / {print $2}'` +echo "`date`: Your IP address is $CurrentIP" >> $Log + +# Office IP addresses associative array +Locations=( + '10.20.0::munkirepo01' # Region1 + '10.40.0::munkirepo02' # Region2 + '192.168.1::munkirepo03' # VPN +) + +# Set fallback repo if no matches are found +Repo='munkirepo01' + +# Go through each IP in our locations map to check for matches +for Location in "${Locations[@]}" ; do + # First 3 octets of repo's IP + IP=${Location%%::*} + + # Check if current IP contains match + if [[ "$CurrentIP" == "$IP"* ]]; then + Repo=${Location##*::} + break + fi +done + +# Update the Munki preferences to match the new repo +{ + defaults write /Library/Preferences/ManagedInstalls.plist SoftwareRepoURL "http://$Repo.company.local/repo" && echo "`date`: Repo set to $Repo" >> $Log +} || { + echo "`date`: Error setting repo to $Repo" >> $Log +} diff --git a/Munki/Sync-MunkiRepos.ps1 b/Munki/Sync-MunkiRepos.ps1 new file mode 100644 index 0000000..6259e64 --- /dev/null +++ b/Munki/Sync-MunkiRepos.ps1 @@ -0,0 +1,86 @@ +function Mail-Results { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string] + $MessageBody, + + [Parameter(Mandatory=$true)] + [string[]] + $Attachments + ) + + $SMTPServer = 'smtp1.corp.local' + $SMTP = New-Object Net.Mail.SmtpClient($SMTPServer) + + $Message = New-Object Net.Mail.MailMessage + $Message.From = 'MunkiAlerts@company.com' + $Message.To.Add('me@company.com') + $Message.Subject = 'Munki Sync Results' + $Message.Body = $MessageBody + foreach ($Report in $Attachments) { + $Message.Attachments.Add($Report) + } + $SMTP.Send($Message) +} + +function Convert-RobocopyExitCode ($ExitCode) { + switch ($ExitCode) { + 16 {'***FATAL ERROR***'} + 15 {'OKCOPY + FAIL + MISMATCHES + XTRA'} + 14 {'FAIL + MISMATCHES + XTRA'} + 13 {'OKCOPY + FAIL + MISMATCHES'} + 12 {'FAIL + MISMATCHES'} + 11 {'OKCOPY + FAIL + XTRA'} + 10 {'FAIL + XTRA'} + 9 {'OKCOPY + FAIL'} + 8 {'FAIL'} + 7 {'OKCOPY + MISMATCHES + XTRA'} + 6 {'MISMATCHES + XTRA'} + 5 {'OKCOPY + MISMATCHES'} + 4 {'MISMATCHES'} + 3 {'OKCOPY + XTRA'} + 2 {'XTRA'} + 1 {'OKCOPY'} + 0 {'No Change'} + default {'Unknown'} + } +} + +# Create our log directory +$LogDir = New-Item -ItemType Directory "$PSScriptRoot\Logs" -Force + +# Repo servers that are currently standing +$Servers = @( + 'munkirepo01', + 'munkirepo02', + 'munkirepo03' +) + +$MessageBody = '' +$Logs = @() +$SourceRepo = '\\macserver.company.local\repo' + +# Distribute content from the central repo to each node repo +foreach ($Repo in $Servers) { + $Log = (New-Item -ItemType File "$LogDir\MunkiSync-$Repo-$( (Get-Date).ToString('yyyyMMdd-HHmm') ).log").FullName + + # Update repo + ROBOCOPY $SourceRepo "\\$Repo\r$\repo" /DCOPY:DA /MIR /FFT /Z /XA:SH /R:10 /LOG:$Log /XJD + $ExitCode = $LASTEXITCODE + + # Get volume info + $Volume = Get-WmiObject Win32_Volume -ComputerName $Repo | Where-Object { $_.Name -eq 'R:\' } | Select-Object Name, Capacity, FreeSpace + + $Results += @([pscustomobject]@{ + Repo = $Repo + 'RoboCopy Results' = "$ExitCode`: $(Convert-RobocopyExitCode $ExitCode)" + 'Free (MB)' = [math]::truncate($Volume.FreeSpace / 1MB) + 'Capacity (MB)' = [math]::truncate($Volume.Capacity / 1MB) + 'Used (MB)' = [math]::truncate(($Volume.Capacity - $Volume.FreeSpace) / 1MB) + 'Completion Time' = Get-Date + }) + $Logs += $Log +} + +Mail-Results -MessageBody ($Results | Format-Table -AutoSize | Out-String) -Attachments $Logs \ No newline at end of file diff --git a/Munki/installmunki b/Munki/installmunki new file mode 100644 index 0000000..ef4fc05 --- /dev/null +++ b/Munki/installmunki @@ -0,0 +1,55 @@ +#!/bin/bash + +## +# Download and install the latest Munki2 tools from munkibuilds.org +## + +cat < $log" >> $crontab + fi +else + echo "SHELL=/bin/sh" > $crontab + echo "PATH=/bin:/sbin:/usr/bin:/usr/sbin" >> $crontab + echo "30 * * * * root /usr/sbin/softwareupdate -l &> $log" >> $crontab +fi + +/usr/bin/tail -1 $log diff --git a/Munki/updatemunkirepo b/Munki/updatemunkirepo new file mode 100644 index 0000000..76e8e9c --- /dev/null +++ b/Munki/updatemunkirepo @@ -0,0 +1,28 @@ +#!/bin/bash + +cat < + +Pause \ No newline at end of file diff --git a/Office/Enable-OutlookQuarantineFolder.ps1 b/Office/Enable-OutlookQuarantineFolder.ps1 new file mode 100644 index 0000000..3d04cc1 --- /dev/null +++ b/Office/Enable-OutlookQuarantineFolder.ps1 @@ -0,0 +1,38 @@ +# Get Outlook object +$Outlook = New-Object -ComObject Outlook.Application + +# Get Inbox +$Namespace = $Outlook.GetNamespace('MAPI') +$Inbox = $Namespace.GetDefaultFolder(6) + +try { + # Create new Quarantine folder + $Quarantine = $Inbox.Folders.Add('Quarantine') +} catch { + # Get existing Quarantine folder + $Quarantine = $Inbox.Folders | Where-Object -Property Name -eq 'Quarantine' +} + +# Set the web Url to the Quarantine admin site and set web view as default +$Quarantine.WebViewURL = 'https://admin.protection.outlook.com/quarantine' +$Quarantine.WebViewOn = $true + +# Add Quarantine folder to favorites +$OutlookModule = $Outlook.ActiveExplorer().NavigationPane.Modules.Item('Mail') +$Favorites = $OutlookModule.NavigationGroups.Item('Favorites') +$Favorites.NavigationFolders.Add($Quarantine) + +# Validate settings +$Quarantine = $Inbox.Folders | Where-Object -Property Name -eq 'Quarantine' + +if ($Quarantine) { + if ($Quarantine.WebViewURL -ne 'https://admin.protection.outlook.com/quarantine') { exit 999 } + + if (-not $Quarantine.WebViewOn) { exit 999 } + + if (-not $Favorites.NavigationFolders.Item($Quarantine.Name)) { exit 999 } + + exit 0 +} else { + exit 999 +} \ No newline at end of file diff --git a/Registry/Check-DotNetVersion.ps1 b/Registry/Check-DotNetVersion.ps1 new file mode 100644 index 0000000..1d2af48 --- /dev/null +++ b/Registry/Check-DotNetVersion.ps1 @@ -0,0 +1,6 @@ +$version = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' -Name Version -ErrorAction SilentlyContinue).Version +if ($version -like "4.5*") { + return $true +} else { + return $false +} \ No newline at end of file diff --git a/Registry/Disable-UAC.ps1 b/Registry/Disable-UAC.ps1 new file mode 100644 index 0000000..c40aec7 --- /dev/null +++ b/Registry/Disable-UAC.ps1 @@ -0,0 +1,3 @@ +New-ItemProperty -Path HKLM:Software\Microsoft\Windows\CurrentVersion\policies\system -Name EnableLUA -PropertyType DWord -Value 0 -Force +Write-Host "You must restart before changes will be in effect." +pause diff --git a/Registry/Suspend-SS.ps1 b/Registry/Suspend-SS.ps1 new file mode 100644 index 0000000..f83a552 --- /dev/null +++ b/Registry/Suspend-SS.ps1 @@ -0,0 +1,54 @@ +[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null +[string]$regPath = 'HKCU:\Software\Policies\Microsoft\Windows\Control Panel\Desktop\' +[string]$Status = "Time before screen saver default settings are restored:" + +# Validate input; must be between 15-240 +do { [int]$Minutes = Read-Host "Minutes to suspend screen saver (15-240)" } +while ((15..240) -notcontains $Minutes) + +[Int32]$Seconds = $Minutes * 60 +[string]$Message = "Screen saver suspended for $Minutes minutes..." + +function Set-SS { + Param ( + [Parameter(Mandatory=$true)] + [int] + $TimeOut, + + [Parameter(Mandatory=$true)] + [int] + $Active, + + [Parameter(Mandatory=$true)] + [int] + $IsSecure + ) + + try { + # Set screen saver registry properties + Set-ItemProperty -Path $regPath -Name ScreenSaveTimeOut -Value $TimeOut -ErrorAction Stop + Set-ItemProperty -Path $regPath -Name ScreenSaveActive -Value $Active -ErrorAction Stop + Set-ItemProperty -Path $regPath -Name ScreenSaverIsSecure -Value $IsSecure -ErrorAction Stop + } catch { + Write-Host 'There was an issue disabling the screen saver:' + Write-Host $_.Exception.Message -ForegroundColor Red + Read-Host 'Press [Enter] to exit' + exit + } +} + +# Disable screen saver +Set-SS -TimeOut 0 -Active 0 -IsSecure 0 + +foreach ($Sec in (1..$Seconds)) { + # Update progress bar every second + Write-Progress -Activity $Message -Status $Status -SecondsRemaining ($Seconds - $Sec) + + # Disable screen saver every 5 minutes + if ( !($Sec % 300) ) { Set-SS -TimeOut 0 -Active 0 -IsSecure 0 } + + Start-Sleep -Seconds 1 + + # Restore screen saver during last second... literally + if ($Sec -eq $Seconds) { Set-SS -TimeOut 900 -Active 1 -IsSecure 1 } +} \ No newline at end of file diff --git a/Registry/Suspend-SSWithAuth.ps1 b/Registry/Suspend-SSWithAuth.ps1 new file mode 100644 index 0000000..ad9c5c0 --- /dev/null +++ b/Registry/Suspend-SSWithAuth.ps1 @@ -0,0 +1,53 @@ +[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null + +#Get User's DN +$objSearcher = New-Object System.DirectoryServices.DirectorySearcher +$objSearcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry +$objSearcher.Filter = "(&(objectCategory=user)(SamAccountname=$($env:USERNAME)))" +$objSearcher.SearchScope = "Subtree" +$obj = $objSearcher.FindOne() +$User = $obj.Properties["distinguishedname"] + +#Now get the members of the group +$Group = "Workstation Admins" +$objSearcher.Filter = "(&(objectCategory=group)(SamAccountname=$Group))" +$objSearcher.SearchScope = "Subtree" +$obj = $objSearcher.FindOne() +[String[]]$Members = $obj.Properties["member"] + +If ($Members -notcontains $User) { + [System.Windows.Forms.MessageBox]::Show("You are not authorized to run this.", "Suspend Screensaver", 0) +} Else { + [string]$regPath = 'HKCU:\Software\Policies\Microsoft\Windows\Control Panel\Desktop\' + [string]$status = "Time before screen saver default settings are restored:" + + # validate input; must be between 15-240 + do { [int]$minutes = Read-Host "Minutes to suspend screen saver (15-240)" } + while ((15..240) -notcontains $minutes) + + [Int32]$seconds = $minutes * 60 + [string]$message = "Screen saver suspended for $minutes minutes..." + + # function for setting screen saver properties... saves lines later + function Set-SS { + Set-ItemProperty -Path $regPath -Name ScreenSaveTimeOut -Value $args[0] + Set-ItemProperty -Path $regPath -Name ScreenSaveActive -Value $args[1] + Set-ItemProperty -Path $regPath -Name ScreenSaverIsSecure -Value $args[2] + } + + # disable screen saver + Set-SS 0 0 0 + + ForEach ($sec in (1..$seconds)) { + # update progress bar every second + Write-Progress -Activity $message -Status $status -SecondsRemaining ($seconds - $sec) + + # disable screen saver every 5 minutes + If ( !($sec % 300) ) { Set-SS 0 0 0 } + + Start-Sleep -Seconds 1 + + # restore screen saver during last second... literally + If ($sec -eq $seconds) { Set-SS 900 1 1 } + } +} diff --git a/Set-ScriptRoot.ps1 b/Set-ScriptRoot.ps1 new file mode 100644 index 0000000..9e2a4b1 --- /dev/null +++ b/Set-ScriptRoot.ps1 @@ -0,0 +1,6 @@ +# Set ScripRoot variable to the path which the script is executed from +$ScriptRoot = if ($PSVersionTable.PSVersion.Major -lt 3) { + Split-Path -Path $MyInvocation.MyCommand.Path +} else { + $PSScriptRoot +} \ No newline at end of file diff --git a/Test-IsAdmin.ps1 b/Test-IsAdmin.ps1 new file mode 100644 index 0000000..a035cd9 --- /dev/null +++ b/Test-IsAdmin.ps1 @@ -0,0 +1,8 @@ +function Test-IsAdmin { + $UserIdentity = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() + + if (-not $UserIdentity.IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { + Update-Log "You are not running this script with an admin account. " -Color 'Red' -NoNewLine + Update-Log "Some tasks may fail if not run with admin credentials.`n" -Color 'Red' + } +} \ No newline at end of file diff --git a/Test-IsIse.ps1 b/Test-IsIse.ps1 new file mode 100644 index 0000000..c715da5 --- /dev/null +++ b/Test-IsIse.ps1 @@ -0,0 +1,2 @@ +# Return $true if run from ISE +function Test-IsISE { if ($psISE) { $true } else { $false } } \ No newline at end of file diff --git a/Windows Updates/Get-InstalledUpdates.ps1 b/Windows Updates/Get-InstalledUpdates.ps1 new file mode 100644 index 0000000..80e3855 --- /dev/null +++ b/Windows Updates/Get-InstalledUpdates.ps1 @@ -0,0 +1,20 @@ +Function Get-InstalledUpdates($title) { + $UpdateSession = New-Object -ComObject Microsoft.Update.Session + $SearchResult = $null + $UpdateSearcher = $UpdateSession.CreateUpdateSearcher() + $UpdateSearcher.Online = $true + $SearchResult = $UpdateSearcher.Search("IsInstalled=1 and Type='Software'") + + $i = 1 + foreach($Update in $SearchResult.Updates) + { + If ($Update.Title -like "*$title*") { + Write-Host "$i) $($Update.Title + " | " + $Update.SecurityBulletinIDs)" + $i += 1 + } + } +} + +cls + +Get-InstalledUpdates("Lync") \ No newline at end of file diff --git a/Windows Updates/Get-MicrosoftUpdates.ps1 b/Windows Updates/Get-MicrosoftUpdates.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..f32fb0f0a45ba03d4769d6bbdfa958af1944c8d8 GIT binary patch literal 1042 zcmb7@%}T>S6ot=P@ErnXkwOi=fC~|SHnxho5*J!4R!l2Nq!j6^tKZEyF_W}a2}ADO z`8j94x%2U`e5}jr zL`BVDALj#db2tof^h|7wBKn8W$XeMht2A(*b`(Np&Vg|=_jdkB=ArE@MLlQS-9eL& zouIisQIGYZ$^@oKO(taEIrpS~5yM-kYA%@7lUzMOod? zx*fcG?`pG;_SsWiP)SB{(ejvRPkjii({;?29dh2s5fxRib5vD+_tt(_-dy9RzYJ>A XyzHPj<>meFZ5p$`zGbWH1A8xDfcKac literal 0 HcmV?d00001 diff --git a/Windows/Get-ComputerNetInfo.ps1 b/Windows/Get-ComputerNetInfo.ps1 new file mode 100644 index 0000000..eed3128 --- /dev/null +++ b/Windows/Get-ComputerNetInfo.ps1 @@ -0,0 +1,31 @@ +function Get-ComputerNetInfo { + param ( + [Parameter( + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [string[]]$ComputerName = $env:COMPUTERNAME + ) + + process { + foreach ($Computer in $ComputerName ) { + # Get hostname and IP address from DNS provider + Write-Verbose "Getting DNS providor info for $Computer..." + $DNSHostEntry = [System.Net.Dns]::GetHostEntry($Computer) + $HostName = $DNSHostEntry.HostName + $IPAddress = $DNSHostEntry.AddressList.IPAddressToString.Split('.', 1)[0] + + # Get MAC from WMI class + Write-Verbose "Getting WMI providor info for $Computer..." + $NetAdapter = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -ComputerName $Computer + $MACAddress = ($NetAdapter | Where-Object {$_.IpAddress -eq $IPAddress}).MACAddress + + # Create and output a custom psobject with the net info + $NetInfo = New-Object -TypeName psobject + $NetInfo | Add-Member -MemberType NoteProperty -Name DNSHostName -Value $HostName + $NetInfo | Add-Member -MemberType NoteProperty -Name IPAddress -Value $IPAddress + $NetInfo | Add-Member -MemberType NoteProperty -Name MACAddress -Value $MACAddress + Write-Output $NetInfo + } + } +} \ No newline at end of file diff --git a/Windows/Get-NTPTimeOffset.ps1 b/Windows/Get-NTPTimeOffset.ps1 new file mode 100644 index 0000000..2b6bd7d --- /dev/null +++ b/Windows/Get-NTPTimeOffset.ps1 @@ -0,0 +1,80 @@ +$Times = @{} +$Count = 0 +$CountLimit = 60 +$Server = 'server05' +$ServerToCompare = 'server06' + +function Get-TimeOffset { + $ResultObject = New-Object -TypeName psobject + Add-Member -InputObject $ResultObject NoteProperty Computer 'pool.ntp.org' + $W32TMResult = w32tm /stripchart /computer:'pool.ntp.org' /dataonly /samples:3 /ipprotocol:4 + + if (-not ($W32TMResult -is [array])) { + Add-Member -InputObject $ResultObject NoteProperty Status "Offline" + Add-Member -InputObject $ResultObject NoteProperty Offset $null + } else { + $FoundTime = $false + + # Go through the 5 samples to find a response with timeoffset + for ($i = 3; $i -lt 8; $i++) { + if (-not $FoundTime) { + if ($W32TMResult[$i] -match ", ([-+]\d+\.\d+)s") { + $Offset = [float]$Matches[1] + Add-Member -InputObject $ResultObject NoteProperty Status "Online" + Add-Member -InputObject $ResultObject NoteProperty Offset $Offset + $FoundTime = $true + } + } + } + + # If no time samples were found check for error + if (-not $FoundTime) { + if ($W32TMResult[3] -match "error") { + #0x800705B4 is not advertising/responding + Add-Member -InputObject $ResultObject NoteProperty Status "NTP not responding" + } else { + Add-Member -InputObject $ResultObject NoteProperty Status $W32TMResult[3] + } + Add-Member -InputObject $ResultObject NoteProperty Offset $null + } + } + + $ResultObject +} + +while ($true) { + $Count++ + + if (Test-Connection -Quiet -ComputerName $Server) { + $Offset = try { + $Result = Invoke-Command -ComputerName $Server -ScriptBlock ${function:Get-TimeOffset} + try { $Result.Offset } catch { $Result.Status } + } catch { + $_ | Out-String + } + + $Times.Add((Get-Date), $Offset) + Start-Sleep -Seconds 10 + } else { + $Times.Add((Get-Date), 'Unreachable') + Start-Sleep -Seconds 10 + } + + if ($Count -ge $CountLimit) { + $OffsetToCompare = try { + $Result = Invoke-Command -ComputerName $Server -ScriptBlock ${function:Get-TimeOffset} + try { $Result.Offset } catch { $Result.Status } + } catch { + $_ | Out-String + } + + $SortedTimesString = $Times.GetEnumerator() | Sort-Object -Property Name | Out-String + + Send-MailMessage -Body "$ServerToCompare offset = $OffsetToCompare`n`n$Server datestamp and offset:`n$SortedTimesString" ` + -Subject 'server05 time script' -From 'Alerts@company.com' -To 'me@company.com' -SmtpServer smtp.company.local + + Write-Host 'Reset the clocks...' + $Count = 0 + $Times = @{} + } +} \ No newline at end of file diff --git a/Windows/Get-NetSessionInfoString.ps1 b/Windows/Get-NetSessionInfoString.ps1 new file mode 100644 index 0000000..1c65c72 --- /dev/null +++ b/Windows/Get-NetSessionInfoString.ps1 @@ -0,0 +1,13 @@ +$Creds = Get-Credential + +$Servers = (Get-ADGroup -Identity 'Domain Controllers' | Get-ADGroupMember).Name + +foreach ($Server in $Servers) { + $Server + + (Invoke-Command -ComputerName $Server -Credential $Creds -ScriptBlock { + $key = "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\DefaultSecurity" + $name = "SrvsvcSessionInfo" + (Get-ItemProperty -Path $key -Name $name).SrvsvcSessionInfo + }) -join '' +} \ No newline at end of file diff --git a/Windows/Test-Is64BitOs.ps1 b/Windows/Test-Is64BitOs.ps1 new file mode 100644 index 0000000..d5cbe00 --- /dev/null +++ b/Windows/Test-Is64BitOs.ps1 @@ -0,0 +1,8 @@ +# Return $true if 64-bit OS +function Test-64BitOS { + if ((Get-WmiObject Win32_OperatingSystem).OSArchitecture -eq '64-bit') { + $true + } else { + $false + } +} \ No newline at end of file diff --git a/Windows/Test-WebPorts.ps1 b/Windows/Test-WebPorts.ps1 new file mode 100644 index 0000000..7aa5ddf --- /dev/null +++ b/Windows/Test-WebPorts.ps1 @@ -0,0 +1,6 @@ +$Servers = @('server02' ,'server08') +$Servers | ForEach-Object { + Write-Host $_ + Test-NetConnection -ComputerName $_ -Port 80 -InformationLevel Quiet + Test-NetConnection -ComputerName $_ -Port 443 -InformationLevel Quiet +} \ No newline at end of file diff --git a/Write-CMTraceLog.ps1 b/Write-CMTraceLog.ps1 new file mode 100644 index 0000000..268dcfc --- /dev/null +++ b/Write-CMTraceLog.ps1 @@ -0,0 +1,32 @@ +$Date = (Get-Date).ToString('yyyyMMdd-HHmm') +$LogFolder = New-Item -ItemType Directory ".\logs" -Force +$Log = New-Item -ItemType File "$LogFolder\Action-$Date.log" + +function Write-CMTraceLog { + Param ( + [Parameter(Mandatory=$false)] $Message, + [Parameter(Mandatory=$false)] $ErrorMessage, + [Parameter(Mandatory=$false)] $Component, + # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) + [Parameter(Mandatory=$false)] [int]$Type, + [Parameter(Mandatory=$true)] $LogFile + ) + + $Time = Get-Date -Format "HH:mm:ss.ffffff" + $Date = Get-Date -Format "MM-dd-yyyy" + + if ($ErrorMessage -ne $null) {$Type = 3} + if ($Component -eq $null) {$Component = " "} + if ($Type -eq $null) {$Type = 1} + + $LogMessage = "" + $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile +} + +<# Usage + $LogFile = "C:\TestFolder\TestLog.Log" + Write-CMTraceLog -LogFile $LogFile + Write-CMTraceLog -Message "This is a normal message" -ErrorMessage $Error -LogFile $LogFile + Write-CMTraceLog -Message "This is a warning" -Type 2 -Component "Test Component" -LogFile $LogFile + Write-CMTraceLog -Message "This is an Error!" -Type 3 -Component "Error Component" -LogFile $LogFile +#> \ No newline at end of file From d9f0279316ce26a91557db12dda9705bec5eb6a8 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Mon, 6 Mar 2017 16:44:15 -0500 Subject: [PATCH 02/65] Moved to its own project at https://github.com/nickrod518/MapDrivesUtility --- MapDrivesUtility/Invoke-MapNetworkDrive.ps1 | 165 +------------------- MapDrivesUtility/Map Network Drives.lnk | Bin 1921 -> 0 bytes MapDrivesUtility/Offices.csv | 4 - MapDrivesUtility/README.txt | 14 -- MapDrivesUtility/Start-MapDrivesUtility.ps1 | 162 ------------------- MapDrivesUtility/Translations.csv | 3 - MapDrivesUtility/invisible.vbs | 1 - MapDrivesUtility/launch.bat | 1 - 8 files changed, 1 insertion(+), 349 deletions(-) delete mode 100644 MapDrivesUtility/Map Network Drives.lnk delete mode 100644 MapDrivesUtility/Offices.csv delete mode 100644 MapDrivesUtility/README.txt delete mode 100644 MapDrivesUtility/Start-MapDrivesUtility.ps1 delete mode 100644 MapDrivesUtility/Translations.csv delete mode 100644 MapDrivesUtility/invisible.vbs delete mode 100644 MapDrivesUtility/launch.bat diff --git a/MapDrivesUtility/Invoke-MapNetworkDrive.ps1 b/MapDrivesUtility/Invoke-MapNetworkDrive.ps1 index c17a17e..2afd4e2 100644 --- a/MapDrivesUtility/Invoke-MapNetworkDrive.ps1 +++ b/MapDrivesUtility/Invoke-MapNetworkDrive.ps1 @@ -1,164 +1 @@ -begin { - # Set $DebugMode to $true to log every map occurrence or $false to disable - $DebugMode = $false - - # Destination for error and debug logs to be copied to - $CentralLogPath = '\\server01\DriveMapError' - - # Enable debug mode if user's name is in debug user list - if ((Get-Content "$CentralLogPath\DebugUsers.txt") -contains (whoami)) { $DebugMode = $true } - - # Error flag - set to false when error occurs - $ErrorFree = $true - - Start-Transcript -Path "$env:TEMP\MapNetworkDrive.log" -Force - Write-Host "Map Network Drives v2.0" - - Add-Type -AssemblyName System.Windows.Forms - - $Network = New-Object -ComObject WScript.Network - - function Get-UserGroups { - [cmdletbinding()] - param() - - $Groups = [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups - - foreach ($Group in $Groups) { - $GroupSID = $Group.Value - $GroupName = New-Object System.Security.Principal.SecurityIdentifier($GroupSID) - $GroupDisplayName = $GroupName.Translate([System.Security.Principal.NTAccount]) - $GroupDisplayName.Value - } - } -} - -process { - # Import the csv's with our mapping rules - $Offices = Import-Csv -Path "\\server01\MapDrivesUtility\Offices.csv" - $Translations = Import-Csv -Path "\\server01\MapDrivesUtility\Translations.csv" - - # Get all groups the user is a member of that match a translated group - $GroupsFound = Get-UserGroups - $UserGroups = foreach ($GroupName in $GroupsFound) { - $Translations | ForEach-Object { if ($_.GroupName -eq $GroupName) { $GroupName } } - } - - Write-Host "$env:USERNAME is a member of the following groups: $($GroupsFound -join ', ')" - Write-Host "The following groups match a map drives translation group: [$($UserGroups -join '], [')]" - - Write-Host "Checking location criteria..." - $Locations = ($Translations | Where-Object { - $Translation = $_ - $Location = $_.Location - Write-Host "$Location..." - - # Group filter - ($Translation.GroupName -eq '' -or - ($Translation.GroupName -split ',' | ForEach-Object { - if ($UserGroups -contains $_) { - Write-Host "+++$_ is in a member group: [$($Translation.GroupName)]" - return $true - } else { - Write-Host "---not in a member group: [$($Translation.GroupName)]" - } - })) -and - - # NOT group filter - ($Translation.NotGroupName -eq '' -or - ($Translation.NotGroupName -split ',' | ForEach-Object { - if ($UserGroups -contains $_) { - Write-Host "---$_ is a member of a NOT group: [$($Translation.NotGroupName)]" - return $false - } else { - Write-Host "+++not a member of a NOT group: [$($Translation.NotGroupName)]" - $true - } - })) -and - - # UserName filter - ($Translation.UserName -eq '' -or - ($Translation.UserName -split ',' | ForEach-Object { - if ((whoami) -eq $_) { - Write-Host "+++$_ is a member user: [$($Translation.UserName)]" - return $true - } else { - Write-Host "---$(whoami) is not a member user: [$($Translation.UserName)]" - } - })) -and - - # ComputerName filter - ($Translation.ComputerName -eq '' -or - ($Translation.ComputerName -split ',' | ForEach-Object { - if ((hostname) -eq $_) { - Write-Host "+++$_ is a member computer: [$($Translation.ComputerName)]" - return $true - } else { - Write-Host "---$(hostname) is not a member computer: [$($Translation.ComputerName)]" - } - })) - }).Location - Write-Host "Mapping the following locations: [$($Locations -join '], [')]" - - # Map the drive mappings the user is a member of - foreach ($Location in $Locations) { - $Offices | Where-Object { - $_.Type -ne 'Local' -and - $_.Location -contains $Location - } | ForEach-Object { - $Location = $_.Location - $DriveLetter = "$($_.DriveLetter)`:" - $DrivePath = $_.DrivePath - - # Remove any old drive mapping - try { - Write-Host "Removing old mapping for drive for $DriveLetter..." -NoNewline - $Network.RemoveNetworkDrive($DriveLetter, $true) - Write-Host "success!" -ForegroundColor Green - } catch { - if ($_.Exception.Message -like '*This network connection does not exist.*') { - Write-Host $_.Exception.Message -ForegroundColor Yellow - } else { - Write-Host "error:`n$($_.Exception.Message)" -ForegroundColor Red - $ErrorFree = $false - } - } - - # Wait a moment before continuing - Start-Sleep -Seconds 1 - - # Map new drive - try { - Write-Host "Mapping drive for $Location ($DriveLetter to $DrivePath)..." -NoNewline - $Network.MapNetworkDrive($DriveLetter, $DrivePath) - Write-Host "success!" -ForegroundColor Green - } catch { - Write-Host "error:`n$($_.Exception.Message)" -ForegroundColor Red - $ErrorFree = $false - } - } - } -} - -end { - Write-Host "Currently mapped drives:" - $New = $true - $Network = New-Object -ComObject WScript.Network - $Network.EnumNetworkDrives() | ForEach-Object { - if ($New) { - Write-Host "$_ = " -NoNewline - $New = $false - } else { - Write-Host $_ - $New = $true - } - } - - Stop-Transcript - - if (-not $ErrorFree -or $DebugMode) { - if (-not $ErrorFree) { $Prefix = 'Error-' } - $Date = (Get-Date).ToString('yyyyMMdd-HHmm') - Copy-Item -Path "$env:TEMP\MapNetworkDrive.log" -Destination "$CentralLogPath\$Prefix$env:COMPUTERNAME-$env:USERNAME-$Date.log" - } -} \ No newline at end of file +This script has been moved to a new project repo - https://github.com/nickrod518/MapDrivesUtility \ No newline at end of file diff --git a/MapDrivesUtility/Map Network Drives.lnk b/MapDrivesUtility/Map Network Drives.lnk deleted file mode 100644 index 3940de588f8a4caa7b8b42b3e6a3e235416ba598..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1921 zcma)7ZERCj7=Bt^94Ojl1LtDoZgB>Jl-B+rOffAjV>Rms6d0I0Wos{6S!tJhOS}04 ze+Wk7*N8!vfMgmz{2E9Q_rowDi~legOEidznxGgB*@!VQVF91__Jejp*tvP$bMAZ2 zeZSvxTLD;VtfatclcM<=Dxq*4nYjK~+t24LGiU6wVl($Gx${NpvD9E;_?OVQ@@G@r z_QCl|EO9vAdkD+CO=|Jb&BaDI&6l_Q+6f!l;>lPlYru^;xEqtBQ!Q^eWXH}kmp&E) z@4xw}x!N&(7Hxp;XbQrX%spw z@1+Jib@UNH9kHHxn2_&eb`dfn#Dm0LL}$bMRvYI~#1}Vuz~JU%rxv_bOO2xjdDJ`5 z@<^)xi-39?yXV7;CQ1*WgZ?P+nVx0nk-@hO^e~PDk!D0|&1_{z4Wg5LjaFs2lCud) z*uiW@(M@hA-_oc@W0@uEnCTSxjnrt&$dTCGg4Jl{BRR9PE)smMF_`xOLoCis^XeX- zsviw|U-``Fnto^Usbh@eNBHvC+fVLe)yY^&I3wYR;r(sysCPKD`%?VJmtBL&D)R4@ z143@(GrE#%IqbOFI5N4W&UWakb<67q{`$0@{J8`c5>j6=dX+V)vzXI@rly0ZE}mHa zl6TK#-{6JCdp{9Pl4n#4DI0E#S-w`)STq}pI$df~OD9rYsU!mNsGc%XThmHVOJ`Gh z4_bw<7@D5Zbj9fI&@}~C1}@if1O7L4n-cJZee3)?S=DO3fQ2uR0^YWnaYLe_%mDN^ zyqHgQX`;*1tT2cQ!e-Id;svJS844NjZ`2jh8C<=7r_;X5!&-l?sJFrx@K+e3ch>>Y zdr`WlWO!aXS1hq0%O=fmrdVn{)KqFop>T5TBW*$lh8j8lL`X|9e?!^enYzo?_TY%m ze*Ea#!4cWpLNM$@n>fqpjV{$!I6AV+yE5kc(`UWaWA`iz?4S4O4^wCUzOwdY$asG2 z_o{v8|2RI~{KfR0_N(d99LnEKX;L#+9H%aL@#>l*|BhE5G`2Rx$&ZO9shK52uA@qw zyIq(SEMf}P8C9mTmn2XL2C9olVzG!f$G4=nDqIiw#ln>fr_k23i#af#-#kFTE#%h) zRUfuZScJ(A%SZ~u`1kXvx$G,Local,,Do not remove! -"City1, USA",Local,F,\\city1server01\Netvol1 -"City2, USA",Local,G,\\city2server01\Netvol1 \ No newline at end of file diff --git a/MapDrivesUtility/README.txt b/MapDrivesUtility/README.txt deleted file mode 100644 index 1148020..0000000 --- a/MapDrivesUtility/README.txt +++ /dev/null @@ -1,14 +0,0 @@ -Offices.csv -This file contains all office drive mapping entries. One unique entry is "", the script treats this entry special in that when selected it removes all currently mapped network drives. Here is a description of each column in the file: -Location - friendly name of the location of the drive mapping as it will appear in the drive mapping utility. -Type - 3 acceptable keywords here, Local is used for mappings that should only apply when using Map Drives Utility, Citrix is used for mappings that should only apply when the utility is invoked from a Citrix GPO, Both is used for mappings that should apply in both local and Citrix sessions. -DriveLetter - the drive letter to use in the mapping. -DrivePath - the network path to use in the mapping. - -Translations.csv -This file is used only in the context of a Citrix GPO. It uses the groups that the user is a member, groups they aren't a member of, the name of the user, and the computer the user is logged into, to get the name of a location that matches a location in the Offices.csv file and map those drives. The user must meet the criteria of all columns. Each column supports comma separated entries. Here is a description of each column in the file: -Location - location name to use in the Offices.csv file. -GroupName - name of the group the user must be a member of. -NotGroupName - name of the group the user must not be a member of. -UserName - the user must have this name. -ComputerName - the user must be logged into this computer. \ No newline at end of file diff --git a/MapDrivesUtility/Start-MapDrivesUtility.ps1 b/MapDrivesUtility/Start-MapDrivesUtility.ps1 deleted file mode 100644 index 095144a..0000000 --- a/MapDrivesUtility/Start-MapDrivesUtility.ps1 +++ /dev/null @@ -1,162 +0,0 @@ -Add-Type -AssemblyName System.Windows.Forms - -$Network = New-Object -ComObject WScript.Network - -function Show-Error { - param([string]$Message) - - [System.Windows.Forms.MessageBox]::Show( - "$Message`n`nClick OK to exit", - 'Map Drives Utility Error', - [System.Windows.Forms.MessageBoxButtons]::OK, - [System.Windows.Forms.MessageBoxIcon]::Hand - ) | Out-Null -} - -try { - $Offices = Import-Csv "\\server01\MapDrivesUtility\Offices.csv" -} catch { - Show-Error 'You must be in an office or connected via VPN for the Map Network Drives utility to launch' - exit -} - -# Get the large logo from a base64 string -$Base64Logo = '' -$IconStream = [System.IO.MemoryStream][System.Convert]::FromBase64String($Base64Icon) -$IconBMP = [System.Drawing.Image]::FromStream($IconStream) -$Hicon = $IconBMP.GetHicon() -$IconBMP = [System.Drawing.Icon]::FromHandle($Hicon) - - -function Get-MappedDrives { - $MappedDrives = @{} - $Letter = '' - - $Network.EnumNetworkDrives() | ForEach-Object { - if ($Letter -ne '') { - $MappedDrives.Add($Letter, $_) - $Letter = '' - } else { - $Letter = $_ - } - } - - $MappedDrives -} - -function Invoke-MapNetworkDrives { - param([psobject[]]$Mappings) - - foreach ($Drive in $Mappings) { - $DriveLetter = "$($Drive.DriveLetter)`:" - $DrivePath = $Drive.DrivePath - - Write-Host "$DriveLetter to $DrivePath" - - # Map drive - try { - # Check if drive letter is already mapped - if (-not (Test-Path -Path $DriveLetter -ErrorAction Stop)) { - Write-Verbose "$DriveLetter was not mapped, mapping" - - # Map new drive if it's not in use - $Network.MapNetworkDrive($DriveLetter, $DrivePath) - } elseif ($DrivePath -ne (Get-MappedDrives).$DriveLetter) { - Write-Verbose "$DrivePath doesn't match, remapping" - - # Remove mapped drive - $Network.RemoveNetworkDrive($DriveLetter, $true) - - # Map new drive - $Network.MapNetworkDrive($DriveLetter, $DrivePath) - } else { - Write-Verbose "$DriveLetter is already mapped to $DrivePath" - # Already mapped - } - } catch { - Show-Error -Message "There was a problem mapping $DriveLetter to [$DrivePath]`n`n$($_.Exception.Message)" - } - } -} - -function Remove-NetworkDrives { - try { - $MappedDrives = Get-MappedDrives - - # Remove each mapped drive - foreach ($DriveLetter in $MappedDrives.Keys) { - $Network.RemoveNetworkDrive($DriveLetter, $true) - } - } catch { - Show-Error -Message "There was a problem removing all mapped drives`n`n$($_.Exception.Message)" - } -} - -$Form = New-Object System.Windows.Forms.Form -$Form.Text = 'Map Network Drives' -$Form.Icon = $IconBMP -$Form.SizeGripStyle = 'Hide' -$Form.FormBorderStyle = 'FixedSingle' -$Form.MaximizeBox = $false -$Form.Width = 300 -$Form.Height = 260 -$Form.BackColor = "#ffffff" - -$Logo = New-Object System.Windows.Forms.PictureBox -$Logo.Image = $LogoBMP -$Logo.Location = New-Object System.Drawing.Point(30, 0) -$logo.AutoSize = $true -$Form.Controls.Add($Logo) - -$OfficeList = New-Object System.Windows.Forms.ComboBox -$OfficeList.DropDownStyle = 'DropDownList' -$OfficeList.Items.Insert(0, 'Please select an Office') -$OfficeList.SelectedIndex = 0 -$OfficeList.Width = 200 -$OfficeList.Location = New-Object System.Drawing.Point(50, 130) -$Offices | Where-Object { $_.Type -ne 'Citrix' } | Select-Object Location -Unique | - ForEach-Object { $OfficeList.Items.Add($_.Location) | Out-Null } -$OfficeList.Add_SelectionChangeCommitted({ - if ($OfficeList.Items[0] -eq 'Please select an Office' -and - $OfficeList.SelectedItem -ne 'Please select an Office') { - $OfficeList.Items.RemoveAt(0) - } -}) -$Form.Controls.Add($OfficeList) - -$ConnectingLabel = New-Object System.Windows.Forms.Label -$ConnectingLabel.Visible = $false -$ConnectingLabel.Text = 'Connecting...' -$ConnectingLabel.Width = 250 -$ConnectingLabel.Height = 20 -$ConnectingLabel.Location = New-Object System.Drawing.Point(110, 200) -$ConnectingLabel.Font = "Microsoft Sans Serif, 8" -$Form.Controls.Add($ConnectingLabel) - -$ConnectButton = New-Object System.Windows.Forms.Button -$ConnectButton.AutoSize = $true -$ConnectButton.UseVisualStyleBackColor = $true -$ConnectButton.Location = New-Object System.Drawing.Point(60, 160) -$ConnectButton.Text = 'Connect Network Drives' -$ConnectButton.Font = 'Microsoft Sans Serif, 11' -$ConnectButton.Add_Click({ - if ($OfficeList.SelectedItem -eq '') { - Remove-NetworkDrives - } elseif ($OfficeList.SelectedItem -ne 'Please select an Office') { - # Show the connecting label - $ConnectingLabel.Visible = $true - - # Get the selected office's drive mappings - $Mappings = $Offices | Where-Object { $_.Location -eq $OfficeList.SelectedItem } - - # Launch selected product - Invoke-MapNetworkDrives $Mappings - - # Exit - $Form.Dispose() - } -}) -$Form.Controls.Add($ConnectButton) - -[void]$Form.ShowDialog() -$Form.Dispose() \ No newline at end of file diff --git a/MapDrivesUtility/Translations.csv b/MapDrivesUtility/Translations.csv deleted file mode 100644 index e22a8de..0000000 --- a/MapDrivesUtility/Translations.csv +++ /dev/null @@ -1,3 +0,0 @@ -Location,GroupName,NotGroupName,UserName,ComputerName -"City1, USA",company\City1 Users,,, -"City2, USA",company\City2 Users,,, \ No newline at end of file diff --git a/MapDrivesUtility/invisible.vbs b/MapDrivesUtility/invisible.vbs deleted file mode 100644 index fb7cb76..0000000 --- a/MapDrivesUtility/invisible.vbs +++ /dev/null @@ -1 +0,0 @@ -CreateObject("Wscript.Shell").Run """" & WScript.Arguments(0) & """", 0, False \ No newline at end of file diff --git a/MapDrivesUtility/launch.bat b/MapDrivesUtility/launch.bat deleted file mode 100644 index f83f395..0000000 --- a/MapDrivesUtility/launch.bat +++ /dev/null @@ -1 +0,0 @@ -PowerShell -WindowStyle Hidden -NoProfile -File Start-MapDrivesUtility.ps1 \ No newline at end of file From bea5e03b33fd0d7d2fe2d28c43597178a9ce002e Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Tue, 7 Mar 2017 13:27:20 -0500 Subject: [PATCH 03/65] Organized. --- .../Convert-RobocopyExitCode.ps1 | 0 .../ConvertFrom-Base64Image.ps1 | 0 .../ConvertTo-Base64Image.ps1 | 0 ConvertTo-Icon.ps1 => Data/ConvertTo-Icon.ps1 | 0 .../Get-MD5Checksum.ps1 | 0 .../Create-CAB.ps1 | 126 ++++++++-------- .../Create-WSP.ps1 | 132 ++++++++--------- File Management/Move-GFRClientFiles.ps1 | 2 +- Migrate-SQLBackup.ps1 => GUI/Read-Menu.ps1 | 46 +++--- .../Enable-BitLocker.ps1 | 140 +++++++++--------- 10 files changed, 223 insertions(+), 223 deletions(-) rename Convert-RobocopyExitCode.ps1 => Data/Convert-RobocopyExitCode.ps1 (100%) rename ConvertFrom-Base64Image.ps1 => Data/ConvertFrom-Base64Image.ps1 (100%) rename ConvertTo-Base64Image.ps1 => Data/ConvertTo-Base64Image.ps1 (100%) rename ConvertTo-Icon.ps1 => Data/ConvertTo-Icon.ps1 (100%) rename Get-MD5Checksum.ps1 => Data/Get-MD5Checksum.ps1 (100%) rename Create-CAB.ps1 => File Management/Create-CAB.ps1 (96%) mode change 100755 => 100644 rename Create-WSP.ps1 => File Management/Create-WSP.ps1 (96%) mode change 100755 => 100644 rename Migrate-SQLBackup.ps1 => GUI/Read-Menu.ps1 (86%) rename Enable-BitLocker.ps1 => Windows/Enable-BitLocker.ps1 (97%) diff --git a/Convert-RobocopyExitCode.ps1 b/Data/Convert-RobocopyExitCode.ps1 similarity index 100% rename from Convert-RobocopyExitCode.ps1 rename to Data/Convert-RobocopyExitCode.ps1 diff --git a/ConvertFrom-Base64Image.ps1 b/Data/ConvertFrom-Base64Image.ps1 similarity index 100% rename from ConvertFrom-Base64Image.ps1 rename to Data/ConvertFrom-Base64Image.ps1 diff --git a/ConvertTo-Base64Image.ps1 b/Data/ConvertTo-Base64Image.ps1 similarity index 100% rename from ConvertTo-Base64Image.ps1 rename to Data/ConvertTo-Base64Image.ps1 diff --git a/ConvertTo-Icon.ps1 b/Data/ConvertTo-Icon.ps1 similarity index 100% rename from ConvertTo-Icon.ps1 rename to Data/ConvertTo-Icon.ps1 diff --git a/Get-MD5Checksum.ps1 b/Data/Get-MD5Checksum.ps1 similarity index 100% rename from Get-MD5Checksum.ps1 rename to Data/Get-MD5Checksum.ps1 diff --git a/Create-CAB.ps1 b/File Management/Create-CAB.ps1 old mode 100755 new mode 100644 similarity index 96% rename from Create-CAB.ps1 rename to File Management/Create-CAB.ps1 index 3857d4b..6f24225 --- a/Create-CAB.ps1 +++ b/File Management/Create-CAB.ps1 @@ -1,63 +1,63 @@ -$project = Read-Host "Folder to convert to a CAB" -$ddf = $project + ".ddf" -$cab = $project + ".cab" - -# delete any preexisting ddf -if (Test-Path $ddf) { - Clear-Content $ddf -} else { - $ddf = New-Item -type file $ddf -} - -# delete any preexisting cab -if (Test-Path $cab) { - Remove-Item $cab -} - -# create the ddf -Add-Content $ddf ".OPTION EXPLICIT" -Add-Content $ddf ".Set CabinetNameTemplate=$cab" -Add-Content $ddf ".Set Cabinet=on" -Add-Content $ddf ".Set Compress=on`n`n" - -# add the manifest and any other files in the top most directory of the project -Get-ChildItem $project -File | ForEach-Object { - $trash, $file = $_.FullName -split $project, 2 - $file = $project + $file - $file = $([char]34) + $file + $([char]34) - - Add-Content $ddf $file -} - -# create a new destination directory for each sub directory -Get-ChildItem $project -Directory -Recurse | ForEach-Object { - - # if the directory has no files, skip it - if ($_.GetFiles().Count) { - - $trash, $folder = $_.FullName -split $project, 2 - $folder = $folder.TrimStart('\') - $folder = $([char]34) + $folder + $([char]34) - - Add-Content $ddf "`n`n.Set DestinationDir=$folder" - - # place the files for each sub directory under its destination directory entry - Get-ChildItem $_.FullName -File | ForEach-Object { - $trash, $file = $_.FullName -split $project, 2 - $file = $project + $file - $file = $([char]34) + $file + $([char]34) - - Add-Content $ddf $file - } - } -} - -# create the cab file -Start-Process MakeCab -ArgumentList "/F ""$ddf""" -Wait - -# clean up -Move-Item "disk1\$cab" . -Remove-Item -Force "disk1" -Remove-Item $ddf -Remove-Item ".\setup.inf" -Remove-Item ".\setup.rpt" +$project = Read-Host "Folder to convert to a CAB" +$ddf = $project + ".ddf" +$cab = $project + ".cab" + +# delete any preexisting ddf +if (Test-Path $ddf) { + Clear-Content $ddf +} else { + $ddf = New-Item -type file $ddf +} + +# delete any preexisting cab +if (Test-Path $cab) { + Remove-Item $cab +} + +# create the ddf +Add-Content $ddf ".OPTION EXPLICIT" +Add-Content $ddf ".Set CabinetNameTemplate=$cab" +Add-Content $ddf ".Set Cabinet=on" +Add-Content $ddf ".Set Compress=on`n`n" + +# add the manifest and any other files in the top most directory of the project +Get-ChildItem $project -File | ForEach-Object { + $trash, $file = $_.FullName -split $project, 2 + $file = $project + $file + $file = $([char]34) + $file + $([char]34) + + Add-Content $ddf $file +} + +# create a new destination directory for each sub directory +Get-ChildItem $project -Directory -Recurse | ForEach-Object { + + # if the directory has no files, skip it + if ($_.GetFiles().Count) { + + $trash, $folder = $_.FullName -split $project, 2 + $folder = $folder.TrimStart('\') + $folder = $([char]34) + $folder + $([char]34) + + Add-Content $ddf "`n`n.Set DestinationDir=$folder" + + # place the files for each sub directory under its destination directory entry + Get-ChildItem $_.FullName -File | ForEach-Object { + $trash, $file = $_.FullName -split $project, 2 + $file = $project + $file + $file = $([char]34) + $file + $([char]34) + + Add-Content $ddf $file + } + } +} + +# create the cab file +Start-Process MakeCab -ArgumentList "/F ""$ddf""" -Wait + +# clean up +Move-Item "disk1\$cab" . +Remove-Item -Force "disk1" +Remove-Item $ddf +Remove-Item ".\setup.inf" +Remove-Item ".\setup.rpt" diff --git a/Create-WSP.ps1 b/File Management/Create-WSP.ps1 old mode 100755 new mode 100644 similarity index 96% rename from Create-WSP.ps1 rename to File Management/Create-WSP.ps1 index b0d5279..222e05d --- a/Create-WSP.ps1 +++ b/File Management/Create-WSP.ps1 @@ -1,66 +1,66 @@ -$project = Read-Host "Folder to convert to a WSP" -$ddf = $project + ".ddf" -$cab = $project + ".cab" - -# delete any preexisting ddf -if (Test-Path $ddf) { - Clear-Content $ddf -} else { - $ddf = New-Item -type file $ddf -} - -# delete any preexisting cab -if (Test-Path $cab) { - Remove-Item $cab -} - -# create the ddf -Add-Content $ddf ".OPTION EXPLICIT" -Add-Content $ddf ".Set CabinetNameTemplate=$cab" -Add-Content $ddf ".Set Cabinet=on" -Add-Content $ddf ".Set Compress=on`n`n" - -# add the manifest and any other files in the top most directory of the project -Get-ChildItem $project -File | ForEach-Object { - $trash, $file = $_.FullName -split $project, 2 - $file = $project + $file - $file = $([char]34) + $file + $([char]34) - - Add-Content $ddf $file -} - -# create a new destination directory for each sub directory -Get-ChildItem $project -Directory -Recurse | ForEach-Object { - - # if the directory has no files, skip it - if ($_.GetFiles().Count) { - - $trash, $folder = $_.FullName -split $project, 2 - $folder = $folder.TrimStart('\') - $folder = $([char]34) + $folder + $([char]34) - - Add-Content $ddf "`n`n.Set DestinationDir=$folder" - - # place the files for each sub directory under its destination directory entry - Get-ChildItem $_.FullName -File | ForEach-Object { - $trash, $file = $_.FullName -split $project, 2 - $file = $project + $file - $file = $([char]34) + $file + $([char]34) - - Add-Content $ddf $file - } - } -} - -# create the cab file -Start-Process MakeCab -ArgumentList "/F ""$ddf""" -Wait - -# clean up -Move-Item "disk1\$cab" . -Remove-Item -Force "disk1" -Remove-Item $ddf -Remove-Item ".\setup.inf" -Remove-Item ".\setup.rpt" - -# rename to wsp -Rename-Item $cab ($project + ".wsp") +$project = Read-Host "Folder to convert to a WSP" +$ddf = $project + ".ddf" +$cab = $project + ".cab" + +# delete any preexisting ddf +if (Test-Path $ddf) { + Clear-Content $ddf +} else { + $ddf = New-Item -type file $ddf +} + +# delete any preexisting cab +if (Test-Path $cab) { + Remove-Item $cab +} + +# create the ddf +Add-Content $ddf ".OPTION EXPLICIT" +Add-Content $ddf ".Set CabinetNameTemplate=$cab" +Add-Content $ddf ".Set Cabinet=on" +Add-Content $ddf ".Set Compress=on`n`n" + +# add the manifest and any other files in the top most directory of the project +Get-ChildItem $project -File | ForEach-Object { + $trash, $file = $_.FullName -split $project, 2 + $file = $project + $file + $file = $([char]34) + $file + $([char]34) + + Add-Content $ddf $file +} + +# create a new destination directory for each sub directory +Get-ChildItem $project -Directory -Recurse | ForEach-Object { + + # if the directory has no files, skip it + if ($_.GetFiles().Count) { + + $trash, $folder = $_.FullName -split $project, 2 + $folder = $folder.TrimStart('\') + $folder = $([char]34) + $folder + $([char]34) + + Add-Content $ddf "`n`n.Set DestinationDir=$folder" + + # place the files for each sub directory under its destination directory entry + Get-ChildItem $_.FullName -File | ForEach-Object { + $trash, $file = $_.FullName -split $project, 2 + $file = $project + $file + $file = $([char]34) + $file + $([char]34) + + Add-Content $ddf $file + } + } +} + +# create the cab file +Start-Process MakeCab -ArgumentList "/F ""$ddf""" -Wait + +# clean up +Move-Item "disk1\$cab" . +Remove-Item -Force "disk1" +Remove-Item $ddf +Remove-Item ".\setup.inf" +Remove-Item ".\setup.rpt" + +# rename to wsp +Rename-Item $cab ($project + ".wsp") diff --git a/File Management/Move-GFRClientFiles.ps1 b/File Management/Move-GFRClientFiles.ps1 index 1a3a908..1df3e0c 100644 --- a/File Management/Move-GFRClientFiles.ps1 +++ b/File Management/Move-GFRClientFiles.ps1 @@ -38,7 +38,7 @@ param( begin { Write-Verbose "Gathering files within $DocsPath..." - $Docs = Get-ChildItem -Path $DocsPath + $Docs = Get-ChildItem -Path $DocsPath -File $DocsCount = $Docs.Count Write-Verbose "$DocsCount files found." diff --git a/Migrate-SQLBackup.ps1 b/GUI/Read-Menu.ps1 similarity index 86% rename from Migrate-SQLBackup.ps1 rename to GUI/Read-Menu.ps1 index dd70ba4..3104943 100644 --- a/Migrate-SQLBackup.ps1 +++ b/GUI/Read-Menu.ps1 @@ -1,24 +1,24 @@ -$OldSQL = Read-Host 'Current SQL server' -$NewSQL = Read-Host 'New SQL server' -$DB = Read-Host 'Name of Database' - -# Prompt the user whether they want to merge the database -Function Prompt { - CLS - $Title = "Database Backup Migration`n`n`tOLD SQL SERVER`t:`t$OldSQL`n`tNEW SQL SERVER`t:`t$NewSQL`n`tDATABASE`t:`t$DB" - $Message = "`nDo you want to merge database?" - $Yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Perform a database merge on $DB, moving it from $OldSQL to $NewSQL." - $No = New-Object System.Management.Automation.Host.ChoiceDescription "&No", 'Exit this utility.' - $Options = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No) - $Result = $Host.UI.PromptForChoice($Title, $Message, $Options, 0) - CLS - - switch ($Result) { - 0 {'Merging...'; Start-Sleep -Seconds 1; Break} - 1 {'Exiting...'; Start-Sleep -Seconds 1; Exit} - } - - Prompt -} - +$OldSQL = Read-Host 'Current SQL server' +$NewSQL = Read-Host 'New SQL server' +$DB = Read-Host 'Name of Database' + +# Prompt the user whether they want to merge the database +Function Prompt { + CLS + $Title = "Database Backup Migration`n`n`tOLD SQL SERVER`t:`t$OldSQL`n`tNEW SQL SERVER`t:`t$NewSQL`n`tDATABASE`t:`t$DB" + $Message = "`nDo you want to merge database?" + $Yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Perform a database merge on $DB, moving it from $OldSQL to $NewSQL." + $No = New-Object System.Management.Automation.Host.ChoiceDescription "&No", 'Exit this utility.' + $Options = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No) + $Result = $Host.UI.PromptForChoice($Title, $Message, $Options, 0) + CLS + + switch ($Result) { + 0 { 'Merging...'; Start-Sleep -Seconds 1; Break } + 1 { 'Exiting...'; Start-Sleep -Seconds 1; Exit } + } + + Prompt +} + Prompt \ No newline at end of file diff --git a/Enable-BitLocker.ps1 b/Windows/Enable-BitLocker.ps1 similarity index 97% rename from Enable-BitLocker.ps1 rename to Windows/Enable-BitLocker.ps1 index c62ed5d..99182b5 100644 --- a/Enable-BitLocker.ps1 +++ b/Windows/Enable-BitLocker.ps1 @@ -1,71 +1,71 @@ -$Creds = Get-Credential - -$Computers = @( - 'computer1', 'computer2' -) - -# Log file -$Date = (Get-Date).ToString('yyyyMMdd-HHmm') -$LogFolder = New-Item -ItemType Directory "C:\Logs" -Force -$Log = New-Item -ItemType File "$LogFolder\Enable-BitLocker-$Date.log" -Force - -foreach ($Computer in $Computers) { - - Add-Content $Log "$Computer..." - - if (Test-Connection $Computer -Quiet -Count 3) { - - $Return = Invoke-Command -ComputerName $Computer -Credential $Creds -ScriptBlock { - - # Delete any current BitLocker pin - try { - $Result = ((manage-bde -protectors -delete C: | Select-String -Pattern "(?<=(ERROR:)).*").ToString()).TrimStart() - $Return = 'Deleted old protectors.' - } catch { - $Return += $Error - } - $Return += "`n" - - # Add new Pin and TPM security - try { - $Result = ((manage-bde -protectors -add C: -tp newpin | Select-String -Pattern "(?<=(ERROR:)).*").ToString()).TrimStart() - $Return += 'Added new protectors.' - } catch { - $Return += $Error - } - $Return += "`n" - - # Get the BitLocker's status after changes - $Result = ((manage-bde -status | Select-String -Pattern "(?<=(Protection Status:)).*").ToString()).Replace('Protection Status:', '').TrimStart() - if (!$Result) { - $Return += 'ERROR: Unable to capture BitLocker status.' - } elseif ($Result -eq 'Protection Off') { - # Enable protection - try { - $Result = ((manage-bde -protectors -enable C: | Select-String -Pattern "(?<=(ERROR:)).*").ToString()).TrimStart() - $Return += 'Enabled protectors.' - } catch { - $Return += $Error - } - $Return += "`n" - - # Get the BitLocker's status after changes - $Result = ((manage-bde -status | Select-String -Pattern "(?<=(Protection Status:)).*").ToString()).Replace('Protection Status:', '').TrimStart() - if ($Result -eq 'Protection Off') { - $Return += 'ERROR: Unable to enable protectors.' - } elseif ($Result -eq 'Protection On') { - $Return += $Result - } - } elseif ($Result -eq 'Protection On') { - $Return += $Result - } - - Return $Return - } - - Add-Content $Log $Return - - } else { - Add-Content $Log "ERROR: Failed to connect." - } +$Creds = Get-Credential + +$Computers = @( + 'computer1', 'computer2' +) + +# Log file +$Date = (Get-Date).ToString('yyyyMMdd-HHmm') +$LogFolder = New-Item -ItemType Directory "C:\Logs" -Force +$Log = New-Item -ItemType File "$LogFolder\Enable-BitLocker-$Date.log" -Force + +foreach ($Computer in $Computers) { + + Add-Content $Log "$Computer..." + + if (Test-Connection $Computer -Quiet -Count 3) { + + $Return = Invoke-Command -ComputerName $Computer -Credential $Creds -ScriptBlock { + + # Delete any current BitLocker pin + try { + $Result = ((manage-bde -protectors -delete C: | Select-String -Pattern "(?<=(ERROR:)).*").ToString()).TrimStart() + $Return = 'Deleted old protectors.' + } catch { + $Return += $Error + } + $Return += "`n" + + # Add new Pin and TPM security + try { + $Result = ((manage-bde -protectors -add C: -tp newpin | Select-String -Pattern "(?<=(ERROR:)).*").ToString()).TrimStart() + $Return += 'Added new protectors.' + } catch { + $Return += $Error + } + $Return += "`n" + + # Get the BitLocker's status after changes + $Result = ((manage-bde -status | Select-String -Pattern "(?<=(Protection Status:)).*").ToString()).Replace('Protection Status:', '').TrimStart() + if (!$Result) { + $Return += 'ERROR: Unable to capture BitLocker status.' + } elseif ($Result -eq 'Protection Off') { + # Enable protection + try { + $Result = ((manage-bde -protectors -enable C: | Select-String -Pattern "(?<=(ERROR:)).*").ToString()).TrimStart() + $Return += 'Enabled protectors.' + } catch { + $Return += $Error + } + $Return += "`n" + + # Get the BitLocker's status after changes + $Result = ((manage-bde -status | Select-String -Pattern "(?<=(Protection Status:)).*").ToString()).Replace('Protection Status:', '').TrimStart() + if ($Result -eq 'Protection Off') { + $Return += 'ERROR: Unable to enable protectors.' + } elseif ($Result -eq 'Protection On') { + $Return += $Result + } + } elseif ($Result -eq 'Protection On') { + $Return += $Result + } + + Return $Return + } + + Add-Content $Log $Return + + } else { + Add-Content $Log "ERROR: Failed to connect." + } } \ No newline at end of file From 188cfe0413768272ab9750be91f23569fc9c38a7 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Wed, 8 Mar 2017 11:00:55 -0500 Subject: [PATCH 04/65] Script to gather MFA details of MFA users. --- MSO/Get-MFAEnabledUser.ps1 | 18 ++++++++++++++++++ MSO/Remove-OneDriveIRM.ps1 | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 MSO/Get-MFAEnabledUser.ps1 diff --git a/MSO/Get-MFAEnabledUser.ps1 b/MSO/Get-MFAEnabledUser.ps1 new file mode 100644 index 0000000..5942245 --- /dev/null +++ b/MSO/Get-MFAEnabledUser.ps1 @@ -0,0 +1,18 @@ +Connect-MsolService + +Get-MsolUser -EnabledFilter EnabledOnly -All | ForEach-Object { + $AuthMethods = $_.StrongAuthenticationMethods + $DefaultMethod = ($AuthMethods | Where-Object -Property IsDefault -EQ $true).MethodType + + New-Object -TypeName psobject -Property @{ + UserPrincipalName = $_.UserPrincipalName + RelyingParty = $_.StrongAuthenticationRequirements.RelyingParty + RememberDevicesNotIssuedBefore = $_.StrongAuthenticationRequirements.RememberDevicesNotIssuedBefore + State = $_.StrongAuthenticationRequirements.State + DefaultMethod = $DefaultMethod + MethodType1 = $AuthMethods[0].MethodType + MethodType2 = $AuthMethods[1].MethodType + MethodType3 = $AuthMethods[2].MethodType + MethodType4 = $AuthMethods[3].MethodType + } +} | Export-Csv -Path ~\Downloads\MfaUsers.csv -NoTypeInformation \ No newline at end of file diff --git a/MSO/Remove-OneDriveIRM.ps1 b/MSO/Remove-OneDriveIRM.ps1 index d8d6ebf..c6711a7 100644 --- a/MSO/Remove-OneDriveIRM.ps1 +++ b/MSO/Remove-OneDriveIRM.ps1 @@ -1,7 +1,7 @@ # These 3 lines get the Documents site from SPO for the specified user -$webUrl = "https://company-my.sharepoint.com/personal/company” +$webUrl = "https://company-my.sharepoint.com/personal/company" $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl) -$list = $clientContext.Web.Lists.GetByTitle(“Documents") +$list = $clientContext.Web.Lists.GetByTitle("Documents") # reset the value to the default settings $list.InformationRightsManagementSettings.Reset() From 609bf0b3957c257effcb2abbc9a6940dd85d1ef3 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Wed, 8 Mar 2017 16:48:45 -0500 Subject: [PATCH 05/65] Updated icon converter and Outlook Quarantine folder scripts. --- .vscode/launch.json | 26 ++++++++ Data/ConvertTo-Icon.ps1 | 29 +++++++-- Office/Enable-OutlookQuarantineFolder.ps1 | 74 +++++++++++++---------- README.md | 1 + 4 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..19505d3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch (current file)", + "script": "${file}", + "args": [], + "cwd": "${file}" + }, + { + "type": "PowerShell", + "request": "attach", + "name": "PowerShell Attach to Host Process", + "processId": "${command.PickPSHostProcess}", + "runspaceId": 1 + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Interactive Session", + "cwd": "${workspaceRoot}" + } + ] +} \ No newline at end of file diff --git a/Data/ConvertTo-Icon.ps1 b/Data/ConvertTo-Icon.ps1 index f234164..6361c8b 100644 --- a/Data/ConvertTo-Icon.ps1 +++ b/Data/ConvertTo-Icon.ps1 @@ -1,4 +1,25 @@ -$IconBMP = [System.Drawing.Image]::FromStream($IconStream) -$IconBMP.MakeTransparent() -$Hicon = $IconBMP.GetHicon() -$IconBMP = [System.Drawing.Icon]::FromHandle($Hicon) \ No newline at end of file +[CmdletBinding()] +param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateScript({ + if (Test-Path -Path $_ -PathType Leaf) { + $true + } else { + throw "[$_] is not a valid file." + $false + } + })] + [string[]]$FilePath +) + +foreach ($Path in $FilePath) { + Add-Type -AssemblyName System.Drawing + + $File = Get-Item -Path $Path + $Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($File.FullName) + $Icon.ToBitmap().Save($File.FullName.Replace($File.Extension, '.ico')) +} \ No newline at end of file diff --git a/Office/Enable-OutlookQuarantineFolder.ps1 b/Office/Enable-OutlookQuarantineFolder.ps1 index 3d04cc1..ec6d164 100644 --- a/Office/Enable-OutlookQuarantineFolder.ps1 +++ b/Office/Enable-OutlookQuarantineFolder.ps1 @@ -1,38 +1,50 @@ -# Get Outlook object -$Outlook = New-Object -ComObject Outlook.Application - -# Get Inbox -$Namespace = $Outlook.GetNamespace('MAPI') -$Inbox = $Namespace.GetDefaultFolder(6) - -try { - # Create new Quarantine folder - $Quarantine = $Inbox.Folders.Add('Quarantine') -} catch { - # Get existing Quarantine folder +begin { Start-Transcript $env:TEMP\olQuarantineEnable.log } + +process { + # Get Outlook object + Add-Type -AssemblyName Microsoft.Office.Interop.Outlook + $Outlook = New-Object -ComObject Outlook.Application + + # Get Inbox + $Namespace = $Outlook.GetNamespace('MAPI') + $Inbox = $Namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox) + + try { + # Create new Quarantine folder + $Quarantine = $Inbox.Folders.Add('Quarantine') + } catch { + # Get existing Quarantine folder + $Quarantine = $Inbox.Folders | Where-Object -Property Name -eq 'Quarantine' + } + + # Set the web Url to the Quarantine admin site and set web view as default + $Quarantine.WebViewURL = 'https://admin.protection.outlook.com/quarantine' + $Quarantine.WebViewOn = $true + + # Add Quarantine folder to favorites + $OutlookModule = $Outlook.ActiveExplorer().NavigationPane.Modules.Item('Mail') + $Favorites = $OutlookModule.NavigationGroups.Item('Favorites') + $Favorites.NavigationFolders.Add($Quarantine) + + # Validate settings $Quarantine = $Inbox.Folders | Where-Object -Property Name -eq 'Quarantine' -} - -# Set the web Url to the Quarantine admin site and set web view as default -$Quarantine.WebViewURL = 'https://admin.protection.outlook.com/quarantine' -$Quarantine.WebViewOn = $true -# Add Quarantine folder to favorites -$OutlookModule = $Outlook.ActiveExplorer().NavigationPane.Modules.Item('Mail') -$Favorites = $OutlookModule.NavigationGroups.Item('Favorites') -$Favorites.NavigationFolders.Add($Quarantine) + if ($Quarantine) { + if ($Quarantine.WebViewURL -ne 'https://admin.protection.outlook.com/quarantine') { exit 996 } -# Validate settings -$Quarantine = $Inbox.Folders | Where-Object -Property Name -eq 'Quarantine' + if (-not $Quarantine.WebViewOn) { exit 997 } -if ($Quarantine) { - if ($Quarantine.WebViewURL -ne 'https://admin.protection.outlook.com/quarantine') { exit 999 } + if (-not $Favorites.NavigationFolders.Item($Quarantine.Name)) { exit 998 } - if (-not $Quarantine.WebViewOn) { exit 999 } - - if (-not $Favorites.NavigationFolders.Item($Quarantine.Name)) { exit 999 } + exit 0 + } else { + exit 995 + } +} - exit 0 -} else { - exit 999 +end { + # Used as detection method in SCCM + New-Item -Path 'HKLM:\Software\SMS' -ItemType Key -Force | + New-ItemProperty -Name olQuarantineEnabled -Value $LASTEXITCODE -PropertyType String -Force + Stop-Transcript } \ No newline at end of file diff --git a/README.md b/README.md index ed8b329..72fec3a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # PowerShell-Scripts Just a bunch of PowerShell scripts that help me get through the day. +Please feel free to submit issues and requests, I work in PowerShell nearly every day and am always happy to script. \ No newline at end of file From d318614b3c37ebf95a2ed77f3c565536cf3f5da4 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 9 Mar 2017 16:13:58 -0500 Subject: [PATCH 06/65] Script to monitor users with MFA enabled --- AD/Update-ADMFARegistrationGroup.ps1 | 18 ++++++++++++++++++ MSO/Get-MFAEnabledUser.ps1 | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 AD/Update-ADMFARegistrationGroup.ps1 diff --git a/AD/Update-ADMFARegistrationGroup.ps1 b/AD/Update-ADMFARegistrationGroup.ps1 new file mode 100644 index 0000000..4c68b9b --- /dev/null +++ b/AD/Update-ADMFARegistrationGroup.ps1 @@ -0,0 +1,18 @@ +Import-Module ActiveDirectory + +# Get all MSO users and their MFA status, convert into AD users +$MFAUsers = . "MSO\Get-MFAEnabledUser.ps1" +$RegisteredUsers = $MFAUsers | Where-Object -Property DefaultMethod -NE $null | ForEach-Object { + Get-ADUser -Filter "UserPrincipalName -eq '$($_.UserPrincipalName)'" +} + +# Get the group that holds unregistered MFA users and the users within +$UnregisteredGroup = Get-ADGroup -Identity 'MFA Registration Incomplete' +$UnregisteredUsers = $UnregisteredGroup | Get-ADGroupMember | Get-ADUser + +# Find users in the group that have a default MFA method +$UsersToRemove = $UnregisteredUsers | Compare-Object -ReferenceObject $RegisteredUsers -IncludeEqual | +Where-Object -Property SideIndicator -eq '==' + +# Remove users with a default MFA method +Remove-ADGroupMember -Identity $UnregisteredGroup -Members $UsersToRemove.InputObject \ No newline at end of file diff --git a/MSO/Get-MFAEnabledUser.ps1 b/MSO/Get-MFAEnabledUser.ps1 index 5942245..216eef1 100644 --- a/MSO/Get-MFAEnabledUser.ps1 +++ b/MSO/Get-MFAEnabledUser.ps1 @@ -15,4 +15,4 @@ Get-MsolUser -EnabledFilter EnabledOnly -All | ForEach-Object { MethodType3 = $AuthMethods[2].MethodType MethodType4 = $AuthMethods[3].MethodType } -} | Export-Csv -Path ~\Downloads\MfaUsers.csv -NoTypeInformation \ No newline at end of file +} \ No newline at end of file From 85c1583c1e861dca2bffc7f2204eb0ff2704e00a Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Fri, 10 Mar 2017 09:06:37 -0500 Subject: [PATCH 07/65] Scripts I use on my home servers. --- Send-LinuxCommand.ps1 | 81 ++++++++++++++++++++++ Windows/Enable-Ping.ps1 | 3 + Windows/Enable-RDP.ps1 | 3 + Windows/Get-BackupStatus.ps1 | 15 ++++ Windows/Get-ServerLoad.ps1 | 26 +++++++ Windows/Install-RemovedWindowsFeatures.ps1 | 5 ++ Windows/Set-TimeZoneEST.ps1 | 1 + 7 files changed, 134 insertions(+) create mode 100644 Send-LinuxCommand.ps1 create mode 100644 Windows/Enable-Ping.ps1 create mode 100644 Windows/Enable-RDP.ps1 create mode 100644 Windows/Get-BackupStatus.ps1 create mode 100644 Windows/Get-ServerLoad.ps1 create mode 100644 Windows/Install-RemovedWindowsFeatures.ps1 create mode 100644 Windows/Set-TimeZoneEST.ps1 diff --git a/Send-LinuxCommand.ps1 b/Send-LinuxCommand.ps1 new file mode 100644 index 0000000..0678d04 --- /dev/null +++ b/Send-LinuxCommand.ps1 @@ -0,0 +1,81 @@ +[CmdletBinding(SupportsShouldProcess = $true)] +Param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet( + 'Linux1', 'Linux2', 'All' + )] + [string[]]$Server, + [switch]$Sudo, + [switch]$Update, + [switch]$Upgrade, + [switch]$DistUpgrade, + [switch]$Clean, + [string[]]$Install, + [string]$Command, + [switch]$Reboot, + [switch]$AsJob, + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [PSCredential]$Credential +) + +$PLinkPath = '\\192.168.1.4\data\Programs\plink.exe' +if (-not (Test-Path -Path $PLinkPath)) { + Write-Warning "$PLinkPath not found, downloading..." + try { + Invoke-WebRequest -Uri 'https://the.earth.li/~sgtatham/putty/latest/w64/plink.exe' -OutFile $PLinkPath + } catch { + Write-Warning "Unable to download PLink." + Pause + exit + } +} + +$UserName = $Creds.GetNetworkCredential().UserName +$Password = $Creds.GetNetworkCredential().Password + +$ServerList = @{ + 'Linux1' = '192.168.1.2'; + 'Linux2' = '192.168.1.3'; +} + +$Commands = @("hostname") +if ($Command) { $Commands += $Command } +if ($Sudo) { $Commands += "echo $Password | sudo -S true;" } +if ($Update) { $Commands += "sudo apt -y update;" } +if ($Upgrade) { $Commands += "sudo apt -y upgrade;" } +if ($DistUpgrade) { $Commands += "sudo apt -y dist-upgrade;" } +if ($Clean) { $Commands += @("sudo apt -y autoremove;", "sudo apt -y autoclean;") } +if ($Install) { $Commands += "sudo apt -y install $Install;" } +if ($Reboot) { $Commands += "sudo shutdown -r;" } + +# Make sure the end of each line contains a semicolon +$Commands = $Commands | ForEach-Object { "$($_.ToString().TrimEnd(';'));" } + +if ($Server -eq 'All') { $Server = $ServerList.Keys } + +foreach ($ComputerName in $Server) { + if ($PSCmdlet.ShouldProcess($Server, "Run the following commands:`n$(($Commands | Out-String) -replace $Password, '***')")) { + if ($AsJob) { + Write-Verbose "Running commands as job..." + Start-Job -Name "$ComputerName Update" -ScriptBlock { + param($IP, $UserName, $Password, $Commands, $PLinkPath) + + Set-Alias plink $PLinkPath + Write-Output 'y' | plink -ssh $ServerList.$ComputerName -l $Username -pw $Password exit + plink -batch -ssh $ServerList.$ComputerName -l $UserName -pw $Password $Commands + } -ArgumentList $ServerList.$ComputerName, $UserName, $Password, $Commands, $PLinkPath + } else { + Set-Alias plink $PLinkPath + Write-Output 'y' | plink -ssh $ServerList.$ComputerName -l $Username -pw $Password exit + plink -batch -ssh $ServerList.$ComputerName -l $UserName -pw $Password $Commands + } + } +} + +if ($AsJob) { + Write-Verbose "Waiting for jobs to finish..." + Get-Job | Wait-Job | Receive-Job + Get-Job | Remove-Job -Force +} \ No newline at end of file diff --git a/Windows/Enable-Ping.ps1 b/Windows/Enable-Ping.ps1 new file mode 100644 index 0000000..d81b88b --- /dev/null +++ b/Windows/Enable-Ping.ps1 @@ -0,0 +1,3 @@ +Invoke-Command -ComputerName (Get-ADComputer -Filter *).DNSHostName -ScriptBlock { + New-NetFirewallRule –DisplayName “Allow Ping” –Direction Inbound –Action Allow –Protocol icmpv4 –Enabled True +} \ No newline at end of file diff --git a/Windows/Enable-RDP.ps1 b/Windows/Enable-RDP.ps1 new file mode 100644 index 0000000..a0dcec8 --- /dev/null +++ b/Windows/Enable-RDP.ps1 @@ -0,0 +1,3 @@ +Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server'-name "fDenyTSConnections" -Value 0 +Enable-NetFirewallRule -DisplayGroup "Remote Desktop" +Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -name "UserAuthentication" -Value 1 \ No newline at end of file diff --git a/Windows/Get-BackupStatus.ps1 b/Windows/Get-BackupStatus.ps1 new file mode 100644 index 0000000..a358f5b --- /dev/null +++ b/Windows/Get-BackupStatus.ps1 @@ -0,0 +1,15 @@ +if ($env:COMPUTERNAME -ne 'BackupServer') { + Write-Warning 'This needs to be run from [BackupServer]' + Pause + exit +} + +Get-WBJob + +while ((Get-WBJob).JobState -eq 'Running') { + (Get-WBJob).CurrentOperation + Start-Sleep -Seconds 30 +} + +Get-WBJob +Pause \ No newline at end of file diff --git a/Windows/Get-ServerLoad.ps1 b/Windows/Get-ServerLoad.ps1 new file mode 100644 index 0000000..bc86f69 --- /dev/null +++ b/Windows/Get-ServerLoad.ps1 @@ -0,0 +1,26 @@ +[CmdletBinding(SupportsShouldProcess = $true)] +Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [string[]]$ComputerName +) + +foreach ($Computer in $ComputerName) { + $CPULoad = Get-WmiObject -Class win32_processor -ComputerName $Computer | + Measure-Object -Property LoadPercentage -Average | Select-Object Average + + $MemLoad = Get-WmiObject -Class win32_operatingsystem -ComputerName $Computer | + Select-Object @{ + Name = "MemoryUsage" + Expression = { "{0:N2}" -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory) * 100) / $_.TotalVisibleMemorySize) } + } + + New-Object -TypeName PSObject -Property @{ + ComputerName = $Computer + CPUUsage = $CPULoad.Average + MemoryUsage = $MemLoad.MemoryUsage + } +} \ No newline at end of file diff --git a/Windows/Install-RemovedWindowsFeatures.ps1 b/Windows/Install-RemovedWindowsFeatures.ps1 new file mode 100644 index 0000000..833cb0b --- /dev/null +++ b/Windows/Install-RemovedWindowsFeatures.ps1 @@ -0,0 +1,5 @@ +# Get server index numbers +dism /get-wiminfo /wimfile:d:\sources\install.wim + +# Change feature name and final index number +Install-WindowsFeature -Name RDS-Gateway -Source wim:D:\sources\install.wim:1 \ No newline at end of file diff --git a/Windows/Set-TimeZoneEST.ps1 b/Windows/Set-TimeZoneEST.ps1 new file mode 100644 index 0000000..a7ee2f4 --- /dev/null +++ b/Windows/Set-TimeZoneEST.ps1 @@ -0,0 +1 @@ +Get-ADComputer -Filter * | Invoke-Command -ScriptBlock { tzutil /s "Eastern Standard Time" } \ No newline at end of file From 2fa4c14350df3e14090fbba0001603dc67941d5a Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 16 Mar 2017 15:16:43 -0400 Subject: [PATCH 08/65] Script to sync Gal of two o365 tenants. object table to html script for pretty emails --- Data/Convert-TableToHTML.ps1 | 22 +++++++ MSO/Sync-Gal.ps1 | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 Data/Convert-TableToHTML.ps1 create mode 100644 MSO/Sync-Gal.ps1 diff --git a/Data/Convert-TableToHTML.ps1 b/Data/Convert-TableToHTML.ps1 new file mode 100644 index 0000000..b17a8a1 --- /dev/null +++ b/Data/Convert-TableToHTML.ps1 @@ -0,0 +1,22 @@ +# You can embed the css in the script or alternatively you can reference a css file + +# File reference +$Message = $Table | ConvertTo-Html -CssUri $CSSFilePath | Out-String + +# Embed +$CSS = @" + +"@ +$Message = $Table | ConvertTo-Html -Head $CSS | Out-String + +# And then use this to send the email +Send-MailMessage -Body $Message -BodyAsHtml -From 'Alerts@company.com' ` + -SmtpServer smtp.company.local -Subject 'Report' -To 'me@company.com' \ No newline at end of file diff --git a/MSO/Sync-Gal.ps1 b/MSO/Sync-Gal.ps1 new file mode 100644 index 0000000..710ddc9 --- /dev/null +++ b/MSO/Sync-Gal.ps1 @@ -0,0 +1,113 @@ +# Syncs the Gal of Exchange Online across two Office 365 tenants +# Note that this will break the creation of external user objects until MS +# addresses a known issue: +# https://products.office.com/en-us/business/office-365-roadmap?filters=&featureid=72273 + +# Log everything +$LogDirectory = (New-Item -ItemType Directory "C:\powershell-scripts\Exchange\Logs" -Force).FullName +$Date = (Get-Date).ToString('yyyyMMdd-HHmm') +Start-Transcript -Path "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" + +# Get credentials for both tenants +Write-Host "Enter credentials for primary tenant" +$PrimaryTenantCreds = Get-Credential +Write-Host "Enter credentials for secondary tenant" +$SecondaryTenantCreds = Get-Credential + +# Create Exchange Online PowerShell session for both tenants +$PrimaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $PrimaryTenantCreds -Authentication Basic -AllowRedirection +$SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $SecondaryTenantCreds -Authentication Basic -AllowRedirection + +# Enter session on primary tenant +Import-PSSession $PrimaryTenantEOSession + +# Get all Gal recipients using the primary filter +$GalFilter = (Get-GlobalAddressList).RecipientFilter +$Gal = Get-Recipient -ResultSize Unlimited -Filter $GalFilter + +# Remove session on primary tenant +Remove-PSSession -Session $PrimaryTenantEOSession + +# Enter session on secondary tenant +Import-PSSession $SecondaryTenantEOSession + +# Create/Update contact for each Gal entry +$Gal | ForEach-Object { + # Create a new contact if one doesn't exist + if (Get-MailContact -Identity $_.Name -ErrorAction SilentlyContinue) { + Write-Verbose "Contact $($_.Name) already exists." + } else { + New-MailContact ` + -ExternalEmailAddress $_.PrimarySmtpAddress ` + -Name $_.Name ` + -FirstName $_.FirstName ` + -LastName $_.LastName ` + -DisplayName $_.DisplayName ` + -Alias $_.Alias + } + + # Update mail contact properties + Set-MailContact ` + -Identity $_.Name ` + -ExternalEmailAddress $_.PrimarySmtpAddress ` + -Name $_.Name ` + -DisplayName $_.DisplayName ` + -Alias $_.Alias ` + -WindowsEmailAddress $_.WindowsLiveID ` + -CustomAttribute1 $_.CustomAttribute1 ` + -CustomAttribute2 $_.CustomAttribute2 ` + -CustomAttribute3 $_.CustomAttribute3 ` + -CustomAttribute4 $_.CustomAttribute4 ` + -CustomAttribute5 $_.CustomAttribute5 ` + -CustomAttribute6 $_.CustomAttribute6 ` + -CustomAttribute7 $_.CustomAttribute7 ` + -CustomAttribute8 $_.CustomAttribute8 ` + -CustomAttribute9 $_.CustomAttribute9 ` + -CustomAttribute10 $_.CustomAttribute10 ` + -CustomAttribute11 $_.CustomAttribute11 ` + -CustomAttribute12 $_.CustomAttribute12 ` + -CustomAttribute13 $_.CustomAttribute13 ` + -CustomAttribute14 $_.CustomAttribute14 ` + -CustomAttribute15 $_.CustomAttribute15 ` + -ExtensionCustomAttribute1 $_.ExtensionCustomAttribute1 ` + -ExtensionCustomAttribute2 $_.ExtensionCustomAttribute2 ` + -ExtensionCustomAttribute3 $_.ExtensionCustomAttribute3 ` + -ExtensionCustomAttribute4 $_.ExtensionCustomAttribute4 ` + -ExtensionCustomAttribute5 $_.ExtensionCustomAttribute5 ` + + # Update contact properties + Set-Contact ` + -Identity $_.Name ` + -FirstName $_.FirstName ` + -LastName $_.LastName ` + -Department $_.Department ` + -Company $_.Company ` + -Phone $_.Phone ` + -HomePhone $_.HomePhone ` + -OtherHomePhone $_.OtherHomePhone ` + -MobilePhone $_.MobilePhone ` + -OtherTelephone $_.OtherTelephone ` + -Pager $_.Pager ` + -Fax $_.Fax ` + -OtherFax $_.OtherFax ` + -Office $_.Office ` + -CountryOrRegion $_.UsageLocation ` + -StreetAddress $_.StreetAddress ` + -City $_.City ` + -StateOrProvince $_.StateOrProvince ` + -PostalCode $_.PostalCode ` + -PostOfficeBox $_.PostOfficeBox ` + -Title $_.Title ` + -Manager $_.Manager ` + -AssistantName $_.AssistantName ` + -Notes $_.Notes +} + +# Remove session on secondary tenant +Remove-PSSession -Session $SecondaryTenantEOSession + +Stop-Transcript \ No newline at end of file From 821d90518638fa6c4ad2d2245708f23e11174854 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 16 Mar 2017 16:32:44 -0400 Subject: [PATCH 09/65] Quick script to get Azure AD Connect Version. --- Application Management/Get-AADConnectVersion.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Application Management/Get-AADConnectVersion.ps1 diff --git a/Application Management/Get-AADConnectVersion.ps1 b/Application Management/Get-AADConnectVersion.ps1 new file mode 100644 index 0000000..a48aff6 --- /dev/null +++ b/Application Management/Get-AADConnectVersion.ps1 @@ -0,0 +1,7 @@ +$Creds = Get-Credential +$DirSyncServer = 'dirsync01' + +Invoke-Command -ComputerName $DirSyncServer -Credential $Creds -ScriptBlock { + $RegKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" + Get-ItemProperty -Path $RegKey | Where-Object -Property DisplayName -EQ 'Microsoft Azure AD Connect' +} | Select-Object -Property DisplayName, PSPath, Version, DisplayVersion | Format-List \ No newline at end of file From 3a96dbbe945f2259eacd7a9fe21e024d9b19d6a1 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Fri, 17 Mar 2017 15:29:08 -0400 Subject: [PATCH 10/65] Some error handling added, moved to Exchange folder. --- Exchange/Sync-Gal.ps1 | 133 ++++++++++++++++++++++++++++++++++++++++++ MSO/Sync-Gal.ps1 | 113 ----------------------------------- 2 files changed, 133 insertions(+), 113 deletions(-) create mode 100644 Exchange/Sync-Gal.ps1 delete mode 100644 MSO/Sync-Gal.ps1 diff --git a/Exchange/Sync-Gal.ps1 b/Exchange/Sync-Gal.ps1 new file mode 100644 index 0000000..42ad846 --- /dev/null +++ b/Exchange/Sync-Gal.ps1 @@ -0,0 +1,133 @@ +# Syncs the Gal of Exchange Online across two Office 365 tenants +# Note that this will break the creation of external user objects until MS +# addresses a known issue: +# https://products.office.com/en-us/business/office-365-roadmap?filters=&featureid=72273 + +# Log everything +$LogDirectory = (New-Item -ItemType Directory "C:\powershell-scripts\Exchange Online\Logs" -Force).FullName +$Date = (Get-Date).ToString('yyyyMMdd-HHmm') +Start-Transcript -Path "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" + +# Get credentials for both tenants +Write-Host "Enter credentials for primary tenant" +$PrimaryTenantCreds = Get-Credential +Write-Host "Enter credentials for secondary tenant" +$SecondaryTenantCreds = Get-Credential + +# Create Exchange Online PowerShell session for both tenants +$PrimaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $PrimaryTenantCreds -Authentication Basic -AllowRedirection +$SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $SecondaryTenantCreds -Authentication Basic -AllowRedirection + +# Enter session on primary tenant +Import-PSSession $PrimaryTenantEOSession + +# Get all Gal recipients using the primary filter +$GalFilter = (Get-GlobalAddressList).RecipientFilter +$Gal = Get-Recipient -ResultSize Unlimited -Filter $GalFilter + +# Export Gal to Csv file +$Gal | Export-Csv -Path "$LogDirectory\Gal.csv" -NoTypeInformation -Force + +# Remove session on primary tenant +Remove-PSSession -Session $PrimaryTenantEOSession + +# Enter session on secondary tenant +Import-PSSession $SecondaryTenantEOSession + +# Create/Update contact for each Gal entry +$Gal | ForEach-Object { + # Create a new contact if one doesn't exist + if (Get-MailContact -Identity $_.Name -ErrorAction SilentlyContinue) { + Write-Host "Contact $($_.Name) already exists." + } else { + try { + New-MailContact ` + -ExternalEmailAddress $_.PrimarySmtpAddress ` + -Name $_.Name ` + -FirstName $_.FirstName ` + -LastName $_.LastName ` + -DisplayName $_.DisplayName ` + -Alias $_.Alias + Write-Host "New contact created for $($_.Name)" + } catch { + Write-Host "Error creating new contact for $($_.Name): $_" + } + } + + try { + # Update mail contact properties + Set-MailContact ` + -Identity $_.Name ` + -ExternalEmailAddress $_.PrimarySmtpAddress ` + -Name $_.Name ` + -DisplayName $_.DisplayName ` + -Alias $_.Alias ` + -CustomAttribute1 $_.CustomAttribute1 ` + -CustomAttribute2 $_.CustomAttribute2 ` + -CustomAttribute3 $_.CustomAttribute3 ` + -CustomAttribute4 $_.CustomAttribute4 ` + -CustomAttribute5 $_.CustomAttribute5 ` + -CustomAttribute6 $_.CustomAttribute6 ` + -CustomAttribute7 $_.CustomAttribute7 ` + -CustomAttribute8 $_.CustomAttribute8 ` + -CustomAttribute9 $_.CustomAttribute9 ` + -CustomAttribute10 $_.CustomAttribute10 ` + -CustomAttribute11 $_.CustomAttribute11 ` + -CustomAttribute12 $_.CustomAttribute12 ` + -CustomAttribute13 $_.CustomAttribute13 ` + -CustomAttribute14 $_.CustomAttribute14 ` + -CustomAttribute15 $_.CustomAttribute15 ` + -ExtensionCustomAttribute1 $_.ExtensionCustomAttribute1 ` + -ExtensionCustomAttribute2 $_.ExtensionCustomAttribute2 ` + -ExtensionCustomAttribute3 $_.ExtensionCustomAttribute3 ` + -ExtensionCustomAttribute4 $_.ExtensionCustomAttribute4 ` + -ExtensionCustomAttribute5 $_.ExtensionCustomAttribute5 ` + | Out-Null + + # Update Windows Email Address only if it's populated + if ($_.WindowsLiveID -ne $null) { Set-MailContact -WindowsEmailAddress $_.WindowsLiveID | Out-Null } + } catch { + Write-Host "Error updating mail contact info for $($_.Name): $_" + } + + try { + # Update contact properties + Set-Contact ` + -Identity $_.Name ` + -FirstName $_.FirstName ` + -LastName $_.LastName ` + -Department $_.Department ` + -Company $_.Company ` + -Phone $_.Phone ` + -HomePhone $_.HomePhone ` + -OtherHomePhone $_.OtherHomePhone ` + -MobilePhone $_.MobilePhone ` + -OtherTelephone $_.OtherTelephone ` + -Pager $_.Pager ` + -Fax $_.Fax ` + -OtherFax $_.OtherFax ` + -Office $_.Office ` + -CountryOrRegion $_.UsageLocation ` + -StreetAddress $_.StreetAddress ` + -City $_.City ` + -StateOrProvince $_.StateOrProvince ` + -PostalCode $_.PostalCode ` + -PostOfficeBox $_.PostOfficeBox ` + -Title $_.Title ` + -Manager $_.Manager ` + -AssistantName $_.AssistantName ` + -Notes $_.Notes ` + | Out-Null + } catch { + Write-Host "Error updating contact info for $($_.Name): $_" + } +} + +# Remove session on secondary tenant +Remove-PSSession -Session $SecondaryTenantEOSession + +Stop-Transcript \ No newline at end of file diff --git a/MSO/Sync-Gal.ps1 b/MSO/Sync-Gal.ps1 deleted file mode 100644 index 710ddc9..0000000 --- a/MSO/Sync-Gal.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -# Syncs the Gal of Exchange Online across two Office 365 tenants -# Note that this will break the creation of external user objects until MS -# addresses a known issue: -# https://products.office.com/en-us/business/office-365-roadmap?filters=&featureid=72273 - -# Log everything -$LogDirectory = (New-Item -ItemType Directory "C:\powershell-scripts\Exchange\Logs" -Force).FullName -$Date = (Get-Date).ToString('yyyyMMdd-HHmm') -Start-Transcript -Path "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" - -# Get credentials for both tenants -Write-Host "Enter credentials for primary tenant" -$PrimaryTenantCreds = Get-Credential -Write-Host "Enter credentials for secondary tenant" -$SecondaryTenantCreds = Get-Credential - -# Create Exchange Online PowerShell session for both tenants -$PrimaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` - -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` - -Credential $PrimaryTenantCreds -Authentication Basic -AllowRedirection -$SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` - -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` - -Credential $SecondaryTenantCreds -Authentication Basic -AllowRedirection - -# Enter session on primary tenant -Import-PSSession $PrimaryTenantEOSession - -# Get all Gal recipients using the primary filter -$GalFilter = (Get-GlobalAddressList).RecipientFilter -$Gal = Get-Recipient -ResultSize Unlimited -Filter $GalFilter - -# Remove session on primary tenant -Remove-PSSession -Session $PrimaryTenantEOSession - -# Enter session on secondary tenant -Import-PSSession $SecondaryTenantEOSession - -# Create/Update contact for each Gal entry -$Gal | ForEach-Object { - # Create a new contact if one doesn't exist - if (Get-MailContact -Identity $_.Name -ErrorAction SilentlyContinue) { - Write-Verbose "Contact $($_.Name) already exists." - } else { - New-MailContact ` - -ExternalEmailAddress $_.PrimarySmtpAddress ` - -Name $_.Name ` - -FirstName $_.FirstName ` - -LastName $_.LastName ` - -DisplayName $_.DisplayName ` - -Alias $_.Alias - } - - # Update mail contact properties - Set-MailContact ` - -Identity $_.Name ` - -ExternalEmailAddress $_.PrimarySmtpAddress ` - -Name $_.Name ` - -DisplayName $_.DisplayName ` - -Alias $_.Alias ` - -WindowsEmailAddress $_.WindowsLiveID ` - -CustomAttribute1 $_.CustomAttribute1 ` - -CustomAttribute2 $_.CustomAttribute2 ` - -CustomAttribute3 $_.CustomAttribute3 ` - -CustomAttribute4 $_.CustomAttribute4 ` - -CustomAttribute5 $_.CustomAttribute5 ` - -CustomAttribute6 $_.CustomAttribute6 ` - -CustomAttribute7 $_.CustomAttribute7 ` - -CustomAttribute8 $_.CustomAttribute8 ` - -CustomAttribute9 $_.CustomAttribute9 ` - -CustomAttribute10 $_.CustomAttribute10 ` - -CustomAttribute11 $_.CustomAttribute11 ` - -CustomAttribute12 $_.CustomAttribute12 ` - -CustomAttribute13 $_.CustomAttribute13 ` - -CustomAttribute14 $_.CustomAttribute14 ` - -CustomAttribute15 $_.CustomAttribute15 ` - -ExtensionCustomAttribute1 $_.ExtensionCustomAttribute1 ` - -ExtensionCustomAttribute2 $_.ExtensionCustomAttribute2 ` - -ExtensionCustomAttribute3 $_.ExtensionCustomAttribute3 ` - -ExtensionCustomAttribute4 $_.ExtensionCustomAttribute4 ` - -ExtensionCustomAttribute5 $_.ExtensionCustomAttribute5 ` - - # Update contact properties - Set-Contact ` - -Identity $_.Name ` - -FirstName $_.FirstName ` - -LastName $_.LastName ` - -Department $_.Department ` - -Company $_.Company ` - -Phone $_.Phone ` - -HomePhone $_.HomePhone ` - -OtherHomePhone $_.OtherHomePhone ` - -MobilePhone $_.MobilePhone ` - -OtherTelephone $_.OtherTelephone ` - -Pager $_.Pager ` - -Fax $_.Fax ` - -OtherFax $_.OtherFax ` - -Office $_.Office ` - -CountryOrRegion $_.UsageLocation ` - -StreetAddress $_.StreetAddress ` - -City $_.City ` - -StateOrProvince $_.StateOrProvince ` - -PostalCode $_.PostalCode ` - -PostOfficeBox $_.PostOfficeBox ` - -Title $_.Title ` - -Manager $_.Manager ` - -AssistantName $_.AssistantName ` - -Notes $_.Notes -} - -# Remove session on secondary tenant -Remove-PSSession -Session $SecondaryTenantEOSession - -Stop-Transcript \ No newline at end of file From ea385b80bf5b3fa8753b15826fee3217058bcce4 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Fri, 17 Mar 2017 16:29:19 -0400 Subject: [PATCH 11/65] Forgot identity param... --- Exchange/Sync-Gal.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Exchange/Sync-Gal.ps1 b/Exchange/Sync-Gal.ps1 index 42ad846..ba7d824 100644 --- a/Exchange/Sync-Gal.ps1 +++ b/Exchange/Sync-Gal.ps1 @@ -41,7 +41,7 @@ Import-PSSession $SecondaryTenantEOSession # Create/Update contact for each Gal entry $Gal | ForEach-Object { # Create a new contact if one doesn't exist - if (Get-MailContact -Identity $_.Name -ErrorAction SilentlyContinue) { + if (Get-MailContact -Identity $_.Name) { Write-Host "Contact $($_.Name) already exists." } else { try { @@ -89,7 +89,7 @@ $Gal | ForEach-Object { | Out-Null # Update Windows Email Address only if it's populated - if ($_.WindowsLiveID -ne $null) { Set-MailContact -WindowsEmailAddress $_.WindowsLiveID | Out-Null } + if ($_.WindowsLiveID -ne '') { Set-MailContact -Identity $_.Name -WindowsEmailAddress $_.WindowsLiveID | Out-Null } } catch { Write-Host "Error updating mail contact info for $($_.Name): $_" } From 921c5f6a4086541b8672a9dbebefc7957ce35128 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Wed, 22 Mar 2017 08:19:07 -0400 Subject: [PATCH 12/65] Updated scripts to sync Gal of 2 O365 tenants. --- Exchange/Sync-Gal.ps1 | 245 +++++++++++++++++---------------- Exchange/Update-GalContact.ps1 | 110 +++++++++++++++ 2 files changed, 233 insertions(+), 122 deletions(-) create mode 100644 Exchange/Update-GalContact.ps1 diff --git a/Exchange/Sync-Gal.ps1 b/Exchange/Sync-Gal.ps1 index ba7d824..00eb153 100644 --- a/Exchange/Sync-Gal.ps1 +++ b/Exchange/Sync-Gal.ps1 @@ -1,133 +1,134 @@ +<# + .DESCRIPTION + Does a one-way sync between two Exchange Online Gal's. + + .PARAMETER AsJob + Enables the updates to be run as PS Jobs. + + .PARAMETER Jobs + The number of PS Jobs to create to run the updates. Limited to 3 because of an O365 default max concurrent connection. + + .EXAMPLE + ./Sync-Gal.ps1 -AsJob -Jobs 3 + + .EXAMPLE + ./Sync-Gal.ps1 + + .NOTES + Created by Nick Rodriguez +#> +[CmdletBinding( + SupportsShouldProcess = $true, + DefaultParameterSetName = 'Synchronous' +)] +Param( + [Parameter( + Mandatory = $false, + ParameterSetName = 'Asynchronous' + )] + [Switch] + $AsJob, + + [Parameter( + Mandatory = $false, + ParameterSetName = 'Asynchronous' + )] + [ValidateRange(1, 3)] + [Int] + $Jobs = 2 +) + # Syncs the Gal of Exchange Online across two Office 365 tenants # Note that this will break the creation of external user objects until MS # addresses a known issue: # https://products.office.com/en-us/business/office-365-roadmap?filters=&featureid=72273 -# Log everything -$LogDirectory = (New-Item -ItemType Directory "C:\powershell-scripts\Exchange Online\Logs" -Force).FullName -$Date = (Get-Date).ToString('yyyyMMdd-HHmm') -Start-Transcript -Path "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" - -# Get credentials for both tenants -Write-Host "Enter credentials for primary tenant" -$PrimaryTenantCreds = Get-Credential -Write-Host "Enter credentials for secondary tenant" -$SecondaryTenantCreds = Get-Credential - -# Create Exchange Online PowerShell session for both tenants -$PrimaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` - -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` - -Credential $PrimaryTenantCreds -Authentication Basic -AllowRedirection -$SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` - -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` - -Credential $SecondaryTenantCreds -Authentication Basic -AllowRedirection - -# Enter session on primary tenant -Import-PSSession $PrimaryTenantEOSession - -# Get all Gal recipients using the primary filter -$GalFilter = (Get-GlobalAddressList).RecipientFilter -$Gal = Get-Recipient -ResultSize Unlimited -Filter $GalFilter - -# Export Gal to Csv file -$Gal | Export-Csv -Path "$LogDirectory\Gal.csv" -NoTypeInformation -Force - -# Remove session on primary tenant -Remove-PSSession -Session $PrimaryTenantEOSession - -# Enter session on secondary tenant -Import-PSSession $SecondaryTenantEOSession - -# Create/Update contact for each Gal entry -$Gal | ForEach-Object { - # Create a new contact if one doesn't exist - if (Get-MailContact -Identity $_.Name) { - Write-Host "Contact $($_.Name) already exists." - } else { - try { - New-MailContact ` - -ExternalEmailAddress $_.PrimarySmtpAddress ` - -Name $_.Name ` - -FirstName $_.FirstName ` - -LastName $_.LastName ` - -DisplayName $_.DisplayName ` - -Alias $_.Alias - Write-Host "New contact created for $($_.Name)" - } catch { - Write-Host "Error creating new contact for $($_.Name): $_" +begin { + # Log everything + $LogDirectory = (New-Item -ItemType Directory "C:\powershell-scripts\Exchange Online\Logs" -Force).FullName + $Date = (Get-Date).ToString('yyyyMMdd-HHmm') + Start-Transcript -Path "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" +} + +process { + # Get credentials for both tenants + Write-Host "Enter credentials for primary tenant" + $PrimaryTenantCreds = Get-Credential + Write-Host "Enter credentials for secondary tenant" + $SecondaryTenantCreds = Get-Credential + + # Create Exchange Online PowerShell session for primary tenant + $PrimaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $PrimaryTenantCreds -Authentication Basic -AllowRedirection + + # Enter session on primary tenant + Import-PSSession $PrimaryTenantEOSession + + # Get all Gal recipients using the primary filter + $GalFilter = (Get-GlobalAddressList).RecipientFilter + $Gal = Get-Recipient -ResultSize Unlimited -Filter $GalFilter + + # Export Gal to Csv file + $Gal | Export-Csv -Path "$LogDirectory\Gal.csv" -NoTypeInformation -Force + + # Remove session on primary tenant + Remove-PSSession -Session $PrimaryTenantEOSession + + # Create/Update contact for each Gal entry + # If Jobs param specified, break up the list into smaller lists that can be started as jobs + if ($AsJob) { + $ContactLists = @{} + $Count = 0 + + # Separate the contacts into smaller lists + $Gal | ForEach-Object { + $ContactLists[$Count % $Jobs] += @($_) + $Count++ } - } - try { - # Update mail contact properties - Set-MailContact ` - -Identity $_.Name ` - -ExternalEmailAddress $_.PrimarySmtpAddress ` - -Name $_.Name ` - -DisplayName $_.DisplayName ` - -Alias $_.Alias ` - -CustomAttribute1 $_.CustomAttribute1 ` - -CustomAttribute2 $_.CustomAttribute2 ` - -CustomAttribute3 $_.CustomAttribute3 ` - -CustomAttribute4 $_.CustomAttribute4 ` - -CustomAttribute5 $_.CustomAttribute5 ` - -CustomAttribute6 $_.CustomAttribute6 ` - -CustomAttribute7 $_.CustomAttribute7 ` - -CustomAttribute8 $_.CustomAttribute8 ` - -CustomAttribute9 $_.CustomAttribute9 ` - -CustomAttribute10 $_.CustomAttribute10 ` - -CustomAttribute11 $_.CustomAttribute11 ` - -CustomAttribute12 $_.CustomAttribute12 ` - -CustomAttribute13 $_.CustomAttribute13 ` - -CustomAttribute14 $_.CustomAttribute14 ` - -CustomAttribute15 $_.CustomAttribute15 ` - -ExtensionCustomAttribute1 $_.ExtensionCustomAttribute1 ` - -ExtensionCustomAttribute2 $_.ExtensionCustomAttribute2 ` - -ExtensionCustomAttribute3 $_.ExtensionCustomAttribute3 ` - -ExtensionCustomAttribute4 $_.ExtensionCustomAttribute4 ` - -ExtensionCustomAttribute5 $_.ExtensionCustomAttribute5 ` - | Out-Null - - # Update Windows Email Address only if it's populated - if ($_.WindowsLiveID -ne '') { Set-MailContact -Identity $_.Name -WindowsEmailAddress $_.WindowsLiveID | Out-Null } - } catch { - Write-Host "Error updating mail contact info for $($_.Name): $_" - } + # Create a job for each sublist of contacts + foreach ($List in $ContactLists.Values) { + Start-Job -ArgumentList $SecondaryTenantCreds, $List -ScriptBlock { + # Create Exchange Online PS session + $SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $args[0] -Authentication Basic -AllowRedirection - try { - # Update contact properties - Set-Contact ` - -Identity $_.Name ` - -FirstName $_.FirstName ` - -LastName $_.LastName ` - -Department $_.Department ` - -Company $_.Company ` - -Phone $_.Phone ` - -HomePhone $_.HomePhone ` - -OtherHomePhone $_.OtherHomePhone ` - -MobilePhone $_.MobilePhone ` - -OtherTelephone $_.OtherTelephone ` - -Pager $_.Pager ` - -Fax $_.Fax ` - -OtherFax $_.OtherFax ` - -Office $_.Office ` - -CountryOrRegion $_.UsageLocation ` - -StreetAddress $_.StreetAddress ` - -City $_.City ` - -StateOrProvince $_.StateOrProvince ` - -PostalCode $_.PostalCode ` - -PostOfficeBox $_.PostOfficeBox ` - -Title $_.Title ` - -Manager $_.Manager ` - -AssistantName $_.AssistantName ` - -Notes $_.Notes ` - | Out-Null - } catch { - Write-Host "Error updating contact info for $($_.Name): $_" + # Enter session on secondary tenant + Import-PSSession $SecondaryTenantEOSession + + Update-GalContact -Gal $args[1] + + # Remove session on secondary tenant + Remove-PSSession -Session $SecondaryTenantEOSession + } -InitializationScript { . 'C:\powershell-scripts\Exchange Online\Update-GalContact.ps1' } + } + + # Wait for all jobs to finish then receive and remove the jobs + Get-Job | Wait-Job | Receive-Job + Get-Job | Remove-Job + } else { + # Import function + . 'C:\powershell-scripts\Exchange Online\Update-GalContact.ps1' + + # Create Exchange Online PS session + $SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential $SecondaryTenantCreds -Authentication Basic -AllowRedirection + + # Enter session on secondary tenant + Import-PSSession $SecondaryTenantEOSession + + Update-GalContact -Gal $Gal + + # Remove session on secondary tenant + Remove-PSSession -Session $SecondaryTenantEOSession } } -# Remove session on secondary tenant -Remove-PSSession -Session $SecondaryTenantEOSession - -Stop-Transcript \ No newline at end of file +end { + # Remove any lingering PSSessions and stop the logging + Get-PSSession | Remove-PSSession + Stop-Transcript +} \ No newline at end of file diff --git a/Exchange/Update-GalContact.ps1 b/Exchange/Update-GalContact.ps1 new file mode 100644 index 0000000..9c90afc --- /dev/null +++ b/Exchange/Update-GalContact.ps1 @@ -0,0 +1,110 @@ +function Update-GalContact { + <# + .DESCRIPTION + Takes an array of recipients and updates the Gal. + + .PARAMETER Gal + The recipient or list of reipients to update. + + .EXAMPLE + Update-GalContact -Gal $ExternalGal + + .NOTES + Created by Nick Rodriguez + This is a helper function for Sync-Gal.ps1 + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [PSObject[]]$Gal + ) + + foreach ($Recipient in $Gal) { + # Create a new contact if one doesn't exist + if (Get-MailContact -Identity $Recipient.Name) { + Write-Host "Contact $($Recipient.Name) already exists." + } else { + try { + New-MailContact ` + -ExternalEmailAddress $Recipient.PrimarySmtpAddress ` + -Name $Recipient.Name ` + -FirstName $Recipient.FirstName ` + -LastName $Recipient.LastName ` + -DisplayName $Recipient.DisplayName ` + -Alias $Recipient.Alias + Write-Host "New contact created for $($Recipient.Name)" + } catch { + Write-Host "Error creating new contact for $($Recipient.Name): $_" + } + } + + try { + # Update mail contact properties + Set-MailContact ` + -Identity $Recipient.Name ` + -ExternalEmailAddress $Recipient.PrimarySmtpAddress ` + -Name $Recipient.Name ` + -DisplayName $Recipient.DisplayName ` + -Alias $Recipient.Alias ` + -CustomAttribute1 $Recipient.CustomAttribute1 ` + -CustomAttribute2 $Recipient.CustomAttribute2 ` + -CustomAttribute3 $Recipient.CustomAttribute3 ` + -CustomAttribute4 $Recipient.CustomAttribute4 ` + -CustomAttribute5 $Recipient.CustomAttribute5 ` + -CustomAttribute6 $Recipient.CustomAttribute6 ` + -CustomAttribute7 $Recipient.CustomAttribute7 ` + -CustomAttribute8 $Recipient.CustomAttribute8 ` + -CustomAttribute9 $Recipient.CustomAttribute9 ` + -CustomAttribute10 $Recipient.CustomAttribute10 ` + -CustomAttribute11 $Recipient.CustomAttribute11 ` + -CustomAttribute12 $Recipient.CustomAttribute12 ` + -CustomAttribute13 $Recipient.CustomAttribute13 ` + -CustomAttribute14 $Recipient.CustomAttribute14 ` + -CustomAttribute15 $Recipient.CustomAttribute15 ` + -ExtensionCustomAttribute1 $Recipient.ExtensionCustomAttribute1 ` + -ExtensionCustomAttribute2 $Recipient.ExtensionCustomAttribute2 ` + -ExtensionCustomAttribute3 $Recipient.ExtensionCustomAttribute3 ` + -ExtensionCustomAttribute4 $Recipient.ExtensionCustomAttribute4 ` + -ExtensionCustomAttribute5 $Recipient.ExtensionCustomAttribute5 ` + | Out-Null + + # Update Windows Email Address only if it's populated + if ($Recipient.WindowsLiveID -ne '') { + Set-MailContact -Identity $Recipient.Name -WindowsEmailAddress $Recipient.WindowsLiveID | Out-Null + } + } catch { + Write-Host "Error updating mail contact info for $($Recipient.Name): $_" + } + + try { + # Update contact properties + Set-Contact ` + -Identity $Recipient.Name ` + -FirstName $Recipient.FirstName ` + -LastName $Recipient.LastName ` + -Department $Recipient.Department ` + -Company $Recipient.Company ` + -Phone $Recipient.Phone ` + -HomePhone $Recipient.HomePhone ` + -OtherHomePhone $Recipient.OtherHomePhone ` + -MobilePhone $Recipient.MobilePhone ` + -OtherTelephone $Recipient.OtherTelephone ` + -Pager $Recipient.Pager ` + -Fax $Recipient.Fax ` + -OtherFax $Recipient.OtherFax ` + -Office $Recipient.Office ` + -CountryOrRegion $Recipient.UsageLocation ` + -StreetAddress $Recipient.StreetAddress ` + -City $Recipient.City ` + -StateOrProvince $Recipient.StateOrProvince ` + -PostalCode $Recipient.PostalCode ` + -PostOfficeBox $Recipient.PostOfficeBox ` + -Title $Recipient.Title ` + -Manager $Recipient.Manager ` + -AssistantName $Recipient.AssistantName ` + -Notes $Recipient.Notes ` + | Out-Null + } catch { + Write-Host "Error updating contact info for $($Recipient.Name): $_" + } + } +} \ No newline at end of file From 89bf8a4758aea94ddd563da40f97280e8a8ccdc3 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 23 Mar 2017 16:46:21 -0400 Subject: [PATCH 13/65] Script to start and Exchange Online PowerShell session. --- Exchange/Enter-EOSession.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Exchange/Enter-EOSession.ps1 diff --git a/Exchange/Enter-EOSession.ps1 b/Exchange/Enter-EOSession.ps1 new file mode 100644 index 0000000..9ed7349 --- /dev/null +++ b/Exchange/Enter-EOSession.ps1 @@ -0,0 +1,7 @@ +# If you get an Access Denied message but you're a member of Exchange Online Admins, +# make sure you don't have MFA enabled +$ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` + -Credential (Get-Credential) -Authentication Basic -AllowRedirection + +Import-PSSession $ExchangeOnlineSession \ No newline at end of file From 75a312842dd37da584f569462dd0a0ce3423921b Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Tue, 28 Mar 2017 16:10:25 -0400 Subject: [PATCH 14/65] Combined GalSync scripts into one module. --- Exchange/GalSync.psm1 | 277 +++++++++++++++++++++++++++++++++ Exchange/Sync-Gal.ps1 | 134 ---------------- Exchange/Update-GalContact.ps1 | 110 ------------- 3 files changed, 277 insertions(+), 244 deletions(-) create mode 100644 Exchange/GalSync.psm1 delete mode 100644 Exchange/Sync-Gal.ps1 delete mode 100644 Exchange/Update-GalContact.ps1 diff --git a/Exchange/GalSync.psm1 b/Exchange/GalSync.psm1 new file mode 100644 index 0000000..45774bc --- /dev/null +++ b/Exchange/GalSync.psm1 @@ -0,0 +1,277 @@ +function Sync-Gal { + <# + .DESCRIPTION + Does a one-way sync between two Exchange Online Gal's. + + .PARAMETER PrimaryTenantCreds + Credentials to login to the primary tenant. + + .PARAMETER SecondaryTenantCreds + Credentials to login to the secondary tenant. + + .PARAMETER AsJob + Enables the updates to be run as PS Jobs. + + .PARAMETER Jobs + The number of PS Jobs to create to run the updates. Limited to 3 because of an O365 default max concurrent connection. + + .PARAMETER ContactLimit + Number of contacts to sync. Default is Unlimited. Useful for doing quick tests. + + .EXAMPLE + ./Sync-Gal.ps1 -PrimaryTenantCreds (Get-Credential) -SecondaryTenantCreds (Get-Credential) -AsJob -Jobs 3 + + .EXAMPLE + ./Sync-Gal.ps1 + + .NOTES + Created by Nick Rodriguez + Syncs the Gal of Exchange Online across two Office 365 tenants + Note that this will break the creation of external user objects until MS addresses a known issue: + https://products.office.com/en-us/business/office-365-roadmap?filters=&featureid=72273 + #> + [CmdletBinding( + SupportsShouldProcess = $true, + DefaultParameterSetName = 'Synchronous' + )] + Param( + [Parameter(Mandatory = $true)] + [PSCredential] + $PrimaryTenantCreds, + + [Parameter(Mandatory = $true)] + [PSCredential] + $SecondaryTenantCreds, + + [Parameter( + Mandatory = $false, + ParameterSetName = 'Asynchronous' + )] + [Switch] + $AsJob, + + [Parameter( + Mandatory = $false, + ParameterSetName = 'Asynchronous' + )] + [ValidateRange(1, 3)] + [Int] + $Jobs = 2, + + [Parameter(Mandatory = $false)] + [ValidateRange(0, [Int]::MaxValue)] + [Int] + $ContactLimit = 0 + ) + + begin { + # Log everything + $LogDirectory = (New-Item -ItemType Directory "C:\powershell-scripts\Exchange Online\Logs" -Force).FullName + $Date = (Get-Date).ToString('yyyyMMdd-HHmm') + Start-Transcript -Path "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" + } + + process { + # Create Exchange Online PowerShell session for primary tenant + $PrimaryTenantEOSession = New-EOSession -Credential $PrimaryTenantCreds + + # Enter session on primary tenant + Import-PSSession $PrimaryTenantEOSession + + # Get all Gal recipients using the primary filter + $GalFilter = (Get-GlobalAddressList).RecipientFilter + $ResultSizeLimit = if ($ContactLimit -eq 0) { 'Unlimited' } else { $ContactLimit } + $Gal = Get-Recipient -ResultSize $ResultSizeLimit -Filter $GalFilter + + # Export Gal to Csv file + $Gal | Export-Csv -Path "$LogDirectory\Gal.csv" -NoTypeInformation -Force + + # Remove session on primary tenant + Remove-PSSession -Session $PrimaryTenantEOSession + + # Create/Update contact for each Gal entry + # If Jobs param specified, break up the list into smaller lists that can be started as jobs + if ($AsJob) { + $ContactLists = @{} + $Count = 0 + + # Separate the contacts into smaller lists + $Gal | ForEach-Object { + $ContactLists[$Count % $Jobs] += @($_) + $Count++ + } + + # Create a job for each sublist of contacts + foreach ($ContactList in $ContactLists.Values) { + Start-Job -ArgumentList $SecondaryTenantCreds, $ContactList -ScriptBlock { + # Create Exchange Online PS session + $SecondaryTenantEOSession = New-EOSession -Credential $args[0] + + # Enter session on secondary tenant + Import-PSSession $SecondaryTenantEOSession + + Update-GalContact -Gal $args[1] + + # Remove session on secondary tenant + Remove-PSSession -Session $SecondaryTenantEOSession + } -InitializationScript { Import-Module 'C:\powershell-scripts\Exchange Online\GalSync.psm1' } + } + + # Wait for all jobs to finish then receive and remove the jobs + Get-Job | Wait-Job | Receive-Job + Get-Job | Remove-Job + } else { + # Create Exchange Online PS session + $SecondaryTenantEOSession = New-EOSession -Credential $SecondaryTenantCreds + + # Enter session on secondary tenant + Import-PSSession $SecondaryTenantEOSession + + Update-GalContact -Gal $Gal + + # Remove session on secondary tenant + Remove-PSSession -Session $SecondaryTenantEOSession + } + } + + end { + # Remove any lingering PSSessions and stop the logging + Get-PSSession | Remove-PSSession + Stop-Transcript + } +} + +function New-EOSession { + <# + .DESCRIPTION + Create a new Exchange Online PowerShell session + + .PARAMETER Credential + The credentials to use for the session. + + .EXAMPLE + New-EOSession -Credential $creds + + .NOTES + Created by Nick Rodriguez + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter(Mandatory = $true)] + [PSCredential] + $Credential + ) + + New-PSSession -ConfigurationName Microsoft.Exchange -Authentication Basic -AllowRedirection ` + -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential +} + +function Update-GalContact { + <# + .DESCRIPTION + Takes an array of recipients and updates the Gal. + + .PARAMETER Gal + The recipient or list of reipients to update. + + .EXAMPLE + Update-GalContact -Gal $ExternalGal + + .NOTES + Created by Nick Rodriguez + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [PSObject[]]$Gal + ) + + foreach ($Recipient in $Gal) { + # Create a new contact if one doesn't exist + if (Get-MailContact -Identity $Recipient.Name) { + Write-Host "Contact $($Recipient.Name) already exists." + } else { + try { + New-MailContact ` + -ExternalEmailAddress $Recipient.PrimarySmtpAddress ` + -Name $Recipient.Name ` + -FirstName $Recipient.FirstName ` + -LastName $Recipient.LastName ` + -DisplayName $Recipient.DisplayName ` + -Alias $Recipient.Alias + Write-Host "New contact created for $($Recipient.Name)" + } catch { + Write-Host "Error creating new contact for $($Recipient.Name): $_" + } + } + + try { + # Update mail contact properties + Set-MailContact ` + -Identity $Recipient.Name ` + -ExternalEmailAddress $Recipient.PrimarySmtpAddress ` + -Name $Recipient.Name ` + -DisplayName $Recipient.DisplayName ` + -Alias $Recipient.Alias ` + -CustomAttribute1 $Recipient.CustomAttribute1 ` + -CustomAttribute2 $Recipient.CustomAttribute2 ` + -CustomAttribute3 $Recipient.CustomAttribute3 ` + -CustomAttribute4 $Recipient.CustomAttribute4 ` + -CustomAttribute5 $Recipient.CustomAttribute5 ` + -CustomAttribute6 $Recipient.CustomAttribute6 ` + -CustomAttribute7 $Recipient.CustomAttribute7 ` + -CustomAttribute8 $Recipient.CustomAttribute8 ` + -CustomAttribute9 $Recipient.CustomAttribute9 ` + -CustomAttribute10 $Recipient.CustomAttribute10 ` + -CustomAttribute11 $Recipient.CustomAttribute11 ` + -CustomAttribute12 $Recipient.CustomAttribute12 ` + -CustomAttribute13 $Recipient.CustomAttribute13 ` + -CustomAttribute14 $Recipient.CustomAttribute14 ` + -CustomAttribute15 $Recipient.CustomAttribute15 ` + -ExtensionCustomAttribute1 $Recipient.ExtensionCustomAttribute1 ` + -ExtensionCustomAttribute2 $Recipient.ExtensionCustomAttribute2 ` + -ExtensionCustomAttribute3 $Recipient.ExtensionCustomAttribute3 ` + -ExtensionCustomAttribute4 $Recipient.ExtensionCustomAttribute4 ` + -ExtensionCustomAttribute5 $Recipient.ExtensionCustomAttribute5 ` + | Out-Null + + # Update Windows Email Address only if it's populated + if ($Recipient.WindowsLiveID -ne '') { + Set-MailContact -Identity $Recipient.Name -WindowsEmailAddress $Recipient.WindowsLiveID | Out-Null + } + } catch { + Write-Host "Error updating mail contact info for $($Recipient.Name): $_" + } + + try { + # Update contact properties + Set-Contact ` + -Identity $Recipient.Name ` + -FirstName $Recipient.FirstName ` + -LastName $Recipient.LastName ` + -Department $Recipient.Department ` + -Company $Recipient.Company ` + -Phone $Recipient.Phone ` + -HomePhone $Recipient.HomePhone ` + -OtherHomePhone $Recipient.OtherHomePhone ` + -MobilePhone $Recipient.MobilePhone ` + -OtherTelephone $Recipient.OtherTelephone ` + -Pager $Recipient.Pager ` + -Fax $Recipient.Fax ` + -OtherFax $Recipient.OtherFax ` + -Office $Recipient.Office ` + -CountryOrRegion $Recipient.UsageLocation ` + -StreetAddress $Recipient.StreetAddress ` + -City $Recipient.City ` + -StateOrProvince $Recipient.StateOrProvince ` + -PostalCode $Recipient.PostalCode ` + -PostOfficeBox $Recipient.PostOfficeBox ` + -Title $Recipient.Title ` + -Manager $Recipient.Manager ` + -AssistantName $Recipient.AssistantName ` + -Notes $Recipient.Notes ` + | Out-Null + } catch { + Write-Host "Error updating contact info for $($Recipient.Name): $_" + } + } +} \ No newline at end of file diff --git a/Exchange/Sync-Gal.ps1 b/Exchange/Sync-Gal.ps1 deleted file mode 100644 index 00eb153..0000000 --- a/Exchange/Sync-Gal.ps1 +++ /dev/null @@ -1,134 +0,0 @@ -<# - .DESCRIPTION - Does a one-way sync between two Exchange Online Gal's. - - .PARAMETER AsJob - Enables the updates to be run as PS Jobs. - - .PARAMETER Jobs - The number of PS Jobs to create to run the updates. Limited to 3 because of an O365 default max concurrent connection. - - .EXAMPLE - ./Sync-Gal.ps1 -AsJob -Jobs 3 - - .EXAMPLE - ./Sync-Gal.ps1 - - .NOTES - Created by Nick Rodriguez -#> -[CmdletBinding( - SupportsShouldProcess = $true, - DefaultParameterSetName = 'Synchronous' -)] -Param( - [Parameter( - Mandatory = $false, - ParameterSetName = 'Asynchronous' - )] - [Switch] - $AsJob, - - [Parameter( - Mandatory = $false, - ParameterSetName = 'Asynchronous' - )] - [ValidateRange(1, 3)] - [Int] - $Jobs = 2 -) - -# Syncs the Gal of Exchange Online across two Office 365 tenants -# Note that this will break the creation of external user objects until MS -# addresses a known issue: -# https://products.office.com/en-us/business/office-365-roadmap?filters=&featureid=72273 - -begin { - # Log everything - $LogDirectory = (New-Item -ItemType Directory "C:\powershell-scripts\Exchange Online\Logs" -Force).FullName - $Date = (Get-Date).ToString('yyyyMMdd-HHmm') - Start-Transcript -Path "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" -} - -process { - # Get credentials for both tenants - Write-Host "Enter credentials for primary tenant" - $PrimaryTenantCreds = Get-Credential - Write-Host "Enter credentials for secondary tenant" - $SecondaryTenantCreds = Get-Credential - - # Create Exchange Online PowerShell session for primary tenant - $PrimaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` - -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` - -Credential $PrimaryTenantCreds -Authentication Basic -AllowRedirection - - # Enter session on primary tenant - Import-PSSession $PrimaryTenantEOSession - - # Get all Gal recipients using the primary filter - $GalFilter = (Get-GlobalAddressList).RecipientFilter - $Gal = Get-Recipient -ResultSize Unlimited -Filter $GalFilter - - # Export Gal to Csv file - $Gal | Export-Csv -Path "$LogDirectory\Gal.csv" -NoTypeInformation -Force - - # Remove session on primary tenant - Remove-PSSession -Session $PrimaryTenantEOSession - - # Create/Update contact for each Gal entry - # If Jobs param specified, break up the list into smaller lists that can be started as jobs - if ($AsJob) { - $ContactLists = @{} - $Count = 0 - - # Separate the contacts into smaller lists - $Gal | ForEach-Object { - $ContactLists[$Count % $Jobs] += @($_) - $Count++ - } - - # Create a job for each sublist of contacts - foreach ($List in $ContactLists.Values) { - Start-Job -ArgumentList $SecondaryTenantCreds, $List -ScriptBlock { - # Create Exchange Online PS session - $SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` - -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` - -Credential $args[0] -Authentication Basic -AllowRedirection - - # Enter session on secondary tenant - Import-PSSession $SecondaryTenantEOSession - - Update-GalContact -Gal $args[1] - - # Remove session on secondary tenant - Remove-PSSession -Session $SecondaryTenantEOSession - } -InitializationScript { . 'C:\powershell-scripts\Exchange Online\Update-GalContact.ps1' } - } - - # Wait for all jobs to finish then receive and remove the jobs - Get-Job | Wait-Job | Receive-Job - Get-Job | Remove-Job - } else { - # Import function - . 'C:\powershell-scripts\Exchange Online\Update-GalContact.ps1' - - # Create Exchange Online PS session - $SecondaryTenantEOSession = New-PSSession -ConfigurationName Microsoft.Exchange ` - -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` - -Credential $SecondaryTenantCreds -Authentication Basic -AllowRedirection - - # Enter session on secondary tenant - Import-PSSession $SecondaryTenantEOSession - - Update-GalContact -Gal $Gal - - # Remove session on secondary tenant - Remove-PSSession -Session $SecondaryTenantEOSession - } -} - -end { - # Remove any lingering PSSessions and stop the logging - Get-PSSession | Remove-PSSession - Stop-Transcript -} \ No newline at end of file diff --git a/Exchange/Update-GalContact.ps1 b/Exchange/Update-GalContact.ps1 deleted file mode 100644 index 9c90afc..0000000 --- a/Exchange/Update-GalContact.ps1 +++ /dev/null @@ -1,110 +0,0 @@ -function Update-GalContact { - <# - .DESCRIPTION - Takes an array of recipients and updates the Gal. - - .PARAMETER Gal - The recipient or list of reipients to update. - - .EXAMPLE - Update-GalContact -Gal $ExternalGal - - .NOTES - Created by Nick Rodriguez - This is a helper function for Sync-Gal.ps1 - #> - [CmdletBinding(SupportsShouldProcess = $true)] - Param( - [PSObject[]]$Gal - ) - - foreach ($Recipient in $Gal) { - # Create a new contact if one doesn't exist - if (Get-MailContact -Identity $Recipient.Name) { - Write-Host "Contact $($Recipient.Name) already exists." - } else { - try { - New-MailContact ` - -ExternalEmailAddress $Recipient.PrimarySmtpAddress ` - -Name $Recipient.Name ` - -FirstName $Recipient.FirstName ` - -LastName $Recipient.LastName ` - -DisplayName $Recipient.DisplayName ` - -Alias $Recipient.Alias - Write-Host "New contact created for $($Recipient.Name)" - } catch { - Write-Host "Error creating new contact for $($Recipient.Name): $_" - } - } - - try { - # Update mail contact properties - Set-MailContact ` - -Identity $Recipient.Name ` - -ExternalEmailAddress $Recipient.PrimarySmtpAddress ` - -Name $Recipient.Name ` - -DisplayName $Recipient.DisplayName ` - -Alias $Recipient.Alias ` - -CustomAttribute1 $Recipient.CustomAttribute1 ` - -CustomAttribute2 $Recipient.CustomAttribute2 ` - -CustomAttribute3 $Recipient.CustomAttribute3 ` - -CustomAttribute4 $Recipient.CustomAttribute4 ` - -CustomAttribute5 $Recipient.CustomAttribute5 ` - -CustomAttribute6 $Recipient.CustomAttribute6 ` - -CustomAttribute7 $Recipient.CustomAttribute7 ` - -CustomAttribute8 $Recipient.CustomAttribute8 ` - -CustomAttribute9 $Recipient.CustomAttribute9 ` - -CustomAttribute10 $Recipient.CustomAttribute10 ` - -CustomAttribute11 $Recipient.CustomAttribute11 ` - -CustomAttribute12 $Recipient.CustomAttribute12 ` - -CustomAttribute13 $Recipient.CustomAttribute13 ` - -CustomAttribute14 $Recipient.CustomAttribute14 ` - -CustomAttribute15 $Recipient.CustomAttribute15 ` - -ExtensionCustomAttribute1 $Recipient.ExtensionCustomAttribute1 ` - -ExtensionCustomAttribute2 $Recipient.ExtensionCustomAttribute2 ` - -ExtensionCustomAttribute3 $Recipient.ExtensionCustomAttribute3 ` - -ExtensionCustomAttribute4 $Recipient.ExtensionCustomAttribute4 ` - -ExtensionCustomAttribute5 $Recipient.ExtensionCustomAttribute5 ` - | Out-Null - - # Update Windows Email Address only if it's populated - if ($Recipient.WindowsLiveID -ne '') { - Set-MailContact -Identity $Recipient.Name -WindowsEmailAddress $Recipient.WindowsLiveID | Out-Null - } - } catch { - Write-Host "Error updating mail contact info for $($Recipient.Name): $_" - } - - try { - # Update contact properties - Set-Contact ` - -Identity $Recipient.Name ` - -FirstName $Recipient.FirstName ` - -LastName $Recipient.LastName ` - -Department $Recipient.Department ` - -Company $Recipient.Company ` - -Phone $Recipient.Phone ` - -HomePhone $Recipient.HomePhone ` - -OtherHomePhone $Recipient.OtherHomePhone ` - -MobilePhone $Recipient.MobilePhone ` - -OtherTelephone $Recipient.OtherTelephone ` - -Pager $Recipient.Pager ` - -Fax $Recipient.Fax ` - -OtherFax $Recipient.OtherFax ` - -Office $Recipient.Office ` - -CountryOrRegion $Recipient.UsageLocation ` - -StreetAddress $Recipient.StreetAddress ` - -City $Recipient.City ` - -StateOrProvince $Recipient.StateOrProvince ` - -PostalCode $Recipient.PostalCode ` - -PostOfficeBox $Recipient.PostOfficeBox ` - -Title $Recipient.Title ` - -Manager $Recipient.Manager ` - -AssistantName $Recipient.AssistantName ` - -Notes $Recipient.Notes ` - | Out-Null - } catch { - Write-Host "Error updating contact info for $($Recipient.Name): $_" - } - } -} \ No newline at end of file From 28a5f648b44434e0adac1595912b7860f1b53ef3 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Tue, 4 Apr 2017 10:47:32 -0400 Subject: [PATCH 15/65] CaseWare SyncServer regkey add. --- CaseWare/New-CWSyncServer.ps1 | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 CaseWare/New-CWSyncServer.ps1 diff --git a/CaseWare/New-CWSyncServer.ps1 b/CaseWare/New-CWSyncServer.ps1 new file mode 100644 index 0000000..ce0f1bd --- /dev/null +++ b/CaseWare/New-CWSyncServer.ps1 @@ -0,0 +1,41 @@ +function New-CWSyncServer { + <# + .DESCRIPTION + Add SmartSync server to CaseWare Working Papers. + + .PARAMETER HostName + Host name of SmartSync server. + + .PARAMETER Label + Friendly name of SmartSync server as it will appear in CaseWare Working Papers. + + .EXAMPLE + New-CWSyncServer site01.company.com + + .NOTES + Created by Nick Rodriguez + Adds new SmartSync server to registry that will appear in CaseWare Working Papers. + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter(Mandatory = $true)] + [String] + $HostName, + + [Parameter(Mandatory = $false)] + [String] + $FriendlyName + ) + + # Create the site key + $RegPath = 'HKCU:\SOFTWARE\CaseWare International\Working Papers\*\SyncServer' + if (-not (Test-Path -Path "$RegPath\{$HostName}")) { New-Item -Path $RegPath -Name "{$HostName}" } + + # Set the two site key properties + Set-ItemProperty -Path "$RegPath\{$HostName}" -Name Host -Value $HostName + Set-ItemProperty -Path "$RegPath\{$HostName}" -Name Label -Value $FriendlyName + + # Validate with SCCM friendly exit codes + $SiteKey = Get-ItemProperty -Path "$RegPath\{$HostName}" + if ($SiteKey.Host -eq $HostName -and $SiteKey.Label -eq $FriendlyName) { exit 0 } else { exit 999 } +} \ No newline at end of file From ed29264bb9fe1d7e23642197b4be9b8121b00192 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Mon, 24 Apr 2017 15:48:14 -0400 Subject: [PATCH 16/65] Enter group name and see which groups the members have in common. --- AD/Get-ADCommonGroup.ps1 | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 AD/Get-ADCommonGroup.ps1 diff --git a/AD/Get-ADCommonGroup.ps1 b/AD/Get-ADCommonGroup.ps1 new file mode 100644 index 0000000..cb2d4cd --- /dev/null +++ b/AD/Get-ADCommonGroup.ps1 @@ -0,0 +1,40 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)] + [ValidateScript({ Get-ADGroup -Identity $_ })] + [string]$GroupName +) + +begin { + Import-Module ActiveDirectory + $Group = Get-ADGroup -Identity $GroupName + $AllGroups = @{} +} + +process { + $Members = Get-ADGroupMember -Identity $Group + + $Members | ForEach-Object { + $User = $_.SamAccountName + + (Get-ADUser $User -Properties MemberOf).MemberOf | ForEach-Object { + # Check if the group exists in our list + if ($AllGroups.ContainsKey($_)) { + $AllGroups.($_)++ + } else { + # Item is unique so add it to the list + $AllGroups.Add($_, 1) + } + } + } + + Write-Host "Results:" + $AllGroups | Format-Table -AutoSize + $AllGroups.GetEnumerator() | Select-Object -Property Key, Value | + Export-Csv -Path .\$GroupName-CommonGroups.csv -NoTypeInformation + + Write-Host "Common memberships:" + $AllGroups.GetEnumerator() | ForEach-Object { + if ($_.Value -eq $Members.Count) { $_.Key } + } +} \ No newline at end of file From bcc681c6ac20476616ad30d3e41afb6388cbc2b7 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Mon, 22 May 2017 15:26:43 -0400 Subject: [PATCH 17/65] Script to consolidate old log files in a directory. --- File Management/Compress-Log.ps1 | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 File Management/Compress-Log.ps1 diff --git a/File Management/Compress-Log.ps1 b/File Management/Compress-Log.ps1 new file mode 100644 index 0000000..ec1ecc6 --- /dev/null +++ b/File Management/Compress-Log.ps1 @@ -0,0 +1,59 @@ +#Requires -Version 5 +<# +.DESCRIPTION +Compress logs older than given time into an archive. Uses LastWriteTime property to determine age. + +.PARAMETER Path +Path of log files. + +.PARAMETER Extension +Extension of the log files to search for, default is 'log'. + +.PARAMETER DaysOld +How many days old the file must be to get archived. + +.EXAMPLE +./CompressLog.ps1 -Path ~\Downloads\logs -DaysOld 8 +Searches for log files (.log extension) in the given path that are older than 8 days old and compresses them. +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +Param( + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Container })] + [string]$Path, + + [Parameter(Mandatory = $false)] + [string]$Extension = 'log', + + [Parameter(Mandatory = $false)] + [int]$DaysOld = 7 +) + +begin { + $CompareDate = (Get-Date).AddDays(-$DaysOld) + Write-Verbose "Threshold date: $CompareDate" + + $ArchiveName = "LogsOlderThan-$($CompareDate.ToString('yyyy-MM-dd'))" + Write-Verbose "Archive name: $ArchiveName" +} + +process { + # Get logs older than the compare date + $Files = Get-ChildItem -Path $Path -File -Filter "*.$Extension" | + Where-Object -Property LastWriteTime -LT $CompareDate + + if ($Files.Count) { + Write-Verbose "$($Files.Count) old logs found" + + # Create archive will old logs in it + if ($PSCmdlet.ShouldProcess($ArchiveName, 'New archive')) { + Compress-Archive -Path $Files.FullName -DestinationPath "$Path\$ArchiveName" -Update + } + + # Delete old logs + $Files | Remove-Item + } else { + Write-Verbose 'No old logs found' + } +} \ No newline at end of file From 2517cee68b1b52448c27e7a63b1c469b2329607a Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 26 May 2017 16:04:14 -0400 Subject: [PATCH 18/65] Module to manage Zoom Video Communications account through REST API. --- Zoom.psm1 | 503 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 Zoom.psm1 diff --git a/Zoom.psm1 b/Zoom.psm1 new file mode 100644 index 0000000..92ed8fe --- /dev/null +++ b/Zoom.psm1 @@ -0,0 +1,503 @@ +# Verify we can get the api key and secret before loading the module +try { + $ApiKey = Get-Content -Path "$PSScriptRoot\api_key" -ErrorAction Stop + $ApiSecret = Get-Content -Path "$PSScriptRoot\api_secret" -ErrorAction Stop +} catch { + Write-Error "There was a problem getting the API key and secret. $_" + exit +} + +function Get-ZoomAuthHeader { + [CmdletBinding()] + Param() + + @{ + 'api_key' = $ApiKey + 'api_secret' = $ApiSecret + } +} + +function Read-ZoomResponse { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true + )] + [PSCustomObject]$Response + ) + + if ($Response.PSObject.Properties.Name -match 'error') { + Write-Error -Message $Response.error.message -ErrorId $Response.error.code + } else { + $Response + } +} + +function Get-ZoomUser { + [CmdletBinding(DefaultParameterSetName = 'All')] + Param( + [Parameter(ParameterSetName = 'Id')] + [ValidateNotNullOrEmpty()] + [string]$Id, + + [Parameter(ParameterSetName = 'Email')] + [ValidateNotNullOrEmpty()] + [string]$Email, + + [Parameter(ParameterSetName = 'All')] + [switch]$All + ) + + $Headers = Get-ZoomAuthHeader + + $Endpoint = if ($PSCmdlet.ParameterSetName -ne 'Id') { + 'https://api.zoom.us/v1/user/list' + } else { + 'https://api.zoom.us/v1/user/get' + } + + if ($PSCmdlet.ParameterSetName -ne 'Id') { + $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + + Write-Verbose "There are $($Result.page_count) pages of users" + foreach ($Page in $Result.page_count) { + $Headers = Get-ZoomAuthHeader + $Headers.Add('page_size', 300) + $Headers.Add('page_number', $Page) + $Users += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).users + } + + if ($PSCmdlet.ParameterSetName -eq 'Email') { + $Users | Where-Object -Property email -eq $Email + } else { + $Users + } + } else { + $Headers.Add('id', $Id) + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } +} + +function Remove-ZoomUser { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string]$Id, + + [switch]$Permanently + ) + + $Endpoint = if ($Permanently) { + 'https://api.zoom.us/v1/user/permanentdelete' + } else { + 'https://api.zoom.us/v1/user/delete' + } + + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $Id) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Remove-ZoomGroup { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string]$Id + ) + + $Endpoint = 'https://api.zoom.us/v1/group/delete' + + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $Id) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Test-ZoomUserEmail { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$Email + ) + + $Endpoint = 'https://api.zoom.us/v1/user/checkemail' + + $Headers = Get-ZoomAuthHeader + $Headers.Add('email', $Email) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Disable-ZoomUser { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string]$Id + ) + + $Headers = Get-ZoomAuthHeader + + $Endpoint = 'https://api.zoom.us/v1/user/deactivate' + + $Headers.Add('id', $Id) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Get-ZoomGroup { + [CmdletBinding(DefaultParameterSetName = 'All')] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = 'Id' + )] + [ValidateNotNullOrEmpty()] + [string]$Id, + + [Parameter(ParameterSetName = 'Name')] + [ValidateNotNullOrEmpty()] + [string]$Name, + + [Parameter(ParameterSetName = 'All')] + [switch]$All + ) + + $Headers = Get-ZoomAuthHeader + + $Endpoint = if ($PSCmdlet.ParameterSetName -ne 'Id') { + 'https://api.zoom.us/v1/group/list' + } else { + 'https://api.zoom.us/v1/group/get' + } + + if ($PSCmdlet.ParameterSetName -ne 'Id') { + $Groups = (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).groups + + if ($PSCmdlet.ParameterSetName -eq 'Name') { + $Groups | Where-Object -Property name -eq $Name + } else { + $Groups + } + } else { + $Headers.Add('id', $Id) + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } +} + +function Set-ZoomUserAssistant { + [CmdletBinding(DefaultParameterSetName = 'Id')] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = 'Id' + )] + [ValidateNotNullOrEmpty()] + [string]$Id, + + [Parameter(ParameterSetName = 'Email')] + [ValidateNotNullOrEmpty()] + [string]$Email, + + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$AssistantEmail + ) + + $Headers = Get-ZoomAuthHeader + $Headers.Add('assistant_email', $AssistantEmail) + + $Endpoint = 'https://api.zoom.us/v1/user/assistant/set' + + if ($PSCmdlet.ParameterSetName -ne 'Id') { + $Headers.Add('id', $Id) + } else { + $Headers.Add('host_email', $Email) + } + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Remove-ZoomUserAssistant { + [CmdletBinding(DefaultParameterSetName = 'Id')] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = 'Id' + )] + [ValidateNotNullOrEmpty()] + [string]$Id, + + [Parameter(ParameterSetName = 'Email')] + [ValidateNotNullOrEmpty()] + [string]$Email + ) + + $Headers = Get-ZoomAuthHeader + + $Endpoint = 'https://api.zoom.us/v1/user/assistant/delete' + + if ($PSCmdlet.ParameterSetName -ne 'Id') { + $Headers.Add('id', $Id) + } else { + $Headers.Add('host_email', $Email) + } + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function New-ZoomGroup { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$Name + ) + + $Endpoint = 'https://api.zoom.us/v1/group/create' + + $Headers = Get-ZoomAuthHeader + $Headers.Add('name', $Name) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Add-ZoomGroupMember { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$GroupId, + + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Id + ) + + $Endpoint = 'https://api.zoom.us/v1/group/member/add' + + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $GroupId) + + foreach ($User in $Id) { + $MemberIds += "$User," + } + $Headers.Add('member_ids', $MemberIds) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Remove-ZoomGroupMember { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$GroupId, + + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Id + ) + + $Endpoint = 'https://api.zoom.us/v1/group/member/delete' + + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $GroupId) + + foreach ($User in $Id) { + $MemberIds += "$User," + } + $Headers.Add('member_ids', $MemberIds) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + +function Get-ZoomGroupMember { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string]$Id + ) + + $Endpoint = 'https://api.zoom.us/v1/group/member/list' + + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $Id) + $Headers.Add('page_size', 300) + $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + + Write-Verbose "There are $($Result.page_count) pages of users" + foreach ($Page in $Result.page_count) { + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $Id) + $Headers.Add('page_size', 300) + $Headers.Add('page_number', $Page) + $Users += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).members + } + + $Users +} + +function Set-ZoomUserLicense { + [CmdletBinding(DefaultParameterSetName = 'All')] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Id, + + [Parameter(Mandatory = $true)] + [ValidateSet('Basic', 'Pro', 'Corp')] + [string]$License + ) + + $Endpoint = 'https://api.zoom.us/v1/user/update' + + $Type = switch ($License) { + 'Basic' { 1 } + 'Pro' { 2 } + 'Corp' { 3 } + } + + foreach ($User in $Id) { + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $User) + $Headers.Add('type', $Type) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } +} + +function Set-ZoomUserPicture { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string]$Id, + + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path $_ -PathType Leaf })] + [ValidatePattern('.jp*.g$')] + [string]$Path + ) + + Write-Warning "This function is experimental and may not work." + + $Endpoint = 'https://api.zoom.us/v1/user/uploadpicture' + + $Bytes = [IO.File]::ReadAllBytes($Path) + $Encoding = [System.Text.Encoding]::ASCII + $FileContent = $Encoding.GetString($Bytes) + + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $Id) + $Headers.Add('pic_file', $FileContent) + + Invoke-RestMethod -Uri $Endpoint -Method Post -Body $Headers -ContentType 'multipart/form-data' +} + +function Set-ZoomUserIntern { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Id + ) + + foreach ($UserId in $Id) { + Set-ZoomUserLicense -Id $UserId -License Basic + + $Groups = Get-ZoomGroup + $InternGroupId = ($Groups | Where-Object -Property name -eq 'Interns/Temps').group_id + $OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').group_id + + $OtherGroupIds | ForEach-Object { + Remove-ZoomGroupMember -GroupId $_ -Id $UserId + } + + $UserId | Add-ZoomGroupMember -GroupId $InternGroupId + } +} + +function New-ZoomSSOUser { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Email, + + [Parameter(Mandatory = $true)] + [ValidateSet('Basic', 'Pro', 'Corp')] + [string]$License + ) + + $Endpoint = 'https://api.zoom.us/v1/user/ssocreate' + + $Type = switch ($License) { + 'Basic' { 1 } + 'Pro' { 2 } + 'Corp' { 3 } + } + + foreach ($User in $Email) { + $Headers = Get-ZoomAuthHeader + $Headers.Add('email', $User) + $Headers.Add('type', $Type) + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } +} + +Export-ModuleMember -Function * \ No newline at end of file From f18cf5d221689bf55c311668f21f59a591604e96 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Wed, 31 May 2017 10:22:28 -0400 Subject: [PATCH 19/65] Added WhatIf support and some comment based help. --- Zoom.psm1 | 275 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 232 insertions(+), 43 deletions(-) diff --git a/Zoom.psm1 b/Zoom.psm1 index 92ed8fe..0d2086f 100644 --- a/Zoom.psm1 +++ b/Zoom.psm1 @@ -8,6 +8,16 @@ try { } function Get-ZoomAuthHeader { + <# + .SYNOPSIS + Gets a hashtable for a new REST body that includes the api key and secret. + + .EXAMPLE + $Headers = Get-ZoomAuthHeader + + .OUTPUTS + Hashtable + #> [CmdletBinding()] Param() @@ -18,6 +28,13 @@ function Get-ZoomAuthHeader { } function Read-ZoomResponse { + <# + .SYNOPSIS + Parses Zoom REST response so errors are returned properly + + .EXAMPLE + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + #> [CmdletBinding()] Param( [Parameter( @@ -35,22 +52,44 @@ function Read-ZoomResponse { } function Get-ZoomUser { + <# + .SYNOPSIS + Gets Zoom users by Id, Email, or All. + + .PARAMETER Id + Gets Zoom user by their Zoom Id. Will accept an array of Id's. + + .PARAMETER Email + Gets all Zoom users and then filters by email. Will accept an array of emails. + + .PARAMETER All + Default. Return all Zoom users. + + .EXAMPLE + Get-ZoomUser + Returns all zoom users. + + .EXAMPLE + Get-ZoomUser -Email user@company.com + Searches for and returns specified user if found. + + .OUTPUTS + PSCustomObject + #> [CmdletBinding(DefaultParameterSetName = 'All')] Param( [Parameter(ParameterSetName = 'Id')] [ValidateNotNullOrEmpty()] - [string]$Id, + [string[]]$Id, [Parameter(ParameterSetName = 'Email')] [ValidateNotNullOrEmpty()] - [string]$Email, + [string[]]$Email, [Parameter(ParameterSetName = 'All')] [switch]$All ) - $Headers = Get-ZoomAuthHeader - $Endpoint = if ($PSCmdlet.ParameterSetName -ne 'Id') { 'https://api.zoom.us/v1/user/list' } else { @@ -58,29 +97,52 @@ function Get-ZoomUser { } if ($PSCmdlet.ParameterSetName -ne 'Id') { + $Headers = Get-ZoomAuthHeader $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse Write-Verbose "There are $($Result.page_count) pages of users" foreach ($Page in $Result.page_count) { - $Headers = Get-ZoomAuthHeader $Headers.Add('page_size', 300) $Headers.Add('page_number', $Page) $Users += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).users } if ($PSCmdlet.ParameterSetName -eq 'Email') { - $Users | Where-Object -Property email -eq $Email + foreach ($User in $Email) { + $Users | Where-Object -Property email -eq $User + } } else { $Users } } else { - $Headers.Add('id', $Id) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + foreach ($User in $Id) { + $Headers = Get-ZoomAuthHeader + $Headers.Add('id', $User) + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } } function Remove-ZoomUser { - [CmdletBinding()] + <# + .SYNOPSIS + Remove Zoom user by Id. + + .PARAMETER Id + Zoom user Id to remove. + + .PARAMETER Permanently + Default is no. Switch that specified whether to delete user permanently. + + .EXAMPLE + Get-ZoomUser -Email user@company.com -Permanently | Remove-ZoomUser + Permanently remove user@company.com. + + .EXAMPLE + Remove-ZoomUser -Id 123asdfjkl + Removes Zoom user with Id 123asdfjkl. + #> + [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter( Mandatory = $true, @@ -94,6 +156,7 @@ function Remove-ZoomUser { ) $Endpoint = if ($Permanently) { + Write-Verbose 'Permanent delete selected.' 'https://api.zoom.us/v1/user/permanentdelete' } else { 'https://api.zoom.us/v1/user/delete' @@ -102,11 +165,24 @@ function Remove-ZoomUser { $Headers = Get-ZoomAuthHeader $Headers.Add('id', $Id) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom user')) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } function Remove-ZoomGroup { - [CmdletBinding()] + <# + .SYNOPSIS + Remove Zoom group by Id. + + .PARAMETER Id + Zoom group Id to remove. + + .EXAMPLE + Get-ZoomGroup -Name TestGroup | Remove-ZoomGroup + Remove group TestGroup. + #> + [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter( Mandatory = $true, @@ -122,10 +198,23 @@ function Remove-ZoomGroup { $Headers = Get-ZoomAuthHeader $Headers.Add('id', $Id) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom group')) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } function Test-ZoomUserEmail { + <# + .SYNOPSIS + Test if given email has an existing account. + + .PARAMETER Email + Zoom user email to test. + + .EXAMPLE + Test-ZoomUserEmail -Email user@company.com + Checks to see if account exists for user@company.com. + #> [CmdletBinding()] Param( [Parameter(Mandatory = $true)] @@ -142,7 +231,18 @@ function Test-ZoomUserEmail { } function Disable-ZoomUser { - [CmdletBinding()] + <# + .SYNOPSIS + Deactivate Zoom user with given Id. + + .PARAMETER Id + Zoom user id to deactivate. + + .EXAMPLE + Get-ZoomUser -Id user@company.com | Disable-ZoomUserEmail + Deactivates Zoom user account with email user@company.com. + #> + [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter( Mandatory = $true, @@ -159,10 +259,36 @@ function Disable-ZoomUser { $Headers.Add('id', $Id) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + if ($pscmdlet.ShouldProcess($Id, 'Deactivate Zoom user')) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } function Get-ZoomGroup { + <# + .SYNOPSIS + Gets Zoom groups by Id, Name, or All. + + .PARAMETER Id + Gets Zoom group by their Zoom Id. + + .PARAMETER Name + Gets all Zoom groups and then filters by name. + + .PARAMETER All + Default. Return all Zoom groups. + + .EXAMPLE + Get-ZoomGroup + Returns all zoom groups. + + .EXAMPLE + Get-ZoomGroup -Name TestGroup + Searches for and returns specified group if found. + + .OUTPUTS + PSCustomObject + #> [CmdletBinding(DefaultParameterSetName = 'All')] Param( [Parameter( @@ -241,7 +367,10 @@ function Set-ZoomUserAssistant { } function Remove-ZoomUserAssistant { - [CmdletBinding(DefaultParameterSetName = 'Id')] + [CmdletBinding( + SupportsShouldProcess = $True, + DefaultParameterSetName = 'Id' + )] Param( [Parameter( Mandatory = $true, @@ -262,12 +391,16 @@ function Remove-ZoomUserAssistant { $Endpoint = 'https://api.zoom.us/v1/user/assistant/delete' if ($PSCmdlet.ParameterSetName -ne 'Id') { + $Assistant = $Id $Headers.Add('id', $Id) } else { + $Assistant = $Email $Headers.Add('host_email', $Email) } - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + if ($pscmdlet.ShouldProcess($Assistant, 'Remove Zoom user assistant')) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } function New-ZoomGroup { @@ -310,13 +443,13 @@ function Add-ZoomGroupMember { foreach ($User in $Id) { $MemberIds += "$User," } - $Headers.Add('member_ids', $MemberIds) + $Headers.Add('member_ids', $MemberIds.TrimEnd(',')) Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } function Remove-ZoomGroupMember { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -341,7 +474,9 @@ function Remove-ZoomGroupMember { } $Headers.Add('member_ids', $MemberIds) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + if ($pscmdlet.ShouldProcess($MemberIds, "Remove Zoom user from $GroupId")) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } function Get-ZoomGroupMember { @@ -376,6 +511,23 @@ function Get-ZoomGroupMember { } function Set-ZoomUserLicense { + <# + .SYNOPSIS + Set license for Zoom user. + + .PARAMETER Id + Zoom user to set license for. + + .PARAMETER License + License type. Basic, Pro, or Corp. + + .EXAMPLE + Get-ZoomUser -Id user@company.com | Set-ZoomUserLicense -License Corp + Sets Zoom license to Corp on user@company.com's account. + + .OUTPUTS + PSCustomObject + #> [CmdletBinding(DefaultParameterSetName = 'All')] Param( [Parameter( @@ -409,6 +561,26 @@ function Set-ZoomUserLicense { } function Set-ZoomUserPicture { + <# + .SYNOPSIS + Upload a new profile picture for the specified Zoom user. + + .PARAMETER Id + Zoom user to upload a new profile picture for. + + .PARAMETER Path + Path to profile picture to upload. + + .EXAMPLE + Get-ZoomUser -Id user@company.com | Set-ZoomUserPicture -Path .\picture.jpg + Uploads new profile picture to user@company.com's account. + + .OUTPUTS + PSCustomObject + + .NOTES + This function does not work in its current state. + #> [CmdletBinding()] Param( [Parameter( @@ -433,41 +605,58 @@ function Set-ZoomUserPicture { $Encoding = [System.Text.Encoding]::ASCII $FileContent = $Encoding.GetString($Bytes) + <#$boundary = [System.Guid]::NewGuid().ToString() + $LF = "`n" + $bodyLines = ( + "--$boundary", + "api_key: Uo4sjR8IQcOBWCUqxFlM_g", + "--$boundary", + "api_secret: 0PxInW592LrbpM0wxoDd3NesO5VbU7OKm8lT", + "--$boundary", + "id: $Id", + "--$boundary", + "Content-Disposition: form-data; name=`"pic_file`"$LF", + $FileContent, + "--$boundary--$LF" + ) -join $LF + $bodyLines = ( + "api_key: Uo4sjR8IQcOBWCUqxFlM_g", + "api_secret: 0PxInW592LrbpM0wxoDd3NesO5VbU7OKm8lT", + "id: $Id", + "--$boundary", + "Content-Disposition: form-data; name=`"pic_file`"$LF", + $FileContent, + "--$boundary--$LF" + ) -join $LF#> + $Headers = Get-ZoomAuthHeader $Headers.Add('id', $Id) $Headers.Add('pic_file', $FileContent) + + + #Invoke-WebRequest -Headers $Headers -Method Post -Uri $Endpoint -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines Invoke-RestMethod -Uri $Endpoint -Method Post -Body $Headers -ContentType 'multipart/form-data' } -function Set-ZoomUserIntern { - [CmdletBinding()] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string[]]$Id - ) - - foreach ($UserId in $Id) { - Set-ZoomUserLicense -Id $UserId -License Basic +function New-ZoomSSOUser { + <# + .SYNOPSIS + Pre-provision Zoom SSO user account. - $Groups = Get-ZoomGroup - $InternGroupId = ($Groups | Where-Object -Property name -eq 'Interns/Temps').group_id - $OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').group_id + .PARAMETER Email + New Zoom user email address. - $OtherGroupIds | ForEach-Object { - Remove-ZoomGroupMember -GroupId $_ -Id $UserId - } + .PARAMETER License + License to grant new Zoom user. Basic, Pro, or Corp. - $UserId | Add-ZoomGroupMember -GroupId $InternGroupId - } -} + .EXAMPLE + New-ZoomSSOUser -Email user@company.com -License Pro + Pre-provisions a Zoom user account for email user@company.com with a Pro license. -function New-ZoomSSOUser { + .OUTPUTS + PSCustomObject + #> [CmdletBinding()] Param( [Parameter( From da6e1eaf5312f6b3c5b8d902699ef3b64c45bfc1 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Wed, 31 May 2017 13:47:38 -0400 Subject: [PATCH 20/65] Updated Zoom module and added some tools for it. --- Zoom/Set-ZoomUserIntern.ps1 | 34 +++++++++++++ Zoom/Set-ZoomUserInternational.ps1 | 15 ++++++ Zoom/Set-ZoomUserPmiAndVanityName.ps1 | 13 +++++ Zoom.psm1 => Zoom/Zoom.psm1 | 71 +++++++++++++++++++++------ 4 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 Zoom/Set-ZoomUserIntern.ps1 create mode 100644 Zoom/Set-ZoomUserInternational.ps1 create mode 100644 Zoom/Set-ZoomUserPmiAndVanityName.ps1 rename Zoom.psm1 => Zoom/Zoom.psm1 (90%) diff --git a/Zoom/Set-ZoomUserIntern.ps1 b/Zoom/Set-ZoomUserIntern.ps1 new file mode 100644 index 0000000..8cb82c2 --- /dev/null +++ b/Zoom/Set-ZoomUserIntern.ps1 @@ -0,0 +1,34 @@ +Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force +Import-Module ActiveDirectory + +function Set-ZoomUserIntern { + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Id + ) + + foreach ($UserId in $Id) { + Set-ZoomUserInfo -Id $UserId -License Basic + + $Groups = Get-ZoomGroup + $InternGroupId = ($Groups | Where-Object -Property name -eq 'Interns/Temps').group_id + $OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').group_id + + $OtherGroupIds | ForEach-Object { + Remove-ZoomGroupMember -GroupId $_ -Id $UserId + } + + $UserId | Add-ZoomGroupMember -GroupId $InternGroupId + } +} + +# Get Zoom intern users from AD group and set their Zoom license and group +$ADUsers = Get-ADGroupMember -Recursive -Identity 'Global - Interns' | + Get-ADUser | Select-Object -ExpandProperty UserPrincipalName +Get-ZoomUser -Email $ADUsers | Set-ZoomUserIntern \ No newline at end of file diff --git a/Zoom/Set-ZoomUserInternational.ps1 b/Zoom/Set-ZoomUserInternational.ps1 new file mode 100644 index 0000000..367ad0f --- /dev/null +++ b/Zoom/Set-ZoomUserInternational.ps1 @@ -0,0 +1,15 @@ +Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force +Import-Module ActiveDirectory + +# Get Zoom international users from AD group +$ADUsers = Get-ADGroupMember -Identity 'Zoom International Calling Accounts' | + Get-ADUser | Select-Object -ExpandProperty UserPrincipalName +$ZoomUsers = Get-ZoomUser -Email $ADUsers + +# Add users to international Zoom group +$InternationalGroup = Get-ZoomGroup -Name 'International Calling' +$ZoomUsers | Add-ZoomGroupMember -GroupId $InternationalGroup.group_id + +# Remove users from other Zoom groups +$OtherGroups = Get-ZoomGroup | Where-Object -Property name -ne 'International Calling' +foreach ($Group in $OtherGroups) { $ZoomUsers | Remove-ZoomGroupMember -GroupId $Group.group_id } \ No newline at end of file diff --git a/Zoom/Set-ZoomUserPmiAndVanityName.ps1 b/Zoom/Set-ZoomUserPmiAndVanityName.ps1 new file mode 100644 index 0000000..57dfcd1 --- /dev/null +++ b/Zoom/Set-ZoomUserPmiAndVanityName.ps1 @@ -0,0 +1,13 @@ +Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force +Import-Module ActiveDirectory + +$Users = Get-ZoomUser + +foreach ($User in $Users) { + $Email = $User.email + $VanityName = $User.email.Split('@')[0] + $Pmi = (Get-ADUser -Filter { UserPrincipalName -eq $Email } -Properties telephoneNumber | + Select-Object -ExpandProperty telephoneNumber) -replace '-', '' + + Set-ZoomUserInfo -Id $User.id -Pmi $Pmi -EnablePmi $true -VanityName $VanityName +} \ No newline at end of file diff --git a/Zoom.psm1 b/Zoom/Zoom.psm1 similarity index 90% rename from Zoom.psm1 rename to Zoom/Zoom.psm1 index 0d2086f..8c46297 100644 --- a/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -510,25 +510,40 @@ function Get-ZoomGroupMember { $Users } -function Set-ZoomUserLicense { +function Set-ZoomUserInfo { <# .SYNOPSIS - Set license for Zoom user. + Set personal meeting Id and vanity name for Zoom user. .PARAMETER Id - Zoom user to set license for. + Zoom user to update. + + .PARAMETER FirstName + User's first name. + + .PARAMETER LastName + User's last name. .PARAMETER License License type. Basic, Pro, or Corp. + .PARAMETER Pmi + Personal Meeting ID, long, length must be 10. + + .PARAMETER EnablePmi + Specify whether to use Personal Meeting Id for instant meetings. True or False. + + .PARAMETER VanityName + Personal meeting room name. + .EXAMPLE - Get-ZoomUser -Id user@company.com | Set-ZoomUserLicense -License Corp + Get-ZoomUser -Id user@company.com | Set-ZoomUserInfo -License Corp Sets Zoom license to Corp on user@company.com's account. .OUTPUTS PSCustomObject #> - [CmdletBinding(DefaultParameterSetName = 'All')] + [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter( Mandatory = $true, @@ -536,25 +551,49 @@ function Set-ZoomUserLicense { ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] - [string[]]$Id, + [string]$Id, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] + [string]$FirstName, + + [Parameter(Mandatory = $false)] + [string]$LastName, + + [Parameter(Mandatory = $false)] [ValidateSet('Basic', 'Pro', 'Corp')] - [string]$License + [string]$License, + + [Parameter(Mandatory = $false)] + [ValidateRange(1000000000, 9999999999)] + [long]$Pmi, + + [Parameter(Mandatory = $false)] + [bool]$EnablePmi, + + [Parameter(Mandatory = $false)] + [string]$VanityName ) $Endpoint = 'https://api.zoom.us/v1/user/update' - $Type = switch ($License) { - 'Basic' { 1 } - 'Pro' { 2 } - 'Corp' { 3 } - } + if ($pscmdlet.ShouldProcess($Id, 'Update Zoom user info')) { - foreach ($User in $Id) { $Headers = Get-ZoomAuthHeader - $Headers.Add('id', $User) - $Headers.Add('type', $Type) + $Headers.Add('id', $Id) + if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } + if ($PSBoundParameters.ContainsKey('License')) { + $Type = switch ($License) { + 'Basic' { 1 } + 'Pro' { 2 } + 'Corp' { 3 } + } + + $Headers.Add('type', $Type) + } + if ($PSBoundParameters.ContainsKey('Pmi')) { $Headers.Add('pmi', $Pmi) } + if ($PSBoundParameters.ContainsKey('EnablePmi')) { $Headers.Add('enable_use_pmi', $EnablePmi) } + if ($PSBoundParameters.ContainsKey('VanityName')) { $Headers.Add('vanity_name', $VanityName) } Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } From 3eebec7fee9dbe4f398b831a3d0898e359457206 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Thu, 1 Jun 2017 12:44:06 -0400 Subject: [PATCH 21/65] Zoom module and tool updates. --- Zoom/Set-ZoomUserIntern.ps1 | 53 ++- Zoom/Set-ZoomUserInternational.ps1 | 39 +- Zoom/Set-ZoomUserPmiAndVanityName.ps1 | 12 +- Zoom/Update-ZoomADUser.ps1 | 31 ++ Zoom/Zoom.psm1 | 572 ++++++++++++++++++++++---- 5 files changed, 584 insertions(+), 123 deletions(-) create mode 100644 Zoom/Update-ZoomADUser.ps1 diff --git a/Zoom/Set-ZoomUserIntern.ps1 b/Zoom/Set-ZoomUserIntern.ps1 index 8cb82c2..d3988f0 100644 --- a/Zoom/Set-ZoomUserIntern.ps1 +++ b/Zoom/Set-ZoomUserIntern.ps1 @@ -1,34 +1,33 @@ +<# +.SYNOPSIS +Get interns from AD and set. + +.DESCRIPTION +Get interns from AD and set their license to Basic and move to Intern group in Zoom. +#> +[CmdletBinding(SupportsShouldProcess = $True)] +Param() + Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force Import-Module ActiveDirectory -function Set-ZoomUserIntern { - [CmdletBinding()] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string[]]$Id - ) +$Groups = Get-ZoomGroup +$InternGroupId = ($Groups | Where-Object -Property name -eq 'Interns/Temps').group_id +$OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').group_id - foreach ($UserId in $Id) { - Set-ZoomUserInfo -Id $UserId -License Basic +# Get Zoom intern users from AD group and set their Zoom license and group +Get-ADGroupMember -Recursive -Identity 'Global - Interns' | Get-ADUser | + Select-Object -ExpandProperty UserPrincipalName | ForEach-Object { + if (Test-ZoomUserEmail -Email $_) { + $Id = Get-ZoomUser -Email $_ | Select-Object -ExpandProperty id - $Groups = Get-ZoomGroup - $InternGroupId = ($Groups | Where-Object -Property name -eq 'Interns/Temps').group_id - $OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').group_id + # Set license to Basic + Set-ZoomUser -Id $Id -License Basic - $OtherGroupIds | ForEach-Object { - Remove-ZoomGroupMember -GroupId $_ -Id $UserId - } + # Remove user from other groups + $OtherGroupIds | ForEach-Object { Remove-ZoomGroupMember -GroupId $_ -Id $Id } - $UserId | Add-ZoomGroupMember -GroupId $InternGroupId - } -} - -# Get Zoom intern users from AD group and set their Zoom license and group -$ADUsers = Get-ADGroupMember -Recursive -Identity 'Global - Interns' | - Get-ADUser | Select-Object -ExpandProperty UserPrincipalName -Get-ZoomUser -Email $ADUsers | Set-ZoomUserIntern \ No newline at end of file + # Add user to intern group + Add-ZoomGroupMember -GroupId $InternGroupId -Id $Id + } + } \ No newline at end of file diff --git a/Zoom/Set-ZoomUserInternational.ps1 b/Zoom/Set-ZoomUserInternational.ps1 index 367ad0f..f2cf85d 100644 --- a/Zoom/Set-ZoomUserInternational.ps1 +++ b/Zoom/Set-ZoomUserInternational.ps1 @@ -1,15 +1,40 @@ +<# +.SYNOPSIS +Get Zoom international users from AD and set them in Zoom. + +#> +[CmdletBinding(SupportsShouldProcess = $True)] +Param() + Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force Import-Module ActiveDirectory # Get Zoom international users from AD group $ADUsers = Get-ADGroupMember -Identity 'Zoom International Calling Accounts' | Get-ADUser | Select-Object -ExpandProperty UserPrincipalName -$ZoomUsers = Get-ZoomUser -Email $ADUsers -# Add users to international Zoom group -$InternationalGroup = Get-ZoomGroup -Name 'International Calling' -$ZoomUsers | Add-ZoomGroupMember -GroupId $InternationalGroup.group_id +$GroupInfo = @{} +foreach ($Group in Get-ZoomGroup -All) { + $GroupInfo.Add($Group.group_id, $(Get-ZoomGroupMember -Id $Group.group_id)) +} + +$InternationalGroup = Get-ZoomGroup -Name 'International Calling' | Select-Object -ExpandProperty group_id + +foreach ($User in $ADUsers) { + if (Test-ZoomUserEmail -Email $User) { + # Get the associated Zoom user + $ZoomUser = Get-ZoomUser -Email $User | Select-Object -ExpandProperty id + + # Add user to Int'l group if they aren't a member already + if ($GroupInfo.$InternationalGroup.id -notcontains $ZoomUser) { + Add-ZoomGroupMember -Id $ZoomUser -GroupId $InternationalGroup + } -# Remove users from other Zoom groups -$OtherGroups = Get-ZoomGroup | Where-Object -Property name -ne 'International Calling' -foreach ($Group in $OtherGroups) { $ZoomUsers | Remove-ZoomGroupMember -GroupId $Group.group_id } \ No newline at end of file + # Remove user from other groups if they are a member + foreach ($Group in $GroupInfo.Keys -ne $InternationalGroup) { + if ($Group.id -contains $ZoomUser) { + Remove-ZoomGroupMember -Id $ZoomUser -GroupId $Group + } + } + } +} \ No newline at end of file diff --git a/Zoom/Set-ZoomUserPmiAndVanityName.ps1 b/Zoom/Set-ZoomUserPmiAndVanityName.ps1 index 57dfcd1..de8c9dc 100644 --- a/Zoom/Set-ZoomUserPmiAndVanityName.ps1 +++ b/Zoom/Set-ZoomUserPmiAndVanityName.ps1 @@ -1,3 +1,13 @@ +<# +.SYNOPSIS +Set every Zoom user's Pmi, vanity name, and enable Pmi. + +.DESCRIPTION +Set every user's private meeting Id to their AD telephone number, set their vanity name to the name in their UserPrincipalName, and enable Pmi. +#> +[CmdletBinding(SupportsShouldProcess = $True)] +Param() + Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force Import-Module ActiveDirectory @@ -9,5 +19,5 @@ foreach ($User in $Users) { $Pmi = (Get-ADUser -Filter { UserPrincipalName -eq $Email } -Properties telephoneNumber | Select-Object -ExpandProperty telephoneNumber) -replace '-', '' - Set-ZoomUserInfo -Id $User.id -Pmi $Pmi -EnablePmi $true -VanityName $VanityName + Set-ZoomUser -Id $User.id -Pmi $Pmi -EnablePmi $true -VanityName $VanityName } \ No newline at end of file diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 new file mode 100644 index 0000000..25383ab --- /dev/null +++ b/Zoom/Update-ZoomADUser.ps1 @@ -0,0 +1,31 @@ +<# +.SYNOPSIS +Sync Zoom users with AD. + +.DESCRIPTION +Get all enabled users from AD and create a Zoom account if they don't have one. Remove disabled AD users from Zoom. +#> +[CmdletBinding(SupportsShouldProcess = $True)] +Param() + +Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force +Import-Module ActiveDirectory + +# Get all the enabled users +$EnabledFilter = { (Enabled -eq 'True') } +$SearchBase = 'OU=People,DC=Company,DC=LOCAL' +$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties telephoneNumber | + Where-Object { $_.distinguishedName -notlike '*OU=Disabled*'} + +# Pre-provision Zoom accounts for all selected AD users that don't already exist +$ZoomUsers = Get-ZoomUser -All +foreach ($User in $ADUsers) { + if ($ZoomUsers.email -notcontains $User.UserPrincipalName) { + New-ZoomSSOUser -Email $($User.UserPrincipalName) -License Pro -Pmi $($User.telephoneNumber -replace '-', '') + } +} + +# Remove any Zoom accounts that don't have matching AD users +Get-ZoomUser -All | ForEach-Object { + if ($ADUsers.UserPrincipalName -notcontains $_.email) { $_ | Remove-ZoomUser } +} \ No newline at end of file diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index 8c46297..42f8b5b 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -1,19 +1,10 @@ -# Verify we can get the api key and secret before loading the module -try { - $ApiKey = Get-Content -Path "$PSScriptRoot\api_key" -ErrorAction Stop - $ApiSecret = Get-Content -Path "$PSScriptRoot\api_secret" -ErrorAction Stop -} catch { - Write-Error "There was a problem getting the API key and secret. $_" - exit -} - -function Get-ZoomAuthHeader { +function Get-ZoomApiAuth { <# .SYNOPSIS - Gets a hashtable for a new REST body that includes the api key and secret. + Gets a hashtable for a Zoom Api REST body that includes the api key and secret. .EXAMPLE - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth .OUTPUTS Hashtable @@ -22,11 +13,62 @@ function Get-ZoomAuthHeader { Param() @{ - 'api_key' = $ApiKey - 'api_secret' = $ApiSecret + 'api_key' = Get-Content -Path "$PSScriptRoot\api_key" + 'api_secret' = Get-Content -Path "$PSScriptRoot\api_secret" } } +function Set-ZoomApiAuth { + <# + .SYNOPSIS + Set the Zoom Api key/secret to the files in the same directory as the module. + + .PARAMETER Key + Optional, sets a new Api key. + + .PARAMETER Secret + Optional, sets a new Api secret. + + .EXAMPLE + Set-ZoomApi -Key 'mysupersecretapikey' -Secret 'mysupersecretapisecret + Sets your Zoom api key and secret to the files in the module directory. + + .EXAMPLE + Set-ZoomApi + User is prompted to enter both key and secret. + + .OUTPUTS + Creates/overrides api_key and api_secret files in module directory. + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$Key, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$Secret + ) + + if ($PSBoundParameters.Keys.Count -eq 0) { + Read-Host 'Enter your Zoom Api key' | Set-Content -Path "$PSScriptRoot\api_key" + Read-Host 'Enter your Zoom Api secret' | Set-Content -Path "$PSScriptRoot\api_secret" + } else { + switch ($PSBoundParameters.Keys) { + 'Key' { $Key | Set-Content -Path "$PSScriptRoot\api_key" } + 'Secret' { $Secret | Set-Content -Path "$PSScriptRoot\api_secret" } + } + } +} + +# Verify we can get the api key and secret before continuing to load the module +try { + Get-ZoomApiAuth -ErrorAction Stop +} catch { + Set-ZoomApiAuth +} + function Read-ZoomResponse { <# .SYNOPSIS @@ -61,6 +103,9 @@ function Get-ZoomUser { .PARAMETER Email Gets all Zoom users and then filters by email. Will accept an array of emails. + + .PARAMETER LoginType + Optional, default is Sso. Login type of the email. .PARAMETER All Default. Return all Zoom users. @@ -78,51 +123,109 @@ function Get-ZoomUser { #> [CmdletBinding(DefaultParameterSetName = 'All')] Param( - [Parameter(ParameterSetName = 'Id')] + [Parameter( + Mandatory = $true, + ParameterSetName = 'Id' + )] [ValidateNotNullOrEmpty()] [string[]]$Id, - [Parameter(ParameterSetName = 'Email')] + [Parameter( + Mandatory = $true, + ParameterSetName = 'Email' + )] [ValidateNotNullOrEmpty()] [string[]]$Email, - [Parameter(ParameterSetName = 'All')] + [Parameter( + Mandatory = $false, + ParameterSetName = 'Email' + )] + [ValidateSet('Facebook', 'Google', 'Api', 'Zoom', 'Sso')] + [string]$LoginType = 'Sso', + + [Parameter( + Mandatory = $false, + ParameterSetName = 'All' + )] [switch]$All ) - $Endpoint = if ($PSCmdlet.ParameterSetName -ne 'Id') { - 'https://api.zoom.us/v1/user/list' - } else { - 'https://api.zoom.us/v1/user/get' - } + if ($PSCmdlet.ParameterSetName -eq 'All') { + $Endpoint = 'https://api.zoom.us/v1/user/list' + + $Headers = Get-ZoomApiAuth + $Headers.Add('page_size', 300) - if ($PSCmdlet.ParameterSetName -ne 'Id') { - $Headers = Get-ZoomAuthHeader $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse Write-Verbose "There are $($Result.page_count) pages of users" - foreach ($Page in $Result.page_count) { + for ($Page = 1; $Page -le $Result.page_count; $Page++) { + $Headers = Get-ZoomApiAuth $Headers.Add('page_size', 300) $Headers.Add('page_number', $Page) - $Users += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).users + (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).users + } + } elseif ($PSCmdlet.ParameterSetName -eq 'Email') { + $Endpoint = 'https://api.zoom.us/v1/user/getbyemail' + + $Type = switch ($LoginType) { + 'Facebook' { '0' } + 'Google' { '1' } + 'Api' { '99' } + 'Zoom' { '100' } + 'Sso' { '101' } } - if ($PSCmdlet.ParameterSetName -eq 'Email') { - foreach ($User in $Email) { - $Users | Where-Object -Property email -eq $User - } - } else { - $Users + foreach ($User in $Email) { + $Headers = Get-ZoomApiAuth + $Headers.Add('email', $User) + $Headers.Add('login_type', $Type) + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } - } else { + } elseif ($PSCmdlet.ParameterSetName -eq 'Id') { + $Endpoint = 'https://api.zoom.us/v1/user/get' + foreach ($User in $Id) { - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('id', $User) Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } } } +function Get-ZoomPendingUser { + <# + .SYNOPSIS + List all the pending users on Zoom. + + .EXAMPLE + Get-ZoomPendingUser + Returns all pending Zoom users. + + .OUTPUTS + PSCustomObject + #> + [CmdletBinding(DefaultParameterSetName = 'All')] + Param() + + $Endpoint = 'https://api.zoom.us/v1/user/pending' + + $Headers = Get-ZoomApiAuth + $Headers.Add('page_size', 300) + $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + + Write-Verbose "There are $($Result.page_count) pages of pending users" + for ($Page = 1; $Page -le $Result.page_count; $Page++) { + $Headers = Get-ZoomApiAuth + $Headers.Add('page_size', 300) + $Headers.Add('page_number', $Page) + $Users += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).users + } + + $Users +} + function Remove-ZoomUser { <# .SYNOPSIS @@ -162,7 +265,7 @@ function Remove-ZoomUser { 'https://api.zoom.us/v1/user/delete' } - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('id', $Id) if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom user')) { @@ -195,7 +298,7 @@ function Remove-ZoomGroup { $Endpoint = 'https://api.zoom.us/v1/group/delete' - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('id', $Id) if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom group')) { @@ -224,10 +327,10 @@ function Test-ZoomUserEmail { $Endpoint = 'https://api.zoom.us/v1/user/checkemail' - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('email', $Email) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).existed_email } function Disable-ZoomUser { @@ -253,7 +356,7 @@ function Disable-ZoomUser { [string]$Id ) - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Endpoint = 'https://api.zoom.us/v1/user/deactivate' @@ -308,7 +411,7 @@ function Get-ZoomGroup { [switch]$All ) - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Endpoint = if ($PSCmdlet.ParameterSetName -ne 'Id') { 'https://api.zoom.us/v1/group/list' @@ -330,7 +433,123 @@ function Get-ZoomGroup { } } +function Get-ZoomMeeting { + <# + .SYNOPSIS + List all the scheduled meetings on Zoom for the user Id. + + .PARAMETER Id + Gets Zoom group by their Zoom Id. + + .EXAMPLE + Get-ZoomGroup -Name TestGroup + Searches for and returns specified group if found. + + .OUTPUTS + PSCustomObject + #> + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Id + ) + + $Headers = Get-ZoomApiAuth + + $Endpoint = 'https://api.zoom.us/v1/meeting/list' + + foreach ($User in $Id) { + $Headers = Get-ZoomApiAuth + $Headers.Add('page_size', 300) + $Headers.Add('host_id', $User) + $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + + Write-Verbose "There are $($Result.page_count) pages of meetings" + for ($Page = 1; $Page -le $Result.page_count; $Page++) { + $Headers = Get-ZoomApiAuth + $Headers.Add('host_id', $User) + $Headers.Add('page_size', 300) + $Headers.Add('page_number', $Page) + $Meetings += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).meetings + } + + $Meetings + } +} + +function Get-ZoomUserScheduler { + <# + .SYNOPSIS + List assigned schedule privilege for host users. + + .PARAMETER Id + The host's user id. + + .PARAMETER Email + The host's email address. + + .EXAMPLE + Get-ZoomUser -Email user@company.com | Get-ZoomUserScheduler + Returns all zoom groups. + + .OUTPUTS + PSCustomObject + #> + [CmdletBinding(DefaultParameterSetName = 'All')] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = 'Id' + )] + [ValidateNotNullOrEmpty()] + [string]$Id, + + [Parameter(ParameterSetName = 'Email')] + [ValidateNotNullOrEmpty()] + [string]$Email + ) + + $Headers = Get-ZoomApiAuth + + $Endpoint = 'https://api.zoom.us/v1/user/scheduleforhost/list' + + if ($PSCmdlet.ParameterSetName -eq 'Id') { + $Headers.Add('id', $Id) + } else { + $Headers.Add('host_email', $Email) + } + + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse +} + function Set-ZoomUserAssistant { + <# + .SYNOPSIS + Set a user's assistant which can schedule meeting for them. + + .PARAMETER Id + The host's user id. + + .PARAMETER Email + The host's email address. + + .PARAMETER AssistantEmail + The assistant's email address. + + .EXAMPLE + Get-ZoomUser -Email user@company.com | Get-ZoomUserAssistant -AssistantEmail assistant@company.com + Sets assistant@company.com as assistant for user@company.com + + .OUTPUTS + PSCustomObject + #> [CmdletBinding(DefaultParameterSetName = 'Id')] Param( [Parameter( @@ -352,7 +571,7 @@ function Set-ZoomUserAssistant { [string]$AssistantEmail ) - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('assistant_email', $AssistantEmail) $Endpoint = 'https://api.zoom.us/v1/user/assistant/set' @@ -367,6 +586,23 @@ function Set-ZoomUserAssistant { } function Remove-ZoomUserAssistant { + <# + .SYNOPSIS + Remove assistants for given user. + + .PARAMETER Id + The host's user id. + + .PARAMETER Email + The host's email address. + + .EXAMPLE + Get-ZoomUser -Email user@company.com | Remove-ZoomUserAssistant + Removes assistants of user@company.com. + + .OUTPUTS + PSCustomObject + #> [CmdletBinding( SupportsShouldProcess = $True, DefaultParameterSetName = 'Id' @@ -386,7 +622,7 @@ function Remove-ZoomUserAssistant { [string]$Email ) - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Endpoint = 'https://api.zoom.us/v1/user/assistant/delete' @@ -404,6 +640,20 @@ function Remove-ZoomUserAssistant { } function New-ZoomGroup { + <# + .SYNOPSIS + Create a group on Zoom, return the new group info. + + .PARAMETER Name + Group name, must be unique in one account. + + .EXAMPLE + New-ZoomGroup -Name TestGroup + Create new group named TestGroup. + + .OUTPUTS + PSCustomObject + #> [CmdletBinding()] Param( [Parameter(Mandatory = $true)] @@ -413,14 +663,27 @@ function New-ZoomGroup { $Endpoint = 'https://api.zoom.us/v1/group/create' - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('name', $Name) Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } function Add-ZoomGroupMember { - [CmdletBinding()] + <# + .SYNOPSIS + Adds members to a group on Zoom. + + .PARAMETER GroupId + Group ID. + + .PARAMETER Id + The member IDs, pipeline and arrays are accepted + + .OUTPUTS + PSCustomObject + #> + [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -437,18 +700,29 @@ function Add-ZoomGroupMember { $Endpoint = 'https://api.zoom.us/v1/group/member/add' - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('id', $GroupId) + $Headers.Add('member_ids', $Id -join ',') - foreach ($User in $Id) { - $MemberIds += "$User," + if ($pscmdlet.ShouldProcess($Id -join ',', "Add Zoom user(s) to $GroupId")) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } - $Headers.Add('member_ids', $MemberIds.TrimEnd(',')) - - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } function Remove-ZoomGroupMember { + <# + .SYNOPSIS + Remove members to a group on Zoom. + + .PARAMETER GroupId + Group ID. + + .PARAMETER Id + The member IDs, pipeline and arrays are accepted + + .OUTPUTS + PSCustomObject + #> [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter(Mandatory = $true)] @@ -466,20 +740,30 @@ function Remove-ZoomGroupMember { $Endpoint = 'https://api.zoom.us/v1/group/member/delete' - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('id', $GroupId) + $Headers.Add('member_ids', $Id -join ',') - foreach ($User in $Id) { - $MemberIds += "$User," - } - $Headers.Add('member_ids', $MemberIds) - - if ($pscmdlet.ShouldProcess($MemberIds, "Remove Zoom user from $GroupId")) { + if ($pscmdlet.ShouldProcess($Id -join ',', "Remove Zoom user(s) from $GroupId")) { Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse } } function Get-ZoomGroupMember { + <# + .SYNOPSIS + Lists the members of a group on Zoom. + + .PARAMETER Id + Group ID. + + .EXAMPLE + Get-ZoomGroup -Name TestGroup | Get-ZoomGroupMember + Gets members of TestGroup. + + .OUTPUTS + PSCustomObject + #> [CmdletBinding()] Param( [Parameter( @@ -493,14 +777,14 @@ function Get-ZoomGroupMember { $Endpoint = 'https://api.zoom.us/v1/group/member/list' - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('id', $Id) $Headers.Add('page_size', 300) $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse Write-Verbose "There are $($Result.page_count) pages of users" - foreach ($Page in $Result.page_count) { - $Headers = Get-ZoomAuthHeader + for ($Page = 1; $Page -le $Result.page_count; $Page++) { + $Headers = Get-ZoomApiAuth $Headers.Add('id', $Id) $Headers.Add('page_size', 300) $Headers.Add('page_number', $Page) @@ -510,10 +794,10 @@ function Get-ZoomGroupMember { $Users } -function Set-ZoomUserInfo { +function Set-ZoomUser { <# .SYNOPSIS - Set personal meeting Id and vanity name for Zoom user. + Update user info on Zoom via user ID. .PARAMETER Id Zoom user to update. @@ -536,8 +820,11 @@ function Set-ZoomUserInfo { .PARAMETER VanityName Personal meeting room name. + .PARAMETER GroupId + User Group ID. If set default user group, the parameter’s default value is the default user group. + .EXAMPLE - Get-ZoomUser -Id user@company.com | Set-ZoomUserInfo -License Corp + Get-ZoomUser -Id user@company.com | Set-ZoomUser -License Corp Sets Zoom license to Corp on user@company.com's account. .OUTPUTS @@ -551,7 +838,7 @@ function Set-ZoomUserInfo { ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] - [string]$Id, + [string[]]$Id, [Parameter(Mandatory = $false)] [string]$FirstName, @@ -571,31 +858,37 @@ function Set-ZoomUserInfo { [bool]$EnablePmi, [Parameter(Mandatory = $false)] - [string]$VanityName + [string]$VanityName, + + [Parameter(Mandatory = $false)] + [string]$GroupId ) $Endpoint = 'https://api.zoom.us/v1/user/update' - if ($pscmdlet.ShouldProcess($Id, 'Update Zoom user info')) { + foreach ($User in $Id) { + if ($pscmdlet.ShouldProcess($User, 'Update Zoom user info')) { - $Headers = Get-ZoomAuthHeader - $Headers.Add('id', $Id) - if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } - if ($PSBoundParameters.ContainsKey('License')) { - $Type = switch ($License) { - 'Basic' { 1 } - 'Pro' { 2 } - 'Corp' { 3 } + $Headers = Get-ZoomApiAuth + $Headers.Add('id', $User) + if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } + if ($PSBoundParameters.ContainsKey('License')) { + $Type = switch ($License) { + 'Basic' { 1 } + 'Pro' { 2 } + 'Corp' { 3 } + } + + $Headers.Add('type', $Type) } - - $Headers.Add('type', $Type) - } - if ($PSBoundParameters.ContainsKey('Pmi')) { $Headers.Add('pmi', $Pmi) } - if ($PSBoundParameters.ContainsKey('EnablePmi')) { $Headers.Add('enable_use_pmi', $EnablePmi) } - if ($PSBoundParameters.ContainsKey('VanityName')) { $Headers.Add('vanity_name', $VanityName) } + if ($PSBoundParameters.ContainsKey('Pmi')) { $Headers.Add('pmi', $Pmi) } + if ($PSBoundParameters.ContainsKey('EnablePmi')) { $Headers.Add('enable_use_pmi', $EnablePmi) } + if ($PSBoundParameters.ContainsKey('VanityName')) { $Headers.Add('vanity_name', $VanityName) } + if ($PSBoundParameters.ContainsKey('GroupId')) { $Headers.Add('group_id', $GroupId) } - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } } @@ -668,7 +961,7 @@ function Set-ZoomUserPicture { "--$boundary--$LF" ) -join $LF#> - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('id', $Id) $Headers.Add('pic_file', $FileContent) @@ -689,6 +982,19 @@ function New-ZoomSSOUser { .PARAMETER License License to grant new Zoom user. Basic, Pro, or Corp. + .PARAMETER FirstName + User's first name. + + .PARAMETER LastName + User's last name. + + .PARAMETER Pmi + Personal Meeting ID, long, length must be 10. + + .PARAMETER GroupId + User Group ID. If set default user group, the parameter’s default value is the default user group. + + .EXAMPLE New-ZoomSSOUser -Email user@company.com -License Pro Pre-provisions a Zoom user account for email user@company.com with a Pro license. @@ -696,7 +1002,7 @@ function New-ZoomSSOUser { .OUTPUTS PSCustomObject #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess = $True)] Param( [Parameter( Mandatory = $true, @@ -704,11 +1010,24 @@ function New-ZoomSSOUser { ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] - [string[]]$Email, + [string]$Email, [Parameter(Mandatory = $true)] [ValidateSet('Basic', 'Pro', 'Corp')] - [string]$License + [string]$License, + + [Parameter(Mandatory = $false)] + [string]$FirstName, + + [Parameter(Mandatory = $false)] + [string]$LastName, + + [Parameter(Mandatory = $false)] + [ValidateRange(1000000000, 9999999999)] + [long]$Pmi, + + [Parameter(Mandatory = $false)] + [string]$GroupId ) $Endpoint = 'https://api.zoom.us/v1/user/ssocreate' @@ -719,12 +1038,89 @@ function New-ZoomSSOUser { 'Corp' { 3 } } + $Headers = Get-ZoomApiAuth + $Headers.Add('email', $Email) + if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } + $Headers.Add('type', $Type) + if ($PSBoundParameters.ContainsKey('Pmi')) { $Headers.Add('pmi', $Pmi) } + if ($PSBoundParameters.ContainsKey('GroupId')) { $Headers.Add('group_id', $GroupId) } + + if ($pscmdlet.ShouldProcess($Email, 'New Zoom SSO user')) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } +} + +function New-ZoomUser { + <# + .SYNOPSIS + Create new Zoom user account. + + .PARAMETER Email + New Zoom user email address. + + .PARAMETER FirstName + User's first name. + + .PARAMETER LastName + User's last name. + + .PARAMETER License + License type. Basic, Pro, or Corp. + + .PARAMETER GroupId + User Group ID. If set default user group, the parameter’s default value is the default user group. + + .EXAMPLE + New-ZoomUser -Email user@company.com -License Pro + Creates a Zoom user account for email user@company.com with a Pro license. + + .OUTPUTS + PSCustomObject + #> + [CmdletBinding(SupportsShouldProcess = $True)] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [ValidateNotNullOrEmpty()] + [string[]]$Email, + + [Parameter(Mandatory = $false)] + [string]$FirstName, + + [Parameter(Mandatory = $false)] + [string]$LastName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Basic', 'Pro', 'Corp')] + [string]$License, + + [Parameter(Mandatory = $false)] + [string]$GroupId + ) + + $Endpoint = 'https://api.zoom.us/v1/user/create' + + $Type = switch ($License) { + 'Basic' { 1 } + 'Pro' { 2 } + 'Corp' { 3 } + } + foreach ($User in $Email) { - $Headers = Get-ZoomAuthHeader + $Headers = Get-ZoomApiAuth $Headers.Add('email', $User) $Headers.Add('type', $Type) + if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } + if ($PSBoundParameters.ContainsKey('GroupId')) { $Headers.Add('group_id', $GroupId) } - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + if ($pscmdlet.ShouldProcess($User, 'New Zoom user')) { + Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + } } } From 762f4e0b0728fffe0a97bead923c6b718249ce5d Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Mon, 5 Jun 2017 11:26:30 -0400 Subject: [PATCH 22/65] Class and enum based playing cards implementation. --- Fun/PlayingCards.ps1 | 201 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 Fun/PlayingCards.ps1 diff --git a/Fun/PlayingCards.ps1 b/Fun/PlayingCards.ps1 new file mode 100644 index 0000000..2206d98 --- /dev/null +++ b/Fun/PlayingCards.ps1 @@ -0,0 +1,201 @@ +#requires -Version 5.0 + +class Card { + [CardCollection]$CardCollection + [CardSuit]$CardSuit + [CardRank]$CardRank + [string]$CardName + [string]$CardImage + + # Constructor + Card() { + $this.CardSuit = [CardSuit]([Enum]::GetValues([CardSuit]) | Get-Random) + $this.CardRank = [CardRank]([Enum]::GetValues([CardRank]) | Get-Random) + $this.CardName = $this.PrintName() + $this.CardImage = $this.PrintImage() + } + + # Constructor + Card([CardSuit]$CardSuit, [CardRank]$CardRank) { + [CardSuit]$this.CardSuit = $CardSuit + [CardRank]$this.CardRank = $CardRank + $this.CardName = $this.PrintName() + $this.CardImage = $this.PrintImage() + } + + # Constructor + Card([CardCollection]$CardCollection, [CardSuit]$CardSuit, [CardRank]$CardRank) { + [CardCollection]$this.CardCollection = $CardCollection + [CardSuit]$this.CardSuit = $CardSuit + [CardRank]$this.CardRank = $CardRank + $this.CardName = $this.PrintName() + $this.CardImage = $this.PrintImage() + } + + hidden [string] PrintName() { + return "$($this.CardRank) of $($this.CardSuit)s" + } + + hidden [string] PrintImage() { + $Rank = switch ($this.CardRank) { + Joker { 'j' } + Ace { 'A' } + Two { '2' } + Three { '3' } + Four { '4' } + Five { '5' } + Six { '6' } + Seven { '7' } + Eight { '8' } + Nine { '9' } + Ten { '10' } + Jack { 'J' } + Queen { 'Q' } + King { 'K' } + } + + $Suit = switch ($this.CardSuit) { + Spade { 'â™ ' } + Club { '♣' } + Heart { '♥' } + Diamond { '♦' } + } + + return "$Rank$Suit" + } +} + +class CardCollection { + [string]$Name = 'Deck' + [System.Collections.ArrayList]$Cards = (New-Object System.Collections.ArrayList) + + CardCollection() { } + + CardCollection([string]$Name) { + [string]$this.Name = $Name + } + + CardCollection([string]$Name, [bool]$Jokers) { + [string]$this.Name = $Name + $this.NewStandardDeck($Jokers) + } + + CardCollection([bool]$Jokers) { + $this.NewStandardDeck($Jokers) + } + + CardCollection([string]$Name, [int]$DeckCount) { + [string]$this.Name = $Name + for ($i = 0; $i -lt $DeckCount; $i++) { + $this.NewStandardDeck($false) + } + } + + CardCollection([string]$Name, [int]$DeckCount, [bool]$Jokers) { + [string]$this.Name = $Name + for ($i = 0; $i -lt $DeckCount; $i++) { + $this.NewStandardDeck($Jokers) + } + } + + CardCollection([int]$DeckCount, [bool]$Jokers) { + for ($i = 0; $i -lt $DeckCount; $i++) { + $this.NewStandardDeck($Jokers) + } + } + + NewStandardDeck([bool]$Jokers) { + foreach ($CardSuit in [enum]::GetValues([CardSuit])) { + foreach ($CardRank in [enum]::GetValues([CardRank])) { + if ($Jokers -or [int]$CardRank -gt 0) { + $this.Push([Card]::new($this, $CardSuit, $CardRank)) + } + } + } + + $this.Shuffle() + } + + [void] Clear() { + $this.Cards = New-Object System.Collections.ArrayList + } + + [void] Push([Card]$Card) { + Write-Verbose "Adding $($Card.PrintImage()) to $($this.Name)." + $Card.CardCollection = $this + $this.Cards.Add($Card) + } + + [Card] Pop() { + $Card = $this.Cards[-1] + Write-Verbose "Removing $($Card.PrintImage()) from $($this.Name)." + $Card.CardCollection = $null + $this.Cards.Remove($Card) + return $Card + } + + [void] Shuffle() { + $this.Cards = $this.Cards | Sort-Object { Get-Random } + } + + [string[]] PrintCards() { + return ($this.Cards.GetEnumerator() | ForEach-Object { $_.PrintImage() }) -join ', ' + } +} + +class Player { + [CardCollection]$Hand = [CardCollection]::new() + [bool]$Active + [int]$Player + + Player([int]$Player) { + [int]$this.Player = $Player + } + + [void] DrawHand([CardCollection]$Deck, [int]$HandMax) { + while ($this.Hand.Cards.Count -lt $HandMax) { + $this.Hand.Push($Deck.Pop()) + } + } +} + +class Game { + [int]$ActivePlayer + [CardCollection]$Deck + [Player[]]$Players + [int]$HandSize + + Game([int]$PlayerCount, [int]$HandSize) { + $this.Deck = [CardCollection]::new($false) + + for ($i = 0; $i -lt $PlayerCount; $i++) { + $this.Players += [Player]::new($i) + } + + $this.Players.DrawHand($this.Deck, $HandSize) + } +} + +enum CardSuit { + Spade = 0 + Club = 1 + Heart = 2 + Diamond = 3 +} + +enum CardRank { + Joker = 0 + Ace = 1 + Two = 2 + Three = 3 + Four = 4 + Five = 5 + Six = 6 + Seven = 7 + Eight = 8 + Nine = 9 + Ten = 10 + Jack = 11 + Queen = 12 + King = 13 +} \ No newline at end of file From d305d34128b6ab969defdee65b2af25299e35c69 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Mon, 5 Jun 2017 17:01:07 -0400 Subject: [PATCH 23/65] Updates to Zoom module. Got photo upload working. --- Zoom/Invoke-ZoomADSync.ps1 | 3 + Zoom/Set-ZoomUserIntern.ps1 | 4 +- Zoom/Set-ZoomUserInternational.ps1 | 4 +- Zoom/Set-ZoomUserPmiAndVanityName.ps1 | 23 ---- Zoom/Update-ZoomADUser.ps1 | 37 ++++++- Zoom/Zoom.psm1 | 151 +++++++++++++++++++------- 6 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 Zoom/Invoke-ZoomADSync.ps1 delete mode 100644 Zoom/Set-ZoomUserPmiAndVanityName.ps1 diff --git a/Zoom/Invoke-ZoomADSync.ps1 b/Zoom/Invoke-ZoomADSync.ps1 new file mode 100644 index 0000000..09f635e --- /dev/null +++ b/Zoom/Invoke-ZoomADSync.ps1 @@ -0,0 +1,3 @@ +.\Update-ZoomADUser.ps1 +.\Set-ZoomUserIntern.ps1 +.\Set-ZoomUserInternational.ps1 \ No newline at end of file diff --git a/Zoom/Set-ZoomUserIntern.ps1 b/Zoom/Set-ZoomUserIntern.ps1 index d3988f0..85d5912 100644 --- a/Zoom/Set-ZoomUserIntern.ps1 +++ b/Zoom/Set-ZoomUserIntern.ps1 @@ -18,7 +18,7 @@ $OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').gro # Get Zoom intern users from AD group and set their Zoom license and group Get-ADGroupMember -Recursive -Identity 'Global - Interns' | Get-ADUser | Select-Object -ExpandProperty UserPrincipalName | ForEach-Object { - if (Test-ZoomUserEmail -Email $_) { + try { $Id = Get-ZoomUser -Email $_ | Select-Object -ExpandProperty id # Set license to Basic @@ -29,5 +29,7 @@ Get-ADGroupMember -Recursive -Identity 'Global - Interns' | Get-ADUser | # Add user to intern group Add-ZoomGroupMember -GroupId $InternGroupId -Id $Id + } catch { + Write-Warning "$_ not found." } } \ No newline at end of file diff --git a/Zoom/Set-ZoomUserInternational.ps1 b/Zoom/Set-ZoomUserInternational.ps1 index f2cf85d..f74ccbf 100644 --- a/Zoom/Set-ZoomUserInternational.ps1 +++ b/Zoom/Set-ZoomUserInternational.ps1 @@ -21,7 +21,7 @@ foreach ($Group in Get-ZoomGroup -All) { $InternationalGroup = Get-ZoomGroup -Name 'International Calling' | Select-Object -ExpandProperty group_id foreach ($User in $ADUsers) { - if (Test-ZoomUserEmail -Email $User) { + try { # Get the associated Zoom user $ZoomUser = Get-ZoomUser -Email $User | Select-Object -ExpandProperty id @@ -36,5 +36,7 @@ foreach ($User in $ADUsers) { Remove-ZoomGroupMember -Id $ZoomUser -GroupId $Group } } + } catch { + Write-Warning "$User not found." } } \ No newline at end of file diff --git a/Zoom/Set-ZoomUserPmiAndVanityName.ps1 b/Zoom/Set-ZoomUserPmiAndVanityName.ps1 deleted file mode 100644 index de8c9dc..0000000 --- a/Zoom/Set-ZoomUserPmiAndVanityName.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -<# -.SYNOPSIS -Set every Zoom user's Pmi, vanity name, and enable Pmi. - -.DESCRIPTION -Set every user's private meeting Id to their AD telephone number, set their vanity name to the name in their UserPrincipalName, and enable Pmi. -#> -[CmdletBinding(SupportsShouldProcess = $True)] -Param() - -Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force -Import-Module ActiveDirectory - -$Users = Get-ZoomUser - -foreach ($User in $Users) { - $Email = $User.email - $VanityName = $User.email.Split('@')[0] - $Pmi = (Get-ADUser -Filter { UserPrincipalName -eq $Email } -Properties telephoneNumber | - Select-Object -ExpandProperty telephoneNumber) -replace '-', '' - - Set-ZoomUser -Id $User.id -Pmi $Pmi -EnablePmi $true -VanityName $VanityName -} \ No newline at end of file diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 index 25383ab..d18a0dd 100644 --- a/Zoom/Update-ZoomADUser.ps1 +++ b/Zoom/Update-ZoomADUser.ps1 @@ -13,15 +13,42 @@ Import-Module ActiveDirectory # Get all the enabled users $EnabledFilter = { (Enabled -eq 'True') } -$SearchBase = 'OU=People,DC=Company,DC=LOCAL' -$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties telephoneNumber | - Where-Object { $_.distinguishedName -notlike '*OU=Disabled*'} +$SearchBase = 'OU=Users,DC=COMPANY,DC=LOCAL' +$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties telephoneNumber, thumbnailPhoto | + Where-Object { $_.distinguishedName -notlike '*OU=zz*'} + +$DefaultGroup = Get-ZoomGroup -Name DHG | Select-Object -ExpandProperty group_id -# Pre-provision Zoom accounts for all selected AD users that don't already exist $ZoomUsers = Get-ZoomUser -All foreach ($User in $ADUsers) { + # Pre-provision Zoom accounts for all selected AD users that don't already exist if ($ZoomUsers.email -notcontains $User.UserPrincipalName) { - New-ZoomSSOUser -Email $($User.UserPrincipalName) -License Pro -Pmi $($User.telephoneNumber -replace '-', '') + $Params = @{ + Email = $User.UserPrincipalName + FirstName = $User.GivenName + LastName = $User.Surname + License = 'Pro' + Pmi = $User.telephoneNumber -replace '-', '' + GroupId = $DefaultGroup + } + New-ZoomSSOUser @Params + # Update existing accounts with their AD info + } else { + $ZoomUser = $ZoomUsers | Where-Object -Property email -eq $User.UserPrincipalName + $Params = @{ + Id = $ZoomUser.id + FirstName = $User.GivenName + LastName = $User.Surname + Pmi = $User.telephoneNumber -replace '-', '' + VanityName = $User.UserPrincipalName.Split('@')[0] + } + Set-ZoomUser @Params + } + + # Upload user photo if it exists + if ($User.thumbnailPhoto) { + $ZoomUserId = Get-ZoomUser -Email $User.UserPrincipalName | Select-Object -ExpandProperty id + Set-ZoomUserPicture -Id $ZoomUserId -ByteArray $User.thumbnailPhoto } } diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index 42f8b5b..ab51f9b 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -903,17 +903,28 @@ function Set-ZoomUserPicture { .PARAMETER Path Path to profile picture to upload. + .PARAMETER ByteArray + Byte array representing the picture. + .EXAMPLE Get-ZoomUser -Id user@company.com | Set-ZoomUserPicture -Path .\picture.jpg Uploads new profile picture to user@company.com's account. + .EXAMPLE + $ThumbnailByteArray = Get-ADUser UserId -Properties thumbnailPhoto | Select-Object -ExpandProperty thumbnailPhoto + Get-ZoomUser -Id user@company.com | Set-ZoomUserPicture -ByteArray $ThumbnailByteArray + Uploads new profile picture to user@company.com's account from their AD thumbnail photo. + .OUTPUTS PSCustomObject .NOTES - This function does not work in its current state. + This function uses C# to form the rest call because Invoke-RestMethod was incompatible. #> - [CmdletBinding()] + [CmdletBinding( + SupportsShouldProcess = $True, + DefaultParameterSetName = 'Path' + )] Param( [Parameter( Mandatory = $true, @@ -923,52 +934,112 @@ function Set-ZoomUserPicture { [ValidateNotNullOrEmpty()] [string]$Id, - [Parameter(Mandatory = $true)] + [Parameter( + Mandatory = $true, + ParameterSetName = 'Path' + )] [ValidateScript({ Test-Path $_ -PathType Leaf })] [ValidatePattern('.jp*.g$')] - [string]$Path - ) + [string]$Path, - Write-Warning "This function is experimental and may not work." + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = 'Id' + )] + [ValidateNotNullOrEmpty()] + [byte[]]$ByteArray + ) $Endpoint = 'https://api.zoom.us/v1/user/uploadpicture' + $ApiAuth = Get-ZoomApiAuth + + $Boundary = [guid]::NewGuid() + + $Source = @" + using System; + using System.IO; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + + namespace Zoom + { + public static class Tools + { + public static string UploadUserPicture(string Id, byte[] byteArray, string fileName) + { + Uri webService = new Uri(@"$Endpoint"); + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, webService); + requestMessage.Headers.ExpectContinue = false; + + MultipartFormDataContent multiPartContent = new MultipartFormDataContent("$Boundary"); + + HttpContent apiKeyContent = new StringContent(@"$($ApiAuth.api_key)"); + multiPartContent.Add(apiKeyContent, "api_key"); + + HttpContent apiSecretContent = new StringContent(@"$($ApiAuth.api_secret)"); + multiPartContent.Add(apiSecretContent, "api_secret"); + + HttpContent idContent = new StringContent(Id); + multiPartContent.Add(idContent, "id"); + + ByteArrayContent byteArrayContent = new ByteArrayContent(byteArray); + byteArrayContent.Headers.Add("Content-Type", "application/octet-stream"); + multiPartContent.Add(byteArrayContent, "pic_file", fileName); + + requestMessage.Content = multiPartContent; + + HttpClient httpClient = new HttpClient(); + httpClient.Timeout = new TimeSpan(0, 2, 0); + try + { + Task httpRequest = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None); + HttpResponseMessage httpResponse = httpRequest.Result; + HttpStatusCode statusCode = httpResponse.StatusCode; + HttpContent responseContent = httpResponse.Content; + + if (responseContent != null) + { + Task stringContentsTask = responseContent.ReadAsStringAsync(); + String stringContents = stringContentsTask.Result; + return stringContents; + } + else + { + return "No response."; + } + } + catch (Exception ex) + { + return ex.Message; + } + } + } + } +"@ - $Bytes = [IO.File]::ReadAllBytes($Path) - $Encoding = [System.Text.Encoding]::ASCII - $FileContent = $Encoding.GetString($Bytes) - - <#$boundary = [System.Guid]::NewGuid().ToString() - $LF = "`n" - $bodyLines = ( - "--$boundary", - "api_key: Uo4sjR8IQcOBWCUqxFlM_g", - "--$boundary", - "api_secret: 0PxInW592LrbpM0wxoDd3NesO5VbU7OKm8lT", - "--$boundary", - "id: $Id", - "--$boundary", - "Content-Disposition: form-data; name=`"pic_file`"$LF", - $FileContent, - "--$boundary--$LF" - ) -join $LF - $bodyLines = ( - "api_key: Uo4sjR8IQcOBWCUqxFlM_g", - "api_secret: 0PxInW592LrbpM0wxoDd3NesO5VbU7OKm8lT", - "id: $Id", - "--$boundary", - "Content-Disposition: form-data; name=`"pic_file`"$LF", - $FileContent, - "--$boundary--$LF" - ) -join $LF#> - - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $Id) - $Headers.Add('pic_file', $FileContent) - + $Assemblies = ( + 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Net.dll', + 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Net.Http.dll' + ) + # We get errors about this already existing after the first run, so silence them + try { Add-Type -TypeDefinition $Source -Language CSharp -ReferencedAssemblies $Assemblies } catch {} - #Invoke-WebRequest -Headers $Headers -Method Post -Uri $Endpoint -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines - Invoke-RestMethod -Uri $Endpoint -Method Post -Body $Headers -ContentType 'multipart/form-data' + if ($pscmdlet.ShouldProcess($Id, 'Update Zoom user picture')) { + if ($PSCmdlet.ParameterSetName -eq 'Path') { + $ByteArray = Get-Content -Path $Path -Encoding Byte + $FileName = $Path.Split('\')[-1] + } else { + $FileName = 'ProfilePicture.jpg' + } + [Zoom.Tools]::UploadUserPicture($Id, $ByteArray, $FileName) | ConvertFrom-Json | Read-ZoomResponse + } } function New-ZoomSSOUser { From 2cec3a615d82a9c4247a9f88265bf3ea6daefa49 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Tue, 6 Jun 2017 10:49:07 -0400 Subject: [PATCH 24/65] Served a specific need renaming some tv episodes. --- File Management/Rename-Season.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 File Management/Rename-Season.ps1 diff --git a/File Management/Rename-Season.ps1 b/File Management/Rename-Season.ps1 new file mode 100644 index 0000000..9fba26c --- /dev/null +++ b/File Management/Rename-Season.ps1 @@ -0,0 +1,13 @@ +[CmdletBinding()] +Param( + [ValidateScript({ Test-Path -Path $_ -PathType Container })] + [string]$Path +) + +Get-ChildItem -Path $Path | ForEach-Object { + $NewName = $_.Name -replace 'Season ', 'S' -replace ', Episode ', 'E' + $Season = 'S' + "{0:D2}" -f [int]($NewName -split 'S', 2 -split 'E', 2)[1] + $Episode = 'E' + "{0:D2}" -f [int]($NewName -split 'E', 2 -split ' ', 2)[1] + $NewName = "$Season$Episode - $(($NewName -split ' ', 2)[1])" + Rename-Item -Path $_.FullName -NewName $NewName +} \ No newline at end of file From 88a7dbfb0cb0c0275a7d8f9e2ac9f2e3574e1f5b Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Thu, 8 Jun 2017 16:22:54 -0400 Subject: [PATCH 25/65] Optimized Api calls on Zoom/AD sync. --- Zoom/Invoke-ZoomADSync.ps1 | 3 ++ Zoom/Set-ZoomUserIntern.ps1 | 25 ++++++++++++--- Zoom/Set-ZoomUserInternational.ps1 | 10 +++--- Zoom/Update-ZoomADUser.ps1 | 50 ++++++++++++++++++++++-------- 4 files changed, 65 insertions(+), 23 deletions(-) diff --git a/Zoom/Invoke-ZoomADSync.ps1 b/Zoom/Invoke-ZoomADSync.ps1 index 09f635e..49688c2 100644 --- a/Zoom/Invoke-ZoomADSync.ps1 +++ b/Zoom/Invoke-ZoomADSync.ps1 @@ -1,3 +1,6 @@ +[CmdletBinding()] +Param() + .\Update-ZoomADUser.ps1 .\Set-ZoomUserIntern.ps1 .\Set-ZoomUserInternational.ps1 \ No newline at end of file diff --git a/Zoom/Set-ZoomUserIntern.ps1 b/Zoom/Set-ZoomUserIntern.ps1 index 85d5912..e42e959 100644 --- a/Zoom/Set-ZoomUserIntern.ps1 +++ b/Zoom/Set-ZoomUserIntern.ps1 @@ -12,23 +12,38 @@ Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force Import-Module ActiveDirectory $Groups = Get-ZoomGroup + +# Get intern group and members $InternGroupId = ($Groups | Where-Object -Property name -eq 'Interns/Temps').group_id +$InternGroupMembers = Get-ZoomGroupMember -Id $InternGroupId + +# Get other group id's and members $OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').group_id +$OtherGroupMembers = @{} +$OtherGroupIds | ForEach-Object { $OtherGroupMembers.Add($_, (Get-ZoomGroupMember -Id $_)) } # Get Zoom intern users from AD group and set their Zoom license and group Get-ADGroupMember -Recursive -Identity 'Global - Interns' | Get-ADUser | Select-Object -ExpandProperty UserPrincipalName | ForEach-Object { try { - $Id = Get-ZoomUser -Email $_ | Select-Object -ExpandProperty id + $ZoomUser = Get-ZoomUser -Email $_ - # Set license to Basic - Set-ZoomUser -Id $Id -License Basic + # Set license to Basic if it isn't already + if ($ZoomUser.type -ne 1) { Set-ZoomUser -Id $ZoomUser.id -License Basic } # Remove user from other groups - $OtherGroupIds | ForEach-Object { Remove-ZoomGroupMember -GroupId $_ -Id $Id } + $OtherGroupIds | ForEach-Object { + if ($OtherGroupMembers.$_.id -contains $ZoomUser.id) { + Remove-ZoomGroupMember -GroupId $_ -Id $ZoomUser.id + } + } # Add user to intern group - Add-ZoomGroupMember -GroupId $InternGroupId -Id $Id + if ($InternGroupMembers.id -notcontains $ZoomUser.id) { + Add-ZoomGroupMember -GroupId $InternGroupId -Id $ZoomUser.id + } else { + Write-Verbose "$($ZoomUser.email) is already a member of the Intern group." + } } catch { Write-Warning "$_ not found." } diff --git a/Zoom/Set-ZoomUserInternational.ps1 b/Zoom/Set-ZoomUserInternational.ps1 index f74ccbf..7881340 100644 --- a/Zoom/Set-ZoomUserInternational.ps1 +++ b/Zoom/Set-ZoomUserInternational.ps1 @@ -23,17 +23,17 @@ $InternationalGroup = Get-ZoomGroup -Name 'International Calling' | Select-Objec foreach ($User in $ADUsers) { try { # Get the associated Zoom user - $ZoomUser = Get-ZoomUser -Email $User | Select-Object -ExpandProperty id + $ZoomUserId = Get-ZoomUser -Email $User | Select-Object -ExpandProperty id # Add user to Int'l group if they aren't a member already - if ($GroupInfo.$InternationalGroup.id -notcontains $ZoomUser) { - Add-ZoomGroupMember -Id $ZoomUser -GroupId $InternationalGroup + if ($GroupInfo.$InternationalGroup.id -notcontains $ZoomUserId) { + Add-ZoomGroupMember -Id $ZoomUserId -GroupId $InternationalGroup } # Remove user from other groups if they are a member foreach ($Group in $GroupInfo.Keys -ne $InternationalGroup) { - if ($Group.id -contains $ZoomUser) { - Remove-ZoomGroupMember -Id $ZoomUser -GroupId $Group + if ($Group.id -contains $ZoomUserId) { + Remove-ZoomGroupMember -Id $ZoomUserId -GroupId $Group } } } catch { diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 index d18a0dd..f311de0 100644 --- a/Zoom/Update-ZoomADUser.ps1 +++ b/Zoom/Update-ZoomADUser.ps1 @@ -13,14 +13,22 @@ Import-Module ActiveDirectory # Get all the enabled users $EnabledFilter = { (Enabled -eq 'True') } -$SearchBase = 'OU=Users,DC=COMPANY,DC=LOCAL' -$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties telephoneNumber, thumbnailPhoto | - Where-Object { $_.distinguishedName -notlike '*OU=zz*'} +$SearchBase = 'OU=Users,DC=Company,DC=LOCAL' +$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties telephoneNumber, thumbnailPhoto, mobile | + Where-Object { $_.distinguishedName -notlike '*OU=Disabled*'} -$DefaultGroup = Get-ZoomGroup -Name DHG | Select-Object -ExpandProperty group_id +$DefaultGroup = Get-ZoomGroup -Name General | Select-Object -ExpandProperty group_id $ZoomUsers = Get-ZoomUser -All foreach ($User in $ADUsers) { + $PhoneNumber = if ($User.telephoneNumber) { + $User.telephoneNumber -replace '-', '' + } elseif ($User.mobile) { + $User.mobile -replace '-', '' + } else { + '' + } + # Pre-provision Zoom accounts for all selected AD users that don't already exist if ($ZoomUsers.email -notcontains $User.UserPrincipalName) { $Params = @{ @@ -28,21 +36,37 @@ foreach ($User in $ADUsers) { FirstName = $User.GivenName LastName = $User.Surname License = 'Pro' - Pmi = $User.telephoneNumber -replace '-', '' GroupId = $DefaultGroup } + if ($PhoneNumber) { $Params.Add('Pmi', $PhoneNumber) } New-ZoomSSOUser @Params # Update existing accounts with their AD info } else { - $ZoomUser = $ZoomUsers | Where-Object -Property email -eq $User.UserPrincipalName - $Params = @{ - Id = $ZoomUser.id - FirstName = $User.GivenName - LastName = $User.Surname - Pmi = $User.telephoneNumber -replace '-', '' - VanityName = $User.UserPrincipalName.Split('@')[0] + $ZoomUser = Get-ZoomUser -Email $User.UserPrincipalName + + $Params = @{ } + + # Add params in Zoom and AD users have mismatched properties + if ($ZoomUser.first_name -ne $User.GivenName) { + $Params.Add('FirstName', $User.GivenName) + } + if ($ZoomUser.last_name -ne $User.Surname) { + $Params.Add('LastName', $User.Surname) + } + if ($PhoneNumber) { + if ($ZoomUser.pmi -ne [int64]$PhoneNumber) { $Params.Add('Pmi', $PhoneNumber) } + } + if ($ZoomUser.vanity_url.Split('/')[-1] -ne $User.UserPrincipalName.Split('@')[0]) { + $Params.Add('VanityName', $User.UserPrincipalName.Split('@')[0]) + } + + # Only update Zoom user properties if they have mismatches + if ($Params.Count -gt 0) { + $Params.Add('id', $ZoomUser.id) + Set-ZoomUser @Params + } else { + Write-Verbose "$($ZoomUser.email) is already up to date." } - Set-ZoomUser @Params } # Upload user photo if it exists From 8f4e4639798c6c81f4cf8a0ca4630be5e8a96ec5 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 9 Jun 2017 13:28:58 -0400 Subject: [PATCH 26/65] Prettied code. Added verbose errors. Var name changes. Logging. --- Zoom/Invoke-ZoomADSync.ps1 | 9 +- Zoom/Update-ZoomADUser.ps1 | 21 ++- Zoom/Zoom.psm1 | 278 ++++++++++++++++++++++--------------- 3 files changed, 193 insertions(+), 115 deletions(-) diff --git a/Zoom/Invoke-ZoomADSync.ps1 b/Zoom/Invoke-ZoomADSync.ps1 index 49688c2..f2ddab5 100644 --- a/Zoom/Invoke-ZoomADSync.ps1 +++ b/Zoom/Invoke-ZoomADSync.ps1 @@ -1,6 +1,13 @@ [CmdletBinding()] Param() +$LogDirectory = (New-Item -ItemType Directory '.\Logs' -Force).FullName +$Date = (Get-Date).ToString('yyyyMMdd-HHmm') +$LogPath = "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" +Start-Transcript -Path $LogPath + .\Update-ZoomADUser.ps1 .\Set-ZoomUserIntern.ps1 -.\Set-ZoomUserInternational.ps1 \ No newline at end of file +.\Set-ZoomUserInternational.ps1 + +Stop-Transcript \ No newline at end of file diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 index f311de0..710d876 100644 --- a/Zoom/Update-ZoomADUser.ps1 +++ b/Zoom/Update-ZoomADUser.ps1 @@ -17,7 +17,7 @@ $SearchBase = 'OU=Users,DC=Company,DC=LOCAL' $ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties telephoneNumber, thumbnailPhoto, mobile | Where-Object { $_.distinguishedName -notlike '*OU=Disabled*'} -$DefaultGroup = Get-ZoomGroup -Name General | Select-Object -ExpandProperty group_id +$DefaultGroup = Get-ZoomGroup -Name Default | Select-Object -ExpandProperty group_id $ZoomUsers = Get-ZoomUser -All foreach ($User in $ADUsers) { @@ -38,7 +38,14 @@ foreach ($User in $ADUsers) { License = 'Pro' GroupId = $DefaultGroup } - if ($PhoneNumber) { $Params.Add('Pmi', $PhoneNumber) } + if ($PhoneNumber) { + if ($ZoomUsers.pmi -notcontains $PhoneNumber) { + $Params.Add('Pmi', $PhoneNumber) + } else { + Write-Warning "Unable to set Pmi for $($User.UserPrincipalName), $PhoneNumber already exists." + } + } + New-ZoomSSOUser @Params # Update existing accounts with their AD info } else { @@ -53,8 +60,14 @@ foreach ($User in $ADUsers) { if ($ZoomUser.last_name -ne $User.Surname) { $Params.Add('LastName', $User.Surname) } - if ($PhoneNumber) { - if ($ZoomUser.pmi -ne [int64]$PhoneNumber) { $Params.Add('Pmi', $PhoneNumber) } + if ($PhoneNumber -and $ZoomUser.type -ne 1) { + if ($ZoomUser.pmi -ne [int64]$PhoneNumber) { + if ($ZoomUsers.pmi -notcontains $PhoneNumber) { + $Params.Add('Pmi', $PhoneNumber) + } else { + Write-Warning "Unable to set Pmi for $($User.UserPrincipalName), $PhoneNumber already exists." + } + } } if ($ZoomUser.vanity_url.Split('/')[-1] -ne $User.UserPrincipalName.Split('@')[0]) { $Params.Add('VanityName', $User.UserPrincipalName.Split('@')[0]) diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index ab51f9b..881bfd2 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -4,7 +4,7 @@ function Get-ZoomApiAuth { Gets a hashtable for a Zoom Api REST body that includes the api key and secret. .EXAMPLE - $Headers = Get-ZoomApiAuth + $RequestBody = Get-ZoomApiAuth .OUTPUTS Hashtable @@ -74,8 +74,18 @@ function Read-ZoomResponse { .SYNOPSIS Parses Zoom REST response so errors are returned properly + .PARAMETER Response + The JSON response from the Api call. + + .PARAMETER RequestBody + The hashtable that was sent through the Api call. + + .PARAMETER Endpoint + Api endpoint Url that was called. + .EXAMPLE - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint -Endpoint $Endpoint #> [CmdletBinding()] Param( @@ -83,12 +93,22 @@ function Read-ZoomResponse { Mandatory = $true, ValueFromPipeline = $true )] - [PSCustomObject]$Response + [PSCustomObject]$Response, + + [Parameter(Mandatory = $true)] + [hashtable]$RequestBody, + + [Parameter(Mandatory = $true)] + [string]$Endpoint ) + $ApiCallInfo = "Api Endpoint: $Endpoint`n" + $ApiCallInfo += "Api call body:$($RequestBody | Out-String)" + if ($Response.PSObject.Properties.Name -match 'error') { - Write-Error -Message $Response.error.message -ErrorId $Response.error.code + Write-Error -Message "$($Response.error.message)`n$ApiCallInfo" -ErrorId $Response.error.code -Category InvalidOperation } else { + Write-Verbose "$($Response.error.message)`nApi call body:$($RequestBody | Out-String)" $Response } } @@ -154,17 +174,20 @@ function Get-ZoomUser { if ($PSCmdlet.ParameterSetName -eq 'All') { $Endpoint = 'https://api.zoom.us/v1/user/list' - $Headers = Get-ZoomApiAuth - $Headers.Add('page_size', 300) + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('page_size', 300) - $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint Write-Verbose "There are $($Result.page_count) pages of users" for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $Headers = Get-ZoomApiAuth - $Headers.Add('page_size', 300) - $Headers.Add('page_number', $Page) - (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).users + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('page_size', 300) + $RequestBody.Add('page_number', $Page) + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | + Select-Object -ExpandProperty users } } elseif ($PSCmdlet.ParameterSetName -eq 'Email') { $Endpoint = 'https://api.zoom.us/v1/user/getbyemail' @@ -178,18 +201,20 @@ function Get-ZoomUser { } foreach ($User in $Email) { - $Headers = Get-ZoomApiAuth - $Headers.Add('email', $User) - $Headers.Add('login_type', $Type) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('email', $User) + $RequestBody.Add('login_type', $Type) + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } elseif ($PSCmdlet.ParameterSetName -eq 'Id') { $Endpoint = 'https://api.zoom.us/v1/user/get' foreach ($User in $Id) { - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $User) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $User) + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } } @@ -211,16 +236,19 @@ function Get-ZoomPendingUser { $Endpoint = 'https://api.zoom.us/v1/user/pending' - $Headers = Get-ZoomApiAuth - $Headers.Add('page_size', 300) - $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('page_size', 300) + $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint Write-Verbose "There are $($Result.page_count) pages of pending users" for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $Headers = Get-ZoomApiAuth - $Headers.Add('page_size', 300) - $Headers.Add('page_number', $Page) - $Users += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).users + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('page_size', 300) + $RequestBody.Add('page_number', $Page) + $Users += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | + Select-Object -ExpandProperty users } $Users @@ -265,11 +293,12 @@ function Remove-ZoomUser { 'https://api.zoom.us/v1/user/delete' } - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $Id) + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $Id) if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom user')) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -298,11 +327,12 @@ function Remove-ZoomGroup { $Endpoint = 'https://api.zoom.us/v1/group/delete' - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $Id) + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $Id) if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom group')) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -327,10 +357,12 @@ function Test-ZoomUserEmail { $Endpoint = 'https://api.zoom.us/v1/user/checkemail' - $Headers = Get-ZoomApiAuth - $Headers.Add('email', $Email) + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('email', $Email) - (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).existed_email + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | + Select-Object -ExpandProperty existed_email } function Disable-ZoomUser { @@ -356,14 +388,15 @@ function Disable-ZoomUser { [string]$Id ) - $Headers = Get-ZoomApiAuth + $RequestBody = Get-ZoomApiAuth $Endpoint = 'https://api.zoom.us/v1/user/deactivate' - $Headers.Add('id', $Id) + $RequestBody.Add('id', $Id) if ($pscmdlet.ShouldProcess($Id, 'Deactivate Zoom user')) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -411,7 +444,7 @@ function Get-ZoomGroup { [switch]$All ) - $Headers = Get-ZoomApiAuth + $RequestBody = Get-ZoomApiAuth $Endpoint = if ($PSCmdlet.ParameterSetName -ne 'Id') { 'https://api.zoom.us/v1/group/list' @@ -420,7 +453,9 @@ function Get-ZoomGroup { } if ($PSCmdlet.ParameterSetName -ne 'Id') { - $Groups = (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).groups + $Groups = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | + Select-Object -ExpandProperty groups if ($PSCmdlet.ParameterSetName -eq 'Name') { $Groups | Where-Object -Property name -eq $Name @@ -428,8 +463,9 @@ function Get-ZoomGroup { $Groups } } else { - $Headers.Add('id', $Id) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + $RequestBody.Add('id', $Id) + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -459,23 +495,26 @@ function Get-ZoomMeeting { [string[]]$Id ) - $Headers = Get-ZoomApiAuth + $RequestBody = Get-ZoomApiAuth $Endpoint = 'https://api.zoom.us/v1/meeting/list' foreach ($User in $Id) { - $Headers = Get-ZoomApiAuth - $Headers.Add('page_size', 300) - $Headers.Add('host_id', $User) - $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('page_size', 300) + $RequestBody.Add('host_id', $User) + $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint Write-Verbose "There are $($Result.page_count) pages of meetings" for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $Headers = Get-ZoomApiAuth - $Headers.Add('host_id', $User) - $Headers.Add('page_size', 300) - $Headers.Add('page_number', $Page) - $Meetings += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).meetings + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('host_id', $User) + $RequestBody.Add('page_size', 300) + $RequestBody.Add('page_number', $Page) + $Meetings += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | + Select-Object -ExpandProperty meetings } $Meetings @@ -516,17 +555,18 @@ function Get-ZoomUserScheduler { [string]$Email ) - $Headers = Get-ZoomApiAuth + $RequestBody = Get-ZoomApiAuth $Endpoint = 'https://api.zoom.us/v1/user/scheduleforhost/list' if ($PSCmdlet.ParameterSetName -eq 'Id') { - $Headers.Add('id', $Id) + $RequestBody.Add('id', $Id) } else { - $Headers.Add('host_email', $Email) + $RequestBody.Add('host_email', $Email) } - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } function Set-ZoomUserAssistant { @@ -571,18 +611,19 @@ function Set-ZoomUserAssistant { [string]$AssistantEmail ) - $Headers = Get-ZoomApiAuth - $Headers.Add('assistant_email', $AssistantEmail) + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('assistant_email', $AssistantEmail) $Endpoint = 'https://api.zoom.us/v1/user/assistant/set' if ($PSCmdlet.ParameterSetName -ne 'Id') { - $Headers.Add('id', $Id) + $RequestBody.Add('id', $Id) } else { - $Headers.Add('host_email', $Email) + $RequestBody.Add('host_email', $Email) } - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } function Remove-ZoomUserAssistant { @@ -622,20 +663,21 @@ function Remove-ZoomUserAssistant { [string]$Email ) - $Headers = Get-ZoomApiAuth + $RequestBody = Get-ZoomApiAuth $Endpoint = 'https://api.zoom.us/v1/user/assistant/delete' if ($PSCmdlet.ParameterSetName -ne 'Id') { $Assistant = $Id - $Headers.Add('id', $Id) + $RequestBody.Add('id', $Id) } else { $Assistant = $Email - $Headers.Add('host_email', $Email) + $RequestBody.Add('host_email', $Email) } if ($pscmdlet.ShouldProcess($Assistant, 'Remove Zoom user assistant')) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -663,10 +705,11 @@ function New-ZoomGroup { $Endpoint = 'https://api.zoom.us/v1/group/create' - $Headers = Get-ZoomApiAuth - $Headers.Add('name', $Name) + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('name', $Name) - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } function Add-ZoomGroupMember { @@ -700,12 +743,13 @@ function Add-ZoomGroupMember { $Endpoint = 'https://api.zoom.us/v1/group/member/add' - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $GroupId) - $Headers.Add('member_ids', $Id -join ',') + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $GroupId) + $RequestBody.Add('member_ids', $Id -join ',') if ($pscmdlet.ShouldProcess($Id -join ',', "Add Zoom user(s) to $GroupId")) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -740,12 +784,13 @@ function Remove-ZoomGroupMember { $Endpoint = 'https://api.zoom.us/v1/group/member/delete' - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $GroupId) - $Headers.Add('member_ids', $Id -join ',') + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $GroupId) + $RequestBody.Add('member_ids', $Id -join ',') if ($pscmdlet.ShouldProcess($Id -join ',', "Remove Zoom user(s) from $GroupId")) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -777,18 +822,21 @@ function Get-ZoomGroupMember { $Endpoint = 'https://api.zoom.us/v1/group/member/list' - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $Id) - $Headers.Add('page_size', 300) - $Result = Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $Id) + $RequestBody.Add('page_size', 300) + $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint Write-Verbose "There are $($Result.page_count) pages of users" for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $Id) - $Headers.Add('page_size', 300) - $Headers.Add('page_number', $Page) - $Users += (Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse).members + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $Id) + $RequestBody.Add('page_size', 300) + $RequestBody.Add('page_number', $Page) + $Users += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | + Select-Object -ExpandProperty members } $Users @@ -869,10 +917,10 @@ function Set-ZoomUser { foreach ($User in $Id) { if ($pscmdlet.ShouldProcess($User, 'Update Zoom user info')) { - $Headers = Get-ZoomApiAuth - $Headers.Add('id', $User) - if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $User) + if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } if ($PSBoundParameters.ContainsKey('License')) { $Type = switch ($License) { 'Basic' { 1 } @@ -880,14 +928,15 @@ function Set-ZoomUser { 'Corp' { 3 } } - $Headers.Add('type', $Type) + $RequestBody.Add('type', $Type) } - if ($PSBoundParameters.ContainsKey('Pmi')) { $Headers.Add('pmi', $Pmi) } - if ($PSBoundParameters.ContainsKey('EnablePmi')) { $Headers.Add('enable_use_pmi', $EnablePmi) } - if ($PSBoundParameters.ContainsKey('VanityName')) { $Headers.Add('vanity_name', $VanityName) } - if ($PSBoundParameters.ContainsKey('GroupId')) { $Headers.Add('group_id', $GroupId) } + if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } + if ($PSBoundParameters.ContainsKey('EnablePmi')) { $RequestBody.Add('enable_use_pmi', $EnablePmi) } + if ($PSBoundParameters.ContainsKey('VanityName')) { $RequestBody.Add('vanity_name', $VanityName) } + if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } } @@ -1038,7 +1087,14 @@ function Set-ZoomUserPicture { } else { $FileName = 'ProfilePicture.jpg' } - [Zoom.Tools]::UploadUserPicture($Id, $ByteArray, $FileName) | ConvertFrom-Json | Read-ZoomResponse + + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $Id) + $RequestBody.Add('file_name', $FileName) + $RequestBody.Add('byte_array', $ByteArray) + + [Zoom.Tools]::UploadUserPicture($Id, $ByteArray, $FileName) | ConvertFrom-Json | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -1109,16 +1165,17 @@ function New-ZoomSSOUser { 'Corp' { 3 } } - $Headers = Get-ZoomApiAuth - $Headers.Add('email', $Email) - if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } - $Headers.Add('type', $Type) - if ($PSBoundParameters.ContainsKey('Pmi')) { $Headers.Add('pmi', $Pmi) } - if ($PSBoundParameters.ContainsKey('GroupId')) { $Headers.Add('group_id', $GroupId) } + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('email', $Email) + if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } + $RequestBody.Add('type', $Type) + if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } + if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } if ($pscmdlet.ShouldProcess($Email, 'New Zoom SSO user')) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } @@ -1182,15 +1239,16 @@ function New-ZoomUser { } foreach ($User in $Email) { - $Headers = Get-ZoomApiAuth - $Headers.Add('email', $User) - $Headers.Add('type', $Type) - if ($PSBoundParameters.ContainsKey('FirstName')) { $Headers.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $Headers.Add('last_name', $LastName) } - if ($PSBoundParameters.ContainsKey('GroupId')) { $Headers.Add('group_id', $GroupId) } + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('email', $User) + $RequestBody.Add('type', $Type) + if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } + if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } if ($pscmdlet.ShouldProcess($User, 'New Zoom user')) { - Invoke-RestMethod -Uri $Endpoint -Body $Headers -Method Post | Read-ZoomResponse + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } } From e29f9560bec11350c1d88c15c149b40353e55170 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 16 Jun 2017 16:47:35 -0400 Subject: [PATCH 27/65] Changed UPN to mail when dealing with AD user emails. Fixed typo. Param for pic add. --- Zoom/Set-ZoomUserIntern.ps1 | 2 +- Zoom/Set-ZoomUserInternational.ps1 | 2 +- Zoom/Update-ZoomADUser.ps1 | 29 ++++++++++++++++------------- Zoom/Zoom.psm1 | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Zoom/Set-ZoomUserIntern.ps1 b/Zoom/Set-ZoomUserIntern.ps1 index e42e959..1361a35 100644 --- a/Zoom/Set-ZoomUserIntern.ps1 +++ b/Zoom/Set-ZoomUserIntern.ps1 @@ -24,7 +24,7 @@ $OtherGroupIds | ForEach-Object { $OtherGroupMembers.Add($_, (Get-ZoomGroupMembe # Get Zoom intern users from AD group and set their Zoom license and group Get-ADGroupMember -Recursive -Identity 'Global - Interns' | Get-ADUser | - Select-Object -ExpandProperty UserPrincipalName | ForEach-Object { + Select-Object -ExpandProperty mail | ForEach-Object { try { $ZoomUser = Get-ZoomUser -Email $_ diff --git a/Zoom/Set-ZoomUserInternational.ps1 b/Zoom/Set-ZoomUserInternational.ps1 index 7881340..6cdf20f 100644 --- a/Zoom/Set-ZoomUserInternational.ps1 +++ b/Zoom/Set-ZoomUserInternational.ps1 @@ -11,7 +11,7 @@ Import-Module ActiveDirectory # Get Zoom international users from AD group $ADUsers = Get-ADGroupMember -Identity 'Zoom International Calling Accounts' | - Get-ADUser | Select-Object -ExpandProperty UserPrincipalName + Get-ADUser | Select-Object -ExpandProperty mail $GroupInfo = @{} foreach ($Group in Get-ZoomGroup -All) { diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 index 710d876..2c98691 100644 --- a/Zoom/Update-ZoomADUser.ps1 +++ b/Zoom/Update-ZoomADUser.ps1 @@ -6,7 +6,10 @@ Sync Zoom users with AD. Get all enabled users from AD and create a Zoom account if they don't have one. Remove disabled AD users from Zoom. #> [CmdletBinding(SupportsShouldProcess = $True)] -Param() +Param( + [Parameter(Mandatory = $false)] + [switch]$UpdatePictureFromAD +) Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force Import-Module ActiveDirectory @@ -14,8 +17,8 @@ Import-Module ActiveDirectory # Get all the enabled users $EnabledFilter = { (Enabled -eq 'True') } $SearchBase = 'OU=Users,DC=Company,DC=LOCAL' -$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties telephoneNumber, thumbnailPhoto, mobile | - Where-Object { $_.distinguishedName -notlike '*OU=Disabled*'} +$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties mail, telephoneNumber, thumbnailPhoto, mobile | + Where-Object { $_.distinguishedName -notlike '*OU=Disabled*' } $DefaultGroup = Get-ZoomGroup -Name Default | Select-Object -ExpandProperty group_id @@ -30,9 +33,9 @@ foreach ($User in $ADUsers) { } # Pre-provision Zoom accounts for all selected AD users that don't already exist - if ($ZoomUsers.email -notcontains $User.UserPrincipalName) { + if ($ZoomUsers.email -notcontains $User.mail) { $Params = @{ - Email = $User.UserPrincipalName + Email = $User.mail FirstName = $User.GivenName LastName = $User.Surname License = 'Pro' @@ -42,14 +45,14 @@ foreach ($User in $ADUsers) { if ($ZoomUsers.pmi -notcontains $PhoneNumber) { $Params.Add('Pmi', $PhoneNumber) } else { - Write-Warning "Unable to set Pmi for $($User.UserPrincipalName), $PhoneNumber already exists." + Write-Warning "Unable to set Pmi for $($User.mail), $PhoneNumber already exists." } } New-ZoomSSOUser @Params # Update existing accounts with their AD info } else { - $ZoomUser = Get-ZoomUser -Email $User.UserPrincipalName + $ZoomUser = Get-ZoomUser -Email $User.mail $Params = @{ } @@ -65,12 +68,12 @@ foreach ($User in $ADUsers) { if ($ZoomUsers.pmi -notcontains $PhoneNumber) { $Params.Add('Pmi', $PhoneNumber) } else { - Write-Warning "Unable to set Pmi for $($User.UserPrincipalName), $PhoneNumber already exists." + Write-Warning "Unable to set Pmi for $($User.mail), $PhoneNumber already exists." } } } - if ($ZoomUser.vanity_url.Split('/')[-1] -ne $User.UserPrincipalName.Split('@')[0]) { - $Params.Add('VanityName', $User.UserPrincipalName.Split('@')[0]) + if ($ZoomUser.vanity_url.Split('/')[-1] -ne $User.mail.Split('@')[0]) { + $Params.Add('VanityName', $User.mail.Split('@')[0]) } # Only update Zoom user properties if they have mismatches @@ -83,13 +86,13 @@ foreach ($User in $ADUsers) { } # Upload user photo if it exists - if ($User.thumbnailPhoto) { - $ZoomUserId = Get-ZoomUser -Email $User.UserPrincipalName | Select-Object -ExpandProperty id + if ($UpdatePictureFromAD -and $User.thumbnailPhoto) { + $ZoomUserId = Get-ZoomUser -Email $User.mail | Select-Object -ExpandProperty id Set-ZoomUserPicture -Id $ZoomUserId -ByteArray $User.thumbnailPhoto } } # Remove any Zoom accounts that don't have matching AD users Get-ZoomUser -All | ForEach-Object { - if ($ADUsers.UserPrincipalName -notcontains $_.email) { $_ | Remove-ZoomUser } + if ($ADUsers.mail -notcontains $_.email) { $_ | Remove-ZoomUser } } \ No newline at end of file diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index 881bfd2..9179001 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -30,7 +30,7 @@ function Set-ZoomApiAuth { Optional, sets a new Api secret. .EXAMPLE - Set-ZoomApi -Key 'mysupersecretapikey' -Secret 'mysupersecretapisecret + Set-ZoomApi -Key 'mysupersecretapikey' -Secret 'mysupersecretapisecret' Sets your Zoom api key and secret to the files in the module directory. .EXAMPLE From c137f01fbab37dff9ead324356e1ee6c90a678fc Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 16 Jun 2017 16:53:23 -0400 Subject: [PATCH 28/65] Added note about Test-ZoomUserEmail. --- Zoom/Zoom.psm1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index 9179001..bfd4d99 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -347,6 +347,9 @@ function Test-ZoomUserEmail { .EXAMPLE Test-ZoomUserEmail -Email user@company.com Checks to see if account exists for user@company.com. + + .NOTES + This will return false if the user has an SSO account but not an Email account. #> [CmdletBinding()] Param( From 79176d76a31803e7c4a83960011f42c6dcef293d Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Wed, 26 Jul 2017 11:47:23 -0400 Subject: [PATCH 29/65] New script to get cool report on AD user group memberships. Other minor stuffs. --- AD/Get-ADGroupReport.ps1 | 119 ++++++++++++++++++++++++++++ AD/Test-Credential.ps1 | 65 +++++++++++++++ Exchange/Set-MSOMailboxAuditing.ps1 | 72 +++++++++++++++-- Google/Get-GoogleHangoutsData.ps1 | 110 +++++++++++++++++++++++++ SCCM/Get-CMDeploymentSummary.ps1 | 29 +++++++ Zoom/Update-ZoomADUser.ps1 | 92 +++++++++++++++++++-- 6 files changed, 475 insertions(+), 12 deletions(-) create mode 100644 AD/Get-ADGroupReport.ps1 create mode 100644 AD/Test-Credential.ps1 create mode 100644 Google/Get-GoogleHangoutsData.ps1 create mode 100644 SCCM/Get-CMDeploymentSummary.ps1 diff --git a/AD/Get-ADGroupReport.ps1 b/AD/Get-ADGroupReport.ps1 new file mode 100644 index 0000000..a8fe501 --- /dev/null +++ b/AD/Get-ADGroupReport.ps1 @@ -0,0 +1,119 @@ +function Get-ADGroupReport { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [string]$SaveToCsv, + + [Parameter(Mandatory = $false)] + [ValidateNotNullorEmpty()] + [string]$ADGroupFilter = 'GroupCategory -eq "Distribution"' + ) + + begin { + Import-Module ActiveDirectory + + Write-Progress -Id 1 -Activity "Getting AD groups using filter $ADGroupFilter..." + $Groups = Get-ADGroup -Filter $ADGroupFilter + $Users = @{} + } + + process { + $GroupCount = $Groups.Count + $GroupCounter = 0 + + foreach ($Group in $Groups) { + $GroupCounter++ + + $GroupName = $Group.Name + + $GroupProgressParams = @{ + 'Id' = 1 + 'Activity' = "Processing group $GroupCounter of $GroupCount" + 'Status' = $GroupName + 'PercentComplete' = ($GroupCounter / $GroupCount) * 100 + } + Write-Progress @GroupProgressParams + + $Members = Get-ADGroupMember $Group -Recursive + + $MemberCount = $Members.Count + $MemberCounter = 0 + + foreach ($Member in $Members) { + $MemberCounter++ + + $MemberName = $Member.SamAccountName + + $MemberProgressParams = @{ + 'Id' = 2 + 'ParentId' = 1 + 'Activity' = "Processing member $MemberCounter of $MemberCount" + 'Status' = $MemberName + } + if ($MemberCount.GetType() -eq [int]) { + $MemberProgressParams.Add('PercentComplete', ($MemberCounter / $MemberCount) * 100) + } + Write-Progress @MemberProgressParams + + if ($Member.objectClass -eq 'user') { + Write-Verbose "$MemberName is a user" + + if (-not $Users.ContainsKey($MemberName)) { + Write-Verbose "Adding $MemberName to users list" + $Users.Add($MemberName, @{}) + } + + Write-Verbose "Adding $($GroupName) to $MemberName's group list" + $Users.($MemberName).Add($GroupName, $true) + } else { + Write-Verbose "$MemberName is not a user" + } + } + + Write-Progress -Id 2 -ParentId 1 -Activity "Processing members" -Completed + } + + $ReportProgressParams = @{ + 'Id' = 1 + 'Activity' = 'Building report...' + } + Write-Progress @ReportProgressParams + + $Results = @() + + foreach ($User in $Users.GetEnumerator()) { + $ADUser = Get-ADUser -Identity $User.Name -Properties @( + 'physicalDeliveryOfficeName', 'Office', 'Department', 'Company', 'City', 'telephoneNumber' + ) + + $UserObject = New-Object psobject -Property @{ + UserId = $User.Name + Email = $ADUser.UserPrincipalName + Phone = $ADUser.telephoneNumber + FullName = $ADUser.Name + Enabled = $ADUser.Enabled + PhysicalOffice = $ADUser.physicalDeliveryOfficeName + Office = $ADUser.Office + Department = $ADUser.Department + Company = $ADUser.Company + City = $ADUser.City + } + + foreach ($Group in $Groups) { + $UserObject | Add-Member -MemberType NoteProperty -Name $Group.Name -Value '' + + if ($User.Value.ContainsKey($Group.Name)) { + $UserObject.($Group.Name) = 'x' + } + } + + $Results += $UserObject + } + } + + end { + if ($SaveToCsv) { $Results | Export-Csv -Path $SaveToCsv -NoTypeInformation } + $Results + } +} \ No newline at end of file diff --git a/AD/Test-Credential.ps1 b/AD/Test-Credential.ps1 new file mode 100644 index 0000000..ac4b048 --- /dev/null +++ b/AD/Test-Credential.ps1 @@ -0,0 +1,65 @@ +<# +.DESCRIPTION + Simulates an Authentication Request in a Domain envrionment using a PSCredential Object. Returns $true if both Username and Password pair are valid. +.VERSION + 1.3 +.GUID + 6a18515f-73d3-4fb4-884f-412395aa5054 +.AUTHOR + Thomas Malkewitz @dotps1 +.TAGS + PSCredential, Credential +.RELEASENOTES + Updated $Domain default value to $Credential.GetNetworkCredential().Domain. + Added support for multipul credential objects to be passed into $Credential. +.PROJECTURI + http://dotps1.github.io +.NOTES + Slight modifications by Nick + #> + +Function Test-Credential { + [OutputType([Bool])] + + Param ( + [Parameter( + Mandatory = $true, + ValueFromPipeLine = $true, + ValueFromPipelineByPropertyName = $true + )] + [Alias('PSCredential')] + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential, + + [Parameter()] + [String] + $Domain = $Credential.GetNetworkCredential().Domain + ) + + Begin { + [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.AccountManagement") | + Out-Null + + $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( + [System.DirectoryServices.AccountManagement.ContextType]::Domain, $Domain + ) + } + + Process { + foreach ($item in $Credential) { + $networkCredential = $Credential.GetNetworkCredential() + + Write-Output -InputObject $( + $principalContext.ValidateCredentials( + $networkCredential.UserName, $networkCredential.Password + ) + ) + } + } + + End { + $principalContext.Dispose() + } +} \ No newline at end of file diff --git a/Exchange/Set-MSOMailboxAuditing.ps1 b/Exchange/Set-MSOMailboxAuditing.ps1 index 9574822..bb76f61 100644 --- a/Exchange/Set-MSOMailboxAuditing.ps1 +++ b/Exchange/Set-MSOMailboxAuditing.ps1 @@ -1,11 +1,18 @@ +# Start a transcript of everything we do +$LogDirectory = (New-Item -ItemType Directory "$PSScriptRoot\Logs" -Force).FullName +$Date = (Get-Date).ToString('yyyyMMdd-HHmm') +$LogPath = "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" +$ResultsPath = "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.csv" + # Connect to Exchange Online -Import-Module ..\OneDrive\OneDrive.psm1 -$Creds = Get-OneDriveCredential +$Creds = Get-Credential $ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange ` -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` -Credential $Creds -Authentication Basic -AllowRedirection Import-PSSession $ExchangeOnlineSession +Start-Transcript -Path $LogPath + # Audit options $AuditOwnerOptions = @( 'Create', 'HardDelete', 'MailboxLogin', 'Move', 'MoveToDeletedItems', @@ -22,10 +29,63 @@ $AuditDelegateOptions = @( 'SendAs', 'SendOnBehalf', 'SoftDelete', 'Update' ) +# Keep track of how many accounts we fix +$BadAccounts = 0 + # Get all users and enable auditing options for their mailboxes -Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox | - Set-Mailbox -AuditEnabled $true -AuditOwner $AuditOwnerOptions ` - -AuditAdmin $AuditAdminOptions -AuditDelegate $AuditDelegateOptions +Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox | ForEach-Object { + $Params = @{ 'Identity' = $_.UserPrincipalName } + + # Verify audit options are enabled + if (-not $_.AuditEnabled) { + Write-Output "$($_.UserPrincipalName) - enabling audit." + $Params.Add('AuditEnabled', $true) + } + + # Verify audit owner options are correct + if (Compare-Object -ReferenceObject $_.AuditOwner -DifferenceObject $AuditOwnerOptions) { + Write-Output "$($_.UserPrincipalName) - resetting Audit Owner options." + $Params.Add('AuditOwner', $AuditOwnerOptions) + } + + # Verify audit admin options are correct + if (Compare-Object -ReferenceObject $_.AuditAdmin -DifferenceObject $AuditAdminOptions) { + Write-Output "$($_.UserPrincipalName) - resetting Audit Admin options." + $Params.Add('AuditAdmin', $AuditAdminOptions) + } + + # Verify audit delegate options are correct + if (Compare-Object -ReferenceObject $_.AuditDelegate -DifferenceObject $AuditDelegateOptions) { + Write-Output "$($_.UserPrincipalName) - resetting Audit Delegate options." + $Params.Add('AuditDelegate', $AuditDelegateOptions) + } + + # Update user options if any don't match out settings + if ($Params.Count -gt 1) { + Write-Output "$($_.UserPrincipalName) - setting mailbox options." + try { Set-Mailbox @Params -WhatIf } catch { $_ } + $BadAccounts++ + } +} + +Write-Output "Audit options were set on $BadAccounts mailboxes." + +# Save resulting audit rules of all users to csv +Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox | + Select-Object PrimarySmtpAddress, Name, Audit* | + Export-Csv -Path $ResultsPath -NoTypeInformation # Disconnect from Exchange Online -Remove-PSSession $ExchangeOnlineSession \ No newline at end of file +Remove-PSSession $ExchangeOnlineSession +Stop-Transcript + +# Send results via email +$Params = @{ + 'Body' = Get-Content -Path $LogPath | Out-String + 'From' = 'Alerts@company.com' + 'SmtpServer' = 'smtp.company.local' + 'Subject' = 'Enable Mailbox Auditing' + 'To' = 'person@company.com' + 'Attachments' = $ResultsPath +} +Send-MailMessage @Params \ No newline at end of file diff --git a/Google/Get-GoogleHangoutsData.ps1 b/Google/Get-GoogleHangoutsData.ps1 new file mode 100644 index 0000000..40af555 --- /dev/null +++ b/Google/Get-GoogleHangoutsData.ps1 @@ -0,0 +1,110 @@ + +$HangoutsFilePath = '~\downloads\hangouts.json' +$HangoutsRaw = Get-Content $HangoutsFilePath +$HangoutsData = Get-Content $HangoutsFilePath | ConvertFrom-Json + +$Conversations = @() +$AllParticipants = @() + +function Process-GoogleHangoutsData { + # First we want to get all participants, so we loop fully once + foreach ($Key in $HangoutsData.conversation_state) { + $Conversation = $HangoutsData.conversation_state.$Key.'conversation_state'.'conversation' + + # Get all participants + foreach ($PersonData in $Conversation.'participant_data') { + $Person = $Conversation.'participant_data'.$PersonData + $Gaia_id = $Person.'id'.'gaia_id' + if (-not $person.'fallback_name' -or $Person.'fallback_name' -eq $null) { break } + if (-not $AllParticipants.$Gaia_id) { + $AllParticipants.$gaia_id = $Person.'fallback_name' + } + } + } + foreach ($Key in $HangoutsData.'conversation_state') { + $conversation_state = $HangoutsData.'conversation_state'.$key + $id = $conversation_state.'conversation_id'.'id' + $conversation = $conversation_state.'conversation_state'.'conversation' + + # Find participants + $participants = @() + $participants_obj = @{} + foreach ($person_key in $conversation.'participant_data') { + $person = $conversation.'participant_data'.$person_key + $gaia_id = $person.'id'.'gaia_id' + $name = 'Unknown' + if ($person.'fallback_name') { + $name = $person.'fallback_name' + } else { + $name = $AllParticipants.$gaia_id + } + $participants.push($name) + $participants_obj.$gaia_id = $name + } + $participants_string = $participants.join(", ") + # Add to list + #$(".convo-list").append("" + participants_string + "") + # Parse events + $events = @() + for (event_key in conversation_state.'conversation_state'.'event'){ + $convo_event = conversation_state.'conversation_state'.'event'.event_key + $timestamp = convo_event.'timestamp' + $msgtime = formatTimestamp(timestamp) + $sender = convo_event.'sender_id'.'gaia_id' + $message = "" + if(convo_event.'chat_message'){ + # Get message + for(msg_key in convo_event.'chat_message'.'message_content'.'segment'){ + $segment = convo_event.'chat_message'.'message_content'.'segment'.msg_key + if(segment.'type' == 'LINE_BREAK') $message += "\n" + if(!segment.'text') continue + message += twemoji.parse(segment.'text') + } + # Check for images on event + if(convo_event.'chat_message'.'message_content'.'attachment'){ + foreach ($attach_key in convo_event.'chat_message'.'message_content'.'attachment'){ + $attachment = convo_event.'chat_message'.'message_content'.'attachment'.attach_key + console.log(attachment) + if(attachment.'embed_item'.'type'.0 == "PLUS_PHOTO"){ + message += "\n" + } + } + } + events.push({msgtime: msgtime, sender: participants_obj.sender, message: message, timestamp: timestamp}) + } + } + <# Sort events by timestamp + $events.sort(function($a, $b){ + $keyA = $a.timestamp, + $keyB = $b.timestamp + if($keyA < $keyB) return -1 + if($keyA > $keyB) return 1 + return 0 + })#> + # Add events + $Conversations.$id = $events + } +} + +function switchConvo($id) { + $('.txt').text('') + foreach ($event_id in Conversations.id){ + $convo_event = Conversations.id.event_id + $('.txt').append($convo_event.msgtime + ": " + $convo_event.sender + ": " + $convo_event.message + "\n") + } +} + +function zeroPad($string) { + return ($string < 10) ? "0" + $string : $string +} + +function formatTimestamp($timestamp) { + $d = new Date($timestamp/1000) + $formattedDate = $d.getFullYear() + "-" + + zeroPad($d.getMonth() + 1) + "-" + + zeroPad($d.getDate()) + $hours = zeroPad($d.getHours()) + $minutes = zeroPad($d.getMinutes()) + $formattedTime = $hours + ":" + $minutes + return $formattedDate + " " + $formattedTime +} \ No newline at end of file diff --git a/SCCM/Get-CMDeploymentSummary.ps1 b/SCCM/Get-CMDeploymentSummary.ps1 new file mode 100644 index 0000000..89704ec --- /dev/null +++ b/SCCM/Get-CMDeploymentSummary.ps1 @@ -0,0 +1,29 @@ +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$CollectionName, + + [Parameter(Mandatory = $true)] + [pscredential]$Credential +) + +Import-Module "\\dcsccm03\SMS_DHG\AdminConsole\bin\ConfigurationManager.psd1" +Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop + +Invoke-Command -Credential $Credential -ComputerName dcsccm03 -ScriptBlock { + Param($CollectionName) + + Import-Module "\\dcsccm03\SMS_DHG\AdminConsole\bin\ConfigurationManager.psd1" + Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop + + Invoke-CMDeploymentSummarization -CollectionName $CollectionName -Verbose + + Start-Sleep -Seconds 10 +} -ArgumentList $CollectionName + +Get-CMDeployment -CollectionName $CollectionName | + Select-Object -Property ApplicationName, NumberSuccess, NumberTargeted, SummarizationTime, + @{ + Name = 'DeploymentSummary' + Expression = { "{0:P0}" -f ($_.NumberSuccess / $_.NumberTargeted) } + } \ No newline at end of file diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 index 2c98691..8666ff4 100644 --- a/Zoom/Update-ZoomADUser.ps1 +++ b/Zoom/Update-ZoomADUser.ps1 @@ -7,20 +7,45 @@ Get all enabled users from AD and create a Zoom account if they don't have one. #> [CmdletBinding(SupportsShouldProcess = $True)] Param( - [Parameter(Mandatory = $false)] - [switch]$UpdatePictureFromAD + [Parameter( + Mandatory = $false, + ParameterSetName = 'AD' + )] + [switch]$UpdatePictureFromAD, + + [Parameter( + Mandatory = $false, + ParameterSetName = 'EO' + )] + [switch]$UpdatePictureFromEO ) -Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force +Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 Import-Module ActiveDirectory +if ($UpdatePictureFromEO) { + $SessionParameters = @{ + 'ConfigurationName' = 'Microsoft.Exchange' + 'ConnectionUri' = 'https://outlook.office365.com/powershell-liveid' + 'Credential' = Get-Credential + 'Authentication' = 'Basic' + 'AllowRedirection' = $true + } + + try { + Import-PSSession (New-PSSession @SessionParameters) + } catch { + Write-Error "Unable to connect to Exchange Online: $_" + exit + } +} # Get all the enabled users $EnabledFilter = { (Enabled -eq 'True') } -$SearchBase = 'OU=Users,DC=Company,DC=LOCAL' +$SearchBase = 'OU=Users,DC=COMPANY,DC=LOCAL' $ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties mail, telephoneNumber, thumbnailPhoto, mobile | Where-Object { $_.distinguishedName -notlike '*OU=Disabled*' } -$DefaultGroup = Get-ZoomGroup -Name Default | Select-Object -ExpandProperty group_id +$DefaultGroup = Get-ZoomGroup -Name DHG | Select-Object -ExpandProperty group_id $ZoomUsers = Get-ZoomUser -All foreach ($User in $ADUsers) { @@ -90,9 +115,64 @@ foreach ($User in $ADUsers) { $ZoomUserId = Get-ZoomUser -Email $User.mail | Select-Object -ExpandProperty id Set-ZoomUserPicture -Id $ZoomUserId -ByteArray $User.thumbnailPhoto } + + if ($UpdatePictureFromEO) { + $PhotoExists = $false + + try { + # Get the photo from Exchange Online + $Photo = Get-UserPhoto -Identity $ZoomUser.email + $PhotoExists = $true + } catch { + Write-Warning "Exchange Online photo does not exist for $($ZoomUser.email)" + } + + if ($PhotoExists) { + # Save the photo to a temporary file + $FilePath = "$env:TEMP\$($ZoomUser.email).jpg" + if (Test-Path $FilePath) { Remove-Item $FilePath } + [IO.File]::WriteAllBytes($FilePath, $Photo.PictureData) + + # Load the photo and its properties + $Image = New-Object -ComObject Wia.ImageFile + $Image.LoadFile($FilePath) + + # Check if the photo is square + if ($Image.Height -eq $Image.Width) { + Set-ZoomUserPicture -Id $ZoomUser.id -ByteArray $Photo.PictureData + } else { + Write-Verbose "Photo is not square, cropping..." + + # Create a new crop filter + $Filter = New-Object -ComObject Wia.ImageProcess + $Filter.Filters.Add($Filter.FilterInfos.Item('Crop').FilterId) + + # Set the height/width to whichever is smallest + if ($Image.Height -lt $Image.Width) { + $PixelsToCrop = ($Image.Width - $Image.Height) / 2 + $Filter.Filters.Item(1).Properties.Item("Left") = $PixelsToCrop + $Filter.Filters.Item(1).Properties.Item("Right") = $PixelsToCrop + } else { + $PixelsToCrop = ($Image.Height - $Image.Width) / 2 + $Filter.Filters.Item(1).Properties.Item("Top") = $PixelsToCrop + $Filter.Filters.Item(1).Properties.Item("Bottom") = $PixelsToCrop + } + + # Apply the filter and upload the new image + $Image = $Filter.Apply($Image) + $CroppedFilePath = "$env:TEMP\$($ZoomUser.email)-cropped.jpg" + if (Test-Path $CroppedFilePath) { Remove-Item $CroppedFilePath } + $Image.SaveFile($CroppedFilePath) + Set-ZoomUserPicture -Id $ZoomUser.id -Path $CroppedFilePath + Remove-Item $CroppedFilePath + } + + Remove-Item $FilePath + } + } } # Remove any Zoom accounts that don't have matching AD users Get-ZoomUser -All | ForEach-Object { - if ($ADUsers.mail -notcontains $_.email) { $_ | Remove-ZoomUser } + if ($ADUsers.mail -notcontains $_.email) { $_ | Remove-ZoomUser -Permanently } } \ No newline at end of file From 4c5cf68089079ee6584e23af40f199cf59541232 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Wed, 26 Jul 2017 14:43:10 -0400 Subject: [PATCH 30/65] Renamed script. --- ...Get-CMDeploymentSummary.ps1 => Invoke-CMDeploymentSummary.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename SCCM/{Get-CMDeploymentSummary.ps1 => Invoke-CMDeploymentSummary.ps1} (100%) diff --git a/SCCM/Get-CMDeploymentSummary.ps1 b/SCCM/Invoke-CMDeploymentSummary.ps1 similarity index 100% rename from SCCM/Get-CMDeploymentSummary.ps1 rename to SCCM/Invoke-CMDeploymentSummary.ps1 From bb7960604b6a2608bb7f4660ecf529f96ae5aa20 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Wed, 26 Jul 2017 14:48:24 -0400 Subject: [PATCH 31/65] update to path --- SCCM/Invoke-CMDeploymentSummary.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SCCM/Invoke-CMDeploymentSummary.ps1 b/SCCM/Invoke-CMDeploymentSummary.ps1 index 89704ec..e8de8c6 100644 --- a/SCCM/Invoke-CMDeploymentSummary.ps1 +++ b/SCCM/Invoke-CMDeploymentSummary.ps1 @@ -7,13 +7,13 @@ Param( [pscredential]$Credential ) -Import-Module "\\dcsccm03\SMS_DHG\AdminConsole\bin\ConfigurationManager.psd1" +Import-Module "C:\Powershell-Scripts\ConfigurationManager.psd1" Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop Invoke-Command -Credential $Credential -ComputerName dcsccm03 -ScriptBlock { Param($CollectionName) - Import-Module "\\dcsccm03\SMS_DHG\AdminConsole\bin\ConfigurationManager.psd1" + Import-Module "C:\Powershell-Scripts\ConfigurationManager.psd1" Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop Invoke-CMDeploymentSummarization -CollectionName $CollectionName -Verbose From 666dba0a4b0be42e17624efdb0e8ac6bfbf08b88 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Wed, 26 Jul 2017 14:56:01 -0400 Subject: [PATCH 32/65] Updated script to reset location. --- SCCM/Invoke-CMDeploymentSummary.ps1 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/SCCM/Invoke-CMDeploymentSummary.ps1 b/SCCM/Invoke-CMDeploymentSummary.ps1 index e8de8c6..19189c8 100644 --- a/SCCM/Invoke-CMDeploymentSummary.ps1 +++ b/SCCM/Invoke-CMDeploymentSummary.ps1 @@ -7,17 +7,21 @@ Param( [pscredential]$Credential ) -Import-Module "C:\Powershell-Scripts\ConfigurationManager.psd1" +Import-Module "C:\powershell-scripts\dependencies\ConfigurationManager.psd1" +$StartingLocation = Get-Location Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop Invoke-Command -Credential $Credential -ComputerName dcsccm03 -ScriptBlock { Param($CollectionName) - Import-Module "C:\Powershell-Scripts\ConfigurationManager.psd1" + Import-Module "C:\powershell-scripts\dependencies\ConfigurationManager.psd1" + $StartingLocation = Get-Location Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop Invoke-CMDeploymentSummarization -CollectionName $CollectionName -Verbose + Set-Location $StartingLocation + Start-Sleep -Seconds 10 } -ArgumentList $CollectionName @@ -26,4 +30,6 @@ Get-CMDeployment -CollectionName $CollectionName | @{ Name = 'DeploymentSummary' Expression = { "{0:P0}" -f ($_.NumberSuccess / $_.NumberTargeted) } - } \ No newline at end of file + } + +Set-Location $StartingLocation \ No newline at end of file From b298d7cc44b64f4d4e4347f7678f9a07c7fcfec8 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Tue, 1 Aug 2017 16:59:00 -0400 Subject: [PATCH 33/65] Updates to module and sync settings for Zoom. --- Zoom/Invoke-ZoomADSync.ps1 | 8 +++- Zoom/Update-ZoomADUser.ps1 | 95 +++++++++++++++++++++++++------------- Zoom/Zoom.psm1 | 77 ++++++++++++++++++++++++------ 3 files changed, 134 insertions(+), 46 deletions(-) diff --git a/Zoom/Invoke-ZoomADSync.ps1 b/Zoom/Invoke-ZoomADSync.ps1 index f2ddab5..775d992 100644 --- a/Zoom/Invoke-ZoomADSync.ps1 +++ b/Zoom/Invoke-ZoomADSync.ps1 @@ -6,7 +6,13 @@ $Date = (Get-Date).ToString('yyyyMMdd-HHmm') $LogPath = "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" Start-Transcript -Path $LogPath -.\Update-ZoomADUser.ps1 +$Params = @{ + 'ADSearchBase' = 'OU=Users,DC=Company,DC=LOCAL' + 'ADNotLikeFilter' = '*OU=Disabled*' + 'DefaultZoomGroup' = 'General' + 'EOCredential' = Get-Credential +} +.\Update-ZoomADUser.ps1 -UpdatePictureFromEO @Params .\Set-ZoomUserIntern.ps1 .\Set-ZoomUserInternational.ps1 diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 index 8666ff4..827afb7 100644 --- a/Zoom/Update-ZoomADUser.ps1 +++ b/Zoom/Update-ZoomADUser.ps1 @@ -7,6 +7,15 @@ Get all enabled users from AD and create a Zoom account if they don't have one. #> [CmdletBinding(SupportsShouldProcess = $True)] Param( + [Parameter(Mandatory = $true)] + [string]$ADSearchBase, + + [Parameter(Mandatory = $true)] + [string]$ADNotLikeFilter, + + [Parameter(Mandatory = $false)] + [switch]$ADIncludeDisabledUsers, + [Parameter( Mandatory = $false, ParameterSetName = 'AD' @@ -17,35 +26,53 @@ Param( Mandatory = $false, ParameterSetName = 'EO' )] - [switch]$UpdatePictureFromEO + [switch]$UpdatePictureFromEO, + + [Parameter( + Mandatory = $false, + ParameterSetName = 'EO' + )] + [pscredential]$EOCredential, + + [Parameter(Mandatory = $false)] + [string]$DefaultZoomGroup, + + [Parameter(Mandatory = $false)] + [switch]$EnableChimeForNewUsers ) Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 Import-Module ActiveDirectory + if ($UpdatePictureFromEO) { - $SessionParameters = @{ - 'ConfigurationName' = 'Microsoft.Exchange' - 'ConnectionUri' = 'https://outlook.office365.com/powershell-liveid' - 'Credential' = Get-Credential - 'Authentication' = 'Basic' - 'AllowRedirection' = $true - } + if ((Get-PSSession).ConfigurationName -notcontains 'Microsoft.Exchange') { + $SessionParameters = @{ + 'ConfigurationName' = 'Microsoft.Exchange' + 'ConnectionUri' = 'https://outlook.office365.com/powershell-liveid' + 'Credential' = $EOCredential + 'Authentication' = 'Basic' + 'AllowRedirection' = $true + } - try { - Import-PSSession (New-PSSession @SessionParameters) - } catch { - Write-Error "Unable to connect to Exchange Online: $_" - exit + try { + Import-PSSession (New-PSSession @SessionParameters) + } catch { + Write-Error "Unable to connect to Exchange Online: $_" + exit + } } } -# Get all the enabled users -$EnabledFilter = { (Enabled -eq 'True') } -$SearchBase = 'OU=Users,DC=COMPANY,DC=LOCAL' -$ADUsers = Get-ADUser -SearchBase $SearchBase -Filter $EnabledFilter -Properties mail, telephoneNumber, thumbnailPhoto, mobile | - Where-Object { $_.distinguishedName -notlike '*OU=Disabled*' } +# Set enabled filter (all by default) +$EnabledFilter = if ($ADIncludeDisabledUsers) { '*' } else { { (Enabled -eq 'True') } } +$ADUsers = Get-ADUser -SearchBase $ADSearchBase -Filter $EnabledFilter -Properties mail, telephoneNumber, thumbnailPhoto, mobile | + Where-Object { $_.distinguishedName -notlike $ADNotLikeFilter } + +# Get the default Zoom group we're going to set if specified +if ($DefaultZoomGroup) { + $DefaultGroup = Get-ZoomGroup -Name $DefaultZoomGroup | Select-Object -ExpandProperty group_id +} -$DefaultGroup = Get-ZoomGroup -Name DHG | Select-Object -ExpandProperty group_id $ZoomUsers = Get-ZoomUser -All foreach ($User in $ADUsers) { @@ -64,8 +91,10 @@ foreach ($User in $ADUsers) { FirstName = $User.GivenName LastName = $User.Surname License = 'Pro' - GroupId = $DefaultGroup } + + if ($DefaultZoomGroup) { $Params.Add('GroupId', $DefaultGroup) } + if ($PhoneNumber) { if ($ZoomUsers.pmi -notcontains $PhoneNumber) { $Params.Add('Pmi', $PhoneNumber) @@ -74,7 +103,14 @@ foreach ($User in $ADUsers) { } } - New-ZoomSSOUser @Params + if ($EnableChimeForNewUsers) { + # Create new user and set chime defaults + New-ZoomSSOUser @Params | Set-ZoomUser -EnterExitChime $true + } else { + # Create new user + New-ZoomSSOUser @Params + } + # Update existing accounts with their AD info } else { $ZoomUser = Get-ZoomUser -Email $User.mail @@ -117,17 +153,12 @@ foreach ($User in $ADUsers) { } if ($UpdatePictureFromEO) { - $PhotoExists = $false - - try { - # Get the photo from Exchange Online - $Photo = Get-UserPhoto -Identity $ZoomUser.email - $PhotoExists = $true - } catch { - Write-Warning "Exchange Online photo does not exist for $($ZoomUser.email)" - } + $ErrorLogPath = ".\Logs\EOErrors.log" - if ($PhotoExists) { + # Get the photo from Exchange Online, send all errors to error log + $Photo = Get-UserPhoto -Identity $ZoomUser.email 2> $ErrorLogPath + + if ($Photo.PictureData -ne $null) { # Save the photo to a temporary file $FilePath = "$env:TEMP\$($ZoomUser.email).jpg" if (Test-Path $FilePath) { Remove-Item $FilePath } @@ -168,6 +199,8 @@ foreach ($User in $ADUsers) { } Remove-Item $FilePath + } else { + Write-Warning "Error getting Exchange Online photo for $($ZoomUser.email): $((Get-Content $ErrorLogPath)[4])" } } } diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index bfd4d99..880c672 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -874,6 +874,12 @@ function Set-ZoomUser { .PARAMETER GroupId User Group ID. If set default user group, the parameter’s default value is the default user group. + .PARAMETER EnterExitChime + Enable enter/exit chime. + + .PARAMETER EnterExitChimeType + Enter/exit chime type. All (0) means heard by all including host and attendees, HostOnly (1) means heard by host only. + .EXAMPLE Get-ZoomUser -Id user@company.com | Set-ZoomUser -License Corp Sets Zoom license to Corp on user@company.com's account. @@ -912,7 +918,14 @@ function Set-ZoomUser { [string]$VanityName, [Parameter(Mandatory = $false)] - [string]$GroupId + [string]$GroupId, + + [Parameter(Mandatory = $false)] + [bool]$EnterExitChime, + + [Parameter(Mandatory = $false)] + [ValidateSet('All', 'HostOnly')] + [string]$EnterExitChimeType ) $Endpoint = 'https://api.zoom.us/v1/user/update' @@ -925,18 +938,27 @@ function Set-ZoomUser { if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } if ($PSBoundParameters.ContainsKey('License')) { - $Type = switch ($License) { + $LicenseType = switch ($License) { 'Basic' { 1 } 'Pro' { 2 } 'Corp' { 3 } } - $RequestBody.Add('type', $Type) + $RequestBody.Add('type', $LicenseType) } if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } if ($PSBoundParameters.ContainsKey('EnablePmi')) { $RequestBody.Add('enable_use_pmi', $EnablePmi) } if ($PSBoundParameters.ContainsKey('VanityName')) { $RequestBody.Add('vanity_name', $VanityName) } if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } + if ($PSBoundParameters.ContainsKey('EnterExitChime')) { $RequestBody.Add('enable_enter_exit_chime', $EnterExitChime) } + if ($PSBoundParameters.ContainsKey('EnterExitChimeType')) { + $ChimeType = switch ($EnterExitChimeType) { + 'All' { 0 } + 'HostOnly' { 1 } + } + + $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) + } Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint @@ -1075,13 +1097,17 @@ function Set-ZoomUserPicture { } "@ - $Assemblies = ( - 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Net.dll', - 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Net.Http.dll' + $Assemblies = ( + # Assemblies can be found downloaded from .NET Framework 4.6.2 Dev Pack + # https://www.microsoft.com/en-us/download/confirmation.aspx?id=53321 + 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2\System.Net.dll', + 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2\System.Net.Http.dll' ) - # We get errors about this already existing after the first run, so silence them - try { Add-Type -TypeDefinition $Source -Language CSharp -ReferencedAssemblies $Assemblies } catch {} + # Only load the Zoom.Tools type if it isn't already loaded + if (-not ([System.Management.Automation.PSTypeName]'Zoom.Tools').Type) { + Add-Type -TypeDefinition $Source -Language CSharp -ReferencedAssemblies $Assemblies + } if ($pscmdlet.ShouldProcess($Id, 'Update Zoom user picture')) { if ($PSCmdlet.ParameterSetName -eq 'Path') { @@ -1124,7 +1150,6 @@ function New-ZoomSSOUser { .PARAMETER GroupId User Group ID. If set default user group, the parameter’s default value is the default user group. - .EXAMPLE New-ZoomSSOUser -Email user@company.com -License Pro Pre-provisions a Zoom user account for email user@company.com with a Pro license. @@ -1162,7 +1187,7 @@ function New-ZoomSSOUser { $Endpoint = 'https://api.zoom.us/v1/user/ssocreate' - $Type = switch ($License) { + $LicenseType = switch ($License) { 'Basic' { 1 } 'Pro' { 2 } 'Corp' { 3 } @@ -1172,7 +1197,7 @@ function New-ZoomSSOUser { $RequestBody.Add('email', $Email) if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } - $RequestBody.Add('type', $Type) + $RequestBody.Add('type', $LicenseType) if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } @@ -1201,6 +1226,12 @@ function New-ZoomUser { .PARAMETER GroupId User Group ID. If set default user group, the parameter’s default value is the default user group. + + .PARAMETER EnterExitChime + Enable enter/exit chime. + + .PARAMETER EnterExitChimeType + Enter/exit chime type. All (0) means heard by all including host and attendees, HostOnly (1) means heard by host only. .EXAMPLE New-ZoomUser -Email user@company.com -License Pro @@ -1230,24 +1261,42 @@ function New-ZoomUser { [string]$License, [Parameter(Mandatory = $false)] - [string]$GroupId + [string]$GroupId, + + [Parameter(Mandatory = $false)] + [bool]$EnterExitChime, + + [Parameter(Mandatory = $false)] + [ValidateSet('All', 'HostOnly')] + [string]$EnterExitChimeType ) $Endpoint = 'https://api.zoom.us/v1/user/create' - $Type = switch ($License) { + $LicenseType = switch ($License) { 'Basic' { 1 } 'Pro' { 2 } 'Corp' { 3 } } + + $ChimeType = switch ($EnterExitChimeType) { + 'All' { 0 } + 'HostOnly' { 1 } + } foreach ($User in $Email) { $RequestBody = Get-ZoomApiAuth $RequestBody.Add('email', $User) - $RequestBody.Add('type', $Type) + $RequestBody.Add('type', $LicenseType) if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } + if ($PSBoundParameters.ContainsKey('EnterExitChime')) { + $RequestBody.Add('enable_enter_exit_chime', $EnterExitChime) + } + if ($PSBoundParameters.ContainsKey('EnterExitChimeType')) { + $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) + } if ($pscmdlet.ShouldProcess($User, 'New Zoom user')) { Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | From 4b693690d513f8120777e7a7a49b02889ffc0512 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Mon, 7 Aug 2017 16:44:33 -0400 Subject: [PATCH 34/65] Added more whatif support and disable_feedback param support. --- Zoom/Update-ZoomADUser.ps1 | 3 +++ Zoom/Zoom.psm1 | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 index 827afb7..0281794 100644 --- a/Zoom/Update-ZoomADUser.ps1 +++ b/Zoom/Update-ZoomADUser.ps1 @@ -136,6 +136,9 @@ foreach ($User in $ADUsers) { if ($ZoomUser.vanity_url.Split('/')[-1] -ne $User.mail.Split('@')[0]) { $Params.Add('VanityName', $User.mail.Split('@')[0]) } + if ($ZoomUser.disable_feedback -ne $false) { + $Params.Add('DisableFeedback', $false) + } # Only update Zoom user properties if they have mismatches if ($Params.Count -gt 0) { diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index 880c672..5975cf0 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -593,7 +593,10 @@ function Set-ZoomUserAssistant { .OUTPUTS PSCustomObject #> - [CmdletBinding(DefaultParameterSetName = 'Id')] + [CmdletBinding( + SupportsShouldProcess = $True, + DefaultParameterSetName = 'Id' + )] Param( [Parameter( Mandatory = $true, @@ -621,12 +624,16 @@ function Set-ZoomUserAssistant { if ($PSCmdlet.ParameterSetName -ne 'Id') { $RequestBody.Add('id', $Id) + $Target = $Id } else { $RequestBody.Add('host_email', $Email) + $Target = $Email } - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint + if ($pscmdlet.ShouldProcess($Target, 'Set Zoom user assistant')) { + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint + } } function Remove-ZoomUserAssistant { @@ -880,6 +887,9 @@ function Set-ZoomUser { .PARAMETER EnterExitChimeType Enter/exit chime type. All (0) means heard by all including host and attendees, HostOnly (1) means heard by host only. + .PARAMETER DisableFeedback + Disable feedback. + .EXAMPLE Get-ZoomUser -Id user@company.com | Set-ZoomUser -License Corp Sets Zoom license to Corp on user@company.com's account. @@ -925,7 +935,10 @@ function Set-ZoomUser { [Parameter(Mandatory = $false)] [ValidateSet('All', 'HostOnly')] - [string]$EnterExitChimeType + [string]$EnterExitChimeType, + + [Parameter(Mandatory = $false)] + [bool]$DisableFeedback ) $Endpoint = 'https://api.zoom.us/v1/user/update' @@ -956,12 +969,14 @@ function Set-ZoomUser { 'All' { 0 } 'HostOnly' { 1 } } - $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) } + if ($PSBoundParameters.ContainsKey('DisableFeedback')) { $RequestBody.Add('disable_feedback', $DisableFeedback) } - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint + if ($pscmdlet.ShouldProcess($User, 'Set Zoom user settings')) { + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint + } } } } @@ -1233,6 +1248,9 @@ function New-ZoomUser { .PARAMETER EnterExitChimeType Enter/exit chime type. All (0) means heard by all including host and attendees, HostOnly (1) means heard by host only. + .PARAMETER DisableFeedback + Disable feedback. + .EXAMPLE New-ZoomUser -Email user@company.com -License Pro Creates a Zoom user account for email user@company.com with a Pro license. @@ -1268,7 +1286,10 @@ function New-ZoomUser { [Parameter(Mandatory = $false)] [ValidateSet('All', 'HostOnly')] - [string]$EnterExitChimeType + [string]$EnterExitChimeType, + + [Parameter(Mandatory = $false)] + [bool]$DisableFeedback ) $Endpoint = 'https://api.zoom.us/v1/user/create' @@ -1297,6 +1318,7 @@ function New-ZoomUser { if ($PSBoundParameters.ContainsKey('EnterExitChimeType')) { $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) } + if ($PSBoundParameters.ContainsKey('DisableFeedback')) { $RequestBody.Add('disable_feedback', $DisableFeedback) } if ($pscmdlet.ShouldProcess($User, 'New Zoom user')) { Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | From 845648e2ec73bc48835ebd6a45cbbbdda06242f9 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Wed, 16 Aug 2017 13:39:48 -0400 Subject: [PATCH 35/65] Updated cat fact Api endpoint. --- Fun/Send-CatFactMessage.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fun/Send-CatFactMessage.ps1 b/Fun/Send-CatFactMessage.ps1 index 55cb81a..ddc99c3 100644 --- a/Fun/Send-CatFactMessage.ps1 +++ b/Fun/Send-CatFactMessage.ps1 @@ -42,7 +42,7 @@ [PSCredential]$Credential ) - $CatFact = (ConvertFrom-Json (Invoke-WebRequest -Uri 'http://catfacts-api.appspot.com/api/facts')).facts + $CatFact = Invoke-RestMethod -Uri 'https://catfact.ninja/fact' | Select-Object -ExpandProperty fact if ($pscmdlet.ShouldProcess("User: $UserName, Computer: $ComputerName", "Send cat fact, $CatFact")) { $ScriptBlock = { From 823fe735f66dcc7338fed46322e377dfdf54ca0a Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 18 Aug 2017 09:11:05 -0400 Subject: [PATCH 36/65] Cat fact enhancement. Added ability to give the message a display timer. --- Fun/Send-CatFactMessage.ps1 | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Fun/Send-CatFactMessage.ps1 b/Fun/Send-CatFactMessage.ps1 index ddc99c3..18894eb 100644 --- a/Fun/Send-CatFactMessage.ps1 +++ b/Fun/Send-CatFactMessage.ps1 @@ -19,6 +19,8 @@ The name the user to display the message to. Default is all users. .PARAMETER PlayAudio Use Windows Speech Synthesizer to output the fact using text to speech. + .PARAMETER DisplaySeconds + Seconds to display the message on the user's screen. Default is 0, which waits for the user to click a button. .PARAMETER Credential The credential object to execute the command with. #> @@ -38,6 +40,9 @@ [Parameter(Mandatory = $false)] [switch]$PlayAudio, + [Parameter(Mandatory = $false)] + [int]$DisplaySeconds = 0, + [Parameter(Mandatory = $false)] [PSCredential]$Credential ) @@ -47,14 +52,24 @@ if ($pscmdlet.ShouldProcess("User: $UserName, Computer: $ComputerName", "Send cat fact, $CatFact")) { $ScriptBlock = { param ( + [Parameter(Mandatory = $true)] [string]$UserName, + [Parameter(Mandatory = $true)] [string]$CatFact, - [bool]$PlayAudio = $false + [Parameter(Mandatory = $true)] + [bool]$PlayAudio = $false, + + [Parameter(Mandatory = $true)] + [int]$DisplaySeconds ) - msg $UserName $CatFact + if ($DisplaySeconds -gt 0) { + msg $UserName /time:$DisplaySeconds $CatFact + } else { + msg $UserName $CatFact + } if ($PlayAudio) { Add-Type -AssemblyName System.Speech @@ -63,15 +78,15 @@ } } - if ($Credential) { - Write-Verbose "Sending cat fact using credential $($Credential.UserName)" - - Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock ` - -ArgumentList $UserName, $CatFact, $PlayAudio -AsJob -Credential $Credential - } else { - Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock ` - -ArgumentList $UserName, $CatFact, $PlayAudio -AsJob + $Params = @{ + 'ComputerName' = $ComputerName + 'ScriptBlock' = $ScriptBlock + 'ArgumentList' = @($UserName, $CatFact, $PlayAudio, $DisplaySeconds) + 'AsJob' = $true } + if ($Credential) { $Params.Add('Credential', $Credential) } + + Invoke-Command @Params Get-Job | Wait-Job | Receive-Job Get-Job | Remove-Job From 2696c1c6617c49788c0e9ba07abe24481e33aba5 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 25 Aug 2017 08:52:02 -0400 Subject: [PATCH 37/65] Get disk info on a machine to determine free space and whether it's GPT. --- Windows/Get-DiskInfo.ps1 | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Windows/Get-DiskInfo.ps1 diff --git a/Windows/Get-DiskInfo.ps1 b/Windows/Get-DiskInfo.ps1 new file mode 100644 index 0000000..c57b1f0 --- /dev/null +++ b/Windows/Get-DiskInfo.ps1 @@ -0,0 +1,69 @@ +<# +.Synopsis + Get info on each partition and logical volume of a machine. + +#> +[CmdletBinding()] +param( + [Parameter( + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + Position = 0 + )] + [string[]]$ComputerName = $env:COMPUTERNAME, + + [pscredential]$Credential +) + +foreach ($Computer in $ComputerName) { + try { + $WmiObjectParams = @{ + ComputerName = $Computer + Credential = $creds + Class = 'Win32_DiskPartition' + Property = 'Name, DiskIndex, Type' + } + $Disks = Get-WmiObject @WmiObjectParams -ErrorAction Stop | + Select-Object -Property Name, DiskIndex, @{ + Name="GPT" + Expression = { $_.Type.StartsWith("GPT") } + } + } catch { + New-Object -TypeName psobject -Property @{ + ComputerName = $Computer + Error = $_.Exception.Message + VolumeName = '' + DiskName = '' + GPT = '' + DiskIndex = '' + DriveLetter = '' + FreeSpaceGB = '' + Utilization = '' + SizeGB = '' + FileSystem = '' + } + continue + } + + Get-WmiObject Win32_LogicalDisk -ComputerName $Computer -Credential $creds | + Where-Object -Property DriveType -EQ 3 | Foreach-Object { + $Query = "Associators of {Win32_LogicalDisk.DeviceID='$($_.DeviceID)'} WHERE ResultRole=Antecedent" + $Volume = Get-WmiObject -ComputerName $Computer -Credential $creds -Query $Query + + $Disk = $Disks | Where-Object -Property DiskIndex -EQ $Volume.DiskIndex + + New-Object -TypeName psobject -Property @{ + ComputerName = $Computer + Error = '' + VolumeName = $_.VolumeName + DiskName = $Disk.Name -join ', ' + GPT = $Disk.GPT -join ', ' + DiskIndex = $Volume.DiskIndex + DriveLetter = $_.DeviceID + FreeSpaceGB = [Math]::Round($_.FreeSpace / 1GB, 2) + Utilization = (1 - (($_.FreeSpace / 1GB) / ($Volume.Size / 1GB))).ToString('P') + SizeGB = [Math]::Round($Volume.Size / 1GB, 2) + FileSystem = $_.FileSystem + } + } +} \ No newline at end of file From 60d2fdb43cc3f95e13caf5ac2b9a84a4fc27f4e3 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Thu, 14 Sep 2017 13:52:55 -0400 Subject: [PATCH 38/65] Fixed TLS issue with cat facts. Updated Zoom to include timezone param for users. --- Fun/Send-CatFactMessage.ps1 | 13 +++- Zoom/Zoom.psm1 | 139 +++++++++++++++++++++++++++--------- Zoom/timezones.csv | 136 +++++++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 Zoom/timezones.csv diff --git a/Fun/Send-CatFactMessage.ps1 b/Fun/Send-CatFactMessage.ps1 index 18894eb..88ef858 100644 --- a/Fun/Send-CatFactMessage.ps1 +++ b/Fun/Send-CatFactMessage.ps1 @@ -2,25 +2,34 @@ <# .SYNOPSIS Send a cat fact to users on a computer. + .DESCRIPTION Send a random cat fact to any number of computers and all users or a specific user. Supports credential passing. + .EXAMPLE Send-CatFactMessage -PlayAudio Sends cat fact message to all users on localhost and outputs fact through speakers. + .EXAMPLE Get-ADComputer -Filter * | Send-CatFactMessage -UserName JDoe -Credential (Get-Credential) Send cat fact to jDoe on all AD computers. Prompt user for credentials to run command with. + .EXAMPLE Send-CatFactMessage -ComputerName pc1, pc2, pc3 Send cat fact to all users on provided computer names. + .PARAMETER ComputerName The computer name to execute against. Default is local computer. + .PARAMETER UserName The name the user to display the message to. Default is all users. + .PARAMETER PlayAudio Use Windows Speech Synthesizer to output the fact using text to speech. + .PARAMETER DisplaySeconds Seconds to display the message on the user's screen. Default is 0, which waits for the user to click a button. + .PARAMETER Credential The credential object to execute the command with. #> @@ -47,7 +56,9 @@ [PSCredential]$Credential ) - $CatFact = Invoke-RestMethod -Uri 'https://catfact.ninja/fact' | Select-Object -ExpandProperty fact + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $CatFact = Invoke-RestMethod -Uri 'https://catfact.ninja/fact' -Method Get | + Select-Object -ExpandProperty fact if ($pscmdlet.ShouldProcess("User: $UserName, Computer: $ComputerName", "Send cat fact, $CatFact")) { $ScriptBlock = { diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index 5975cf0..a0898b1 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -69,6 +69,14 @@ try { Set-ZoomApiAuth } +function Get-ZoomTimeZones { + $TimeZones = @{} + Import-Csv -Path "$PSScriptRoot\timezones.csv" -Delimiter ',' | ForEach-Object { + $TimeZones.Add($_.name, $_.id) + } + $TimeZones +} + function Read-ZoomResponse { <# .SYNOPSIS @@ -890,6 +898,12 @@ function Set-ZoomUser { .PARAMETER DisableFeedback Disable feedback. + .PARAMETER TimeZone + The time zone id for user profile. For a list of id's refer to https://zoom.github.io/api/#timezones. + + .PARAMETER Department + Department for user profile, use for reporting. + .EXAMPLE Get-ZoomUser -Id user@company.com | Set-ZoomUser -License Corp Sets Zoom license to Corp on user@company.com's account. @@ -938,44 +952,98 @@ function Set-ZoomUser { [string]$EnterExitChimeType, [Parameter(Mandatory = $false)] - [bool]$DisableFeedback + [bool]$DisableFeedback, + + [Parameter(Mandatory = $false)] + [string]$Department ) - $Endpoint = 'https://api.zoom.us/v1/user/update' + DynamicParam { + # Set the dynamic parameters' name + $ParameterName = 'TimeZone' + + # Create the dictionary + $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + + # Create the collection of attributes + $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] + + # Create and set the parameters' attributes + $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute + $ParameterAttribute.Mandatory = $false + + # Add the attributes to the attributes collection + $AttributeCollection.Add($ParameterAttribute) + + # Generate and set the ValidateSet + $ValidatedParams = @() + (Get-ZoomTimeZones).GetEnumerator() | ForEach-Object { + $ValidatedParams += $_.Key + $ValidatedParams += $_.Value + } + $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($ValidatedParams) - foreach ($User in $Id) { - if ($pscmdlet.ShouldProcess($User, 'Update Zoom user info')) { + # Add the ValidateSet to the attributes collection + $AttributeCollection.Add($ValidateSetAttribute) - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $User) - if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } - if ($PSBoundParameters.ContainsKey('License')) { - $LicenseType = switch ($License) { - 'Basic' { 1 } - 'Pro' { 2 } - 'Corp' { 3 } + # Create and return the dynamic parameter + $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) + $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) + return $RuntimeParameterDictionary + } + + begin { + $TimeZone = $PSBoundParameters.TimeZone + } + + process{ + $TimeZone = $PSBoundParameters.TimeZone + $Endpoint = 'https://api.zoom.us/v1/user/update' + + foreach ($User in $Id) { + if ($pscmdlet.ShouldProcess($User, 'Update Zoom user info')) { + + $RequestBody = Get-ZoomApiAuth + $RequestBody.Add('id', $User) + if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } + if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } + if ($PSBoundParameters.ContainsKey('License')) { + $LicenseType = switch ($License) { + 'Basic' { 1 } + 'Pro' { 2 } + 'Corp' { 3 } + } + + $RequestBody.Add('type', $LicenseType) } - - $RequestBody.Add('type', $LicenseType) - } - if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } - if ($PSBoundParameters.ContainsKey('EnablePmi')) { $RequestBody.Add('enable_use_pmi', $EnablePmi) } - if ($PSBoundParameters.ContainsKey('VanityName')) { $RequestBody.Add('vanity_name', $VanityName) } - if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } - if ($PSBoundParameters.ContainsKey('EnterExitChime')) { $RequestBody.Add('enable_enter_exit_chime', $EnterExitChime) } - if ($PSBoundParameters.ContainsKey('EnterExitChimeType')) { - $ChimeType = switch ($EnterExitChimeType) { - 'All' { 0 } - 'HostOnly' { 1 } + if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } + if ($PSBoundParameters.ContainsKey('EnablePmi')) { $RequestBody.Add('enable_use_pmi', $EnablePmi) } + if ($PSBoundParameters.ContainsKey('VanityName')) { $RequestBody.Add('vanity_name', $VanityName) } + if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } + if ($PSBoundParameters.ContainsKey('EnterExitChime')) { + $RequestBody.Add('enable_enter_exit_chime', $EnterExitChime) } - $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) - } - if ($PSBoundParameters.ContainsKey('DisableFeedback')) { $RequestBody.Add('disable_feedback', $DisableFeedback) } + if ($PSBoundParameters.ContainsKey('EnterExitChimeType')) { + $ChimeType = switch ($EnterExitChimeType) { + 'All' { 0 } + 'HostOnly' { 1 } + } + $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) + } + if ($PSBoundParameters.ContainsKey('DisableFeedback')) { + $RequestBody.Add('disable_feedback', $DisableFeedback) + } + if ($PSBoundParameters.ContainsKey('TimeZone')) { + $TimeZones = Get-ZoomTimeZones + if ($TimeZones.Contains($TimeZone)) { $TimeZone = $TimeZones.$TimeZone } + $RequestBody.Add('timezone', $TimeZone) + } + if ($PSBoundParameters.ContainsKey('Department')) { $RequestBody.Add('dept', $Department) } - if ($pscmdlet.ShouldProcess($User, 'Set Zoom user settings')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint + if ($pscmdlet.ShouldProcess($User, 'Set Zoom user settings')) { + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint + } } } } @@ -1251,6 +1319,9 @@ function New-ZoomUser { .PARAMETER DisableFeedback Disable feedback. + .PARAMETER Department + Department for user profile, use for reporting. + .EXAMPLE New-ZoomUser -Email user@company.com -License Pro Creates a Zoom user account for email user@company.com with a Pro license. @@ -1289,7 +1360,10 @@ function New-ZoomUser { [string]$EnterExitChimeType, [Parameter(Mandatory = $false)] - [bool]$DisableFeedback + [bool]$DisableFeedback, + + [Parameter(Mandatory = $false)] + [string]$Department ) $Endpoint = 'https://api.zoom.us/v1/user/create' @@ -1319,6 +1393,7 @@ function New-ZoomUser { $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) } if ($PSBoundParameters.ContainsKey('DisableFeedback')) { $RequestBody.Add('disable_feedback', $DisableFeedback) } + if ($PSBoundParameters.ContainsKey('Department')) { $RequestBody.Add('dept', $Department) } if ($pscmdlet.ShouldProcess($User, 'New Zoom user')) { Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | diff --git a/Zoom/timezones.csv b/Zoom/timezones.csv new file mode 100644 index 0000000..0b2f59b --- /dev/null +++ b/Zoom/timezones.csv @@ -0,0 +1,136 @@ +id,name +Pacific/Midway,"Midway Island, Samoa" +Pacific/Pago_Pago,Pago Pago +Pacific/Honolulu,Hawaii +America/Anchorage,Alaska +America/Vancouver,Vancouver +America/Los_Angeles,Pacific Time (US and Canada) +America/Tijuana,Tijuana +America/Edmonton,Edmonton +America/Denver,Mountain Time (US and Canada) +America/Phoenix,Arizona +America/Mazatlan,Mazatlan +America/Winnipeg,Winnipeg +America/Regina,Saskatchewan +America/Chicago,Central Time (US and Canada) +America/Mexico_City,Mexico City +America/Guatemala,Guatemala +America/El_Salvador,El Salvador +America/Managua,Managua +America/Costa_Rica,Costa Rica +America/Montreal,Montreal +America/New_York,Eastern Time (US and Canada) +America/Indianapolis,Indiana (East) +America/Panama,Panama +America/Bogota,Bogota +America/Lima,Lima +America/Halifax,Halifax +America/Puerto_Rico,Puerto Rico +America/Caracas,Caracas +America/Santiago,Santiago +America/St_Johns,Newfoundland and Labrador +America/Montevideo,Montevideo +America/Araguaina,Brasilia +America/Argentina/Buenos_Aires,"Buenos Aires, Georgetown" +America/Godthab,Greenland +America/Sao_Paulo,Sao Paulo +Atlantic/Azores,Azores +Canada/Atlantic,Atlantic Time (Canada) +Atlantic/Cape_Verde,Cape Verde Islands +UTC,Universal Time UTC +Etc/Greenwich,Greenwich Mean Time +Europe/Belgrade,"Belgrade, Bratislava, Ljubljana" +CET,"Sarajevo, Skopje, Zagreb" +Atlantic/Reykjavik,Reykjavik +Europe/Dublin,Dublin +Europe/London,London +Europe/Lisbon,Lisbon +Africa/Casablanca,Casablanca +Africa/Nouakchott,Nouakchott +Europe/Oslo,Oslo +Europe/Copenhagen,Copenhagen +Europe/Brussels,Brussels +Europe/Berlin,"Amsterdam, Berlin, Rome, Stockholm, Vienna" +Europe/Helsinki,Helsinki +Europe/Amsterdam,Amsterdam +Europe/Rome,Rome +Europe/Stockholm,Stockholm +Europe/Vienna,Vienna +Europe/Luxembourg,Luxembourg +Europe/Paris,Paris +Europe/Zurich,Zurich +Europe/Madrid,Madrid +Africa/Bangui,West Central Africa +Africa/Algiers,Algiers +Africa/Tunis,Tunis +Africa/Harare,"Harare, Pretoria" +Africa/Nairobi,Nairobi +Europe/Warsaw,Warsaw +Europe/Prague,Prague Bratislava +Europe/Budapest,Budapest +Europe/Sofia,Sofia +Europe/Istanbul,Istanbul +Europe/Athens,Athens +Europe/Bucharest,Bucharest +Asia/Nicosia,Nicosia +Asia/Beirut,Beirut +Asia/Damascus,Damascus +Asia/Jerusalem,Jerusalem +Asia/Amman,Amman +Africa/Tripoli,Tripoli +Africa/Cairo,Cairo +Africa/Johannesburg,Johannesburg +Europe/Moscow,Moscow +Asia/Baghdad,Baghdad +Asia/Kuwait,Kuwait +Asia/Riyadh,Riyadh +Asia/Bahrain,Bahrain +Asia/Qatar,Qatar +Asia/Aden,Aden +Asia/Tehran,Tehran +Africa/Khartoum,Khartoum +Africa/Djibouti,Djibouti +Africa/Mogadishu,Mogadishu +Asia/Dubai,Dubai +Asia/Muscat,Muscat +Asia/Baku,"Baku, Tbilisi, Yerevan" +Asia/Kabul,Kabul +Asia/Yekaterinburg,Yekaterinburg +Asia/Tashkent,"Islamabad, Karachi, Tashkent" +Asia/Calcutta,India +Asia/Kathmandu,Kathmandu +Asia/Novosibirsk,Novosibirsk +Asia/Almaty,Almaty +Asia/Dacca,Dacca +Asia/Krasnoyarsk,Krasnoyarsk +Asia/Dhaka,"Astana, Dhaka" +Asia/Bangkok,Bangkok +Asia/Saigon,Vietnam +Asia/Jakarta,Jakarta +Asia/Irkutsk,"Irkutsk, Ulaanbaatar" +Asia/Shanghai,"Beijing, Shanghai" +Asia/Hong_Kong,Hong Kong +Asia/Taipei,Taipei +Asia/Kuala_Lumpur,Kuala Lumpur +Asia/Singapore,Singapore +Australia/Perth,Perth +Asia/Yakutsk,Yakutsk +Asia/Seoul,Seoul +Asia/Tokyo,"Osaka, Sapporo, Tokyo" +Australia/Darwin,Darwin +Australia/Adelaide,Adelaide +Asia/Vladivostok,Vladivostok +Pacific/Port_Moresby,"Guam, Port Moresby" +Australia/Brisbane,Brisbane +Australia/Sydney,"Canberra, Melbourne, Sydney" +Australia/Hobart,Hobart +Asia/Magadan,Magadan +SST,Solomon Islands +Pacific/Noumea,New Caledonia +Asia/Kamchatka,Kamchatka +Pacific/Fiji,"Fiji Islands, Marshall Islands" +Pacific/Auckland,"Auckland, Wellington" +Asia/Kolkata,"Mumbai, Kolkata, New Delhi" +Europe/Kiev,Kiev +America/Tegucigalpa,Tegucigalpa +Pacific/Apia,Independent State of Samoa \ No newline at end of file From 5dcc84fadd3a64bfcf00388ee862385072599c5c Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Tue, 28 Nov 2017 17:07:41 -0500 Subject: [PATCH 39/65] Update changes for Azure compatibility, and set picture fix. --- Zoom/Zoom.psm1 | 400 ++++++++++++++++++++++++++++----------------- Zoom/timezones.csv | 136 --------------- 2 files changed, 247 insertions(+), 289 deletions(-) delete mode 100644 Zoom/timezones.csv diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 index a0898b1..de70378 100644 --- a/Zoom/Zoom.psm1 +++ b/Zoom/Zoom.psm1 @@ -1,3 +1,6 @@ +$ZoomApiKey = '' +$ZoomApiSecret = '' + function Get-ZoomApiAuth { <# .SYNOPSIS @@ -12,69 +15,170 @@ function Get-ZoomApiAuth { [CmdletBinding()] Param() - @{ - 'api_key' = Get-Content -Path "$PSScriptRoot\api_key" - 'api_secret' = Get-Content -Path "$PSScriptRoot\api_secret" - } -} - -function Set-ZoomApiAuth { - <# - .SYNOPSIS - Set the Zoom Api key/secret to the files in the same directory as the module. - - .PARAMETER Key - Optional, sets a new Api key. - - .PARAMETER Secret - Optional, sets a new Api secret. - - .EXAMPLE - Set-ZoomApi -Key 'mysupersecretapikey' -Secret 'mysupersecretapisecret' - Sets your Zoom api key and secret to the files in the module directory. - - .EXAMPLE - Set-ZoomApi - User is prompted to enter both key and secret. - - .OUTPUTS - Creates/overrides api_key and api_secret files in module directory. - #> - [CmdletBinding()] - Param( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$Key, + try { + if (-not $Global:ZoomApiKey) { + $Global:ZoomApiKey = if ($PSPrivateMetadata.JobId) { + Get-AutomationVariable -Name ZoomApiKey + } else { + Read-Host 'Enter Zoom Api key' + } + } - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [string]$Secret - ) + if (-not $Global:ZoomApiSecret) { + $Global:ZoomApiSecret = if ($PSPrivateMetadata.JobId) { + Get-AutomationVariable -Name ZoomApiSecret + } else { + Read-Host 'Enter Zoom Api secret' + } + } - if ($PSBoundParameters.Keys.Count -eq 0) { - Read-Host 'Enter your Zoom Api key' | Set-Content -Path "$PSScriptRoot\api_key" - Read-Host 'Enter your Zoom Api secret' | Set-Content -Path "$PSScriptRoot\api_secret" - } else { - switch ($PSBoundParameters.Keys) { - 'Key' { $Key | Set-Content -Path "$PSScriptRoot\api_key" } - 'Secret' { $Secret | Set-Content -Path "$PSScriptRoot\api_secret" } + @{ + 'api_key' = $Global:ZoomApiKey + 'api_secret' = $Global:ZoomApiSecret } + } catch { + Write-Error "Problem getting Zoom Api Authorization variables:`n$_" } } -# Verify we can get the api key and secret before continuing to load the module -try { - Get-ZoomApiAuth -ErrorAction Stop -} catch { - Set-ZoomApiAuth -} - function Get-ZoomTimeZones { - $TimeZones = @{} - Import-Csv -Path "$PSScriptRoot\timezones.csv" -Delimiter ',' | ForEach-Object { - $TimeZones.Add($_.name, $_.id) + @{ + 'Pacific/Midway' = '"Midway Island, Samoa"' + 'Pacific/Pago_Pago' = 'Pago Pago' + 'Pacific/Honolulu' = 'Hawaii' + 'America/Anchorage' = 'Alaska' + 'America/Vancouver' = 'Vancouver' + 'America/Los_Angeles' = 'Pacific Time (US and Canada)' + 'America/Tijuana' = 'Tijuana' + 'America/Edmonton' = 'Edmonton' + 'America/Denver' = 'Mountain Time (US and Canada)' + 'America/Phoenix' = 'Arizona' + 'America/Mazatlan' = 'Mazatlan' + 'America/Winnipeg' = 'Winnipeg' + 'America/Regina' = 'Saskatchewan' + 'America/Chicago' = 'Central Time (US and Canada)' + 'America/Mexico_City' = 'Mexico City' + 'America/Guatemala' = 'Guatemala' + 'America/El_Salvador' = 'El Salvador' + 'America/Managua' = 'Managua' + 'America/Costa_Rica' = 'Costa Rica' + 'America/Montreal' = 'Montreal' + 'America/New_York' = 'Eastern Time (US and Canada)' + 'America/Indianapolis' = 'Indiana (East)' + 'America/Panama' = 'Panama' + 'America/Bogota' = 'Bogota' + 'America/Lima' = 'Lima' + 'America/Halifax' = 'Halifax' + 'America/Puerto_Rico' = 'Puerto Rico' + 'America/Caracas' = 'Caracas' + 'America/Santiago' = 'Santiago' + 'America/St_Johns' = 'Newfoundland and Labrador' + 'America/Montevideo' = 'Montevideo' + 'America/Araguaina' = 'Brasilia' + 'America/Argentina/Buenos_Aires' = '"Buenos Aires, Georgetown"' + 'America/Godthab' = 'Greenland' + 'America/Sao_Paulo' = 'Sao Paulo' + 'Atlantic/Azores' = 'Azores' + 'Canada/Atlantic' = 'Atlantic Time (Canada)' + 'Atlantic/Cape_Verde' = 'Cape Verde Islands' + 'UTC' = 'Universal Time UTC' + 'Etc/Greenwich' = 'Greenwich Mean Time' + 'Europe/Belgrade' = '"Belgrade, Bratislava, Ljubljana"' + 'CET' = '"Sarajevo, Skopje, Zagreb"' + 'Atlantic/Reykjavik' = 'Reykjavik' + 'Europe/Dublin' = 'Dublin' + 'Europe/London' = 'London' + 'Europe/Lisbon' = 'Lisbon' + 'Africa/Casablanca' = 'Casablanca' + 'Africa/Nouakchott' = 'Nouakchott' + 'Europe/Oslo' = 'Oslo' + 'Europe/Copenhagen' = 'Copenhagen' + 'Europe/Brussels' = 'Brussels' + 'Europe/Berlin' = '"Amsterdam, Berlin, Rome, Stockholm, Vienna"' + 'Europe/Helsinki' = 'Helsinki' + 'Europe/Amsterdam' = 'Amsterdam' + 'Europe/Rome' = 'Rome' + 'Europe/Stockholm' = 'Stockholm' + 'Europe/Vienna' = 'Vienna' + 'Europe/Luxembourg' = 'Luxembourg' + 'Europe/Paris' = 'Paris' + 'Europe/Zurich' = 'Zurich' + 'Europe/Madrid' = 'Madrid' + 'Africa/Bangui' = 'West Central Africa' + 'Africa/Algiers' = 'Algiers' + 'Africa/Tunis' = 'Tunis' + 'Africa/Harare' = '"Harare, Pretoria"' + 'Africa/Nairobi' = 'Nairobi' + 'Europe/Warsaw' = 'Warsaw' + 'Europe/Prague' = 'Prague Bratislava' + 'Europe/Budapest' = 'Budapest' + 'Europe/Sofia' = 'Sofia' + 'Europe/Istanbul' = 'Istanbul' + 'Europe/Athens' = 'Athens' + 'Europe/Bucharest' = 'Bucharest' + 'Asia/Nicosia' = 'Nicosia' + 'Asia/Beirut' = 'Beirut' + 'Asia/Damascus' = 'Damascus' + 'Asia/Jerusalem' = 'Jerusalem' + 'Asia/Amman' = 'Amman' + 'Africa/Tripoli' = 'Tripoli' + 'Africa/Cairo' = 'Cairo' + 'Africa/Johannesburg' = 'Johannesburg' + 'Europe/Moscow' = 'Moscow' + 'Asia/Baghdad' = 'Baghdad' + 'Asia/Kuwait' = 'Kuwait' + 'Asia/Riyadh' = 'Riyadh' + 'Asia/Bahrain' = 'Bahrain' + 'Asia/Qatar' = 'Qatar' + 'Asia/Aden' = 'Aden' + 'Asia/Tehran' = 'Tehran' + 'Africa/Khartoum' = 'Khartoum' + 'Africa/Djibouti' = 'Djibouti' + 'Africa/Mogadishu' = 'Mogadishu' + 'Asia/Dubai' = 'Dubai' + 'Asia/Muscat' = 'Muscat' + 'Asia/Baku' = '"Baku, Tbilisi, Yerevan"' + 'Asia/Kabul' = 'Kabul' + 'Asia/Yekaterinburg' = 'Yekaterinburg' + 'Asia/Tashkent' = '"Islamabad, Karachi, Tashkent"' + 'Asia/Calcutta' = 'India' + 'Asia/Kathmandu' = 'Kathmandu' + 'Asia/Novosibirsk' = 'Novosibirsk' + 'Asia/Almaty' = 'Almaty' + 'Asia/Dacca' = 'Dacca' + 'Asia/Krasnoyarsk' = 'Krasnoyarsk' + 'Asia/Dhaka' = '"Astana, Dhaka"' + 'Asia/Bangkok' = 'Bangkok' + 'Asia/Saigon' = 'Vietnam' + 'Asia/Jakarta' = 'Jakarta' + 'Asia/Irkutsk' = '"Irkutsk, Ulaanbaatar"' + 'Asia/Shanghai' = '"Beijing, Shanghai"' + 'Asia/Hong_Kong' = 'Hong Kong' + 'Asia/Taipei' = 'Taipei' + 'Asia/Kuala_Lumpur' = 'Kuala Lumpur' + 'Asia/Singapore' = 'Singapore' + 'Australia/Perth' = 'Perth' + 'Asia/Yakutsk' = 'Yakutsk' + 'Asia/Seoul' = 'Seoul' + 'Asia/Tokyo' = '"Osaka, Sapporo, Tokyo"' + 'Australia/Darwin' = 'Darwin' + 'Australia/Adelaide' = 'Adelaide' + 'Asia/Vladivostok' = 'Vladivostok' + 'Pacific/Port_Moresby' = '"Guam, Port Moresby"' + 'Australia/Brisbane' = 'Brisbane' + 'Australia/Sydney' = '"Canberra, Melbourne, Sydney"' + 'Australia/Hobart' = 'Hobart' + 'Asia/Magadan' = 'Magadan' + 'SST' = 'Solomon Islands' + 'Pacific/Noumea' = 'New Caledonia' + 'Asia/Kamchatka' = 'Kamchatka' + 'Pacific/Fiji' = '"Fiji Islands, Marshall Islands"' + 'Pacific/Auckland' = '"Auckland, Wellington"' + 'Asia/Kolkata' = '"Mumbai, Kolkata, New Delhi"' + 'Europe/Kiev' = 'Kiev' + 'America/Tegucigalpa' = 'Tegucigalpa' + 'Pacific/Apia' = 'Independent State of Samoa' } - $TimeZones } function Read-ZoomResponse { @@ -91,6 +195,9 @@ function Read-ZoomResponse { .PARAMETER Endpoint Api endpoint Url that was called. + .PARAMETER RetryOnRequestLimitReached + If the Api request limit is reached, retry once after 1 second. + .EXAMPLE Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint -Endpoint $Endpoint @@ -107,7 +214,10 @@ function Read-ZoomResponse { [hashtable]$RequestBody, [Parameter(Mandatory = $true)] - [string]$Endpoint + [string]$Endpoint, + + [Parameter(Mandatory = $false)] + [bool]$RetryOnRequestLimitReached = $true ) $ApiCallInfo = "Api Endpoint: $Endpoint`n" @@ -115,6 +225,14 @@ function Read-ZoomResponse { if ($Response.PSObject.Properties.Name -match 'error') { Write-Error -Message "$($Response.error.message)`n$ApiCallInfo" -ErrorId $Response.error.code -Category InvalidOperation + + if ($RetryOnRequestLimitReached -and $Response.error.code -eq 403) { + Write-Warning "Retrying in one second..." + Start-Sleep -Seconds 1 + + Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | + Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint + } } else { Write-Verbose "$($Response.error.message)`nApi call body:$($RequestBody | Out-String)" $Response @@ -196,6 +314,8 @@ function Get-ZoomUser { Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | Select-Object -ExpandProperty users + + Start-Sleep -Milliseconds 500 } } elseif ($PSCmdlet.ParameterSetName -eq 'Email') { $Endpoint = 'https://api.zoom.us/v1/user/getbyemail' @@ -257,6 +377,8 @@ function Get-ZoomPendingUser { $Users += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | Select-Object -ExpandProperty users + + Start-Sleep -Milliseconds 500 } $Users @@ -526,6 +648,8 @@ function Get-ZoomMeeting { $Meetings += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | Select-Object -ExpandProperty meetings + + Start-Sleep -Milliseconds 500 } $Meetings @@ -855,6 +979,8 @@ function Get-ZoomGroupMember { $Users += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | Select-Object -ExpandProperty members + + Start-Sleep -Milliseconds 500 } $Users @@ -957,7 +1083,7 @@ function Set-ZoomUser { [Parameter(Mandatory = $false)] [string]$Department ) - +<# DynamicParam { # Set the dynamic parameters' name $ParameterName = 'TimeZone' @@ -995,8 +1121,8 @@ function Set-ZoomUser { begin { $TimeZone = $PSBoundParameters.TimeZone } - - process{ +#> + process { $TimeZone = $PSBoundParameters.TimeZone $Endpoint = 'https://api.zoom.us/v1/user/update' @@ -1033,11 +1159,11 @@ function Set-ZoomUser { if ($PSBoundParameters.ContainsKey('DisableFeedback')) { $RequestBody.Add('disable_feedback', $DisableFeedback) } - if ($PSBoundParameters.ContainsKey('TimeZone')) { + <#if ($PSBoundParameters.ContainsKey('TimeZone')) { $TimeZones = Get-ZoomTimeZones if ($TimeZones.Contains($TimeZone)) { $TimeZone = $TimeZones.$TimeZone } $RequestBody.Add('timezone', $TimeZone) - } + }#> if ($PSBoundParameters.ContainsKey('Department')) { $RequestBody.Add('dept', $Department) } if ($pscmdlet.ShouldProcess($User, 'Set Zoom user settings')) { @@ -1093,6 +1219,8 @@ function Set-ZoomUserPicture { [Parameter( Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Path' )] [ValidateScript({ Test-Path $_ -PathType Leaf })] @@ -1103,110 +1231,76 @@ function Set-ZoomUserPicture { Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'Id' + ParameterSetName = 'ByteArray' + )] + [ValidateNotNullOrEmpty()] + [byte[]]$ByteArray, + + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = 'Binary' )] [ValidateNotNullOrEmpty()] - [byte[]]$ByteArray + $Binary ) $Endpoint = 'https://api.zoom.us/v1/user/uploadpicture' - $ApiAuth = Get-ZoomApiAuth - - $Boundary = [guid]::NewGuid() - - $Source = @" - using System; - using System.IO; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using System.Net; - using System.Net.Http; - using System.Net.Http.Headers; - - namespace Zoom - { - public static class Tools - { - public static string UploadUserPicture(string Id, byte[] byteArray, string fileName) - { - Uri webService = new Uri(@"$Endpoint"); - HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, webService); - requestMessage.Headers.ExpectContinue = false; - - MultipartFormDataContent multiPartContent = new MultipartFormDataContent("$Boundary"); - - HttpContent apiKeyContent = new StringContent(@"$($ApiAuth.api_key)"); - multiPartContent.Add(apiKeyContent, "api_key"); - - HttpContent apiSecretContent = new StringContent(@"$($ApiAuth.api_secret)"); - multiPartContent.Add(apiSecretContent, "api_secret"); - - HttpContent idContent = new StringContent(Id); - multiPartContent.Add(idContent, "id"); - - ByteArrayContent byteArrayContent = new ByteArrayContent(byteArray); - byteArrayContent.Headers.Add("Content-Type", "application/octet-stream"); - multiPartContent.Add(byteArrayContent, "pic_file", fileName); - - requestMessage.Content = multiPartContent; - - HttpClient httpClient = new HttpClient(); - httpClient.Timeout = new TimeSpan(0, 2, 0); - try - { - Task httpRequest = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None); - HttpResponseMessage httpResponse = httpRequest.Result; - HttpStatusCode statusCode = httpResponse.StatusCode; - HttpContent responseContent = httpResponse.Content; - - if (responseContent != null) - { - Task stringContentsTask = responseContent.ReadAsStringAsync(); - String stringContents = stringContentsTask.Result; - return stringContents; - } - else - { - return "No response."; - } - } - catch (Exception ex) - { - return ex.Message; - } - } - } - } -"@ - $Assemblies = ( - # Assemblies can be found downloaded from .NET Framework 4.6.2 Dev Pack - # https://www.microsoft.com/en-us/download/confirmation.aspx?id=53321 - 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2\System.Net.dll', - 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2\System.Net.Http.dll' - ) + if ($PSCmdlet.ParameterSetName -eq 'Path') { + $FileName = $Path.Split('\')[-1] + $ByteArray = Get-Content -Path $Path -Encoding Byte + } else { + $FileName = 'ProfilePicture.jpg' + } - # Only load the Zoom.Tools type if it isn't already loaded - if (-not ([System.Management.Automation.PSTypeName]'Zoom.Tools').Type) { - Add-Type -TypeDefinition $Source -Language CSharp -ReferencedAssemblies $Assemblies + if (-not $Binary) { + $encoding = [System.Text.Encoding]::GetEncoding('iso-8859-1') + $encodedFile = $encoding.GetString($ByteArray) + } else { + $encodedFile = $Binary } + $newLine = "`r`n" + $boundary = [guid]::NewGuid() + + $RequestBody = ( + "--$boundary", + "Content-Type: text/plain; charset=utf-8", + "Content-Disposition: form-data; name=api_key$newLine", + (Get-ZoomApiAuth).api_key, + + "--$boundary", + "Content-Type: text/plain; charset=utf-8", + "Content-Disposition: form-data; name=api_secret$newLine", + (Get-ZoomApiAuth).api_secret, + + "--$boundary", + "Content-Type: text/plain; charset=utf-8", + "Content-Disposition: form-data; name=id$newLine", + $Id, + + "--$boundary", + "Content-Type: application/octet-stream", + "Content-Disposition: form-data; name=pic_file; filename=$FileName; filename*=utf-8''$FileName$newLine", + $encodedFile, + + "--$boundary--$newLine" + ) -join $newLine + if ($pscmdlet.ShouldProcess($Id, 'Update Zoom user picture')) { - if ($PSCmdlet.ParameterSetName -eq 'Path') { - $ByteArray = Get-Content -Path $Path -Encoding Byte - $FileName = $Path.Split('\')[-1] + $response = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" + + $ApiCallInfo = "Api Endpoint: $Endpoint`n" + $ApiCallInfo += "Api call body:$($RequestBody | Out-String)" + + if ($response.PSObject.Properties.Name -match 'error') { + Write-Error -Message "$($response.error.message)`n$ApiCallInfo" -ErrorId $response.error.code -Category InvalidOperation } else { - $FileName = 'ProfilePicture.jpg' + Write-Verbose "$($response.error.message)`nApi call body:$($RequestBody | Out-String)" + $response } - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $Id) - $RequestBody.Add('file_name', $FileName) - $RequestBody.Add('byte_array', $ByteArray) - - [Zoom.Tools]::UploadUserPicture($Id, $ByteArray, $FileName) | ConvertFrom-Json | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint } } diff --git a/Zoom/timezones.csv b/Zoom/timezones.csv deleted file mode 100644 index 0b2f59b..0000000 --- a/Zoom/timezones.csv +++ /dev/null @@ -1,136 +0,0 @@ -id,name -Pacific/Midway,"Midway Island, Samoa" -Pacific/Pago_Pago,Pago Pago -Pacific/Honolulu,Hawaii -America/Anchorage,Alaska -America/Vancouver,Vancouver -America/Los_Angeles,Pacific Time (US and Canada) -America/Tijuana,Tijuana -America/Edmonton,Edmonton -America/Denver,Mountain Time (US and Canada) -America/Phoenix,Arizona -America/Mazatlan,Mazatlan -America/Winnipeg,Winnipeg -America/Regina,Saskatchewan -America/Chicago,Central Time (US and Canada) -America/Mexico_City,Mexico City -America/Guatemala,Guatemala -America/El_Salvador,El Salvador -America/Managua,Managua -America/Costa_Rica,Costa Rica -America/Montreal,Montreal -America/New_York,Eastern Time (US and Canada) -America/Indianapolis,Indiana (East) -America/Panama,Panama -America/Bogota,Bogota -America/Lima,Lima -America/Halifax,Halifax -America/Puerto_Rico,Puerto Rico -America/Caracas,Caracas -America/Santiago,Santiago -America/St_Johns,Newfoundland and Labrador -America/Montevideo,Montevideo -America/Araguaina,Brasilia -America/Argentina/Buenos_Aires,"Buenos Aires, Georgetown" -America/Godthab,Greenland -America/Sao_Paulo,Sao Paulo -Atlantic/Azores,Azores -Canada/Atlantic,Atlantic Time (Canada) -Atlantic/Cape_Verde,Cape Verde Islands -UTC,Universal Time UTC -Etc/Greenwich,Greenwich Mean Time -Europe/Belgrade,"Belgrade, Bratislava, Ljubljana" -CET,"Sarajevo, Skopje, Zagreb" -Atlantic/Reykjavik,Reykjavik -Europe/Dublin,Dublin -Europe/London,London -Europe/Lisbon,Lisbon -Africa/Casablanca,Casablanca -Africa/Nouakchott,Nouakchott -Europe/Oslo,Oslo -Europe/Copenhagen,Copenhagen -Europe/Brussels,Brussels -Europe/Berlin,"Amsterdam, Berlin, Rome, Stockholm, Vienna" -Europe/Helsinki,Helsinki -Europe/Amsterdam,Amsterdam -Europe/Rome,Rome -Europe/Stockholm,Stockholm -Europe/Vienna,Vienna -Europe/Luxembourg,Luxembourg -Europe/Paris,Paris -Europe/Zurich,Zurich -Europe/Madrid,Madrid -Africa/Bangui,West Central Africa -Africa/Algiers,Algiers -Africa/Tunis,Tunis -Africa/Harare,"Harare, Pretoria" -Africa/Nairobi,Nairobi -Europe/Warsaw,Warsaw -Europe/Prague,Prague Bratislava -Europe/Budapest,Budapest -Europe/Sofia,Sofia -Europe/Istanbul,Istanbul -Europe/Athens,Athens -Europe/Bucharest,Bucharest -Asia/Nicosia,Nicosia -Asia/Beirut,Beirut -Asia/Damascus,Damascus -Asia/Jerusalem,Jerusalem -Asia/Amman,Amman -Africa/Tripoli,Tripoli -Africa/Cairo,Cairo -Africa/Johannesburg,Johannesburg -Europe/Moscow,Moscow -Asia/Baghdad,Baghdad -Asia/Kuwait,Kuwait -Asia/Riyadh,Riyadh -Asia/Bahrain,Bahrain -Asia/Qatar,Qatar -Asia/Aden,Aden -Asia/Tehran,Tehran -Africa/Khartoum,Khartoum -Africa/Djibouti,Djibouti -Africa/Mogadishu,Mogadishu -Asia/Dubai,Dubai -Asia/Muscat,Muscat -Asia/Baku,"Baku, Tbilisi, Yerevan" -Asia/Kabul,Kabul -Asia/Yekaterinburg,Yekaterinburg -Asia/Tashkent,"Islamabad, Karachi, Tashkent" -Asia/Calcutta,India -Asia/Kathmandu,Kathmandu -Asia/Novosibirsk,Novosibirsk -Asia/Almaty,Almaty -Asia/Dacca,Dacca -Asia/Krasnoyarsk,Krasnoyarsk -Asia/Dhaka,"Astana, Dhaka" -Asia/Bangkok,Bangkok -Asia/Saigon,Vietnam -Asia/Jakarta,Jakarta -Asia/Irkutsk,"Irkutsk, Ulaanbaatar" -Asia/Shanghai,"Beijing, Shanghai" -Asia/Hong_Kong,Hong Kong -Asia/Taipei,Taipei -Asia/Kuala_Lumpur,Kuala Lumpur -Asia/Singapore,Singapore -Australia/Perth,Perth -Asia/Yakutsk,Yakutsk -Asia/Seoul,Seoul -Asia/Tokyo,"Osaka, Sapporo, Tokyo" -Australia/Darwin,Darwin -Australia/Adelaide,Adelaide -Asia/Vladivostok,Vladivostok -Pacific/Port_Moresby,"Guam, Port Moresby" -Australia/Brisbane,Brisbane -Australia/Sydney,"Canberra, Melbourne, Sydney" -Australia/Hobart,Hobart -Asia/Magadan,Magadan -SST,Solomon Islands -Pacific/Noumea,New Caledonia -Asia/Kamchatka,Kamchatka -Pacific/Fiji,"Fiji Islands, Marshall Islands" -Pacific/Auckland,"Auckland, Wellington" -Asia/Kolkata,"Mumbai, Kolkata, New Delhi" -Europe/Kiev,Kiev -America/Tegucigalpa,Tegucigalpa -Pacific/Apia,Independent State of Samoa \ No newline at end of file From 9f84d277515aade6745704565eebfd9fc9fa10b5 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 23 Feb 2018 15:57:17 -0500 Subject: [PATCH 40/65] Add Yammer API module. Update cmtrace log writer. --- Write-CMTraceLog.ps1 | 71 +++-- Yammer.psm1 | 635 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 678 insertions(+), 28 deletions(-) create mode 100644 Yammer.psm1 diff --git a/Write-CMTraceLog.ps1 b/Write-CMTraceLog.ps1 index 268dcfc..c896aa6 100644 --- a/Write-CMTraceLog.ps1 +++ b/Write-CMTraceLog.ps1 @@ -1,32 +1,47 @@ -$Date = (Get-Date).ToString('yyyyMMdd-HHmm') -$LogFolder = New-Item -ItemType Directory ".\logs" -Force -$Log = New-Item -ItemType File "$LogFolder\Action-$Date.log" - function Write-CMTraceLog { - Param ( - [Parameter(Mandatory=$false)] $Message, - [Parameter(Mandatory=$false)] $ErrorMessage, - [Parameter(Mandatory=$false)] $Component, - # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) - [Parameter(Mandatory=$false)] [int]$Type, - [Parameter(Mandatory=$true)] $LogFile - ) + <# + .Description + Write to a cmtrace readable log. + .Example + $LogFile = "C:\TestFolder\TestLog.Log" + Write-CMTraceLog -LogFile $LogFile + Write-CMTraceLog -Message "This is a normal message" -ErrorMessage $Error -LogFile $LogFile + Write-CMTraceLog -Message "This is a warning" -Type 2 -Component "Test Component" -LogFile $LogFile + Write-CMTraceLog -Message "This is an Error!" -Type 3 -Component "Error Component" -LogFile $LogFile + #> + param ( + [Parameter(Mandatory = $false)] + [string]$Message, + + [Parameter(Mandatory = $false)] + [string]$ErrorMessage, + + [Parameter(Mandatory = $false)] + [string]$Component, + + # 1 = Normal, 2 = Warning (yellow), 3 = Error (red) + [Parameter(Mandatory = $false)] + [int]$Type, + + [Parameter(Mandatory = $true)] + [string]$LogFile + ) - $Time = Get-Date -Format "HH:mm:ss.ffffff" - $Date = Get-Date -Format "MM-dd-yyyy" + $Time = Get-Date -Format "HH:mm:ss.ffffff" + $Date = Get-Date -Format "MM-dd-yyyy" - if ($ErrorMessage -ne $null) {$Type = 3} - if ($Component -eq $null) {$Component = " "} - if ($Type -eq $null) {$Type = 1} + if ($ErrorMessage -ne $null) { + $Type = 3 + } + if ($Component -eq $null) { + $Component = " " + } + if ($Type -eq $null) { + $Type = 1 + } - $LogMessage = "" - $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile -} - -<# Usage - $LogFile = "C:\TestFolder\TestLog.Log" - Write-CMTraceLog -LogFile $LogFile - Write-CMTraceLog -Message "This is a normal message" -ErrorMessage $Error -LogFile $LogFile - Write-CMTraceLog -Message "This is a warning" -Type 2 -Component "Test Component" -LogFile $LogFile - Write-CMTraceLog -Message "This is an Error!" -Type 3 -Component "Error Component" -LogFile $LogFile -#> \ No newline at end of file + $LogMessage = "" + $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile + $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile + $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile +} \ No newline at end of file diff --git a/Yammer.psm1 b/Yammer.psm1 new file mode 100644 index 0000000..459dfe8 --- /dev/null +++ b/Yammer.psm1 @@ -0,0 +1,635 @@ +$Global:YammerAuthToken = "your-auth-token" + +function Get-YammerAuthHeader { + @{ AUTHORIZATION = "Bearer $YammerAuthToken" } +} + +function Export-YammerData { + <# + .SYNOPSIS + Export Yammer data. + + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter(Mandatory = $false)] + [string]$Path = "$PSScriptRoot\export.zip", + + [Parameter(Mandatory = $false)] + [ValidateSet('User', 'Group', 'Message', 'MessageVersion', 'Topic', 'UploadedFileVersion', 'DocumentVersion')] + [string[]]$Model, + + [Parameter(Mandatory = $true)] + [datetime]$StartDate, + + [Parameter(Mandatory = $false)] + [datetime]$EndDate, + + [Parameter(Mandatory = $false)] + [ValidateSet('Csv', 'All')] + [string]$IncludeFileAttachments + ) + + $Uri = 'https://www.yammer.com/api/v1/export?' + + if ($PSBoundParameters.ContainsKey('Model')) { + foreach ($Type in $Model) { + if (-not $Uri.EndsWith('?')) { + $Uri += '&' + } + $Uri += "model=$Type" + } + } + + if ($PSBoundParameters.ContainsKey('StartDate')) { + if (-not $Uri.EndsWith('?')) { + $Uri += '&' + } + $Uri += "since=$(Get-Date -Date $StartDate -Format s)" + } + + if ($PSBoundParameters.ContainsKey('EndDate')) { + if (-not $Uri.EndsWith('?')) { + $Uri += '&' + } + $Uri += "until=$(Get-Date -Date $EndDate -Format s)" + } + + if ($PSBoundParameters.ContainsKey('IncludeFileAttachments')) { + if (-not $Uri.EndsWith('?')) { + $Uri += '&' + } + $Uri += "include=$IncludeFileAttachments" + } + + if ($pscmdlet.ShouldProcess($Uri, 'Export Yammer data')) { + $authHeader = Get-YammerAuthHeader + Invoke-RestMethod -Uri $Uri -OutFile $Path -Headers $authHeader + } +} + +function Remove-YammerMessage { + <# + .SYNOPSIS + Delete message from Yammer by message ID. + + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter(Mandatory = $true)] + [int]$Id + ) + + $authHeader = Get-YammerAuthHeader + $Uri = "https://yammer.com/api/v1/messages/$Id" + + if ($pscmdlet.ShouldProcess($Id, 'Delete Yammer message')) { + Invoke-RestMethod -Uri $Uri -Method Delete -Headers $authHeader + } +} + +function Get-YammerToken { + <# + .SYNOPSIS + Used to get Yammer API tokens under the current account. + + #> + $IE = New-Object -ComObject InternetExplorer.Application + $IE.Navigate('https://www.yammer.com/client_applications') + $IE.Visible = $true + + Write-Host 'Login and select the app you want to use and provide some information.' + $ClientId = Read-Host 'Client ID' + $ClientSecret = Read-Host 'Client secret' + + $IE.Quit() + + $CodeUrl = "https://www.yammer.com/dialog/oauth?client_id=$ClientId" + $SleepInterval = 1 + + $IE = New-Object -ComObject InternetExplorer.Application + $IE.Navigate($codeUrl) + $IE.Visible = $true + + while ($IE.LocationUrl -notmatch 'code=') { + Write-Debug -Message ('Sleeping {0} seconds for access URL' -f $SleepInterval) + Start-Sleep -Seconds $SleepInterval + } + + Write-Debug -Message ('Callback URL is: {0}' -f $IE.LocationUrl) + [Void]($IE.LocationUrl -match '=([\w\.]+)') + $TempCode = $Matches[1] + + $IE.Quit() + + $Request = Invoke-WebRequest https://www.yammer.com/oauth2/access_token.json?client_id=$ClientId"&"client_secret=$ClientSecret"&"code=$TempCode | + ConvertFrom-Json + + Write-Host "Temporary code used: $TempCode" + $AccessToken = $Request.access_token.token + + $Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $Headers.Add("Authorization", 'Bearer ' + $AccessToken) + + $Headers + + $CurrentUser = Invoke-RestMethod 'https://www.yammer.com/api/v1/users/current.json' -Headers $Headers + $AllTokens = Invoke-RestMethod 'https://www.yammer.com/api/v1/oauth/tokens.json'-Headers $Headers + + + Write-Host $CurrentUser.name + Write-Host $CurrentUser.network_name + Write-Host $CurrentUser.email + + + foreach ($Token in $AllTokens) { + $Token | Format-Table user_id, network_name, token -AutoSize + } +} + +function Compare-YammerADUserJobTitle { + <# + .SYNOPSIS + Export all Yammer users and compare to MSO users. + + .NOTES + Modified from MS script + + #> + [CmdletBinding()] + Param( + $YammerUsers + ) + + $usersAD = Get-ADUser -Filter * -Property Title, mail + + foreach ($user in $YammerUsers) { + $usersAD | Where-Object { + $_.mail -eq $user.email -and $_.Title -ne $user.job_title + } | ForEach-Object { + New-Object -TypeName psobject -Property @{ + Name = $user.name + Id = $user.id + SamAccountName = $_.SamAccountName + UserPrincipalName = $_.UserPrincipalName + Email = $user.email + TitleAD = $_.Title + TitleYammer = $user.job_title + ApiUrl = $user.url + ADEnabled = $_.Enabled + YammerState = $user.state + } + } + } +} + +function Compare-YammerMsoUser { + <# + .SYNOPSIS + Export all Yammer users and compare to MSO users. + + #> + Param( + [switch]$UseExistingMsoConnection, + + [Parameter(Mandatory = $true)] + [pscredential]$Credential + ) + + Write-Progress -Id 1 -Activity "Getting Yammer users..." + + # Export users from Yammer + $startDate = (Get-Date).AddYears(-20) + $exportPath = "$env:TEMP\export.zip" + Export-YammerData -Model User -Path $exportPath -StartDate $startDate + Expand-Archive -Path $exportPath -DestinationPath "$env:TEMP\export" + Remove-Item -Path $exportPath + $users = Import-Csv -Path "$env:TEMP\export\Users.csv" + Remove-Item -Path "$env:TEMP\export" -Recurse + $yammerUsers = $users | Where-Object -Property state -eq "active" + + if (-not $UseExistingMsoConnection) { + Connect-MsolService -Credential $Credential + } + + Write-Progress -Id 1 -Activity "Getting MSO users..." + + $msoUsers = Get-MsolUser -All | Select-Object UserPrincipalName, ProxyAddresses, ObjectId, IsLicensed + + $userCounter = 0 + $userCount = $msoUsers.Count + $o365usershash = @{} + + foreach ($msoUser in $msoUsers) { + $upn = $msoUser.UserPrincipalName + + $GroupProgressParams = @{ + Id = 1 + Activity = "Processing user $userCounter of $userCount" + Status = "Processing MSO users..." + CurrentOperation = $upn + PercentComplete = ($userCounter / $userCount) * 100 + } + Write-Progress @GroupProgressParams + + $o365usershash.Add($upn, $msoUser) + + $msoUser.ProxyAddresses | ForEach-Object { + $email = ($msoUser -Replace "SMTP:(\\*)*", "").Trim() + + if (-not $o365usershash.Contains($email)) { + $o365usershash.Add($email, $msoUser) + } + } + + $userCounter++ + } + + $userCounter = 0 + $userCount = $yammerUsers.Count + + $yammerUsers | ForEach-Object { + $email = $_.email + + $GroupProgressParams = @{ + Id = 1 + Activity = "Updating user $userCounter of $userCount" + Status = "Updating Yammer users..." + CurrentOperation = $email + PercentComplete = ($userCounter / $userCount) * 100 + } + Write-Progress @GroupProgressParams + + $enabledInAD = Get-ADUser -Filter { UserPrincipalName -eq $email } -Properties Enabled | + Select-Object -ExpandProperty Enabled + $_ | Add-Member -MemberType NoteProperty -Name "ad_enabled" -Value $enabledInAD + + $o365user = $o365usershash[$email] + $existsInAzure = ($o365user -ne $null) + $_ | Add-Member -MemberType NoteProperty -Name "azure_exists" -Value $existsInAzure + + if ($existsInAzure) { + $_ | Add-Member -MemberType NoteProperty -Name "azure_object_id" -Value $o365user.ObjectId + $_ | Add-Member -MemberType NoteProperty -Name "azure_licensed" -Value $o365user.IsLicensed + } + + Write-Output $_ + + $userCounter++ + } +} + +function Get-YammerUser { + <# + .SYNOPSIS + Get Yammer user(s). + + #> + [CmdletBinding(DefaultParameterSetName = "All")] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "Id" + )] + [int]$Id, + + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "UserPrincipalName" + )] + [string]$UserPrincipalName, + + [Parameter( + Mandatory = $true, + ParameterSetName = "Current" + )] + [switch]$Current, + + [Parameter( + Mandatory = $true, + ParameterSetName = "All" + )] + [switch]$All + ) + + begin { + function Get-YammerAllUsers { + [CmdletBinding()] + param( + [int]$Page = 1, + + [System.Collections.ArrayList]$UserList = (New-Object System.Collections.ArrayList($null)) + ) + + try { + $uri = "https://www.yammer.com/api/v1/users.json?page=$Page" + $authHeader = Get-YammerAuthHeader + $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader + } catch { + throw $_ + exit + } + + if ($response.Count -ne 0) { + $UserList.AddRange($response) + + if ($UserList.Count % 50 -eq 0) { + return Get-YammerAllUsers -Page ($Page + 1) -UserList $UserList + } + } + + return $UserList + } + } + + process { + if ($PSCmdlet.ParameterSetName -ne "All") { + $uri = "https://www.yammer.com/api/v1/users" + + $uri += switch ($PSCmdlet.ParameterSetName) { + Id { + "/$Id.json" + } + UserPrincipalName { + "/by_email.json?email=$UserPrincipalName" + } + Current { + "/current.json" + } + } + + $authHeader = Get-YammerAuthHeader + Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader + } else { + Get-YammerAllUsers + } + } +} + +function Remove-YammerUser { + <# + .SYNOPSIS + Suspend or permanently delete Yammer user(s) by ID. + + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [int[]]$Id, + + [Parameter(Mandatory = $false)] + [switch]$TrueDelete + ) + + begin { + $Uri = "https://www.yammer.com/api/v1/users/$Id" + + if ($TrueDelete) { + $Uri += "&delete=TRUE" + } + + $action = if ($TrueDelete) { + "Permanently delete" + } else { + "Suspend" + } + $authHeader = Get-YammerAuthHeader + } + + process { + foreach ($user in $Id) { + if ($pscmdlet.ShouldProcess($user, "$action Yammer user")) { + Invoke-RestMethod -Uri $Uri -Method Delete -Headers $authHeader + } + } + } +} + +function Update-YammerUser { + <# + .SYNOPSIS + Set properties of Yammer user. + + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [int]$Id, + + [string]$JobTitle + ) + + $uri = "https://www.yammer.com/api/v1/users/$Id.json" + + $requestBody = @{} + + if ($PSBoundParameters.ContainsKey("JobTitle")) { + $requestBody.Add("job_title", $JobTitle) + } + + if ($pscmdlet.ShouldProcess($Id, "Update Yammer user")) { + $authHeader = Get-YammerAuthHeader + $jsonRequestBody = $requestBody | ConvertTo-Json + Invoke-RestMethod -Uri $uri -Body $jsonRequestBody -Method Put -Headers $authHeader -ContentType "application/json" + } +} + +function Get-YammerGroup { + <# + .SYNOPSIS + Get Yammer group(s). + + #> + [CmdletBinding(DefaultParameterSetName = "All")] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "Id" + )] + [int]$Id, + + [Parameter( + Mandatory = $true, + ParameterSetName = "All" + )] + [switch]$All + ) + + begin { + function Get-YammerAllGroups { + [CmdletBinding()] + param( + [int]$Page = 1, + + [System.Collections.ArrayList]$GroupList = (New-Object System.Collections.ArrayList($null)) + ) + + try { + $uri = "https://www.yammer.com/api/v1/groups.json?page=$Page" + $authHeader = Get-YammerAuthHeader + $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader + } catch { + throw $_ + exit + } + + if ($response.Count -ne 0) { + $GroupList.AddRange($response) + + if ($GroupList.Count % 50 -eq 0) { + return Get-YammerAllGroups -Page ($Page + 1) -GroupList $GroupList + } + } + + return $GroupList + } + } + + process { + if ($PSCmdlet.ParameterSetName -ne "All") { + $uri = "https://www.yammer.com/api/v1/groups/$Id.json" + + $authHeader = Get-YammerAuthHeader + Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader + } else { + Get-YammerAllGroups + } + } +} + +function Get-YammerGroupMember { + <# + .SYNOPSIS + Get all members of a Yammer group specified by AD. + + #> + [CmdletBinding()] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [int]$Id + ) + + begin { + function Get-YammerAllMembers { + [CmdletBinding()] + param( + [int]$Id, + + [int]$Page = 1, + + [System.Collections.ArrayList]$MemberList = (New-Object System.Collections.ArrayList($null)) + ) + + try { + # $uri = "https://www.yammer.com/api/v1/users/in_group/$Id.json?page=$Page" + # We're using the undocumented endpoint below because it supports the is_admin property + $uri = "https://www.yammer.com/api/v1/groups/$Id/members.json?page=$Page" + $authHeader = Get-YammerAuthHeader + $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader + $users = if ($response.users) { + $response.users + } else { + $null + } + } catch { + throw $_ + exit + } + + if ($users.Count -ne 0) { + $MemberList.AddRange($users) + + if ($MemberList.Count % 50 -eq 0) { + return Get-YammerAllMembers -Id $Id -Page ($Page + 1) -MemberList $MemberList + } + } + + return $MemberList + } + } + + process { + Get-YammerAllMembers -Id $Id + } +} + +function Send-YammerMessage { + <# + .SYNOPSIS + Post message to Yammer user, group, or reply. + + #> + [CmdletBinding( + SupportsShouldProcess = $true, + DefaultParameterSetName = "User" + )] + Param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "User" + )] + [int[]]$Id, + + [Parameter( + Mandatory = $true, + ParameterSetName = "Group" + )] + [int]$GroupId, + + [Parameter( + Mandatory = $true, + ParameterSetName = "Reply" + )] + [int]$ReplyId, + + [Parameter(Mandatory = $true)] + [string]$Message + ) + + $uri = "https://www.yammer.com/api/v1/messages.json" + + $requestBody = @{} + + switch ($PSCmdlet.ParameterSetName) { + User { + $requestBody.Add("direct_to_user_ids", $Id) + } + Group { + $requestBody.Add("group_id", $GroupId) + } + Reply { + $requestBody.Add("replied_to_id", $ReplyId) + } + } + + $requestBody.Add("body", $Message) + + $jsonBody = $requestBody | ConvertTo-Json + + if ($pscmdlet.ShouldProcess($jsonBody, "Send Yammer message")) { + $authHeader = Get-YammerAuthHeader + Invoke-RestMethod -Uri $uri -Body $jsonBody -Method Post -Headers $authHeader -ContentType "application/json" + } +} + +Export-ModuleMember -Function * \ No newline at end of file From 95e0c83c2629ded9d678d2eeaf30ffebef75d218 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Fri, 23 Feb 2018 16:05:18 -0500 Subject: [PATCH 41/65] Add GUI script o help manage CM mobile devices. --- SCCM/Manage-CMMobileDevice.ps1 | 361 +++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 SCCM/Manage-CMMobileDevice.ps1 diff --git a/SCCM/Manage-CMMobileDevice.ps1 b/SCCM/Manage-CMMobileDevice.ps1 new file mode 100644 index 0000000..9596708 --- /dev/null +++ b/SCCM/Manage-CMMobileDevice.ps1 @@ -0,0 +1,361 @@ +# Search and replace the following strings with whatever's appropriate for your environment +# CMSERVER, DOMAINNAME, MOBILEDEVICECOLLECTIONID + +# Update the output log that the user sees, and scroll to keep up +function Update-Log { + param( + [string]$Text, + + [Parameter(Mandatory = $false)] + #[ValidateSet('White', 'Yellow', 'Red', 'Green')] + [string]$Color = 'White', + + [switch]$NoNewLine + ) + + $LogTextBox.SelectionColor = $Color + $LogTextBox.AppendText("$Text") + if (-not $NoNewLine) { $LogTextBox.AppendText("`n") } + $LogTextBox.Update() + $LogTextBox.ScrollToCaret() +} + +# Verify that the username exists +function Test-User { + param([string]$UserID) + + $UserObject = Get-CMUser -Name "DOMAINNAME\$UserID" + + if ($UserObject -ne $null) { + Update-Log "$UserID is a valid user." + + return $UserObject + } else { + if ($UserID -eq '') { + Update-Log "User ID field is blank." -Color Red -NoNewLine + } else { + Update-Log "$UserID was not found." -Color Red -NoNewLine + } + + Update-Log " Aborted - no actions performed.`n" -Color Red + + return + } +} + +# verify that the device exists +function Test-Device { + param([string]$DeviceID) + + $DeviceObject = Get-CMDevice -ResourceId $DeviceID + + if ($DeviceObject -ne $null) { + Update-Log "$DeviceID is a valid device ID." + return $DeviceObject + } else { + if ($DeviceID -eq '') { + Update-Log "device ID field is blank." -Color Red -NoNewLine + } else { + Update-Log "$DeviceID was not found." -Color Red -NoNewLine + } + + Update-Log " Aborted - no actions performed." -Color Red + + return + } +} + +# allow the user to use "*" to search for usernames and select the one they want +function Get-User { + param([string]$UserID) + + $Users = Get-CMUser -Name "DOMAINNAME\$UserID" + + if ($Users -ne $null) { + try { + $Selected = if ($Users.Count -gt 1) { + $Users.Name | Out-GridView -Title 'Select a user' -OutputMode Single + } else { + $Users.Name + } + + $Selected = $Selected.Replace("DOMAINNAME\","") + $Selected = $Selected.Split(" ")[0] + $UserTextBox.Text = $Selected + + Update-Log "Updated user ID field to $Selected." + } catch { } + } else { + if ($UserID -eq '') { + Update-Log "User ID field is blank." -Color Yellow + } else { + Update-Log "No users were found with ID $UserID." -Color Yellow + } + } +} + +# get the mobile devices assigned to the user +function Get-UserDevices { + param([string]$UserID) + + $UserObject = Test-User $UserID + + Try { + # Get all the primary devices for the user + $PrimaryDevices = Get-CMUserDeviceAffinity -UserId $UserObject.ResourceID + + # With those id's, create a list of device objects + Update-Log 'Searching for devices' -NoNewLine + + $Devices = foreach ($Device in $PrimaryDevices) { + Get-CMDevice -ResourceID $Device.ResourceID + Update-Log '.' -NoNewLine + } + + Update-Log '' + + # Filter the objects list so we're only looking at mobile devices not equal to OS X + $MobileDevices = $Devices | Where-Object { ($_.ClientType -eq 3) -and ($_.DeviceOS -notlike "OS X*") } + + # Output what we have and let + $Selected = if ($MobileDevices.Count -gt 1) { + $MobileDevices | Select-Object -Property ResourceID, Name, DeviceOS, LastActiveTime, WipeStatus | Out-GridView -Title 'Select a device' -OutputMode Single + } else { + $MobileDevices + } + + if ($Selected) { + $DeviceIDTextBox.Text = $Selected.ResourceID + $DeviceNameTextBox.Text = $Selected.Name + Update-Log "Set device field to resource ID $($Selected.ResourceID) and name field to $($Selected.Name)." + } else { + Update-Log "No mobile devices found for $UserID." + } + } Catch { + Update-Log "There was a problem trying to update the device ID and name fields." -Color Yellow + } +} + +# generate a list of all mobile devices inactive for 30+ days +function Get-InactiveDevices { + Update-Log 'Searching for devices' -NoNewLine + Get-CMDevice -CollectionId 'MOBILEDEVICECOLLECTIONID' | Where-Object { $_.LastActiveTime -lt (Get-Date).AddDays(-30) } | ForEach-Object { + New-Object PSObject -Property @{ + UserName = (Get-CMUserDeviceAffinity -DeviceId $_.ResourceID).UniqueUserName.Replace("DOMAINNAME\","") + ResourceID = [string]$_.ResourceID + Name = $_.Name + DeviceOS = $_.DeviceOS + LastActiveTime = $_.LastActiveTime + WipeStatus = $_.WipeStatus + } + + Update-Log '.' -NoNewLine + } | Select-Object -Property UserName, ResourceID, Name, DeviceOS, LastActiveTime, WipeStatus | Out-GridView -Title 'Inactive devices' + + Update-Log '' +} + +function Write-Title { + Update-Log " __ ___ __ _ __ ___ _ " -Color Orange + Update-Log " / |/ /__ / / (_) /__ / _ \___ _ __(_)______ " -Color Orange + Update-Log " / /|_/ / _ \/ _ \/ / / -_) / // / -_) |/ / / __/ -_)" -Color Orange + Update-Log " /_/__/_/\___/_.__/_/_/\__/ /____/\__/|___/_/\__/\__/ " -Color Orange + Update-Log " / |/ /__ ____ ___ ____ ____ __ _ ___ ___ / /_ " -Color Orange + Update-Log " / /|_/ / _ ``/ _ \/ _ ``/ _ ``/ -_) ' \/ -_) _ \/ __/ " -Color Orange + Update-Log " /_/ /_/\_,_/_//_/\_,_/\_, /\__/_/_/_/\__/_//_/\__/ v1.2" -Color Orange + Update-Log " /___/ " -Color Orange -NoNewLine + Update-Log " by Nick Rodriguez " -Color Gold + Update-Log '' +} + +function New-UtilityForm { + # References for building forms + [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") + [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") + + $Form = New-Object System.Windows.Forms.Form + $Form.FormBorderStyle = 'FixedDialog' + $Form.Text = 'Config Manager Mobile Device Management' + $Form.Size = New-Object System.Drawing.Size(490, 590) + $Form.StartPosition = "CenterScreen" + + # Creates output textbox + $LogTextBox = New-Object System.Windows.Forms.RichTextBox + $LogTextBox.Location = New-Object System.Drawing.Size(12, 120) + $LogTextBox.Size = New-Object System.Drawing.Size(460, 430) + $LogTextBox.ReadOnly = 'True' + $LogTextBox.BackColor = 'Black' + $LogTextBox.ForeColor = 'White' + $LogTextBox.Font = 'Consolas' + $Form.Controls.Add($LogTextBox) + + # User id input + $UserSearchButton = New-Object System.Windows.Forms.Button + $UserSearchButton.Location = New-Object System.Drawing.Size(12, 14) + $UserSearchButton.Size = New-Object System.Drawing.Size(75, 22) + $UserSearchButton.Text = "User ID" + $UserSearchButton.Add_Click( + { Script:Get-User $UserTextBox.Text }) + $Form.Controls.Add($UserSearchButton) + + $UserTextBox = New-Object System.Windows.Forms.TextBox + $UserTextBox.Location = New-Object System.Drawing.Size(110, 15) + $UserTextBox.Size = New-Object System.Drawing.Size(60, 20) + $Form.Controls.Add($UserTextBox) + + # Button to search for devices assigned to user + $DeviceSearchButton = New-Object System.Windows.Forms.Button + $DeviceSearchButton.Location = New-Object System.Drawing.Size(250, 14) + $DeviceSearchButton.Size = New-Object System.Drawing.Size(75, 22) + $DeviceSearchButton.Text = 'Device ID' + $DeviceSearchButton.Add_Click({ Get-UserDevices $UserTextBox.Text }) + $Form.Controls.Add($DeviceSearchButton) + + # Device ID + $DeviceIDTextBox = New-Object System.Windows.Forms.TextBox + $DeviceIDTextBox.Location = New-Object System.Drawing.Size(345, 15) + $DeviceIDTextBox.Size = New-Object System.Drawing.Size(125, 20) + $DeviceIDTextBox.ReadOnly = 'True' + $Form.Controls.Add($DeviceIDTextBox) + + # Device ID + $DeviceNameLabel = New-Object System.Windows.Forms.Label + $DeviceNameLabel.Location = New-Object System.Drawing.Size(250, 48) + $DeviceNameLabel.Size = New-Object System.Drawing.Size(75, 20) + $DeviceNameLabel.Text = "Device Name" + $Form.Controls.Add($DeviceNameLabel) + + # Device Name + $DeviceNameTextBox = New-Object System.Windows.Forms.TextBox + $DeviceNameTextBox.Location = New-Object System.Drawing.Size(345, 44) + $DeviceNameTextBox.Size = New-Object System.Drawing.Size(125, 20) + $DeviceNameTextBox.ReadOnly = 'True' + $Form.Controls.Add($DeviceNameTextBox) + + # Retire button + $MigrateButton = New-Object System.Windows.Forms.Button + $MigrateButton.Location = New-Object System.Drawing.Size(50, 85) + $MigrateButton.Size = New-Object System.Drawing.Size(75, 22) + $MigrateButton.Text = 'Retire' + $MigrateButton.Add_Click({ + try { + Invoke-CMDeviceRetire -Id $DeviceIDTextBox.Text -Force + Update-Log "Successfully retired $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)]." -Color Green + } catch { + Update-Log "There was a problem retiring $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)]:" -Color Red + Update-Log $_.Exception.Message -Color Red + } + }) + $Form.Controls.Add($MigrateButton) + + # Lock button + $LockButton = New-Object System.Windows.Forms.Button + $LockButton.Location = New-Object System.Drawing.Size(150, 85) + $LockButton.Size = New-Object System.Drawing.Size(75, 22) + $LockButton.Text = 'Lock' + $LockButton.Add_Click({ + try { + Invoke-CMDeviceAction -Id $DeviceIDTextBox.Text -Action Lock -ErrorAction Stop + + Update-Log "Lock on $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)] successfully initiated." -Color Green + } catch { + Update-Log "There was a problem performing Lock on $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)]:" -Color Red + Update-Log $_.Exception.Message -Color Red + } + }) + $Form.Controls.Add($LockButton) + + # Reset pin button + $ResetPinButton = New-Object System.Windows.Forms.Button + $ResetPinButton.Location = New-Object System.Drawing.Size(250, 85) + $ResetPinButton.Size = New-Object System.Drawing.Size(75, 22) + $ResetPinButton.Text = 'Reset Pin' + $ResetPinButton.Add_Click({ + try { + Invoke-CMDeviceAction -Id $DeviceIDTextBox.Text -Action PinReset -ErrorAction Stop + + Update-Log "Pin Reset on $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)] successfully initiated." -Color Green + } catch { + Update-Log "There was a problem performing Pin Reset on $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)]:" -Color Red + Update-Log $_.Exception.Message -Color Red + } + }) + $Form.Controls.Add($ResetPinButton) + + # Status button + $StatusButton = New-Object System.Windows.Forms.Button + $StatusButton.Location = New-Object System.Drawing.Size(350, 85) + $StatusButton.Size = New-Object System.Drawing.Size(75, 22) + $StatusButton.Text = 'Status' + $StatusButton.Add_Click({ + try { + $Action = Get-CMDeviceAction -Id $DeviceIDTextBox.Text -Fast -ErrorAction Stop + + if ($Action -ne $null) { + Update-Log "Getting action history for $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)]..." + Update-Log "Action`t`tState`t`tLast Update Time`tPin" -Color Gray + + $Action | ForEach-Object { + Update-Log "$($_.Action)$(if ($_.Action -eq 'Lock') { "`t" })`t" -NoNewLine + Update-Log "$(switch ($_.State) { 1 { "Pending`t" } 4 { "Complete" } })`t" -NoNewLine + Update-Log "$($_.LastUpdateTime)`t" -NoNewLine + if ($_.Action -eq 'PinReset') { + $PinResetState = Get-WmiObject -ComputerName CMSERVER -NameSpace root/SMS/site_$(Get-PSDrive -PSProvider CMSite) ` + -Class SMS_DeviceAction -Filter "Action='PinReset' and ResourceID='$($DeviceIDTextBox.Text)'" -ErrorAction Stop + + $Pin = ([wmi]$PinResetState.__PATH).ResponseText + Update-Log $Pin + } else { + Update-Log 'N/A' + } + } + } + else { + Update-Log "No state information available for $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)]." -Color Yellow + } + } catch { + Update-Log "Failed to get the state information for $($DeviceNameTextBox.Text) [$($DeviceIDTextBox.Text)]:" -Color Red + Update-Log $_.Exception.Message -Color Red + } + }) + $Form.Controls.Add($StatusButton) + + # Report button + $ReportButton = New-Object System.Windows.Forms.Button + $ReportButton.Location = New-Object System.Drawing.Size(110, 44) + $ReportButton.Size = New-Object System.Drawing.Size(110, 22) + $ReportButton.Text = "Inactive Devices" + $ReportButton.Add_Click({ Get-InactiveDevices }) + $Form.Controls.Add($ReportButton) + + # Clear log button + $ClearButton = New-Object System.Windows.Forms.Button + $ClearButton.Location = New-Object System.Drawing.Size(12, 44) + $ClearButton.Size = New-Object System.Drawing.Size(75, 22) + $ClearButton.Text = "Clear Log" + $ClearButton.Add_Click({ $LogTextBox.Clear() }) + $Form.Controls.Add($ClearButton) + + Write-Title + + $Form.Add_Shown({ $Form.Activate() }) + $Form.ShowDialog() +} + +try { + # Verify we have access to CM commands before we continue + Import-Module ConfigurationManager + Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop + + $CMPSSuppressFastNotUsedCheck = $true + + New-UtilityForm +} catch { + [System.Windows.Forms.MessageBox]::Show( + "Failed to set CM site drive. Are you running this from CMSERVER and is the console is up to date? + `n`nError: $($_.Exception.Message)", + 'Fail!' + ) + + exit +} \ No newline at end of file From 593b925c0dd2e63d9d4ecca16ac671b560ba205e Mon Sep 17 00:00:00 2001 From: ashish Date: Sat, 6 Oct 2018 10:48:24 +0530 Subject: [PATCH 42/65] one line powershell to update user photo in AD Powershell script to add user photo in AD --- Windows/UploadUserPhotoinAD.ps1 | 1 + 1 file changed, 1 insertion(+) create mode 100644 Windows/UploadUserPhotoinAD.ps1 diff --git a/Windows/UploadUserPhotoinAD.ps1 b/Windows/UploadUserPhotoinAD.ps1 new file mode 100644 index 0000000..4c81d8b --- /dev/null +++ b/Windows/UploadUserPhotoinAD.ps1 @@ -0,0 +1 @@ +Set-UserPhoto "" -PictureData ([System.IO.File]::ReadAllBytes("")) From 40e9404b753e62b1eb700c750b5b19f1723a3d9a Mon Sep 17 00:00:00 2001 From: ashish Date: Wed, 10 Oct 2018 15:59:05 +0530 Subject: [PATCH 43/65] Get details of groups user member of Powershell one line script to get details of all groups user memberof --- Windows/Get-DetailsofGroupsUserMemberOf.ps1 | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Windows/Get-DetailsofGroupsUserMemberOf.ps1 diff --git a/Windows/Get-DetailsofGroupsUserMemberOf.ps1 b/Windows/Get-DetailsofGroupsUserMemberOf.ps1 new file mode 100644 index 0000000..5f34a37 --- /dev/null +++ b/Windows/Get-DetailsofGroupsUserMemberOf.ps1 @@ -0,0 +1,3 @@ +#Powershell one line script to get details of all groups user memberof + +Get-ADPrincipalGroupMembership ashishanand |Get-ADGroup -Properties * | select name, managedby, * From 8f2d49dde257a65cfee279b391750b2248127627 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Tue, 29 Jan 2019 15:25:01 -0500 Subject: [PATCH 44/65] Update BTC script. Add speedtest script. --- Fun/Get-BitcoinPriceChart.ps1 | 8 +-- Get-WanSpeed.ps1 | 100 ++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 Get-WanSpeed.ps1 diff --git a/Fun/Get-BitcoinPriceChart.ps1 b/Fun/Get-BitcoinPriceChart.ps1 index 9c02b8e..a6f7f2e 100644 --- a/Fun/Get-BitcoinPriceChart.ps1 +++ b/Fun/Get-BitcoinPriceChart.ps1 @@ -1,6 +1,6 @@ $PriceData = [System.Collections.Specialized.OrderedDictionary]@{} -$Data = (Invoke-RestMethod -Method Get -Uri 'https://api.coindesk.com/v1/bpi/historical/close.json' ` +$Data = (Invoke-RestMethod -Method Get -Uri 'http://api.coindesk.com/v1/bpi/historical/close.json' ` -ContentType 'application/json').bpi | Out-String $Data.Trim() -split "`r`n" | ForEach-Object { @@ -38,10 +38,10 @@ $Chart.Series['Price'].ChartArea = "ChartArea1" $Chart.Series['Price'].Color = "#62B5CC" $Chart.Series['Price'].Points.DataBindXY($PriceData.Keys, $PriceData.Values) -# Display the chart on a form +# Display the chart on a form $Chart.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor - [System.Windows.Forms.AnchorStyles]::Right -bor - [System.Windows.Forms.AnchorStyles]::Top -bor + [System.Windows.Forms.AnchorStyles]::Right -bor + [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left $Form = New-Object Windows.Forms.Form $Form.Text = "Bitcoin Price Chart" diff --git a/Get-WanSpeed.ps1 b/Get-WanSpeed.ps1 new file mode 100644 index 0000000..aeb6085 --- /dev/null +++ b/Get-WanSpeed.ps1 @@ -0,0 +1,100 @@ +<#PSScriptInfo + +.VERSION 2.0 + +.GUID a6048a09-3e66-467a-acd4-ce3e97098a65 + +.AUTHOR velecky@velecky.onmicrosoft.com modified by Nick Rodriguez + +.PROJECTURI https://www.powershellgallery.com/packages/Speedtest/2.0 + +.DESCRIPTION + WAN speed test + +#> + +[CmdletBinding()] +param( + [int]$Repetitions = 4 +) + +function Invoke-SpeedTest { + param( + [string]$UploadUrl + ) + + $topServerUrlSpilt = $UploadUrl -split 'upload' + $url = $topServerUrlSpilt[0] + 'random2000x2000.jpg' + $col = New-Object System.Collections.Specialized.NameValueCollection + $wc = New-Object System.Net.WebClient + $wc.QueryString = $col + $downloadElaspedTime = (Measure-Command { $webpage1 = $wc.DownloadData($url) }).TotalMilliseconds + $downSize = ($webpage1.length + $webpage2.length) / 1MB + $downloadSize = [Math]::Round($downSize, 2) + $downloadTimeSec = $downloadElaspedTime * 0.001 + $downSpeed = ($downloadSize / $downloadTimeSec) * 8 + $downloadSpeed = [Math]::Round($downSpeed, 2) + + Write-Verbose "Downloaded $downloadSize MB in $downloadTimeSec seconds at a speed of $downloadSpeed mbps" + + return $downloadSpeed +} + +# Interact with speedtest page avoiding api +$objXmlHttp = New-Object -ComObject MSXML2.ServerXMLHTTP +$objXmlHttp.Open("GET", "http://www.speedtest.net/speedtest-config.php", $False) +$objXmlHttp.Send() +[xml]$content = $objXmlHttp.responseText + +# Select closest server based on lat/lon +$oriLat = $content.settings.client.lat +$oriLon = $content.settings.client.lon +Write-Verbose "Latitude: $oriLat" +Write-Verbose "Longitude: $oriLon" + +# Make another request, this time to get the server list from the site +$objXmlHttp.Open("GET", "http://www.speedtest.net/speedtest-servers.php", $False) +$objXmlHttp.Send() +[xml]$ServerList = $objXmlHttp.responseText + +# Cons contains all of the information about every server in the speedtest.net database +$cons = $ServerList.settings.servers.server + +# Calculate servers relative closeness by doing math against latitude and longitude +Write-Verbose "Searching for closest geographical servers from list of $($cons.Count)..." +foreach ($val in $cons) { + $R = 6371 + $pi = [Math]::PI + + [float]$dlat = ([float]$oriLat - [float]$val.lat) * $pi / 180 + [float]$dlon = ([float]$oriLon - [float]$val.lon) * $pi / 180 + [float]$a = [math]::Sin([float]$dLat / 2) * [math]::Sin([float]$dLat / 2) + [math]::Cos([float]$oriLat * $pi / 180 ) * [math]::Cos([float]$val.lat * $pi / 180 ) * [math]::Sin([float]$dLon / 2) * [math]::Sin([float]$dLon / 2) + [float]$c = 2 * [math]::Atan2([math]::Sqrt([float]$a ), [math]::Sqrt(1 - [float]$a)) + [float]$d = [float]$R * [float]$c + + $serverInformation += @([pscustomobject]@{ + Distance = $d + Country = $val.country + Sponsor = $val.sponsor + Url = $val.url + }) +} + +$serverInformation = $serverInformation | Sort-Object -Property distance + +$speedResults = @() + +for ($i = 0; $i -lt $Repetitions; $i++) { + $url = $serverInformation[$i].url + Write-Verbose "Download attempt ($($i + 1) of $Repetitions) from $url..." + $speed = Invoke-SpeedTest $url + $speedResults += $speed +} + +$results = $speedResults | Measure-Object -Average -Minimum -Maximum + +New-Object psobject -Property @{ + "Fastest (mbps)" = $results.Maximum + "Slowest (mbps)" = $results.Minimum + "Average (mbps)" = $results.Average +} \ No newline at end of file From b40c5c4736af45c071979fd9aede71c62ec3d105 Mon Sep 17 00:00:00 2001 From: Rodriguez Date: Tue, 29 Jan 2019 15:34:08 -0500 Subject: [PATCH 45/65] Add scripts to install RemoteApp and refresh icons. --- RemoteApp/Install-Apps.ps1 | 132 +++++++++++++++++++++++++++++++++++++ RemoteApp/Update-Apps.ps1 | 24 +++++++ 2 files changed, 156 insertions(+) create mode 100644 RemoteApp/Install-Apps.ps1 create mode 100644 RemoteApp/Update-Apps.ps1 diff --git a/RemoteApp/Install-Apps.ps1 b/RemoteApp/Install-Apps.ps1 new file mode 100644 index 0000000..52e6fad --- /dev/null +++ b/RemoteApp/Install-Apps.ps1 @@ -0,0 +1,132 @@ +$url = 'RDSBROKER.COMPANY.COM' +$domain = 'DOMAIN' + +Write-Host "Cleaning up previous RemoteApp install..." + +try { + $feeds = Get-ChildItem 'HKCU:\Software\Microsoft\Workspaces\Feeds' -ErrorAction Stop +} +catch { + Write-Host "No feeds found" +} + +if ($feeds) { + foreach ($feed in $feeds) { + $id = (Get-ItemProperty $feed.PSPath -Name WorkspaceId).WorkspaceId + + if ($id -eq $url) { + Write-Host "Previous install found" + + Write-Host "Removing Workspace folder..." + $workspaceFolder = (Get-ItemProperty $feed.PSPath -Name WorkspaceFolder).WorkspaceFolder + Remove-Item $workspaceFolder -Recurse -ErrorAction SilentlyContinue + + Write-Host "Removing Desktop icons..." + $startFolder = (Get-ItemProperty $feed.PSPath -Name StartMenuRoot).StartMenuRoot + $apps = Get-ChildItem $startFolder + $desktopIcons = Get-ChildItem "$env:USERPROFILE\Desktop" + foreach ($icon in $desktopIcons) { + if ($apps.Name -contains $icon.Name) { + Remove-Item $icon.FullName + } + } + + Write-Host "Removing Start Menu items..." + Remove-Item $startFolder -Recurse + + Write-Host "Removing registry items..." + Remove-Item $feed.PSPath -Recurse + } + + Write-Host "Cleanup complete" + + break + } +} + +Write-Host "`n`nEnter your credentials..." + +try { + $creds = Get-Credential -Credential $null +} +catch { + Write-Warning "You must enter credentials to complete the setup" + Read-Host "`n`nPress [Enter] to exit" + exit +} + +$username = $creds.UserName +$password = $creds.GetNetworkCredential().Password + +Write-Host "`n`nAdding credentials to Credential Manager..." +cmdkey /add:$url /user:$domain$username /password:$password +cmdkey /add:*.company.com /user:$domain$username /password:$password +cmdkey /add:TERMSRV/$url /user:$domain$username /password:$password + +Write-Host "`n`nSetting up Workspace..." + +$winVer = [System.Environment]::OSVersion.Version.Major +if ($winVer -ne "10" ) { + Write-Host "Windows 10 not detected" + $userProf = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\RemoteApp and Desktop Connections\Work Resources\" +} +else { + Write-Host "Windows 10 detected" + $userProf = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Work Resources (RADC)" +} + +# Create the wcx file +Write-Host "Creating setup wcx file..." +$wcxPath = "$env:TEMP\RDSWebSetup.wcx" +$config = @" + + + + +"@ +New-Item -Path $wcxPath -ItemType "File" -Value $config -Force | Out-Null + +# Silently run the RemoteApp config +Write-Host "Running wcx setup..." +rundll32.exe tsworkspace, WorkspaceSilentSetup $wcxPath + + + +# Wait until the icons appear in the user profile and then copy them to the desktop +Write-Host "Waiting for Workspace icons to be created..." +$counter = 0 +$timeout = $false + +while (-not (Test-Path $userProf) -and $counter -lt 15) { + Start-Sleep -Seconds 2 + $counter++ +} + +if ($counter -eq 15) { + $timeout = $true +} + +$found = $false + +$feeds = Get-ChildItem 'HKCU:\Software\Microsoft\Workspaces\Feeds' +foreach ($feed in $feeds) { + $id = (Get-ItemProperty $feed.PSPath -Name WorkspaceId).WorkspaceId + + if ($id -eq $url) { + $found = $true + } +} + +if (-not $found -or $timeout) { + Write-Host "`n`nCredentials invalid or timeout reached. Please follow the instructions in the new window..." -ForegroundColor Red + + Start-Process $wcxPath -Wait +} + +Remove-Item $wcxPath + +Write-Host "`n`nCopying icons to desktop..." +Copy-Item "$userProf\*" "$env:USERPROFILE\Desktop\" -Recurse -Force + +Read-Host "`n`nPress [Enter] to exit" +exit \ No newline at end of file diff --git a/RemoteApp/Update-Apps.ps1 b/RemoteApp/Update-Apps.ps1 new file mode 100644 index 0000000..c709592 --- /dev/null +++ b/RemoteApp/Update-Apps.ps1 @@ -0,0 +1,24 @@ +$url = 'RDSBROKER.COMPANY.COM' + +$feeds = Get-ChildItem 'HKCU:\Software\Microsoft\Workspaces\Feeds' +foreach ($feed in $feeds) { + $id = (Get-ItemProperty $feed.PSPath -Name WorkspaceId).WorkspaceId + + if ($id -eq $url) { + # Remove Start folder and desktop icons + $startFolder = (Get-ItemProperty $feed.PSPath -Name StartMenuRoot).StartMenuRoot + $apps = Get-ChildItem $startFolder + $desktopIcons = Get-ChildItem "$env:USERPROFILE\Desktop" + foreach ($icon in $desktopIcons) { + if ($apps.Name -contains $icon.Name) { + Remove-Item $icon.FullName + } + } + } +} + +rundll32 tsworkspace,TaskUpdateWorkspaces2 + +Start-Sleep -Seconds 2 + +Copy-Item "$startFolder\*" "$env:USERPROFILE\Desktop\" -Recurse -Force \ No newline at end of file From 2fc7ccff287a8bc8626556ad2364ba3f3733af48 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 7 Feb 2019 11:08:43 -0500 Subject: [PATCH 46/65] Add script to get latest nvidia drivers --- Fun/Get-nVidiaDriver.ps1 | 140 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 Fun/Get-nVidiaDriver.ps1 diff --git a/Fun/Get-nVidiaDriver.ps1 b/Fun/Get-nVidiaDriver.ps1 new file mode 100644 index 0000000..e8149b3 --- /dev/null +++ b/Fun/Get-nVidiaDriver.ps1 @@ -0,0 +1,140 @@ +# Most logic taken from https://github.com/ElPumpo/TinyNvidiaUpdateChecker/blob/master/TinyNvidiaUpdateChecker/MainConsole.cs + +$osVersion = [Environment]::OSVersion.Version.ToString() +$is64Bit = [Environment]::Is64BitOperatingSystem + +switch ($osVersion) { + { $_ -like '10.0*' } { + $winVer = '10' + + if ($is64Bit) { + $osId = 57 + } + else { + $osId = 56 + } + } + + { $_ -like '6.3*' } { + $winVer = '8.1' + + if ($is64Bit) { + $osId = 41 + } + else { + $osId = 40 + } + } + + { $_ -like '6.2*' } { + $winVer = '8' + + if ($is64Bit) { + $osId = 28 + } + else { + $osId = 27 + } + } + + { $_ -like '6.1*' } { + $winVer = '7' + + if ($is64Bit) { + $osId = 19 + } + else { + $osId = 18 + } + } +} + +Write-Verbose "Windows $winVer version $osVersion" +Write-Verbose "64-Bit: $is64Bit" + +$langId = switch (Get-Culture) { + 'en-US' { 1 } + 'en-GB' { 2 } + 'zh-CHS' { 5 } + 'zh-CHT' { 6 } + 'ja-JP' { 7 } + 'ko-KR' { 8 } + 'de-DE' { 9 } + 'es-ES' { 10 } + 'fr-FR' { 12 } + 'it-IT' { 13 } + 'pl-PL' { 14 } + 'pt-BR' { 15 } + 'ru-RU' { 16 } + 'tr-TR' { 19 } + default { 17 } +} + +Write-Verbose "Language ID: $langId" + +foreach ($gpu in Get-CimInstance -ClassName Win32_VideoController) { + Write-Verbose $gpu.Description + + if ($gpu.Description.Split() -contains 'nvidia') { + $gpuName = $gpu.Description.Trim() + $offlineGpuVersion = $gpu.DriverVersion + } + elseif ($gpu.PNPDeviceID.Split() -contains 'ven_10de') { + Get-CimInstance -ClassName Win32_SystemEnclosure | ForEach-Object { + if ($_.ChassisTypes -eq 3) { + $gpuName = "GTX" + } + else { + $gpuName = "GTX M" + } + } + } +} + +if (-not $gpuName) { + Write-Warning "No nVidia GPU found" + exit +} + +if ($gpuName -contains 'M') { + $psId = 99 + $pfId = 758 +} +else { + $psId = 98 + $pfId = 756 +} + +$gpuUrl = "http://www.nvidia.com/Download/processDriver.aspx?psid=$psID&pfid=$pfID&rpf=1&osid=$osId&lid=$langID&ctk=0" +$processUrl = Invoke-WebRequest $gpuUrl | Select-Object -ExpandProperty Content + +$objXmlHttp = New-Object -ComObject MSXML2.ServerXMLHTTP +$objXmlHttp.Open("GET", $processUrl, $False) +$objXmlHttp.Send() +$response = $objXmlHttp.responseText +$html = New-Object -Com "HTMLFile" +$html.IHTMLDocument2_write($response) + +$version = $html.getElementById("tdVersion").innerText.Split(' ')[0] +$releaseDate = $html.getElementById("tdReleaseDate").innerText.Split(' ')[0].Split('.') +$friendlyReleaseDate = Get-Date -Year $releaseDate[0] -Month $releaseDate[1] -Day $releaseDate[2] -Format D +$releaseNotes = $html.getElementsByTagName('a') | Where-Object href -like "*release-notes.pdf*" | Select-Object -ExpandProperty href +$releaseDescription = $html.getElementById("tab1_content").innerText +$confirmUrl = $html.getElementsByTagName('a') | Where-Object href -like "*/content/DriverDownload-March2009/*" | ForEach-Object { + 'http://www.nvidia.com/' + $_.pathname + $_.search +} + +$objXmlHttp.Open("GET", $confirmUrl, $False) +$objXmlHttp.Send() +$response = $objXmlHttp.responseText +$html = New-Object -Com "HTMLFile" +$html.IHTMLDocument2_write($response) + +$downloadUrl = $html.getElementsByTagName('a') | Where-Object href -like "*download.nvidia*" | Select-Object -ExpandProperty href + + + +Write-Output "Download URL: $downloadUrl" +Write-Output "Release notes: $releaseNotes" +Write-Output "Offline version: $offlineGpuVersion" +Write-Output "Online version: $version released on $friendlyReleaseDate" From 2427c61d46131a6657bd4eff361129e3e8ca7d56 Mon Sep 17 00:00:00 2001 From: pm091 Date: Mon, 20 May 2019 16:53:39 +0100 Subject: [PATCH 47/65] Retrieve 1st line only from input file Retrieves the 1st line only to avoid passing the file object across the pipeline. --- Validate-CSVHeaders.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Validate-CSVHeaders.ps1 b/Validate-CSVHeaders.ps1 index 35b4b09..ae3ae84 100644 --- a/Validate-CSVHeaders.ps1 +++ b/Validate-CSVHeaders.ps1 @@ -8,7 +8,7 @@ function Validate-CSVHeaders ($correctHeaders) { [ValidateSet('Yes','No')]$validateHeaders = Read-Host "Validate headers?" if ($validateHeaders -eq 'Yes') { # put all the headers into a comma separated array - $headers = (Get-Content $fileName | Select-Object -First 1).Split(",") + $headers = (Get-Content $fileName -TotalCount 1).Split(",") for ($i = 0; $i -lt $headers.Count; $i++) { # trim any leading white space and compare the headers @@ -21,4 +21,4 @@ function Validate-CSVHeaders ($correctHeaders) { } } -Validate-CSVHeaders $correctHeaders \ No newline at end of file +Validate-CSVHeaders $correctHeaders From f1d1846f8b6f703a21380db6b7c5491d3a32557b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 14:07:57 +0200 Subject: [PATCH 48/65] Automatically detect module path --- Retire-CMApplication.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Retire-CMApplication.ps1 b/Retire-CMApplication.ps1 index a299076..ddfe82d 100644 --- a/Retire-CMApplication.ps1 +++ b/Retire-CMApplication.ps1 @@ -6,7 +6,7 @@ function Retire-CMApplication { ) # import cm module - Import-Module 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1' + Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1') # change to the cm site drive $PSD = Get-PSDrive -PSProvider CMSite From 3e5f9c9eff1c4f6471b0571ce0d300f630fc3d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 14:18:16 +0200 Subject: [PATCH 49/65] Automatically detect module path --- SCCM/Retire-CMApplication.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index 9813701..4de8ba6 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -6,7 +6,7 @@ function Retire-CMApplication { ) # import cm module - Import-Module '\\sccm01\SMS_Company\AdminConsole\bin\ConfigurationManager.psd1' + Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1') # change to the cm site drive $PSD = Get-PSDrive -PSProvider CMSite From 03c172a2fb01d6afb7ceba538e82e7d4afa251e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 07:32:24 -0500 Subject: [PATCH 50/65] Automatically detect module path --- SCCM/Retire-CMApplicationGUI.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCCM/Retire-CMApplicationGUI.ps1 b/SCCM/Retire-CMApplicationGUI.ps1 index c8f7a2a..59d0cfb 100644 --- a/SCCM/Retire-CMApplicationGUI.ps1 +++ b/SCCM/Retire-CMApplicationGUI.ps1 @@ -140,7 +140,7 @@ function Create-UtilityForm { try { # make sure we have access to CM commands before we continue - Import-Module 'E:\SCCM\AdminConsole\bin\ConfigurationManager.psd1' + Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1') Set-Location -Path "$(Get-PSDrive -PSProvider CMSite):\" -ErrorAction Stop Create-UtilityForm } catch { From c5c1d9500b5f6d936d6bfc07e63f6f2fcbfc0511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 08:30:19 -0500 Subject: [PATCH 51/65] Actually remove content from DPs --- SCCM/Retire-CMApplication.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index 4de8ba6..a85de5a 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -36,7 +36,7 @@ function Retire-CMApplication { # remove content from all dp's and dpg's Write-Host -NoNewline "Removing content from all distribution points" - $DPs = Get-CMDistributionPoint + $DPs = Get-CMDistributionPoint -AllSite foreach ($DP in $DPs) { Write-Host -NoNewline "." try { From b048314911008af3b9f6e8af2674b7a0a2e711d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 09:05:51 -0500 Subject: [PATCH 52/65] Make content removal actually work --- SCCM/Retire-CMApplication.ps1 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index a85de5a..a32ecea 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -37,11 +37,16 @@ function Retire-CMApplication { # remove content from all dp's and dpg's Write-Host -NoNewline "Removing content from all distribution points" $DPs = Get-CMDistributionPoint -AllSite - foreach ($DP in $DPs) { + foreach ($DP in $DPs) + { + $dpNetworkOSPath = $dp.NetworkOSPath #TODO: unify 2 variables + $dpName = ($dp.NetworkOSPath).Substring(2,$dpNetworkOSPath.Length-2) Write-Host -NoNewline "." - try { - Remove-CMContentDistribution -Application $RetiringApp -DistributionPointName ($DP).NetworkOSPath -Force -EA SilentlyContinue - } catch { } + try + { + Remove-CMContentDistribution -ApplicationName "$RetiringApp" -DistributionPointName $dpName -Force -EA SilentlyContinue + } + catch { } } Write-Host Write-Host -NoNewline "Removing content from all distribution point groups" From 0b0a1ad12b62c9b70c378eeb54c9e6aa70b3e0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 09:06:12 -0500 Subject: [PATCH 53/65] Variable name changes --- SCCM/Retire-CMApplication.ps1 | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index a32ecea..4fefa8e 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -13,15 +13,15 @@ function Retire-CMApplication { cd "$($PSD):" # for each provided app name, remove deployments, rename, and retire - foreach ($app in $RetiringApps) { - if ($RetiringApp = Get-CMApplication -Name $app) { - Write-Host "So long, $app!" + foreach ($RetiringApp in $RetiringApps) { + if ($RetiringApp = Get-CMApplication -Name $RetiringApp) { + Write-Host "So long, $RetiringApp!" # checking retired status, setting to active so that we can make changes if ($RetiringApp.IsExpired) { - $appWMI = gwmi -Namespace Root\SMS\Site_$PSD -class SMS_ApplicationLatest -Filter "LocalizedDisplayName = '$app'" - $appWMI.SetIsExpired($false) | Out-Null - Write-Host "Setting Status of $app to Active so that changes can be made." + $RetiringAppWMI = gwmi -Namespace Root\SMS\Site_$PSD -class SMS_ApplicationLatest -Filter "LocalizedDisplayName = '$RetiringApp'" + $RetiringAppWMI.SetIsExpired($false) | Out-Null + Write-Host "Setting Status of $RetiringApp to Active so that changes can be made." } $oldDeploys = Get-CMDeployment -SoftwareName $RetiringApp.LocalizedDisplayName @@ -29,9 +29,9 @@ function Retire-CMApplication { # remove all deployments for the app if ($oldDeploys) { $oldDeploys | ForEach-Object { - Remove-CMDeployment -ApplicationName $app -DeploymentId $_.DeploymentID -Force + Remove-CMDeployment -ApplicationName $RetiringApp -DeploymentId $_.DeploymentID -Force } - Write-Host "Removed $($oldDeploys.Count) deployments of $app." + Write-Host "Removed $($oldDeploys.Count) deployments of $RetiringApp." } # remove content from all dp's and dpg's @@ -54,17 +54,17 @@ function Retire-CMApplication { foreach ($DPG in $DPGs) { Write-Host -NoNewline "." try { - Remove-CMContentDistribution -Application $RetiringApp -DistributionPointGroupName ($DPG).Name -Force -EA SilentlyContinue + Remove-CMContentDistribution -ApplicationName $RetiringApp -DistributionPointGroupName ($DPG).Name -Force -EA SilentlyContinue } catch { } } Write-Host # rename the app - $app = $app.Replace('Retired-', '') + $RetiringApp = $RetiringApp.Replace('Retired-', '') try { - Set-CMApplication -Name $app -NewName "Retired-$app" + Set-CMApplication -Name $RetiringApp -NewName "Retired-$RetiringApp" } catch { } - Write-Host "Renamed to Retired-$app." + Write-Host "Renamed to Retired-$RetiringApp." # move the app according to category if ($RetiringApp.LocalizedCategoryInstanceNames -eq "Mac") { @@ -77,8 +77,8 @@ function Retire-CMApplication { # retire the app if (!$RetiringApp.IsExpired) { - $appWMI = gwmi -Namespace Root\SMS\Site_$PSD -class SMS_ApplicationLatest -Filter "LocalizedDisplayName = 'Retired-$app'" - $appWMI.SetIsExpired($true) | Out-Null + $RetiringAppWMI = gwmi -Namespace Root\SMS\Site_$PSD -class SMS_ApplicationLatest -Filter "LocalizedDisplayName = 'Retired-$RetiringApp'" + $RetiringAppWMI.SetIsExpired($true) | Out-Null Write-Host "Set status to Retired." } else { Write-Host "Status was already set to Retired." @@ -90,7 +90,7 @@ function Retire-CMApplication { Write-Host "Don't forget to delete the source files from $loc." } else { - Write-Host "$app was not found. No actions performed." + Write-Host "$RetiringApp was not found. No actions performed." } } } From 6dfbcc26ed83d32b3dc691d802926a0eb230bd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 09:12:29 -0500 Subject: [PATCH 54/65] Rename variables again --- SCCM/Retire-CMApplication.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index 4fefa8e..3395092 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -13,15 +13,15 @@ function Retire-CMApplication { cd "$($PSD):" # for each provided app name, remove deployments, rename, and retire - foreach ($RetiringApp in $RetiringApps) { - if ($RetiringApp = Get-CMApplication -Name $RetiringApp) { + foreach ($RetiringAppName in $RetiringApps) { + if ($RetiringAppName = Get-CMApplication -Name $RetiringApp) { Write-Host "So long, $RetiringApp!" # checking retired status, setting to active so that we can make changes if ($RetiringApp.IsExpired) { $RetiringAppWMI = gwmi -Namespace Root\SMS\Site_$PSD -class SMS_ApplicationLatest -Filter "LocalizedDisplayName = '$RetiringApp'" $RetiringAppWMI.SetIsExpired($false) | Out-Null - Write-Host "Setting Status of $RetiringApp to Active so that changes can be made." + Write-Host "Setting Status of $RetiringAppName to Active so that changes can be made." } $oldDeploys = Get-CMDeployment -SoftwareName $RetiringApp.LocalizedDisplayName @@ -29,7 +29,7 @@ function Retire-CMApplication { # remove all deployments for the app if ($oldDeploys) { $oldDeploys | ForEach-Object { - Remove-CMDeployment -ApplicationName $RetiringApp -DeploymentId $_.DeploymentID -Force + Remove-CMDeployment -ApplicationName $RetiringAppName -DeploymentId $_.DeploymentID -Force } Write-Host "Removed $($oldDeploys.Count) deployments of $RetiringApp." } @@ -54,15 +54,15 @@ function Retire-CMApplication { foreach ($DPG in $DPGs) { Write-Host -NoNewline "." try { - Remove-CMContentDistribution -ApplicationName $RetiringApp -DistributionPointGroupName ($DPG).Name -Force -EA SilentlyContinue + Remove-CMContentDistribution -ApplicationName $RetiringAppName -DistributionPointGroupName ($DPG).Name -Force -EA SilentlyContinue } catch { } } Write-Host # rename the app - $RetiringApp = $RetiringApp.Replace('Retired-', '') + $RetiringAppName = $RetiringApp.Replace('Retired-', '') try { - Set-CMApplication -Name $RetiringApp -NewName "Retired-$RetiringApp" + Set-CMApplication -Name $RetiringAppName -NewName "Retired-$RetiringApp" } catch { } Write-Host "Renamed to Retired-$RetiringApp." @@ -90,7 +90,7 @@ function Retire-CMApplication { Write-Host "Don't forget to delete the source files from $loc." } else { - Write-Host "$RetiringApp was not found. No actions performed." + Write-Host "$RetiringAppName was not found. No actions performed." } } } From 7f3b3ef290729a2d7ce60ae7a1e06a6f5f3445d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 10:33:58 -0500 Subject: [PATCH 55/65] Properly retire/re-instate apps --- SCCM/Retire-CMApplication.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index 3395092..283bf37 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -18,9 +18,9 @@ function Retire-CMApplication { Write-Host "So long, $RetiringApp!" # checking retired status, setting to active so that we can make changes - if ($RetiringApp.IsExpired) { - $RetiringAppWMI = gwmi -Namespace Root\SMS\Site_$PSD -class SMS_ApplicationLatest -Filter "LocalizedDisplayName = '$RetiringApp'" - $RetiringAppWMI.SetIsExpired($false) | Out-Null + if ($RetiringApp.IsExpired) + { + Resume-CMApplication -Name "$RetiringAppName" Write-Host "Setting Status of $RetiringAppName to Active so that changes can be made." } From b53a91647853ceb167286e9fc011cc3f01acbb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 10:41:24 -0500 Subject: [PATCH 56/65] Make renaming optional --- SCCM/Retire-CMApplication.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index 283bf37..273c000 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -2,7 +2,9 @@ function Retire-CMApplication { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - $RetiringApps = @() + $RetiringApps = @(), + [Parameter(Mandatory = $false)] + $rename = $false ) # import cm module @@ -61,6 +63,7 @@ function Retire-CMApplication { # rename the app $RetiringAppName = $RetiringApp.Replace('Retired-', '') + If ($rename){ try { Set-CMApplication -Name $RetiringAppName -NewName "Retired-$RetiringApp" } catch { } @@ -74,6 +77,7 @@ function Retire-CMApplication { Move-CMObject -FolderPath "Application\Retired" -InputObject $RetiringApp Write-Host "Moved to Retired." } + } # retire the app if (!$RetiringApp.IsExpired) { From 65c4222ae0d5f46ff816ada12ab7358f7a5ef7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 10:42:36 -0500 Subject: [PATCH 57/65] Various fixes --- SCCM/Retire-CMApplication.ps1 | 57 ++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index 273c000..ed26a9f 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -16,8 +16,10 @@ function Retire-CMApplication { # for each provided app name, remove deployments, rename, and retire foreach ($RetiringAppName in $RetiringApps) { - if ($RetiringAppName = Get-CMApplication -Name $RetiringApp) { - Write-Host "So long, $RetiringApp!" + + if ($RetiringApp = Get-CMApplication -Name $RetiringAppName) + { + Write-Host "So long, $RetiringAppName!" # checking retired status, setting to active so that we can make changes if ($RetiringApp.IsExpired) @@ -41,12 +43,15 @@ function Retire-CMApplication { $DPs = Get-CMDistributionPoint -AllSite foreach ($DP in $DPs) { - $dpNetworkOSPath = $dp.NetworkOSPath #TODO: unify 2 variables - $dpName = ($dp.NetworkOSPath).Substring(2,$dpNetworkOSPath.Length-2) + #$dpNetworkOSPath = $dp.NetworkOSPath #TODO: unify 2 variables + $dpName = ($dp.NetworkOSPath).Substring(2) + + + Write-Verbose "Removing $RetiringAppName from $dpName" Write-Host -NoNewline "." try { - Remove-CMContentDistribution -ApplicationName "$RetiringApp" -DistributionPointName $dpName -Force -EA SilentlyContinue + Remove-CMContentDistribution -ApplicationName "$RetiringAppName" -DistributionPointName $dpName -Force -EA SilentlyContinue #TODO: parallelize this } catch { } } @@ -56,35 +61,37 @@ function Retire-CMApplication { foreach ($DPG in $DPGs) { Write-Host -NoNewline "." try { - Remove-CMContentDistribution -ApplicationName $RetiringAppName -DistributionPointGroupName ($DPG).Name -Force -EA SilentlyContinue + Remove-CMContentDistribution -ApplicationName "$RetiringAppName" -DistributionPointGroupName ($DPG).Name -Force -EA SilentlyContinue #TODO: parallelize this } catch { } } Write-Host - # rename the app - $RetiringAppName = $RetiringApp.Replace('Retired-', '') If ($rename){ - try { - Set-CMApplication -Name $RetiringAppName -NewName "Retired-$RetiringApp" - } catch { } - Write-Host "Renamed to Retired-$RetiringApp." + # rename the app + $RetiringAppName = $RetiringApp.Replace('Retired-', '') + try { + Set-CMApplication -Name $RetiringAppName -NewName "Retired-$RetiringApp" + } catch { } + Write-Host "Renamed to Retired-$RetiringAppName." - # move the app according to category - if ($RetiringApp.LocalizedCategoryInstanceNames -eq "Mac") { - Move-CMObject -FolderPath "Application\Retired" -InputObject $RetiringApp - Write-Host "Moved to Retired." - } else { - Move-CMObject -FolderPath "Application\Retired" -InputObject $RetiringApp - Write-Host "Moved to Retired." - } + # move the app according to category + if ($RetiringApp.LocalizedCategoryInstanceNames -eq "Mac") { + Move-CMObject -FolderPath "Application\Retired" -InputObject $RetiringApp + Write-Host "Moved to Retired." + } else { + Move-CMObject -FolderPath "Application\Retired" -InputObject $RetiringApp + Write-Host "Moved to Retired." + } } - + # retire the app - if (!$RetiringApp.IsExpired) { - $RetiringAppWMI = gwmi -Namespace Root\SMS\Site_$PSD -class SMS_ApplicationLatest -Filter "LocalizedDisplayName = 'Retired-$RetiringApp'" - $RetiringAppWMI.SetIsExpired($true) | Out-Null + if (!$RetiringApp.IsExpired) + { + Suspend-CMApplication -Name "$RetiringAppName" Write-Host "Set status to Retired." - } else { + } + else + { Write-Host "Status was already set to Retired." } From 17edc9643d300c01d4ea6d4a702c7fc8edf5e729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mieszko=20=C5=9Alusarczyk?= Date: Wed, 29 May 2019 10:48:53 -0500 Subject: [PATCH 58/65] Cleanup --- SCCM/Retire-CMApplication.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/SCCM/Retire-CMApplication.ps1 b/SCCM/Retire-CMApplication.ps1 index ed26a9f..b1ec9f5 100644 --- a/SCCM/Retire-CMApplication.ps1 +++ b/SCCM/Retire-CMApplication.ps1 @@ -43,7 +43,6 @@ function Retire-CMApplication { $DPs = Get-CMDistributionPoint -AllSite foreach ($DP in $DPs) { - #$dpNetworkOSPath = $dp.NetworkOSPath #TODO: unify 2 variables $dpName = ($dp.NetworkOSPath).Substring(2) From c257a34219b32ec11f573afecef0b7475dbb570a Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 28 May 2020 08:52:06 -0400 Subject: [PATCH 59/65] Create README.md --- Zoom/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 Zoom/README.md diff --git a/Zoom/README.md b/Zoom/README.md new file mode 100644 index 0000000..8294f5d --- /dev/null +++ b/Zoom/README.md @@ -0,0 +1 @@ +I've migrated the Zoom module and scripts to their own repo for better visibility. I've also updated the repo to support their new auth and much more functionality. Check it out over at https://github.com/nickrod518/Zoom-PowerShell From 0706859a4c2856bdc0e01665586ed9254b24d93a Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 28 May 2020 08:52:22 -0400 Subject: [PATCH 60/65] Delete Zoom.psm1 --- Zoom/Zoom.psm1 | 1499 ------------------------------------------------ 1 file changed, 1499 deletions(-) delete mode 100644 Zoom/Zoom.psm1 diff --git a/Zoom/Zoom.psm1 b/Zoom/Zoom.psm1 deleted file mode 100644 index de70378..0000000 --- a/Zoom/Zoom.psm1 +++ /dev/null @@ -1,1499 +0,0 @@ -$ZoomApiKey = '' -$ZoomApiSecret = '' - -function Get-ZoomApiAuth { - <# - .SYNOPSIS - Gets a hashtable for a Zoom Api REST body that includes the api key and secret. - - .EXAMPLE - $RequestBody = Get-ZoomApiAuth - - .OUTPUTS - Hashtable - #> - [CmdletBinding()] - Param() - - try { - if (-not $Global:ZoomApiKey) { - $Global:ZoomApiKey = if ($PSPrivateMetadata.JobId) { - Get-AutomationVariable -Name ZoomApiKey - } else { - Read-Host 'Enter Zoom Api key' - } - } - - if (-not $Global:ZoomApiSecret) { - $Global:ZoomApiSecret = if ($PSPrivateMetadata.JobId) { - Get-AutomationVariable -Name ZoomApiSecret - } else { - Read-Host 'Enter Zoom Api secret' - } - } - - @{ - 'api_key' = $Global:ZoomApiKey - 'api_secret' = $Global:ZoomApiSecret - } - } catch { - Write-Error "Problem getting Zoom Api Authorization variables:`n$_" - } -} - -function Get-ZoomTimeZones { - @{ - 'Pacific/Midway' = '"Midway Island, Samoa"' - 'Pacific/Pago_Pago' = 'Pago Pago' - 'Pacific/Honolulu' = 'Hawaii' - 'America/Anchorage' = 'Alaska' - 'America/Vancouver' = 'Vancouver' - 'America/Los_Angeles' = 'Pacific Time (US and Canada)' - 'America/Tijuana' = 'Tijuana' - 'America/Edmonton' = 'Edmonton' - 'America/Denver' = 'Mountain Time (US and Canada)' - 'America/Phoenix' = 'Arizona' - 'America/Mazatlan' = 'Mazatlan' - 'America/Winnipeg' = 'Winnipeg' - 'America/Regina' = 'Saskatchewan' - 'America/Chicago' = 'Central Time (US and Canada)' - 'America/Mexico_City' = 'Mexico City' - 'America/Guatemala' = 'Guatemala' - 'America/El_Salvador' = 'El Salvador' - 'America/Managua' = 'Managua' - 'America/Costa_Rica' = 'Costa Rica' - 'America/Montreal' = 'Montreal' - 'America/New_York' = 'Eastern Time (US and Canada)' - 'America/Indianapolis' = 'Indiana (East)' - 'America/Panama' = 'Panama' - 'America/Bogota' = 'Bogota' - 'America/Lima' = 'Lima' - 'America/Halifax' = 'Halifax' - 'America/Puerto_Rico' = 'Puerto Rico' - 'America/Caracas' = 'Caracas' - 'America/Santiago' = 'Santiago' - 'America/St_Johns' = 'Newfoundland and Labrador' - 'America/Montevideo' = 'Montevideo' - 'America/Araguaina' = 'Brasilia' - 'America/Argentina/Buenos_Aires' = '"Buenos Aires, Georgetown"' - 'America/Godthab' = 'Greenland' - 'America/Sao_Paulo' = 'Sao Paulo' - 'Atlantic/Azores' = 'Azores' - 'Canada/Atlantic' = 'Atlantic Time (Canada)' - 'Atlantic/Cape_Verde' = 'Cape Verde Islands' - 'UTC' = 'Universal Time UTC' - 'Etc/Greenwich' = 'Greenwich Mean Time' - 'Europe/Belgrade' = '"Belgrade, Bratislava, Ljubljana"' - 'CET' = '"Sarajevo, Skopje, Zagreb"' - 'Atlantic/Reykjavik' = 'Reykjavik' - 'Europe/Dublin' = 'Dublin' - 'Europe/London' = 'London' - 'Europe/Lisbon' = 'Lisbon' - 'Africa/Casablanca' = 'Casablanca' - 'Africa/Nouakchott' = 'Nouakchott' - 'Europe/Oslo' = 'Oslo' - 'Europe/Copenhagen' = 'Copenhagen' - 'Europe/Brussels' = 'Brussels' - 'Europe/Berlin' = '"Amsterdam, Berlin, Rome, Stockholm, Vienna"' - 'Europe/Helsinki' = 'Helsinki' - 'Europe/Amsterdam' = 'Amsterdam' - 'Europe/Rome' = 'Rome' - 'Europe/Stockholm' = 'Stockholm' - 'Europe/Vienna' = 'Vienna' - 'Europe/Luxembourg' = 'Luxembourg' - 'Europe/Paris' = 'Paris' - 'Europe/Zurich' = 'Zurich' - 'Europe/Madrid' = 'Madrid' - 'Africa/Bangui' = 'West Central Africa' - 'Africa/Algiers' = 'Algiers' - 'Africa/Tunis' = 'Tunis' - 'Africa/Harare' = '"Harare, Pretoria"' - 'Africa/Nairobi' = 'Nairobi' - 'Europe/Warsaw' = 'Warsaw' - 'Europe/Prague' = 'Prague Bratislava' - 'Europe/Budapest' = 'Budapest' - 'Europe/Sofia' = 'Sofia' - 'Europe/Istanbul' = 'Istanbul' - 'Europe/Athens' = 'Athens' - 'Europe/Bucharest' = 'Bucharest' - 'Asia/Nicosia' = 'Nicosia' - 'Asia/Beirut' = 'Beirut' - 'Asia/Damascus' = 'Damascus' - 'Asia/Jerusalem' = 'Jerusalem' - 'Asia/Amman' = 'Amman' - 'Africa/Tripoli' = 'Tripoli' - 'Africa/Cairo' = 'Cairo' - 'Africa/Johannesburg' = 'Johannesburg' - 'Europe/Moscow' = 'Moscow' - 'Asia/Baghdad' = 'Baghdad' - 'Asia/Kuwait' = 'Kuwait' - 'Asia/Riyadh' = 'Riyadh' - 'Asia/Bahrain' = 'Bahrain' - 'Asia/Qatar' = 'Qatar' - 'Asia/Aden' = 'Aden' - 'Asia/Tehran' = 'Tehran' - 'Africa/Khartoum' = 'Khartoum' - 'Africa/Djibouti' = 'Djibouti' - 'Africa/Mogadishu' = 'Mogadishu' - 'Asia/Dubai' = 'Dubai' - 'Asia/Muscat' = 'Muscat' - 'Asia/Baku' = '"Baku, Tbilisi, Yerevan"' - 'Asia/Kabul' = 'Kabul' - 'Asia/Yekaterinburg' = 'Yekaterinburg' - 'Asia/Tashkent' = '"Islamabad, Karachi, Tashkent"' - 'Asia/Calcutta' = 'India' - 'Asia/Kathmandu' = 'Kathmandu' - 'Asia/Novosibirsk' = 'Novosibirsk' - 'Asia/Almaty' = 'Almaty' - 'Asia/Dacca' = 'Dacca' - 'Asia/Krasnoyarsk' = 'Krasnoyarsk' - 'Asia/Dhaka' = '"Astana, Dhaka"' - 'Asia/Bangkok' = 'Bangkok' - 'Asia/Saigon' = 'Vietnam' - 'Asia/Jakarta' = 'Jakarta' - 'Asia/Irkutsk' = '"Irkutsk, Ulaanbaatar"' - 'Asia/Shanghai' = '"Beijing, Shanghai"' - 'Asia/Hong_Kong' = 'Hong Kong' - 'Asia/Taipei' = 'Taipei' - 'Asia/Kuala_Lumpur' = 'Kuala Lumpur' - 'Asia/Singapore' = 'Singapore' - 'Australia/Perth' = 'Perth' - 'Asia/Yakutsk' = 'Yakutsk' - 'Asia/Seoul' = 'Seoul' - 'Asia/Tokyo' = '"Osaka, Sapporo, Tokyo"' - 'Australia/Darwin' = 'Darwin' - 'Australia/Adelaide' = 'Adelaide' - 'Asia/Vladivostok' = 'Vladivostok' - 'Pacific/Port_Moresby' = '"Guam, Port Moresby"' - 'Australia/Brisbane' = 'Brisbane' - 'Australia/Sydney' = '"Canberra, Melbourne, Sydney"' - 'Australia/Hobart' = 'Hobart' - 'Asia/Magadan' = 'Magadan' - 'SST' = 'Solomon Islands' - 'Pacific/Noumea' = 'New Caledonia' - 'Asia/Kamchatka' = 'Kamchatka' - 'Pacific/Fiji' = '"Fiji Islands, Marshall Islands"' - 'Pacific/Auckland' = '"Auckland, Wellington"' - 'Asia/Kolkata' = '"Mumbai, Kolkata, New Delhi"' - 'Europe/Kiev' = 'Kiev' - 'America/Tegucigalpa' = 'Tegucigalpa' - 'Pacific/Apia' = 'Independent State of Samoa' - } -} - -function Read-ZoomResponse { - <# - .SYNOPSIS - Parses Zoom REST response so errors are returned properly - - .PARAMETER Response - The JSON response from the Api call. - - .PARAMETER RequestBody - The hashtable that was sent through the Api call. - - .PARAMETER Endpoint - Api endpoint Url that was called. - - .PARAMETER RetryOnRequestLimitReached - If the Api request limit is reached, retry once after 1 second. - - .EXAMPLE - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint -Endpoint $Endpoint - #> - [CmdletBinding()] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true - )] - [PSCustomObject]$Response, - - [Parameter(Mandatory = $true)] - [hashtable]$RequestBody, - - [Parameter(Mandatory = $true)] - [string]$Endpoint, - - [Parameter(Mandatory = $false)] - [bool]$RetryOnRequestLimitReached = $true - ) - - $ApiCallInfo = "Api Endpoint: $Endpoint`n" - $ApiCallInfo += "Api call body:$($RequestBody | Out-String)" - - if ($Response.PSObject.Properties.Name -match 'error') { - Write-Error -Message "$($Response.error.message)`n$ApiCallInfo" -ErrorId $Response.error.code -Category InvalidOperation - - if ($RetryOnRequestLimitReached -and $Response.error.code -eq 403) { - Write-Warning "Retrying in one second..." - Start-Sleep -Seconds 1 - - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } - } else { - Write-Verbose "$($Response.error.message)`nApi call body:$($RequestBody | Out-String)" - $Response - } -} - -function Get-ZoomUser { - <# - .SYNOPSIS - Gets Zoom users by Id, Email, or All. - - .PARAMETER Id - Gets Zoom user by their Zoom Id. Will accept an array of Id's. - - .PARAMETER Email - Gets all Zoom users and then filters by email. Will accept an array of emails. - - .PARAMETER LoginType - Optional, default is Sso. Login type of the email. - - .PARAMETER All - Default. Return all Zoom users. - - .EXAMPLE - Get-ZoomUser - Returns all zoom users. - - .EXAMPLE - Get-ZoomUser -Email user@company.com - Searches for and returns specified user if found. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(DefaultParameterSetName = 'All')] - Param( - [Parameter( - Mandatory = $true, - ParameterSetName = 'Id' - )] - [ValidateNotNullOrEmpty()] - [string[]]$Id, - - [Parameter( - Mandatory = $true, - ParameterSetName = 'Email' - )] - [ValidateNotNullOrEmpty()] - [string[]]$Email, - - [Parameter( - Mandatory = $false, - ParameterSetName = 'Email' - )] - [ValidateSet('Facebook', 'Google', 'Api', 'Zoom', 'Sso')] - [string]$LoginType = 'Sso', - - [Parameter( - Mandatory = $false, - ParameterSetName = 'All' - )] - [switch]$All - ) - - if ($PSCmdlet.ParameterSetName -eq 'All') { - $Endpoint = 'https://api.zoom.us/v1/user/list' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('page_size', 300) - - $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - - Write-Verbose "There are $($Result.page_count) pages of users" - for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('page_size', 300) - $RequestBody.Add('page_number', $Page) - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | - Select-Object -ExpandProperty users - - Start-Sleep -Milliseconds 500 - } - } elseif ($PSCmdlet.ParameterSetName -eq 'Email') { - $Endpoint = 'https://api.zoom.us/v1/user/getbyemail' - - $Type = switch ($LoginType) { - 'Facebook' { '0' } - 'Google' { '1' } - 'Api' { '99' } - 'Zoom' { '100' } - 'Sso' { '101' } - } - - foreach ($User in $Email) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('email', $User) - $RequestBody.Add('login_type', $Type) - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } - } elseif ($PSCmdlet.ParameterSetName -eq 'Id') { - $Endpoint = 'https://api.zoom.us/v1/user/get' - - foreach ($User in $Id) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $User) - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } - } -} - -function Get-ZoomPendingUser { - <# - .SYNOPSIS - List all the pending users on Zoom. - - .EXAMPLE - Get-ZoomPendingUser - Returns all pending Zoom users. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(DefaultParameterSetName = 'All')] - Param() - - $Endpoint = 'https://api.zoom.us/v1/user/pending' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('page_size', 300) - $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - - Write-Verbose "There are $($Result.page_count) pages of pending users" - for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('page_size', 300) - $RequestBody.Add('page_number', $Page) - $Users += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | - Select-Object -ExpandProperty users - - Start-Sleep -Milliseconds 500 - } - - $Users -} - -function Remove-ZoomUser { - <# - .SYNOPSIS - Remove Zoom user by Id. - - .PARAMETER Id - Zoom user Id to remove. - - .PARAMETER Permanently - Default is no. Switch that specified whether to delete user permanently. - - .EXAMPLE - Get-ZoomUser -Email user@company.com -Permanently | Remove-ZoomUser - Permanently remove user@company.com. - - .EXAMPLE - Remove-ZoomUser -Id 123asdfjkl - Removes Zoom user with Id 123asdfjkl. - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string]$Id, - - [switch]$Permanently - ) - - $Endpoint = if ($Permanently) { - Write-Verbose 'Permanent delete selected.' - 'https://api.zoom.us/v1/user/permanentdelete' - } else { - 'https://api.zoom.us/v1/user/delete' - } - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $Id) - - if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom user')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function Remove-ZoomGroup { - <# - .SYNOPSIS - Remove Zoom group by Id. - - .PARAMETER Id - Zoom group Id to remove. - - .EXAMPLE - Get-ZoomGroup -Name TestGroup | Remove-ZoomGroup - Remove group TestGroup. - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string]$Id - ) - - $Endpoint = 'https://api.zoom.us/v1/group/delete' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $Id) - - if ($pscmdlet.ShouldProcess($Id, 'Remove Zoom group')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function Test-ZoomUserEmail { - <# - .SYNOPSIS - Test if given email has an existing account. - - .PARAMETER Email - Zoom user email to test. - - .EXAMPLE - Test-ZoomUserEmail -Email user@company.com - Checks to see if account exists for user@company.com. - - .NOTES - This will return false if the user has an SSO account but not an Email account. - #> - [CmdletBinding()] - Param( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$Email - ) - - $Endpoint = 'https://api.zoom.us/v1/user/checkemail' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('email', $Email) - - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | - Select-Object -ExpandProperty existed_email -} - -function Disable-ZoomUser { - <# - .SYNOPSIS - Deactivate Zoom user with given Id. - - .PARAMETER Id - Zoom user id to deactivate. - - .EXAMPLE - Get-ZoomUser -Id user@company.com | Disable-ZoomUserEmail - Deactivates Zoom user account with email user@company.com. - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string]$Id - ) - - $RequestBody = Get-ZoomApiAuth - - $Endpoint = 'https://api.zoom.us/v1/user/deactivate' - - $RequestBody.Add('id', $Id) - - if ($pscmdlet.ShouldProcess($Id, 'Deactivate Zoom user')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function Get-ZoomGroup { - <# - .SYNOPSIS - Gets Zoom groups by Id, Name, or All. - - .PARAMETER Id - Gets Zoom group by their Zoom Id. - - .PARAMETER Name - Gets all Zoom groups and then filters by name. - - .PARAMETER All - Default. Return all Zoom groups. - - .EXAMPLE - Get-ZoomGroup - Returns all zoom groups. - - .EXAMPLE - Get-ZoomGroup -Name TestGroup - Searches for and returns specified group if found. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(DefaultParameterSetName = 'All')] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'Id' - )] - [ValidateNotNullOrEmpty()] - [string]$Id, - - [Parameter(ParameterSetName = 'Name')] - [ValidateNotNullOrEmpty()] - [string]$Name, - - [Parameter(ParameterSetName = 'All')] - [switch]$All - ) - - $RequestBody = Get-ZoomApiAuth - - $Endpoint = if ($PSCmdlet.ParameterSetName -ne 'Id') { - 'https://api.zoom.us/v1/group/list' - } else { - 'https://api.zoom.us/v1/group/get' - } - - if ($PSCmdlet.ParameterSetName -ne 'Id') { - $Groups = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | - Select-Object -ExpandProperty groups - - if ($PSCmdlet.ParameterSetName -eq 'Name') { - $Groups | Where-Object -Property name -eq $Name - } else { - $Groups - } - } else { - $RequestBody.Add('id', $Id) - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function Get-ZoomMeeting { - <# - .SYNOPSIS - List all the scheduled meetings on Zoom for the user Id. - - .PARAMETER Id - Gets Zoom group by their Zoom Id. - - .EXAMPLE - Get-ZoomGroup -Name TestGroup - Searches for and returns specified group if found. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding()] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string[]]$Id - ) - - $RequestBody = Get-ZoomApiAuth - - $Endpoint = 'https://api.zoom.us/v1/meeting/list' - - foreach ($User in $Id) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('page_size', 300) - $RequestBody.Add('host_id', $User) - $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - - Write-Verbose "There are $($Result.page_count) pages of meetings" - for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('host_id', $User) - $RequestBody.Add('page_size', 300) - $RequestBody.Add('page_number', $Page) - $Meetings += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | - Select-Object -ExpandProperty meetings - - Start-Sleep -Milliseconds 500 - } - - $Meetings - } -} - -function Get-ZoomUserScheduler { - <# - .SYNOPSIS - List assigned schedule privilege for host users. - - .PARAMETER Id - The host's user id. - - .PARAMETER Email - The host's email address. - - .EXAMPLE - Get-ZoomUser -Email user@company.com | Get-ZoomUserScheduler - Returns all zoom groups. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(DefaultParameterSetName = 'All')] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'Id' - )] - [ValidateNotNullOrEmpty()] - [string]$Id, - - [Parameter(ParameterSetName = 'Email')] - [ValidateNotNullOrEmpty()] - [string]$Email - ) - - $RequestBody = Get-ZoomApiAuth - - $Endpoint = 'https://api.zoom.us/v1/user/scheduleforhost/list' - - if ($PSCmdlet.ParameterSetName -eq 'Id') { - $RequestBody.Add('id', $Id) - } else { - $RequestBody.Add('host_email', $Email) - } - - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint -} - -function Set-ZoomUserAssistant { - <# - .SYNOPSIS - Set a user's assistant which can schedule meeting for them. - - .PARAMETER Id - The host's user id. - - .PARAMETER Email - The host's email address. - - .PARAMETER AssistantEmail - The assistant's email address. - - .EXAMPLE - Get-ZoomUser -Email user@company.com | Get-ZoomUserAssistant -AssistantEmail assistant@company.com - Sets assistant@company.com as assistant for user@company.com - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding( - SupportsShouldProcess = $True, - DefaultParameterSetName = 'Id' - )] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'Id' - )] - [ValidateNotNullOrEmpty()] - [string]$Id, - - [Parameter(ParameterSetName = 'Email')] - [ValidateNotNullOrEmpty()] - [string]$Email, - - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$AssistantEmail - ) - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('assistant_email', $AssistantEmail) - - $Endpoint = 'https://api.zoom.us/v1/user/assistant/set' - - if ($PSCmdlet.ParameterSetName -ne 'Id') { - $RequestBody.Add('id', $Id) - $Target = $Id - } else { - $RequestBody.Add('host_email', $Email) - $Target = $Email - } - - if ($pscmdlet.ShouldProcess($Target, 'Set Zoom user assistant')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function Remove-ZoomUserAssistant { - <# - .SYNOPSIS - Remove assistants for given user. - - .PARAMETER Id - The host's user id. - - .PARAMETER Email - The host's email address. - - .EXAMPLE - Get-ZoomUser -Email user@company.com | Remove-ZoomUserAssistant - Removes assistants of user@company.com. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding( - SupportsShouldProcess = $True, - DefaultParameterSetName = 'Id' - )] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'Id' - )] - [ValidateNotNullOrEmpty()] - [string]$Id, - - [Parameter(ParameterSetName = 'Email')] - [ValidateNotNullOrEmpty()] - [string]$Email - ) - - $RequestBody = Get-ZoomApiAuth - - $Endpoint = 'https://api.zoom.us/v1/user/assistant/delete' - - if ($PSCmdlet.ParameterSetName -ne 'Id') { - $Assistant = $Id - $RequestBody.Add('id', $Id) - } else { - $Assistant = $Email - $RequestBody.Add('host_email', $Email) - } - - if ($pscmdlet.ShouldProcess($Assistant, 'Remove Zoom user assistant')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function New-ZoomGroup { - <# - .SYNOPSIS - Create a group on Zoom, return the new group info. - - .PARAMETER Name - Group name, must be unique in one account. - - .EXAMPLE - New-ZoomGroup -Name TestGroup - Create new group named TestGroup. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding()] - Param( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$Name - ) - - $Endpoint = 'https://api.zoom.us/v1/group/create' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('name', $Name) - - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint -} - -function Add-ZoomGroupMember { - <# - .SYNOPSIS - Adds members to a group on Zoom. - - .PARAMETER GroupId - Group ID. - - .PARAMETER Id - The member IDs, pipeline and arrays are accepted - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$GroupId, - - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string[]]$Id - ) - - $Endpoint = 'https://api.zoom.us/v1/group/member/add' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $GroupId) - $RequestBody.Add('member_ids', $Id -join ',') - - if ($pscmdlet.ShouldProcess($Id -join ',', "Add Zoom user(s) to $GroupId")) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function Remove-ZoomGroupMember { - <# - .SYNOPSIS - Remove members to a group on Zoom. - - .PARAMETER GroupId - Group ID. - - .PARAMETER Id - The member IDs, pipeline and arrays are accepted - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$GroupId, - - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string[]]$Id - ) - - $Endpoint = 'https://api.zoom.us/v1/group/member/delete' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $GroupId) - $RequestBody.Add('member_ids', $Id -join ',') - - if ($pscmdlet.ShouldProcess($Id -join ',', "Remove Zoom user(s) from $GroupId")) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function Get-ZoomGroupMember { - <# - .SYNOPSIS - Lists the members of a group on Zoom. - - .PARAMETER Id - Group ID. - - .EXAMPLE - Get-ZoomGroup -Name TestGroup | Get-ZoomGroupMember - Gets members of TestGroup. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding()] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string]$Id - ) - - $Endpoint = 'https://api.zoom.us/v1/group/member/list' - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $Id) - $RequestBody.Add('page_size', 300) - $Result = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - - Write-Verbose "There are $($Result.page_count) pages of users" - for ($Page = 1; $Page -le $Result.page_count; $Page++) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $Id) - $RequestBody.Add('page_size', 300) - $RequestBody.Add('page_number', $Page) - $Users += Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint | - Select-Object -ExpandProperty members - - Start-Sleep -Milliseconds 500 - } - - $Users -} - -function Set-ZoomUser { - <# - .SYNOPSIS - Update user info on Zoom via user ID. - - .PARAMETER Id - Zoom user to update. - - .PARAMETER FirstName - User's first name. - - .PARAMETER LastName - User's last name. - - .PARAMETER License - License type. Basic, Pro, or Corp. - - .PARAMETER Pmi - Personal Meeting ID, long, length must be 10. - - .PARAMETER EnablePmi - Specify whether to use Personal Meeting Id for instant meetings. True or False. - - .PARAMETER VanityName - Personal meeting room name. - - .PARAMETER GroupId - User Group ID. If set default user group, the parameter’s default value is the default user group. - - .PARAMETER EnterExitChime - Enable enter/exit chime. - - .PARAMETER EnterExitChimeType - Enter/exit chime type. All (0) means heard by all including host and attendees, HostOnly (1) means heard by host only. - - .PARAMETER DisableFeedback - Disable feedback. - - .PARAMETER TimeZone - The time zone id for user profile. For a list of id's refer to https://zoom.github.io/api/#timezones. - - .PARAMETER Department - Department for user profile, use for reporting. - - .EXAMPLE - Get-ZoomUser -Id user@company.com | Set-ZoomUser -License Corp - Sets Zoom license to Corp on user@company.com's account. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string[]]$Id, - - [Parameter(Mandatory = $false)] - [string]$FirstName, - - [Parameter(Mandatory = $false)] - [string]$LastName, - - [Parameter(Mandatory = $false)] - [ValidateSet('Basic', 'Pro', 'Corp')] - [string]$License, - - [Parameter(Mandatory = $false)] - [ValidateRange(1000000000, 9999999999)] - [long]$Pmi, - - [Parameter(Mandatory = $false)] - [bool]$EnablePmi, - - [Parameter(Mandatory = $false)] - [string]$VanityName, - - [Parameter(Mandatory = $false)] - [string]$GroupId, - - [Parameter(Mandatory = $false)] - [bool]$EnterExitChime, - - [Parameter(Mandatory = $false)] - [ValidateSet('All', 'HostOnly')] - [string]$EnterExitChimeType, - - [Parameter(Mandatory = $false)] - [bool]$DisableFeedback, - - [Parameter(Mandatory = $false)] - [string]$Department - ) -<# - DynamicParam { - # Set the dynamic parameters' name - $ParameterName = 'TimeZone' - - # Create the dictionary - $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary - - # Create the collection of attributes - $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] - - # Create and set the parameters' attributes - $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute - $ParameterAttribute.Mandatory = $false - - # Add the attributes to the attributes collection - $AttributeCollection.Add($ParameterAttribute) - - # Generate and set the ValidateSet - $ValidatedParams = @() - (Get-ZoomTimeZones).GetEnumerator() | ForEach-Object { - $ValidatedParams += $_.Key - $ValidatedParams += $_.Value - } - $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($ValidatedParams) - - # Add the ValidateSet to the attributes collection - $AttributeCollection.Add($ValidateSetAttribute) - - # Create and return the dynamic parameter - $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) - $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) - return $RuntimeParameterDictionary - } - - begin { - $TimeZone = $PSBoundParameters.TimeZone - } -#> - process { - $TimeZone = $PSBoundParameters.TimeZone - $Endpoint = 'https://api.zoom.us/v1/user/update' - - foreach ($User in $Id) { - if ($pscmdlet.ShouldProcess($User, 'Update Zoom user info')) { - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('id', $User) - if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } - if ($PSBoundParameters.ContainsKey('License')) { - $LicenseType = switch ($License) { - 'Basic' { 1 } - 'Pro' { 2 } - 'Corp' { 3 } - } - - $RequestBody.Add('type', $LicenseType) - } - if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } - if ($PSBoundParameters.ContainsKey('EnablePmi')) { $RequestBody.Add('enable_use_pmi', $EnablePmi) } - if ($PSBoundParameters.ContainsKey('VanityName')) { $RequestBody.Add('vanity_name', $VanityName) } - if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } - if ($PSBoundParameters.ContainsKey('EnterExitChime')) { - $RequestBody.Add('enable_enter_exit_chime', $EnterExitChime) - } - if ($PSBoundParameters.ContainsKey('EnterExitChimeType')) { - $ChimeType = switch ($EnterExitChimeType) { - 'All' { 0 } - 'HostOnly' { 1 } - } - $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) - } - if ($PSBoundParameters.ContainsKey('DisableFeedback')) { - $RequestBody.Add('disable_feedback', $DisableFeedback) - } - <#if ($PSBoundParameters.ContainsKey('TimeZone')) { - $TimeZones = Get-ZoomTimeZones - if ($TimeZones.Contains($TimeZone)) { $TimeZone = $TimeZones.$TimeZone } - $RequestBody.Add('timezone', $TimeZone) - }#> - if ($PSBoundParameters.ContainsKey('Department')) { $RequestBody.Add('dept', $Department) } - - if ($pscmdlet.ShouldProcess($User, 'Set Zoom user settings')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } - } - } - } -} - -function Set-ZoomUserPicture { - <# - .SYNOPSIS - Upload a new profile picture for the specified Zoom user. - - .PARAMETER Id - Zoom user to upload a new profile picture for. - - .PARAMETER Path - Path to profile picture to upload. - - .PARAMETER ByteArray - Byte array representing the picture. - - .EXAMPLE - Get-ZoomUser -Id user@company.com | Set-ZoomUserPicture -Path .\picture.jpg - Uploads new profile picture to user@company.com's account. - - .EXAMPLE - $ThumbnailByteArray = Get-ADUser UserId -Properties thumbnailPhoto | Select-Object -ExpandProperty thumbnailPhoto - Get-ZoomUser -Id user@company.com | Set-ZoomUserPicture -ByteArray $ThumbnailByteArray - Uploads new profile picture to user@company.com's account from their AD thumbnail photo. - - .OUTPUTS - PSCustomObject - - .NOTES - This function uses C# to form the rest call because Invoke-RestMethod was incompatible. - #> - [CmdletBinding( - SupportsShouldProcess = $True, - DefaultParameterSetName = 'Path' - )] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string]$Id, - - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'Path' - )] - [ValidateScript({ Test-Path $_ -PathType Leaf })] - [ValidatePattern('.jp*.g$')] - [string]$Path, - - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'ByteArray' - )] - [ValidateNotNullOrEmpty()] - [byte[]]$ByteArray, - - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true, - ParameterSetName = 'Binary' - )] - [ValidateNotNullOrEmpty()] - $Binary - ) - - $Endpoint = 'https://api.zoom.us/v1/user/uploadpicture' - - if ($PSCmdlet.ParameterSetName -eq 'Path') { - $FileName = $Path.Split('\')[-1] - $ByteArray = Get-Content -Path $Path -Encoding Byte - } else { - $FileName = 'ProfilePicture.jpg' - } - - if (-not $Binary) { - $encoding = [System.Text.Encoding]::GetEncoding('iso-8859-1') - $encodedFile = $encoding.GetString($ByteArray) - } else { - $encodedFile = $Binary - } - - $newLine = "`r`n" - $boundary = [guid]::NewGuid() - - $RequestBody = ( - "--$boundary", - "Content-Type: text/plain; charset=utf-8", - "Content-Disposition: form-data; name=api_key$newLine", - (Get-ZoomApiAuth).api_key, - - "--$boundary", - "Content-Type: text/plain; charset=utf-8", - "Content-Disposition: form-data; name=api_secret$newLine", - (Get-ZoomApiAuth).api_secret, - - "--$boundary", - "Content-Type: text/plain; charset=utf-8", - "Content-Disposition: form-data; name=id$newLine", - $Id, - - "--$boundary", - "Content-Type: application/octet-stream", - "Content-Disposition: form-data; name=pic_file; filename=$FileName; filename*=utf-8''$FileName$newLine", - $encodedFile, - - "--$boundary--$newLine" - ) -join $newLine - - if ($pscmdlet.ShouldProcess($Id, 'Update Zoom user picture')) { - $response = Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" - - $ApiCallInfo = "Api Endpoint: $Endpoint`n" - $ApiCallInfo += "Api call body:$($RequestBody | Out-String)" - - if ($response.PSObject.Properties.Name -match 'error') { - Write-Error -Message "$($response.error.message)`n$ApiCallInfo" -ErrorId $response.error.code -Category InvalidOperation - } else { - Write-Verbose "$($response.error.message)`nApi call body:$($RequestBody | Out-String)" - $response - } - } -} - -function New-ZoomSSOUser { - <# - .SYNOPSIS - Pre-provision Zoom SSO user account. - - .PARAMETER Email - New Zoom user email address. - - .PARAMETER License - License to grant new Zoom user. Basic, Pro, or Corp. - - .PARAMETER FirstName - User's first name. - - .PARAMETER LastName - User's last name. - - .PARAMETER Pmi - Personal Meeting ID, long, length must be 10. - - .PARAMETER GroupId - User Group ID. If set default user group, the parameter’s default value is the default user group. - - .EXAMPLE - New-ZoomSSOUser -Email user@company.com -License Pro - Pre-provisions a Zoom user account for email user@company.com with a Pro license. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string]$Email, - - [Parameter(Mandatory = $true)] - [ValidateSet('Basic', 'Pro', 'Corp')] - [string]$License, - - [Parameter(Mandatory = $false)] - [string]$FirstName, - - [Parameter(Mandatory = $false)] - [string]$LastName, - - [Parameter(Mandatory = $false)] - [ValidateRange(1000000000, 9999999999)] - [long]$Pmi, - - [Parameter(Mandatory = $false)] - [string]$GroupId - ) - - $Endpoint = 'https://api.zoom.us/v1/user/ssocreate' - - $LicenseType = switch ($License) { - 'Basic' { 1 } - 'Pro' { 2 } - 'Corp' { 3 } - } - - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('email', $Email) - if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } - $RequestBody.Add('type', $LicenseType) - if ($PSBoundParameters.ContainsKey('Pmi')) { $RequestBody.Add('pmi', $Pmi) } - if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } - - if ($pscmdlet.ShouldProcess($Email, 'New Zoom SSO user')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } -} - -function New-ZoomUser { - <# - .SYNOPSIS - Create new Zoom user account. - - .PARAMETER Email - New Zoom user email address. - - .PARAMETER FirstName - User's first name. - - .PARAMETER LastName - User's last name. - - .PARAMETER License - License type. Basic, Pro, or Corp. - - .PARAMETER GroupId - User Group ID. If set default user group, the parameter’s default value is the default user group. - - .PARAMETER EnterExitChime - Enable enter/exit chime. - - .PARAMETER EnterExitChimeType - Enter/exit chime type. All (0) means heard by all including host and attendees, HostOnly (1) means heard by host only. - - .PARAMETER DisableFeedback - Disable feedback. - - .PARAMETER Department - Department for user profile, use for reporting. - - .EXAMPLE - New-ZoomUser -Email user@company.com -License Pro - Creates a Zoom user account for email user@company.com with a Pro license. - - .OUTPUTS - PSCustomObject - #> - [CmdletBinding(SupportsShouldProcess = $True)] - Param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [ValidateNotNullOrEmpty()] - [string[]]$Email, - - [Parameter(Mandatory = $false)] - [string]$FirstName, - - [Parameter(Mandatory = $false)] - [string]$LastName, - - [Parameter(Mandatory = $true)] - [ValidateSet('Basic', 'Pro', 'Corp')] - [string]$License, - - [Parameter(Mandatory = $false)] - [string]$GroupId, - - [Parameter(Mandatory = $false)] - [bool]$EnterExitChime, - - [Parameter(Mandatory = $false)] - [ValidateSet('All', 'HostOnly')] - [string]$EnterExitChimeType, - - [Parameter(Mandatory = $false)] - [bool]$DisableFeedback, - - [Parameter(Mandatory = $false)] - [string]$Department - ) - - $Endpoint = 'https://api.zoom.us/v1/user/create' - - $LicenseType = switch ($License) { - 'Basic' { 1 } - 'Pro' { 2 } - 'Corp' { 3 } - } - - $ChimeType = switch ($EnterExitChimeType) { - 'All' { 0 } - 'HostOnly' { 1 } - } - - foreach ($User in $Email) { - $RequestBody = Get-ZoomApiAuth - $RequestBody.Add('email', $User) - $RequestBody.Add('type', $LicenseType) - if ($PSBoundParameters.ContainsKey('FirstName')) { $RequestBody.Add('first_name', $FirstName) } - if ($PSBoundParameters.ContainsKey('LastName')) { $RequestBody.Add('last_name', $LastName) } - if ($PSBoundParameters.ContainsKey('GroupId')) { $RequestBody.Add('group_id', $GroupId) } - if ($PSBoundParameters.ContainsKey('EnterExitChime')) { - $RequestBody.Add('enable_enter_exit_chime', $EnterExitChime) - } - if ($PSBoundParameters.ContainsKey('EnterExitChimeType')) { - $RequestBody.Add('option_enter_exit_chime_type', $ChimeType) - } - if ($PSBoundParameters.ContainsKey('DisableFeedback')) { $RequestBody.Add('disable_feedback', $DisableFeedback) } - if ($PSBoundParameters.ContainsKey('Department')) { $RequestBody.Add('dept', $Department) } - - if ($pscmdlet.ShouldProcess($User, 'New Zoom user')) { - Invoke-RestMethod -Uri $Endpoint -Body $RequestBody -Method Post | - Read-ZoomResponse -RequestBody $RequestBody -Endpoint $Endpoint - } - } -} - -Export-ModuleMember -Function * \ No newline at end of file From 76dfb8b8627cfcd75ffaaa62f3d8fc542df8b7d5 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 28 May 2020 08:54:21 -0400 Subject: [PATCH 61/65] Migrate to https://github.com/nickrod518/Zoom-PowerShell --- Zoom/Invoke-ZoomADSync.ps1 | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 Zoom/Invoke-ZoomADSync.ps1 diff --git a/Zoom/Invoke-ZoomADSync.ps1 b/Zoom/Invoke-ZoomADSync.ps1 deleted file mode 100644 index 775d992..0000000 --- a/Zoom/Invoke-ZoomADSync.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -[CmdletBinding()] -Param() - -$LogDirectory = (New-Item -ItemType Directory '.\Logs' -Force).FullName -$Date = (Get-Date).ToString('yyyyMMdd-HHmm') -$LogPath = "$LogDirectory\$($MyInvocation.MyCommand.Name)-$Date.log" -Start-Transcript -Path $LogPath - -$Params = @{ - 'ADSearchBase' = 'OU=Users,DC=Company,DC=LOCAL' - 'ADNotLikeFilter' = '*OU=Disabled*' - 'DefaultZoomGroup' = 'General' - 'EOCredential' = Get-Credential -} -.\Update-ZoomADUser.ps1 -UpdatePictureFromEO @Params -.\Set-ZoomUserIntern.ps1 -.\Set-ZoomUserInternational.ps1 - -Stop-Transcript \ No newline at end of file From 8ff4e9d8be22feeff7df080d35f66df1105f68af Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 28 May 2020 08:54:27 -0400 Subject: [PATCH 62/65] Migrate to https://github.com/nickrod518/Zoom-PowerShell --- Zoom/Set-ZoomUserIntern.ps1 | 50 ------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 Zoom/Set-ZoomUserIntern.ps1 diff --git a/Zoom/Set-ZoomUserIntern.ps1 b/Zoom/Set-ZoomUserIntern.ps1 deleted file mode 100644 index 1361a35..0000000 --- a/Zoom/Set-ZoomUserIntern.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -<# -.SYNOPSIS -Get interns from AD and set. - -.DESCRIPTION -Get interns from AD and set their license to Basic and move to Intern group in Zoom. -#> -[CmdletBinding(SupportsShouldProcess = $True)] -Param() - -Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force -Import-Module ActiveDirectory - -$Groups = Get-ZoomGroup - -# Get intern group and members -$InternGroupId = ($Groups | Where-Object -Property name -eq 'Interns/Temps').group_id -$InternGroupMembers = Get-ZoomGroupMember -Id $InternGroupId - -# Get other group id's and members -$OtherGroupIds = ($Groups | Where-Object -Property name -ne 'Interns/Temps').group_id -$OtherGroupMembers = @{} -$OtherGroupIds | ForEach-Object { $OtherGroupMembers.Add($_, (Get-ZoomGroupMember -Id $_)) } - -# Get Zoom intern users from AD group and set their Zoom license and group -Get-ADGroupMember -Recursive -Identity 'Global - Interns' | Get-ADUser | - Select-Object -ExpandProperty mail | ForEach-Object { - try { - $ZoomUser = Get-ZoomUser -Email $_ - - # Set license to Basic if it isn't already - if ($ZoomUser.type -ne 1) { Set-ZoomUser -Id $ZoomUser.id -License Basic } - - # Remove user from other groups - $OtherGroupIds | ForEach-Object { - if ($OtherGroupMembers.$_.id -contains $ZoomUser.id) { - Remove-ZoomGroupMember -GroupId $_ -Id $ZoomUser.id - } - } - - # Add user to intern group - if ($InternGroupMembers.id -notcontains $ZoomUser.id) { - Add-ZoomGroupMember -GroupId $InternGroupId -Id $ZoomUser.id - } else { - Write-Verbose "$($ZoomUser.email) is already a member of the Intern group." - } - } catch { - Write-Warning "$_ not found." - } - } \ No newline at end of file From 5b9c5364a2a7eb66beed6166cca0f5cd6c53c003 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 28 May 2020 08:54:33 -0400 Subject: [PATCH 63/65] Migrate to https://github.com/nickrod518/Zoom-PowerShell --- Zoom/Set-ZoomUserInternational.ps1 | 42 ------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 Zoom/Set-ZoomUserInternational.ps1 diff --git a/Zoom/Set-ZoomUserInternational.ps1 b/Zoom/Set-ZoomUserInternational.ps1 deleted file mode 100644 index 6cdf20f..0000000 --- a/Zoom/Set-ZoomUserInternational.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -<# -.SYNOPSIS -Get Zoom international users from AD and set them in Zoom. - -#> -[CmdletBinding(SupportsShouldProcess = $True)] -Param() - -Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Force -Import-Module ActiveDirectory - -# Get Zoom international users from AD group -$ADUsers = Get-ADGroupMember -Identity 'Zoom International Calling Accounts' | - Get-ADUser | Select-Object -ExpandProperty mail - -$GroupInfo = @{} -foreach ($Group in Get-ZoomGroup -All) { - $GroupInfo.Add($Group.group_id, $(Get-ZoomGroupMember -Id $Group.group_id)) -} - -$InternationalGroup = Get-ZoomGroup -Name 'International Calling' | Select-Object -ExpandProperty group_id - -foreach ($User in $ADUsers) { - try { - # Get the associated Zoom user - $ZoomUserId = Get-ZoomUser -Email $User | Select-Object -ExpandProperty id - - # Add user to Int'l group if they aren't a member already - if ($GroupInfo.$InternationalGroup.id -notcontains $ZoomUserId) { - Add-ZoomGroupMember -Id $ZoomUserId -GroupId $InternationalGroup - } - - # Remove user from other groups if they are a member - foreach ($Group in $GroupInfo.Keys -ne $InternationalGroup) { - if ($Group.id -contains $ZoomUserId) { - Remove-ZoomGroupMember -Id $ZoomUserId -GroupId $Group - } - } - } catch { - Write-Warning "$User not found." - } -} \ No newline at end of file From 5f1304c0b0a8aaccf9c875e09124799a0f178e4e Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Thu, 28 May 2020 08:54:39 -0400 Subject: [PATCH 64/65] Migrate to https://github.com/nickrod518/Zoom-PowerShell --- Zoom/Update-ZoomADUser.ps1 | 214 ------------------------------------- 1 file changed, 214 deletions(-) delete mode 100644 Zoom/Update-ZoomADUser.ps1 diff --git a/Zoom/Update-ZoomADUser.ps1 b/Zoom/Update-ZoomADUser.ps1 deleted file mode 100644 index 0281794..0000000 --- a/Zoom/Update-ZoomADUser.ps1 +++ /dev/null @@ -1,214 +0,0 @@ -<# -.SYNOPSIS -Sync Zoom users with AD. - -.DESCRIPTION -Get all enabled users from AD and create a Zoom account if they don't have one. Remove disabled AD users from Zoom. -#> -[CmdletBinding(SupportsShouldProcess = $True)] -Param( - [Parameter(Mandatory = $true)] - [string]$ADSearchBase, - - [Parameter(Mandatory = $true)] - [string]$ADNotLikeFilter, - - [Parameter(Mandatory = $false)] - [switch]$ADIncludeDisabledUsers, - - [Parameter( - Mandatory = $false, - ParameterSetName = 'AD' - )] - [switch]$UpdatePictureFromAD, - - [Parameter( - Mandatory = $false, - ParameterSetName = 'EO' - )] - [switch]$UpdatePictureFromEO, - - [Parameter( - Mandatory = $false, - ParameterSetName = 'EO' - )] - [pscredential]$EOCredential, - - [Parameter(Mandatory = $false)] - [string]$DefaultZoomGroup, - - [Parameter(Mandatory = $false)] - [switch]$EnableChimeForNewUsers -) - -Import-Module C:\powershell-scripts\Zoom\Zoom.psm1 -Import-Module ActiveDirectory - -if ($UpdatePictureFromEO) { - if ((Get-PSSession).ConfigurationName -notcontains 'Microsoft.Exchange') { - $SessionParameters = @{ - 'ConfigurationName' = 'Microsoft.Exchange' - 'ConnectionUri' = 'https://outlook.office365.com/powershell-liveid' - 'Credential' = $EOCredential - 'Authentication' = 'Basic' - 'AllowRedirection' = $true - } - - try { - Import-PSSession (New-PSSession @SessionParameters) - } catch { - Write-Error "Unable to connect to Exchange Online: $_" - exit - } - } -} - -# Set enabled filter (all by default) -$EnabledFilter = if ($ADIncludeDisabledUsers) { '*' } else { { (Enabled -eq 'True') } } -$ADUsers = Get-ADUser -SearchBase $ADSearchBase -Filter $EnabledFilter -Properties mail, telephoneNumber, thumbnailPhoto, mobile | - Where-Object { $_.distinguishedName -notlike $ADNotLikeFilter } - -# Get the default Zoom group we're going to set if specified -if ($DefaultZoomGroup) { - $DefaultGroup = Get-ZoomGroup -Name $DefaultZoomGroup | Select-Object -ExpandProperty group_id -} - - -$ZoomUsers = Get-ZoomUser -All -foreach ($User in $ADUsers) { - $PhoneNumber = if ($User.telephoneNumber) { - $User.telephoneNumber -replace '-', '' - } elseif ($User.mobile) { - $User.mobile -replace '-', '' - } else { - '' - } - - # Pre-provision Zoom accounts for all selected AD users that don't already exist - if ($ZoomUsers.email -notcontains $User.mail) { - $Params = @{ - Email = $User.mail - FirstName = $User.GivenName - LastName = $User.Surname - License = 'Pro' - } - - if ($DefaultZoomGroup) { $Params.Add('GroupId', $DefaultGroup) } - - if ($PhoneNumber) { - if ($ZoomUsers.pmi -notcontains $PhoneNumber) { - $Params.Add('Pmi', $PhoneNumber) - } else { - Write-Warning "Unable to set Pmi for $($User.mail), $PhoneNumber already exists." - } - } - - if ($EnableChimeForNewUsers) { - # Create new user and set chime defaults - New-ZoomSSOUser @Params | Set-ZoomUser -EnterExitChime $true - } else { - # Create new user - New-ZoomSSOUser @Params - } - - # Update existing accounts with their AD info - } else { - $ZoomUser = Get-ZoomUser -Email $User.mail - - $Params = @{ } - - # Add params in Zoom and AD users have mismatched properties - if ($ZoomUser.first_name -ne $User.GivenName) { - $Params.Add('FirstName', $User.GivenName) - } - if ($ZoomUser.last_name -ne $User.Surname) { - $Params.Add('LastName', $User.Surname) - } - if ($PhoneNumber -and $ZoomUser.type -ne 1) { - if ($ZoomUser.pmi -ne [int64]$PhoneNumber) { - if ($ZoomUsers.pmi -notcontains $PhoneNumber) { - $Params.Add('Pmi', $PhoneNumber) - } else { - Write-Warning "Unable to set Pmi for $($User.mail), $PhoneNumber already exists." - } - } - } - if ($ZoomUser.vanity_url.Split('/')[-1] -ne $User.mail.Split('@')[0]) { - $Params.Add('VanityName', $User.mail.Split('@')[0]) - } - if ($ZoomUser.disable_feedback -ne $false) { - $Params.Add('DisableFeedback', $false) - } - - # Only update Zoom user properties if they have mismatches - if ($Params.Count -gt 0) { - $Params.Add('id', $ZoomUser.id) - Set-ZoomUser @Params - } else { - Write-Verbose "$($ZoomUser.email) is already up to date." - } - } - - # Upload user photo if it exists - if ($UpdatePictureFromAD -and $User.thumbnailPhoto) { - $ZoomUserId = Get-ZoomUser -Email $User.mail | Select-Object -ExpandProperty id - Set-ZoomUserPicture -Id $ZoomUserId -ByteArray $User.thumbnailPhoto - } - - if ($UpdatePictureFromEO) { - $ErrorLogPath = ".\Logs\EOErrors.log" - - # Get the photo from Exchange Online, send all errors to error log - $Photo = Get-UserPhoto -Identity $ZoomUser.email 2> $ErrorLogPath - - if ($Photo.PictureData -ne $null) { - # Save the photo to a temporary file - $FilePath = "$env:TEMP\$($ZoomUser.email).jpg" - if (Test-Path $FilePath) { Remove-Item $FilePath } - [IO.File]::WriteAllBytes($FilePath, $Photo.PictureData) - - # Load the photo and its properties - $Image = New-Object -ComObject Wia.ImageFile - $Image.LoadFile($FilePath) - - # Check if the photo is square - if ($Image.Height -eq $Image.Width) { - Set-ZoomUserPicture -Id $ZoomUser.id -ByteArray $Photo.PictureData - } else { - Write-Verbose "Photo is not square, cropping..." - - # Create a new crop filter - $Filter = New-Object -ComObject Wia.ImageProcess - $Filter.Filters.Add($Filter.FilterInfos.Item('Crop').FilterId) - - # Set the height/width to whichever is smallest - if ($Image.Height -lt $Image.Width) { - $PixelsToCrop = ($Image.Width - $Image.Height) / 2 - $Filter.Filters.Item(1).Properties.Item("Left") = $PixelsToCrop - $Filter.Filters.Item(1).Properties.Item("Right") = $PixelsToCrop - } else { - $PixelsToCrop = ($Image.Height - $Image.Width) / 2 - $Filter.Filters.Item(1).Properties.Item("Top") = $PixelsToCrop - $Filter.Filters.Item(1).Properties.Item("Bottom") = $PixelsToCrop - } - - # Apply the filter and upload the new image - $Image = $Filter.Apply($Image) - $CroppedFilePath = "$env:TEMP\$($ZoomUser.email)-cropped.jpg" - if (Test-Path $CroppedFilePath) { Remove-Item $CroppedFilePath } - $Image.SaveFile($CroppedFilePath) - Set-ZoomUserPicture -Id $ZoomUser.id -Path $CroppedFilePath - Remove-Item $CroppedFilePath - } - - Remove-Item $FilePath - } else { - Write-Warning "Error getting Exchange Online photo for $($ZoomUser.email): $((Get-Content $ErrorLogPath)[4])" - } - } -} - -# Remove any Zoom accounts that don't have matching AD users -Get-ZoomUser -All | ForEach-Object { - if ($ADUsers.mail -notcontains $_.email) { $_ | Remove-ZoomUser -Permanently } -} \ No newline at end of file From b721e5a19e8b9eb7353d3923f2c73897ab8f3709 Mon Sep 17 00:00:00 2001 From: Nick Rodriguez Date: Mon, 13 Feb 2023 21:54:05 -0500 Subject: [PATCH 65/65] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 72fec3a..203aa3f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ # PowerShell-Scripts -Just a bunch of PowerShell scripts that help me get through the day. -Please feel free to submit issues and requests, I work in PowerShell nearly every day and am always happy to script. \ No newline at end of file +Just a bunch of PowerShell scripts for sysadmins.