2,000+ setting updates

Alkraizer

Titanium Partner
Joined
Dec 27, 2025
Messages
9
Reaction score
4
Greetings 3cx Community,
I am really new to 3cx, and my boss at my new job has asked me to make changes to 5 of our clients' IP Phones, specifically 4 settings for each phone need to be set:
1. Screensaver time out - 6 hours
2. Backlight timeout - 6 hours
3. Time format - 12 hours
4. LED blinking behavior - voicemails only

The issue is, each client has between 300 and 1,600 IP phones. I've started working on doing this by going to each phone 1 by 1 to adjust these settings, after around 1,000 I've completely lost my mind and started thinking that there should be an easier way to do this. My Boss has told me that we can't use a template for this, because every time there is an update for the phone it wipes out the template settings and we have to re apply it to each phone again, and it also seems to prevent any other options from being set if the user decides they don't like the settings we applied, i.e. if they want the 24 hour time format, they can't select it in the app once the template is applied. I also understand that templates aren't officially supported by 3cx, so If at all possible I want to use a supported method.

All clients are on at least v20.6 of 3cx hosted on-site and the phones are all Yealink phones, in the case of the biggest client they are using Yealink T-33G phones.

Please tell me there is a supported way to make these changes in bulk to save both my sanity and my mouse clicking finger?
 
Please tell me there is a supported way to make these changes in bulk to save both my sanity and my mouse clicking finger?
Use the Force ... of the 3CX Configuration API.
Note: Everything (and a bit more) that you can click on in the 3CX Admin menu can be automated. You can use the browser development tools to see how 3CX does it themselves.

We use PowershellI, so i would use my example ps script header and:
  1. fetch the IDs of all 3CX users via an API request
    Code:
    $users = (Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users" -headers $headers -Method GET -ContentType "application/json").value.Id
  2. loop through each user and get their data
    Code:
    Foreach( $user in $users) {
        $userdata = (Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users($user)?`$expand=Phones" -headers $headers -Method GET -ContentType "application/json")
  3. loop through all the user phones
    Code:
    For( $phonecnt = 0; $phonecnt -lt $userdata.Phones.Count; $phonecnt++ ) {
  4. check if the phone matches one of the desired templates (e.g., yealinkT4x.ph.xml)
    Code:
    if( $userdata.Phones[$phonecnt].TemplateName -eq "yealinkT4x.ph.xml")
    ...
  5. if yes, modify the data as needed
    Code:
    $userdata.Phones[$phonecnt].Settings.ScreenSaver = "6 hours"
    $userdata.Phones[$phonecnt].Settings.Backlight = "6 hour"
    $userdata.Phones[$phonecnt].Settings.TimeFormat = "12-hour clock (AM/PM)"
    $userdata.Phones[$phonecnt].Settings.PowerLed = "Voicemails only"
  6. and then write it back with a PATCH request
    Code:
    $userdata.PSObject.Properties.Remove('@odata.context')
    $patchdata = $userdata | ConvertTo-Json -Depth 20
    Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users($user)" -headers $headers -Method PATCH -body $patchdata -ContentType "application/json"
... or something like that. This is just a rough handmade draft. Feel free to test and adjust it as needed.
 
Last edited:
@fxbastler is right that the API is a great way to automate things. Nice post :)

However its also possible to create a custom template which will not be overwritten and you can manage yourself. That requires some know how of Yealink phones. Nothing crazy but you cant expect to take the custom template, change a parameter without knowing anything about yealink t33 phones and then forget about it forever. To do this you must make a copy of the custom templates and then re-import it and use it when configuring the phone. This separate template will not be overwritten.

There is a doc although its not up to date. So you have to apply some knowledge here. We will update it in the coming weeks.
How to configure a custom template for an IP phone
 
Last edited:
I really appreciate the advice posted here. I was wondering if API was the way to go. I don't have a lot of experience in API calls, but I think that's a good thing to learn.

Thank You!
 
I was wondering if API was the way to go.
Yes, it is, that's exactly what this API is for. Even the 3CX management interface uses it.

One should always remember the following: If more than 30 things need to be changed and an algorithm exists then let the adjustments be automated.

But well, of course, there are other ways to solve your current problem without using the 3CX Config API. A few ideas that come to mind are:
  1. Export all users with phones, delete all these users, change the phones in the exported file and then re-import the users. The downside to this approach is that various other settings (links, runtime data of the users) may be lost or corrupted. If you really want to do this you should definitely test it thoroughly first. </bad_idea_in_your_case>
  2. You can also back up 3CX, modify the backup file and then restore the backup. </not_so_bad_idea>
  3. You can partially automate the adjustments by, for example, using MouseRecorderPro2 to cleverly click through all the necessary changes and repeat the execution. </silly_idea>
  4. You can also stop all 3CX services except for the database and change the settings directly in the database. However this is not supported by 3CX. </very_unsupported_idea>"
I think others might have some more funny and creative ideas as well.
 
Thank you again for your input. I'll do my best to learn and test the scripts you mentioned above. If i have other issues, i'll report back.
Boss didn't like the idea of taking a backup and modifying the file, so i'll have to skip that idea for now :)
 
  • Like
Reactions: fxbastler
I"ve been working all day to try to get this running, and I was wondering if i could enlist some more help for tomorrow's attempts.
I didn't mention it above, but our installations are on premise so i'm not sure how much that changes things.
I have, thus far been unable to run this script in any meaningful ways as i don't want to test it in production. I'm trying to get a test server set up, but I did run the whole script through AI to help further explain what i'm trying to do for my own understanding and it told me the following:

"This script successfully reads users and phones, but all attempted phone setting changes are ineffective because those settings are not writable via the 3CX Configuration API and the PATCH request targets the wrong entity."

Is this just the AI not knowing about the full capabilities or is it something I've missed while working on it?
 
Is this just the AI not knowing about the full capabilities
yes, the AI doesn't know or can't do something like that

I didn't mention it above, but our installations are on premise so i'm not sure how much that changes things.
no, it does not

I have, thus far been unable to run this script in any meaningful ways as i don't want to test it in production.
That's bad. That should definitely be done. What about your own system?

Please post the code you have by now and I'll take a quick look at it.
 
Sadly i don't have any phones in my system, and i only created my account to post on here. I've just been running the commands you mentioned, and trying to decipher and understand what i'm running, then getting distracted by other work or meetings. I should be able to run more commands once i have a working place to test everything, i was just really worried about breaking a customer phone system.
I'll post back when i have more info.
 
Get a 3CX hosted by 3CX for free to test, install an SBC and get one or two IP phones. Then you can test it yourself.

Also, post your current code.
 
Ok i'm thinking I might be way out of my league here... I ran the commands that were put up earlier in this thread, and i was able to get some results, like it would find at least 1 user in the list, but when it came to running the code:

Code:
$userdata.Phones[$phonecnt].Settings.ScreenSaver = "6 hours"
$userdata.Phones[$phonecnt].Settings.Backlight = "6 hour"
$userdata.Phones[$phonecnt].Settings.TimeFormat = "12-hour clock (AM/PM)"
$userdata.Phones[$phonecnt].Settings.PowerLed = "Voicemails only"

I got some errors saying that the properties didn't exist.

I feel like i'm missing a step here, but with this being my first time i don't know how or where it is.
 
pls post your complete code - without credentials
 
  • Like
Reactions: N_G
ah, I see: if the property doesn't exist then it throws an error

This happens when none of the four values of the phone have ever been adjusted. TimeFormat is always used so this shouldn’t result in an error. Meaning in the worst case only three errors would occur in these four lines of code. But that doesn’t help you.
So I’ve adjusted it for you. If you take the post from above (my first answer) and I add this change then the script looks like this:
3cx-powershell-xapi-set-phonedata.ps1
Code:
$tcxurl = "mypbx.mydomain.de"       # needs port if not 443

$jsonauthbody = (@{
    Username = "100"                # 3CX user, maybe email address
    Password = "Super_Secret0815"   # 3CX user password
    SecurityCode = ""
} | ConvertTo-Json)

$LoginResponse = Invoke-RestMethod -Uri "https://$tcxurl/webclient/api/Login/GetAccessToken" -Body $jsonauthbody -Method POST -ContentType "application/json"

if( $LoginResponse.Status -ne "AuthSuccess" ) {
    Write-Host "abort: authentication not successful"
    exit -1
}

$headers = @{Authorization = "Bearer $($LoginResponse.Token.access_token)"}

$users = (Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users" -headers $headers -Method GET -ContentType "application/json").value.Id
Foreach( $user in $users ) {
    $userdata = (Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users($user)?`$expand=Phones" -headers $headers -Method GET -ContentType "application/json")
    For( $phonecnt = 0; $phonecnt -lt $userdata.Phones.Count; $phonecnt++ ) {
        If( $userdata.Phones[$phonecnt].TemplateName -eq "yealinkT4x.ph.xml") {
            If( $userdata.Phones[$phonecnt].Settings.ScreenSaver -ne $null ) {
                $userdata.Phones[$phonecnt].Settings.ScreenSaver = "6 hours"
            } Else {
                $userdata.Phones[$phonecnt].Settings | Add-Member -Name "ScreenSaver" -Type NoteProperty -Value "6 hours"
            }
            If( $userdata.Phones[$phonecnt].Settings.Backlight -ne $null ) {
                $userdata.Phones[$phonecnt].Settings.Backlight = "6 hour"
            } Else {
                $userdata.Phones[$phonecnt].Settings | Add-Member -Name "Backlight" -Type NoteProperty -Value "6 hour"
            }
            If( $userdata.Phones[$phonecnt].Settings.TimeFormat -ne $null ) {
                $userdata.Phones[$phonecnt].Settings.TimeFormat = "12-hour clock (AM/PM)"
            } Else {
                $userdata.Phones[$phonecnt].Settings | Add-Member -Name "TimeFormat" -Type NoteProperty -Value "12-hour clock (AM/PM)"
            }
            If( $userdata.Phones[$phonecnt].Settings.PowerLed -ne $null ) {
                $userdata.Phones[$phonecnt].Settings.PowerLed = "Voicemails only"
            } Else {
                $userdata.Phones[$phonecnt].Settings | Add-Member -Name "PowerLed" -Type NoteProperty -Value "Voicemails only"
            }
            $userdata.PSObject.Properties.Remove('@odata.context')
            $patchdata = $userdata | ConvertTo-Json -Depth 20
            Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users($user)" -headers $headers -Method PATCH -body $patchdata -ContentType "application/json"
        }
    }
}

You could shorten the script with a subroutine; that would make it much clearer (the four if / else...). I've written it out fully for now.

Please send a small barrel of Radeberger beer to Dresden. We already have the cooler, tap system and CO2 bottle.
Thanks in advance. :)
 
Last edited:
Here's an updated version with an extra function (ChangePhoneData) for changes. It's much easier to read and also simpler to expand.
3cx-powershell-xapi-set-phonedata.ps1
Code:
$tcxurl = "mypbx.mydomain.de"       # needs port if not 443

$jsonauthbody = (@{
    Username = "100"                # 3CX user, maybe email address
    Password = "Super_Secret0815"   # 3CX user password
    SecurityCode = ""
} | ConvertTo-Json)

$LoginResponse = Invoke-RestMethod -Uri "https://$tcxurl/webclient/api/Login/GetAccessToken" -Body $jsonauthbody -Method POST -ContentType "application/json"

if( $LoginResponse.Status -ne "AuthSuccess" ) {
    Write-Host "abort: authentication not successful"
    exit -1
}

$headers = @{Authorization = "Bearer $($LoginResponse.Token.access_token)"}

Function ChangePhoneData {
    [CmdletBinding()]
    param(
        [Parameter(Position=0,mandatory=$true)]
        [PSObject] $userdata,
        [Parameter(Position=1,mandatory=$true)]
        [int] $phonecnt,
        [Parameter(Position=2,mandatory=$true)]
        [string] $phoneparam,
        [Parameter(Position=3,mandatory=$true)]
        [string] $phoneparamdata
        )
    process{
        If( $userdata.Phones[$phonecnt].Settings.PSObject.Properties.Name -contains $phoneparam ) {
            $userdata.Phones[$phonecnt].Settings.$phoneparam = $phoneparamdata
        } Else {
            $userdata.Phones[$phonecnt].Settings | Add-Member -Name $phoneparam -Type NoteProperty -Value $phoneparamdata
        }
    }
}

$users = (Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users" -headers $headers -Method GET -ContentType "application/json").value.Id
Foreach( $user in $users ) {
    $userdata = (Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users($user)?`$expand=Phones" -headers $headers -Method GET -ContentType "application/json")
    For( $phonecnt = 0; $phonecnt -lt $userdata.Phones.Count; $phonecnt++ ) {
        If( $userdata.Phones[$phonecnt].TemplateName -eq "yealinkT4x.ph.xml") {
            ChangePhoneData $userdata $phonecnt "ScreenSaver" "6 hours"
            ChangePhoneData $userdata $phonecnt "Backlight" "6 hour"
            ChangePhoneData $userdata $phonecnt "TimeFormat" "12-hour clock (AM/PM)"
            ChangePhoneData $userdata $phonecnt "PowerLed" "Voicemails only"
            $userdata.PSObject.Properties.Remove('@odata.context')
            $patchdata = $userdata | ConvertTo-Json -Depth 20
            Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Users($user)" -headers $headers -Method PATCH -body $patchdata -ContentType "application/json"
        }
    }
}

If the connection speed of the PC running the PowerShell script to the 3CX is fast enough and the 3CX has sufficient CPU and I/O power the script will run through over 1000 users and 1000 phones in less than 4 minutes.

edit:
I hope I haven't made another copy/paste mistake this time. So definitely test it beforehand.

edit edit:
I tested it with 98 users and 101 phones (one user with two and one user with three phones) via LAN (slow pbx): under 10 seconds. Note: creating three test users, exporting, expanding to 98 using LibreOffice calc and importing was much faster than deleting the 98 users and writing it here.
I'll change the order to a medium barrel of beer. :)
After all, it's now production-ready and tested with many phones.
But again: test it yourself.
 
Last edited:
  • Like
Reactions: N_G
I don't know what kind of Wizard you are, but it works! I was able to update my test environment very quickly using this script. I'm going to attempt to roll it out for a customer tomorrow!

Thank you so much!
 
  • Like
Reactions: fxbastler
but it works
The last code is essentially the same like in the first answer - except the fact that previously not edited properties should be created first.


Thank you so much!
you're welcome

Please give us feedback on how it went and how long it took for how many users and phones. I’d be interested to know.
 
  • Like
Reactions: bitn2
@fxbastler thank you so much for your detailed help!!
 
Greetings,
I have returned from applying the knowledge i gained from this thread and I'm happy to say that everything worked perfectly! As requested here is some additional feedback:

I've run this script on 5 clients with user counts ranging from 300 to 1,900 users. While each client had different hardware they were hosting 3cx on, the script ran in between 45 - 90 seconds on all but the biggest client (around 1,900 users) which took around 4 minutes to completely run.

I am very grateful for all of the assistance that was provided to me. Wishing you all the best in the new year!

Thank You!
 
  • Like
Reactions: moniputerPBX

Members Online Now

Forum statistics

Threads
111,831
Messages
589,277
Members
164,660
Latest member
RJenkinsROCK