Script to delete attachment's version
Asked by Nguyen Tran:

I'm trying to write a script job to scan and delete attachment versions on a page.

For example on a page I have attach file A and reattach a few times to get 5 versions for file A. I want to write a script to delete the first 4 version.

Here's my attempt:

```
import com.atlassian.confluence.pages.Attachment
import com.atlassian.confluence.pages.AttachmentManager
import com.atlassian.sal.api.component.ComponentLocator


def attachmentManager = ComponentLocator.getComponent(AttachmentManager)

hits.each { attachment ->
def attachmentOnPage = attachmentManager.getAttachments(attachment)
if (attachmentOnPage.size() >1)
{
    log.warn "attachmentOnPage: ${attachmentOnPage}"
    log.warn "size: ${attachmentOnPage.size()}"
    int size = attachmentOnPage.size()
    int sizeMax = size - 1
    List<String> attachmentDeleteList = new ArrayList<String>()
    for(int i=0;i<size;i++)
    {
        if (i<sizeMax)
        {
            def attachmentSingle = attachmentOnPage.get(i)
            attachmentDeleteList.add(attachmentSingle)
            log.warn "attachmentDeleteList: ${attachmentDeleteList}"
            //log.warn "attachmentSingle: ${attachmentSingle}"
            //attachmentManager.removeAttachmentFromServer(attachmentSingle)
        }
    }
}
}
```

However, when I try to run the removeAttachmentFromServer method, it delete every versions and the file itself in the database. I also tried the removeAttachment(attachmentDeleteList) and it does the same thing. Is there any method that just delete the version not the attachment itself.


Answers:
author: Jeff Abbott
body: You can set retention rules to automatically remove attachments over a number/count.

Confluence administration-\>Retention rules {#toc-hId--496422181}
-----------------------------------------------------------------

comments: author: oib
body: Yes, this 'Confluence Retention Rule' feature is a very good addition to Confluence.

The only problem I have with it, is that it is not really about data retention, as it only works on historic data (versions older than the latest). And trash bin.

In our test, we found that the system needed 6 weeks to calm down. That is, after 6 weeks to total page size of the site did not change significantly anymore. I only checked total pages number and not total attachments number. I assume, as it is in the same process, the background process works on both items simultaneously.

The important message I would like to convey is:  
Do not expect immediate results from this feature.


author: Sinan Yildirim
body: This REST Call is asking for confirmation. How to confirm it via script in Windows PowerShell?

```
https://www.your-conlfuence.com/pages/confirmattachmentversionremoval.action?pageId=######&fileName=<your file name>&version=n
```


author: Rafa? ?ydek
body: Here

```
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.pages.AttachmentManager

def attachments
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def space=spaceManager.getSpace('RUBIK')
def pages = pageManager.getPages(space, true)
for (page in pages) { //loop for every active page in the space
for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version
attachmentManager.removeAttachmentVersionFromServer(it)
}
} 
}
```

comments: author: Piotr Nowak
body: hi,  
Thank you, the script is very useful.  
Could anyone tell me how to modify this script to leave eg the last 5 versions of each attachment? I'm not a developer and I'm trying to modify it by trial and error.


author: Rafa? ?ydek
body: Not final work, but only a concept. Good luck.

```
for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments
def newestVersion = attachment.version //not sure if this is right field
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version

//here need some logs to see if the  results are sorted from the newest

//something like that: 
if(it.version < newestVersion-5) {
  attachmentManager.removeAttachmentVersionFromServer(it)
}

}
} 
```

```
?
```


author: Piotr Nowak
body: Thanks.  
I will test it.


author: Jeff Abbott
body: I get the following error after each delete.  
Object of class \[com.atlassian.confluence.pages.Page\] with identifier \[6521421\]: optimistic locking failed; nested exception is net.sf.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : \[com.atlassian.confluence.pages.Page#6521421\]


author: Bill Bailey
body: Well maybe it is easier to do via a REST call? The URL for deleting an attachment version takes the form of:

```
https://www.your-conlfuence.com/pages/confirmattachmentversionremoval.action?pageId=######&fileName=<your file name>&version=n
```

Where n is the version number. Now I don't know if you delete version 1, if the rest of the attachments will renumber so you will have to experiment (meaning you may have to delete version 1 four times in your case).

comments: author: Nguyen Tran
body: We would like to automate this using the scheduled script job in Scriptrunner though. I've managed to make my script works. Will post the answer here shortly.


author: Syed Shah
body: Hey Nyugen,

Any chance you got this script working - looking to do something similar so would be a great help.

Thanks


author: Rafa? ?ydek
body: I've made it.

```
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.pages.AttachmentManager

def attachments
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def space=spaceManager.getSpace('RUBIK')
def pages = pageManager.getPages(space, true)
for (page in pages) { //loop for every active page in the space

for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version
attachmentManager.removeAttachmentVersionFromServer(it)
}
} 
}
```


author: Steve Letch
body: Thanks for this, quick question, do you know if this works fine on pages regardless of restrictions?

I've been looking at using the built in scripts but they have massive limitations, such as only showing (even to an admin) pages they have explicit access to. This is due to the built in script abiding by the confines of the quick search bar, that doesn't give admins search capability on something they don't have explicit access to. Then to make matters worse, when I tied to get the space owner to run it, it errors out.


author: Steve Letch
body: Also, do you know whether or not this triggers the content updated notification email or not? A space owner was bombarded with the notification emails after we used the built in script, which isn't useful if we want to do this in the background for many spaces.

Is it possible you could put up a variation that runs across the entire instance?

thanks


author: Steve Letch
body: [@Rafal Zydek](/t5/user/viewprofilepage/user-id/733634) :)


author: Rafa? ?ydek
body: This is working on admin permission of app. Doesn't look on restrictions.

I suggest to test it first on acc on few spaces. In some cases we have got problems with missing attachments.

About all spaces variant:

import com.atlassian.confluence.spaces.SpaceManager  
import com.atlassian.sal.api.component.ComponentLocator  
import com.atlassian.confluence.pages.PageManager  
import com.atlassian.confluence.pages.AttachmentManager

def attachments  
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)  
def spaceManager = ComponentLocator.getComponent(SpaceManager)  
def pageManager = ComponentLocator.getComponent(PageManager)  
def allSpaces = spaceManager.getAllSpaces()

for( def space : allSpaces)  
{  
def pages = pageManager.getPages(space, true)  
for (page in pages) { //loop for every active page in the space  
for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments  
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version  
attachmentManager.removeAttachmentVersionFromServer(it)  
}  
}  
}  
}


author: Steve Letch
body: Oooo, going to try it now :)


author: Steve Letch
body: Where do you run it from for it to not run as a regular user but to run as the system? Do you just put it in the jobs area of Scriptrunner? Although that does ask for a user to run as.


author: Rafa? ?ydek
body: This script is based on java classes inside confluence. You need to run as Admin on Confluence to use it / run it.

Some scripts I think use current login user, other (script and function) doesn't need any authentication to run a code.


author: Steve Letch
body: Ah ok so just stick it in the jobs area. It seems to slowly be running through it anyway, this ia n improvement over the Scriptrunner built in script as it always times out when using theirs because of proxy timeouts, even when raised to 10 minutes.

You may be able to help me with another issue. I need to bulk add a specific group to every page in my instance. Because of this issue:

<https://jira.atlassian.com/browse/CONFSERVER-36393>

But the script Adaptavist made for me doesn't work because of this issue:

<https://jira.atlassian.com/browse/CONFSERVER-58536>

This is the script below, do you have any ideas on how it could get to work?

```
import com.atlassian.confluence.core.ContentPermissionManager
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.security.ContentPermission
import com.atlassian.confluence.security.ContentPermissionSet
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator

import static com.atlassian.confluence.security.ContentPermission.EDIT_PERMISSION
import static com.atlassian.confluence.security.ContentPermission.VIEW_PERMISSION

/**
* This script runs through all spaces and pages (with true parameter in getPages, eagerly fetch permissions at the same time)
* and assign view permission to the 'groupToAddPermission' variable. If this group already has View or edit permission,
* the script does not make any change
*/
SpaceManager spaceManager = ComponentLocator.getComponent(SpaceManager)
PageManager pageManager = ComponentLocator.getComponent(PageManager)
ContentPermissionManager contentPermissionManager = ComponentLocator.getComponent(ContentPermissionManager)
final String groupToAddPermission = 'confluence-administrators'

ContentPermission viewPermission = ContentPermission.createGroupPermission(VIEW_PERMISSION, groupToAddPermission)
ContentPermission editPermission = ContentPermission.createGroupPermission(EDIT_PERMISSION, groupToAddPermission)
log.error "view Permission ${viewPermission}"
log.error "edit Permission ${editPermission}"

spaceManager.allSpaces.each { Space space ->
log.error "Space ${space}"
if(space.key == '<KEY>'){
pageManager.getPages(space, true).each { Page page ->
log.error "Page ${page}"
ContentPermissionSet viewContentPermissionSet = page.getContentPermissionSet(VIEW_PERMISSION)
ContentPermissionSet editContentPermissionSet = page.getContentPermissionSet(EDIT_PERMISSION)
log.error "viewContentPermissionSet: ${viewContentPermissionSet}"
log.error "editContentPermissionSet: ${editContentPermissionSet}"
boolean containsGroupPermissions = viewContentPermissionSet?.contains(viewPermission) || editContentPermissionSet?.contains(editPermission)
boolean containsAnyRestrictions = viewContentPermissionSet || editContentPermissionSet

if (!containsGroupPermissions && containsAnyRestrictions) {
log.error "Assign new Content"
contentPermissionManager.addContentPermission(viewPermission, page)
log.error "viewContentPermissionSet Result1: ${ page.getContentPermissionSet(VIEW_PERMISSION)}"
log.error "editContentPermissionSet Result1: ${ page.getContentPermissionSet(EDIT_PERMISSION)}"
}
}
}
}
```


author: Steve Letch
body: [@Rafa? ?ydek](/t5/user/viewprofilepage/user-id/1201030)

Do you think you could combine your script with the one further up, so it can run on all pages and also keep the last 5 versions?

Been having trouble trying to combine the two myself.

thanks


author: oib
body: To be honest I don't have right now time to try this. From our side, we probably decided on that plugin. It can help us keeping always required numbers of old version pages.

[Purge Versions for Confluence \| Atlassian Marketplace](https://marketplace.atlassian.com/apps/1218985/purge-versions-for-confluence?hosting=server&tab=overview)

You can also try to use it as dry run.


Tags: confluence-server

scriptrunner

server