Mar 29 2023 02:19 PM
Has anybody worked out how to add custom backgrounds programmatically? We will need to push our corp imagery to make it available for all staff. Previously this was done by GPO putting images into %APPDATA%\Microsoft\Teams\Backgrounds. The new location seems to be %LOCALAPPDATA%\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Backgrounds\Uploads - however manual copy of images to this location, even if you include the image and thumb from the old location don't work. When using the GUI to add images, they are put into the uploads folder with a randomised name and auto generated thumb. I also tried copying these to a different users machine same location but it doesn't recognise them, so they must be indexed somehow elsewhere?
Apr 01 2023 08:34 AM
@aaronm443 Interested to follow along and see how this thread goes. I'm hoping it's purely the preview nature of the new Teams, and nothing as nefarious as MS taking away functionality found in the old client in preparation to re-sell to us new functionality in "premium" Teams, which does have a centralised backgrounds deployment feature I seem to recall.
Apr 04 2023 01:18 AM
Apr 10 2023 09:48 PM
Apr 11 2023 01:44 AM
May 01 2023 07:53 AM
@aaronm443 Any update on this? I think it was rotten enough to take away the ability to push custom backgrounds through the Teams admin console, but at least with "old" Teams the IT department could work around this by using other file distribution methods. I just upgraded to New Teams last week and I'm having the same experience as you did a month ago, with randomized file names in a hard-to-find location path.
May 01 2023 01:39 PM
Jul 17 2023 01:44 PM
Aug 08 2023 09:05 AM - edited Aug 08 2023 11:49 AM
@aaronm443
Just worked on this. Yes, custom Backrgounds can be deployed here:
%localappdata%\Local\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Backgrounds\Uploads
Make sure you follow the notation of Backgrounds you add manually. Seems the new Teams client renames the files to "a GUID.jpeg". If you follow that notation, your backgrounds appear in the new Teams.
I copied the filename of a BG I added manually and incremented a number. Seems to work so far. I can definitely see MSFT pay-walling this though.
Aug 08 2023 03:42 PM
@yamautomate thank you for looking into this. I can't replicate your success with just <guid>.jpeg, however I found that manually adding/creating a <guid>_thumb.jpeg with a maximum width of 280px seems to have worked!
Aug 09 2023 02:53 AM - edited Aug 09 2023 03:19 AM
@aaronm443
Yes you also need to have a Thumbnail for each background picture. That Thumbnail needs to match the GUID of the background picture:
In my approach, I use an Azure Blob to store the pictures with their Thumbail (I add them manually to "classic" Teams beforehand, grab them from the "Uploads" Folder and upload them to the Blob).
Then my PowerShell Script (distributed via Intune) downloads all Files from that Blob. If NewTeamsIsPresent on the System where the Script runs, it downloads the same file as for "classic" Teams but creates a GUID from the Filename.
As I was quite happy that you found the needed Path for the "New" Teams Client, I'll happily share my working Script with you. May it be helpful for some1 out there:
Aug 30 2023 12:53 PM
I've created a powershell script to convert a directory of image files to png, create both an original and thumbnail image with the correct guid and filename. I was then able to see the files in the new teams.
# Install the necessary .NET namespace
Add-Type -AssemblyName System.Drawing
# Import the necessary .NET namespace
Add-Type -AssemblyName System.Drawing
# Dummy function to satisfy the GetThumbnailImage method
function dummyCallback { return $false }
# Define the folder containing the image files
$sourceFolder = "C:\Path\To\Source\Images"
# Define the folder to save the converted images
$destinationFolder = "C:\Path\To\Destination"
# Loop through each image file in the source folder
Get-ChildItem -Path $sourceFolder -File | ForEach-Object {
# Generate a GUID for the new image name
$guid = [guid]::NewGuid().ToString()
# Create a .NET Bitmap object from the image file
$originalImage = [System.Drawing.Image]::FromFile($_.FullName)
# Save the image as a PNG with a GUID-based name
$originalImage.Save("$destinationFolder\$guid.png", [System.Drawing.Imaging.ImageFormat]::Png)
# Create a thumbnail image
$thumbWidth = 278
$thumbHeight = 159
$thumbnailImage = $originalImage.GetThumbnailImage($thumbWidth, $thumbHeight, [System.Drawing.Image+GetThumbnailImageAbort]$dummyCallback, [System.IntPtr]::Zero)
# Save the thumbnail image as a PNG with a GUID-based name and "_thumb" suffix
$thumbnailImage.Save("$destinationFolder\$guid`_thumb.png", [System.Drawing.Imaging.ImageFormat]::Png)
# Dispose of the image objects to free resources
$originalImage.Dispose()
$thumbnailImage.Dispose()
}
Sep 02 2023 08:54 AM
Sep 05 2023 04:40 AM
Sep 05 2023 12:20 PM - edited Sep 05 2023 12:37 PM
@Steve Prentice So I'm not hosting these images in an Azure blob. What I implemented in my environment was deploying this as an Intune Win32 App and using PowerShell scripts to Install, Uninstall and Detect the correct files are placed in both the Old Teams Background directory and the New Teams. Since our org are still on a mix of both app variants. As far as wanting to update images, what you can do is when their are new images you can just create a new Intune App with the new photos whenever you have new images to upload. The GUID is a unique identifier so different images won't have the same GUID.
If anyone would like to go that route, below is what I setup.
%SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -command .\install.ps1
%SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -command .\uninstall.ps1
Below are the scripts for this app:
$PackageName = "Teams-Backgrounds"
$Version = "1"
$Path_4Log = "$ENV:LOCALAPPDATA\_MEM"
Start-Transcript -Path "$Path_4Log\Log\$PackageName-install.log" -Force
$ErrorActionPreference = "Stop"
try{
# Local folder
$TeamsBG_Folder = "$env:APPDATA\Microsoft\Teams\Backgrounds\Uploads"
$TeamsBG_Folder_New = "$env:LOCALAPPDATA\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Backgrounds\Uploads"
# Ensure the folder is present
New-Item -ItemType directory -Path $TeamsBG_Folder -Force
# Copy backgrounds
New-Item -ItemType directory -Path $TeamsBG_Folder_New -Force
Copy-Item -path ".\bg\*" -Destination $TeamsBG_Folder_New -Recurse -Force
Copy-Item -path '.\bg\*' -Destination $TeamsBG_Folder -Recurse -Force
# Validation File
New-Item -Path "$Path_4Log\Validation\$PackageName" -ItemType "file" -Value $Version -Force
}catch{
Write-Host "_____________________________________________________________________"
Write-Host "ERROR"
Write-Host "$_"
Write-Host "_____________________________________________________________________"
}
Stop-Transcript
$PackageName = "Teams-Backgrounds"
$Path_4Log = "$ENV:LOCALAPPDATA\_MEM"
Start-Transcript -Path "$Path_4Log\Log\$PackageName-uninstall.log" -Force
$TeamsBG_Folder = "$env:APPDATA\Microsoft\Teams\Backgrounds\Uploads"
$TeamsBG_Folder_New = "$env:LOCALAPPDATA\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Backgrounds\Uploads"
$TeamsBG_Files = Get-ChildItem -Path '.\bg' -Name
# Delete distributed backgrounds
Get-ChildItem $TeamsBG_Folder_New | Where{$_.Name -in $TeamsBG_Files} | Remove-Item
Get-ChildItem $TeamsBG_Folder | Where{$_.Name -in $TeamsBG_Files} | Remove-Item
# Delete detection file
Remove-Item -Path "$Path_4Log\Validation\$PackageName" -Force
Stop-Transcript
$PackageName = "Teams-Backgrounds"
$Version = "1"
$Path_4Log = "$ENV:LOCALAPPDATA\_MEM"
$ProgramVersion_current = Get-Content -Path "$Path_4Log\Validation\$PackageName"
if($ProgramVersion_current -eq $Version){
Write-Host "Found it!"
}
I hope this helps anyone looking for an alternative way to deploy teams backgrounds.
Sep 07 2023 08:23 PM
Like everything in IT, many solutions are available. Here's mine in case it's of any help for someone else.
What it does:
Download images from Azure Blob directly to New Teams Upload folder
How it does:
checks for New Teams to be installed and for Blob URL to be reacheable
Creates "Upload" folder if doesn't exist and bittransfer all files contained in a Blob folder into New Teams user folder using Intune Win32 app deploy
How it checks:
Detection is a dynamic script which check for each file in Azure Blob folder to exist in New Teams folder. If one or more files are missing, it will trigger a new deploy therefore Azure Blob folder can have pictures removed or added and it will dynamically reflects on clients devices
It's a Win32 app that needs to be packed with intunewinapputil
Main Script:
# hide windows terminal console
# https://stackoverflow.com/questions/74968665/hide-the-windows-terminal-console-window-with-powershell
function Hide-ConsoleWindow() {
$ShowWindowAsyncCode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$ShowWindowAsync = Add-Type -MemberDefinition $ShowWindowAsyncCode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$hwnd = (Get-Process -PID $pid).MainWindowHandle
if ($hwnd -ne [System.IntPtr]::Zero) {
# When you got HWND of the console window:
# (It would appear that Windows Console Host is the default terminal application)
$ShowWindowAsync::ShowWindowAsync($hwnd, 0)
} else {
# When you failed to get HWND of the console window:
# (It would appear that Windows Terminal is the default terminal application)
# Mark the current console window with a unique string.
$UniqueWindowTitle = New-Guid
$Host.UI.RawUI.WindowTitle = $UniqueWindowTitle
$StringBuilder = New-Object System.Text.StringBuilder 1024
# Search the process that has the window title generated above.
$TerminalProcess = (Get-Process | Where-Object { $_.MainWindowTitle -eq $UniqueWindowTitle })
# Get the window handle of the terminal process.
# Note that GetConsoleWindow() in Win32 API returns the HWND of
# powershell.exe itself rather than the terminal process.
# When you call ShowWindowAsync(HWND, 0) with the HWND from GetConsoleWindow(),
# the Windows Terminal window will be just minimized rather than hidden.
$hwnd = $TerminalProcess.MainWindowHandle
if ($hwnd -ne [System.IntPtr]::Zero) {
$ShowWindowAsync::ShowWindowAsync($hwnd, 0)
} else {
Write-Host "Failed to hide the console window."
}
}
}
Hide-ConsoleWindow
## Set variables
$name = "NewTeams_backgrounds"
$ver = "1.0"
$type = "APP"
$logname = "$type-$name.log"
$logPath = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\"
$LogFile = Join-Path $logPath $logname
# Checking permissions
Write-Output "checking permissions on $logPath"
$acl = Get-Acl -Path $logPath
$aclcheck = $acl.Access | Where-Object { $_.IsInherited -eq $false -and $_.FileSystemRights -match 'Modify'}
if ($null -eq $aclcheck) {
try {
# Changing permissions to Logs folder
Write-Output "adding Modify permissions on builtin\users group"
$rights = 'Modify'
$inheritance = 'ContainerInherit, ObjectInherit'
$propagation = 'None'
$type = 'Allow'
$SID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-545")
$ACE = New-Object System.Security.AccessControl.FileSystemAccessRule($SID, $rights, $inheritance, $propagation, $type)
$acl = Get-Acl -Path $logPath
$acl.SetAccessRule($ACE)
$acl | Set-Acl -Path $logPath
Write-Output "permissions modified on $logPath"
}
catch {
Write-Output "Error setting up permissions for $logPath. The error is below:"
Write-Output $_
}
}
Write-Output "permissions are fine"
# Function to write log entries
function Write-Log {
param(
[string]$Message
)
$Timestamp = Get-Date -Format "dd-MM-yyyy HH:mm:ss"
$LogEntry = "$Timestamp - $Message"
$LogEntry | Out-File -FilePath $LogFile -Append
}
# Main script
try {
Write-Log "Installing $name version $ver"
Write-Log "checking New Teams folders"
$NewTeams_background = "$env:LOCALAPPDATA\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Backgrounds"
$NewTeams_Uploads = "$env:LOCALAPPDATA\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Backgrounds\Uploads"
if (!(Test-Path -path $NewTeams_background)) {
Write-Log "creating folder $NewTeams_background"
New-Item $NewTeams_background -itemtype directory -force};
if (!(Test-Path -path $NewTeams_Uploads)) {
Write-Log "creating folder $NewTeams_Uploads"
New-Item $NewTeams_Uploads -itemtype directory -force};
$URL = "YOUR_BLOB_URL_WITH_SAS"
$uri = "https://yourcompanyblobname.blob.core.windows.net/yourcompanyfolder/NewTeams_Background"
$body = Invoke-RestMethod -uri $URL
$xml = [xml]$body.Substring($body.IndexOf('<'))
$files = $xml.ChildNodes.Blobs.Blob.Name | where-Object {$_ -like "NewTeams_Background/*"}
$files = $files -replace "NewTeams_Background/",""
Write-Log "following files are on Azure Blob: $files"
$files | ForEach-Object {
$downloadpath = $NewTeams_Uploads + "/" + $_
if (!(Test-Path -Path $downloadpath)){
$URL = "$($uri)/$_"
Write-Log "Downloading $_"
#Invoke-WebRequest -Uri $URL -OutFile "$NewTeams_Uploads\$_" -TimeoutSec 30 -UseBasicParsing:$true -ErrorAction SilentlyContinue
Start-BitsTransfer -Source $URL -Destination "$NewTeams_Uploads\$_" -Priority Normal -ErrorAction SilentlyContinue
}
}
Write-Log "Installation of $name completed"
}
catch {
Write-Log "Error running $name install. The error is below:"
Write-Log $_
}
you'd need to change $uri and $URL accordingly
Pictures need to be uploaded into $uri location
It has a function to hide the powershell and terminal window since it's installed under User environment
It saves log into the default Intune log folder and since it runs as user, it checks for log folder correct permissions
Detection script:
$NewTeams_Uploads = "$env:LOCALAPPDATA\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Backgrounds\Uploads"
$URL = "YOUR_COMPANY_BLOB_URL_WITH_SAS"
$missing = 0
$body = Invoke-RestMethod -uri $URL
$xml = [xml]$body.Substring($body.IndexOf('<'))
$files = $xml.ChildNodes.Blobs.Blob.Name | where-Object {$_ -like "NewTeams_Background/*"}
$files = $files -replace "NewTeams_Background/",""
$files | ForEach-Object {
$download_path = $NewTeams_Uploads + "/" + $_
if (!(Test-Path -Path $download_path)){
$missing++
}
}
if ($missing -eq "0"){
Write-Host "New Teams backgrounds pictures installed"
}
Then you have simple requirements script, one for Blob URL to be reacheable:
$url = "YOUR_COMPANY_BLOB_URL_WITH_SAS"
$client = New-Object System.Net.WebClient
$client.DownloadString($url) | Out-Null
Write-Host "The URL is reachable"
and one for New Teams folder to exist (to be checked with logged on credentials):
# runs with logged on creds
$NewTeams_path = "$env:LOCALAPPDATA\Packages\MSTeams_8wekyb3d8bbwe"
if (Test-Path $NewTeams_path)
{
Write-Host "New Teams installed"
}
Enjoy
Sep 11 2023 06:47 AM
I had an existing script that downloaded images with standard names like "hps_background.jpg"
To get around the GUID requirement, I created an SHA1 hash of the image post-download (which downloads to the legacy app location), and then create a GUID based on the first 32-characters of that hash.
That way the hash is always unique and can be run regularly.
Sep 12 2023 04:52 AM - edited Sep 12 2023 05:01 AM
Thanks @hps_steve that's a great idea, totally solves the random GUID issue. :)
For anyone else wanting to do the same, something like the following PowerShell would help:
$FilePath = "C:\Path\To\Saved\Picture.png"
# Calculate the SHA-1 hash of the downloaded file
$hasher = [System.Security.Cryptography.SHA1]::Create()
$fileStream = [System.IO.File]::OpenRead($FilePath)
$hashBytes = $hasher.ComputeHash($fileStream)
$fileStream.Close()
$hasher.Dispose()
# Convert the original hash to a hexadecimal string and make it lowercase
$originalSha1Hash = [BitConverter]::ToString($hashBytes).ToLower() -replace '-', ''
# Create a shorter hash (e.g., first 32 characters) for the GUID
$shortSha1Hash = $originalSha1Hash.Substring(0, 32)
# Create a GUID from the short hash
$guidFromHash = $shortSha1Hash.Substring(0, 8) + '-' + $shortSha1Hash.Substring(8, 4) + '-' + $shortSha1Hash.Substring(12, 4) + '-' + $shortSha1Hash.Substring(16, 4) + '-' + $shortSha1Hash.Substring(20)
# Display the SHA-1 hash and GUID
Write-Host "SHA-1 Hash: $originalSha1Hash"
Write-Host "GUID from Hash: $guidFromHash"
Sep 12 2023 05:04 AM
@Steve Prentice You could use "Get-FileHash" to get the SHA1 hash.
$hash = Get-FileHash -Path $FilePath -Algorithm SHA1
$originalSha1Hash = $hash.Hash.toLower()