Skip to content

Instantly share code, notes, and snippets.

@simonmcconnell
Last active October 1, 2018 11:54
Show Gist options
  • Save simonmcconnell/c8bc34be9a1d6c7fd9c343d7884fa317 to your computer and use it in GitHub Desktop.
Save simonmcconnell/c8bc34be9a1d6c7fd9c343d7884fa317 to your computer and use it in GitHub Desktop.
Maintain version history when upgrading from VSS to SVN version control in Proficy Change Management
'*********************************************************************************
' Product Module : Scheduler Scripts
' Script Name : getAllVersions.vb
' Function : Backup each version of a Machine Edition project and its history
' file. Designed to be used in conjunction with proficy_import.py
' script when migrating from VSS to SVN.
' retreives <projectname>_<version>.zip and <projectname>_history.xml
' for all projects selected in a manually triggered Scheduler Event.
'
' Change BackupFolder to the location of the project backup files.
'
'
' Version: 9.50 (Build 7677)
'
' Revision History:
' SM - Original 25/05/2018
'*********************************************************************************
'
Execute fxIncludeStartupScript("fxConstants.vb")
Execute fxIncludeStartupScript("scriptErrors.vb")
Execute fxIncludeStartupScript("Sched_Logging.vb")
Execute fxIncludeStartupScript("GlobalScripts.vb")
Dim ProjectType
Dim ProjectName
Dim MaxVersion
Dim v
Dim WorkingDir
Dim BackupFolder
Dim LogFileName
Dim LogFile
Dim LogEvent
Dim CheckoutStatus
CleanupVariables()
ProjectType = CurrentProjectType ' product type should be MACHINE EDITION
ProjectName = CurrentProject
MaxVersion = fxGetProjectVersionMax(ProjectType, ProjectName)
WorkingDir = fxGetDirPath(5) & "\" & CurrentServer & "\FrameworX\"
'"C:\Users\Public\Documents\GE Intelligent Platforms\PME\SecurWORX\<PCM SERVER NAME>\FrameworX"
fxDirSetCurrent(WorkingDir & ProjectName)
BackupFolder = "E:\ProficyBackup\"
CheckDir WorkingDir
CheckDir BackupFolder
CreateLogFile()
If ( Not Main() ) Then LogEvent("Something went wrong")
Cleanup()
Function Main()
HistoryReport()
fxDirSetCurrent(WorkingDir & "\" & ProjectName)
' if it is checked out on this computer, backup this version also
CheckoutStatus = fxGetProjectCheckoutStatus(ProjectType, ProjectName)
select case CheckoutStatus
' -3: An internal error occurred while attempting to acquire the checkout status.
' Tip: When fxGetProjectCheckoutStatus returns this value, use fxGetLastError (and fxGetErrorDescription) for more information on the error that occurred.
' -2: A file or project exists with the specified ProjectName, but it is not recognizable as a project.
' -1: The specified project cannot be found.
' 0: The project is not checked out by anyone.
' 1: The project is checked out by the current user on the current computer.
' 2: The project is checked out by the current user on a different computer.
' 3: The project is checked out by a different user on the current computer.
' 4: The project is checked out by a different user on a different computer.
' 5: The project is a local project. It does not exist on the Server and thus cannot be checked out.
' 6: The project is a new project created by the current user while disconnected from the Server.
' 7: The project is a new project created by someone else while disconnected from the Server.
case -3
AddToLog "An internal error occurred while attempting to acquire the checkout status. Error: " & CStr(fxGetLastError) & " " & fxGetErrorDescription(fxGetLastError)
Exit Function
case -2
AddToLog "A file or project exists with the specified ProjectName, but it is not recognizable as a project."
Exit Function
case -1
AddToLog "The specified project cannot be found."
Exit Function
case 0
If fxGetProjectLocalStatus(ProjectType, ProjectName) = 1 Then Call fxDeleteLocal(ProjectType, ProjectName)
bDeleteLocal = True
BackupAllVersions()
case 1
sBackupZip = BackupFolder & ProjectName & "_working.zip"
fxZip WorkingDir & ProjectName, "*.*", sBackupZip, 1
' undo checkout
iRet = fxUndoCheckout(ProjectType, ProjectName, 0)
lastErr = fxGetLastError()
if iRet <> 0 Then
AddToLog "Failed to undo checkout project with error: " & CStr(lastErr) & " " & fxGetErrorDescription(lastErr)
Else
BackupAllVersions()
' copy working version back
fxUnzip sBackupZip, fxDirGetCurrent(), "*.*" ' might have to use WorkingDir & "\" & ProjectName
' checkout w/o overwriting files
If ( fxCheckout(ProjectType, ProjectName, 1) <> 0 ) Then
lastErr = fxGetLastError()
AddToLog "Failed to checkout project with error: " & CStr(lastErr) & " " & fxGetErrorDescription(lastErr)
End If
End If
AddToLog "The project was checked out by the current user on the current computer. Check the project in and retry. Backed up as " & ProjectName & "_working.zip"
case 2
AddToLog "The project is checked out by the current user on a different computer. Check that version into the new PCM server after restoring the full history."
bDeleteLocal = True
BackupAllVersions()
case 3
AddToLog "The project is checked out by a different user on the current computer. Please have them check this project in and try again."
case 4
AddToLog "The project is checked out by a different user on a different computer. Check that version into the new PCM server after restoring the full history."
bDeleteLocal = True
BackupAllVersions()
end select
if bDeleteLocal Then
Call fxDeleteLocal(ProjectType, ProjectName)
End If
Main = True
End Function
Function BackupAllVersions()
v = 1
Do Until v > MaxVersion
' be nice if this worked...
' bGetSuccess = fxGetProjectImage(ProjectType, ProjectName, v, BackupFolder, 2)
' If bGetSuccess = 0 Then
' AddToLog ProjectType & "\" & ProjectName & " Failed to get version " & CStr(v) & " to " & BackupFolder
' End If
' clean the working directory
Call fxDeleteLocal(ProjectType, ProjectName)
' get the specific version. Files are output to WorkingDir.
iRet = fxGet(ProjectType, ProjectName, CStr(v), 0)
if iRet = 0 Then
' Zip the contents of the Get folder (local working directory).
' This is essentially backing up the project but fxGetProjectImage refuses to work.
fxDirSetCurrent(WorkingDir & ProjectName)
fxZip fxDirGetCurrent(), "*.*", BackupFolder & ProjectName & "_" & CStr(v) & ".zip", 1
Else
AddToLog "fxGet failed with error: " & fxGetErrorDescription(fxGetLastError())
End If
v = v + 1
Loop
BackupAllVersions = True
End Function
Function CreateLogFile()
LogFileName = ProjectName & "_backup.log"
fxDirSetCurrent(BackupFolder)
If fxFileExists(LogFileName) then fxFileDelete(LogFileName)
LogFile = fxFileCreate(LogFileName)
CreateLogFile = True
End Function
Function HistoryReport()
' generate XML history report for project: overwrite files and display oldest to newest
If ( fxHistoryReport(ProjectType, ProjectName, BackupFolder & ProjectName & "_history.xml", 6) <> 0 ) Then
AddToLog "Failed to get history report."
End If
HistoryReport = True
End Function
Function Cleanup()
fxDirSetCurrent(BackupFolder)
fxFileClose LogFile
' delete empty log files
set fso = createobject("scripting.filesystemobject")
if not fso.folderexists(BackupFolder) then
set fso = nothing
exit function
end if
for each ofile in fso.getfolder(BackupFolder).files
if ofile.size = 0 then fso.deletefile ofile.path, true
next
set fso = nothing
CleanupVariables()
End Function
Function CheckDir(Dir)
if (Right(Dir, 1) <> "\") then
Dir = Dir & "\"
end if
End Function
Sub AddToLog(MyString)
PreviousDir = fxDirGetCurrent()
fxDirSetCurrent(BackupFolder)
If (MyString <> "") Then
LogEvent("[" & ProjectType & "\" & ProjectName & "]: " & MyString)
iRet = fxFileWriteString(LogFile, CStr(Time()) & " [" & ProjectType & "\" & ProjectName & "]: " & MyString)
if iRet = 0 then LogEvent("Failed to write " & MyString & " to file " & LogFile)
iRet = fxFileWriteEOL(LogFile)
if iRet = 0 then LogEvent("Failed to write EOL to file " & LogFile)
End If
fxDirSetCurrent(PreviousDir)
End Sub
'**************************************
' Cleanup Variables used by the script.
'**************************************
Function CleanupVariables()
CheckoutStatus = Empty
ProjectType = Empty
ProjectName = Empty
MaxVersion = Empty
WorkingDir = Empty
BackupFolder = Empty
LogFileName = Empty
LogFile = Empty
' return our success
CleanupVariables = True
End Function
# import projects into PCM that were backed up to <PROJECT>_<VER>.ZIP
# from the getAllVersions.vb Scheduler script
# can also rename projects on the way
# useful when migrating from VSS to SVN
import sys
from pathlib import Path
from shutil import make_archive, unpack_archive, copy2
import re
from xml.etree.ElementTree import ParseError, parse
import pywintypes
import win32com.client
# TODO: add logging
PCM_SERVER = 'MYSERVERNAME'
LOC_WORKING_DIR = Path(r'C:\Users\Public\Documents\GE Intelligent Platforms\PME\SecurWORX\Local\FrameworX')
SVR_WORKING_DIR = Path(r'C:\Users\Public\Documents\GE Intelligent Platforms\PME\SecurWORX') / PCM_SERVER / 'FrameworX'
def get_source_dir():
# directory containing the backups
while True:
source_dir = Path(input("Enter directory containing backup files: "))
if source_dir.exists() and source_dir.is_dir():
break
else:
print(f'"{source_dir}" is not a valid directory! Ensure it exists...')
return source_dir
def get_backup_dir():
# where to save backup files
while True:
backup_dir = Path(input("Where do you want to save the renamed backups to? "))
if backup_dir.exists():
if backup_dir.is_dir():
break
else:
print(f'"{backup_dir}" is not a valid directory!')
else:
if input(f'"{backup_dir} doesn\'t exist, create it? ').lower() == 'y' or 'yes':
backup_dir.mkdir(parents=True)
break
return backup_dir
def rename_backups(ProjectName, NewProjectName, source_dir=None, backup_dir=None):
# check if there is a project of this name stored locally already
# if so: ask if we want to delete it or abort
# for each backup in backup directory:
# restore
# rename to new name
# backup to <new name>_<version> in user specified directory
# need to be concious of not overwriting existing backups, could we rename to <new name>_<version>x.zip?
def order_versions(files):
"""
Return a sorted list of lists containing the version and filename.
[[1, ProjectName_1.zip], [2, ProjectName_2.zip] ... ]
:param files: list of files to sort
:return: sorted list of lists containing the version and filename
"""
def key(item):
return item[0]
backups = []
for f in files:
version = int(f.name.replace('.zip', '').split('_')[-1])
backups.append([version, f])
return sorted(backups, key=key)
print(f'\nAttempting to rename all {ProjectName} backups to {NewProjectName}...\n')
if Projects.DoesProjectExist(NewProjectName, 1):
while True:
if input(f"Planned project name already exists locally. Delete it? ").lower() == 'yes' or 'y':
Projects.DeleteProject(NewProjectName, 1)
break
else:
print("Project renaming aborted!")
print(f"Please remove or rename the local copy of {NewProjectName} and try again.")
return
if Projects.DoesProjectExist(ProjectName, 1):
while True:
delete = input(f"Project {ProjectName} already exists locally. Do you want to delete it? ")
if delete.lower() == 'y' or 'yes':
Projects.DeleteProject(ProjectName, 1)
break
elif delete.lower() == 'n' or 'no':
print("Project renaming aborted!")
print(f"Please remove or rename the local copy of {ProjectName} and try again.")
return
if source_dir is None:
source_dir = get_source_dir()
if backup_dir is None:
backup_dir = get_backup_dir()
# ordered list of the backup files [version, filename]
backups = order_versions([
f
for f in list(source_dir.glob(f'{ProjectName}_*.zip'))
if '_'.join(f.name.split('_')[:-1]) == ProjectName
])
print(f'{backups[-1][0]} versions of {ProjectName} found...')
for v, f in backups:
# restore
# rename
# backup to <new name>_<version> in user specified directory (zip the contents of the folder)
print(f"renaming version {v} of {ProjectName} to {NewProjectName}")
Projects.RestoreProject(f) # TODO: add exception checking
Projects.RenameProject(NewProjectName, ProjectName, 1) # TODO: add exception checking
try:
zipfile = backup_dir / (NewProjectName + '_' + str(v))
make_archive(zipfile, 'zip', LOC_WORKING_DIR / NewProjectName)
except FileNotFoundError:
print(f'Failed to zip: folder "{zipfile}" not found')
return # TODO: add error code for return value
finally:
Projects.DeleteProject(NewProjectName, 1)
else:
copy2(source_dir / (ProjectName + '_history.xml'), backup_dir / (NewProjectName + '_history.xml'))
def read_xml(xmlfile):
# open xml file
# create structure of [ {version, date, user, comment}, ... ]
# trim the comment to 255 chars
# return list of dicts\
try:
tree = parse(xmlfile)
except ParseError:
print(f'Failed to parse xml file "{xmlfile}"!')
return [], 0
except FileNotFoundError:
print(f'File not found: "{xmlfile}"')
return [], 0
root = tree.getroot()
versions = root.findall('./HistoryVersions/Version')
vcount = int(root.find('HistoryVersions').attrib['count'])
vlist = []
for v in versions:
comment = v.find('Comment').text
if not comment:
comment = ''
regex = re.compile('Created *')
action = v.find('Action').text
if re.match(regex, action):
comment = 'Created ' + comment
vlist.append({
'version': v.find('VersionNumber').text,
'date': v.find('VersionDate').text,
'user': v.find('User').text,
'comment': comment,
'action': action
})
def version_key(item):
return int(item['version'])
return sorted(vlist, key=version_key), vcount
# TODO: move to ./util/
def delete_folder(folder: Path):
if folder.exists():
for subdir in folder.iterdir():
if subdir.is_dir():
delete_folder(subdir)
else:
subdir.unlink()
folder.rmdir()
else:
print(f"Cannot delete '{folder}', it doesn't exist!")
def make_comment(version):
c = ' '.join([version['date'][:11], version['user'], version['comment']])
if len(c) > 255:
c = ' '.join([version['date'][:11], version['user'], version['comment']])
print(c)
if len(c) > 255:
c = ' '.join([version['date'][:11], version['comment']])
print(c)
if len(c) > 255:
return version['comment']
return c
def add_backup_to_pcm(ProjectName, zipfile, comment):
"""
Add a backup of an existing project to the Proficy Change Management server.
The project must be checked in on the server before executing this function.
:rtype: bool
:param ProjectName: Project name
:param zipfile: Backup zip file
:param comment: Check in comment
:return: True if successful
"""
# check that we can check out the project
# if it is checked out already, abort
# otherwise:
# check out the project
# delete the local copy from working directory
# restore our backup to working directory
# check in w/comment from *_history.xml
print(f"Check in comment: {comment}")
if Manager.CanCheckoutProject(ProjectName):
Manager.CheckOut(ProjectName, '') # TODO: add exception handling
project_folder = SVR_WORKING_DIR / ProjectName
delete_folder(project_folder)
unpack_archive(zipfile, project_folder, 'zip')
try:
Manager.CheckIn(ProjectName, comment, 1) # TODO: extract comment
return True
except:
return False
else:
print(f'Unable to check out {ProjectName}! Already checked out?')
return False
def restore_backups(ProjectName, source_dir=None, skip_v1=False):
"""
Restore all backups in the user specified directory that are in the format <Project Name>_<Version>.zip
to Proficy Change Management.
:param ProjectName: The project name
:param source_dir: Directory containing backup files
:param skip_v1: Set True to skip version 1
:return:
"""
# get list of versions to add from xml file
# add each version to PCM
# cleanup?
if source_dir is None:
source_dir = get_source_dir()
vlist, vcount = read_xml(Path(source_dir / (ProjectName + '_history.xml')))
print(f"\n{vcount} versions for {ProjectName}")
for v in vlist:
if skip_v1 and v['version'] == '1':
pass
else:
print(f"Adding {ProjectName} version {v['version']} to PCM server {PCM_SERVER}")
add_backup_to_pcm(
ProjectName,
Path(source_dir) / (ProjectName + '_' + v['version'] + '.zip'),
make_comment(v)
)
# if restored:
# print(f"Restored {ProjectName} version {v['version']} to {PCM_SERVER}")
# else:
# print(f"Failed to restore {ProjectName} version {v['version']} to {PCM_SERVER}")
def connect():
"""
Create a Machine Edition (FrameworX.exe) COM object, CimplicityME.Applicaiton, and the Projects and Manager interfaces.
"""
try:
fx = win32com.client.Dispatch('CimplicityME.Application')
except pywintypes.com_error:
print("Failed to load Proficy automation interface 'CimplicityME.Applicaion'")
print("Please close all instances of Machine Edition")
sys.exit(1)
try:
Projects = fx.Projects
except AttributeError:
print("Failed to get Projects automation interface")
fx.Quit()
sys.exit(1)
try:
Manager = fx.ManagerObject
except AttributeError:
print("Failed to get Manager automation interface")
fx.Quit()
sys.exit(1)
return fx, Projects, Manager
if __name__ == '__main__':
proficy, Projects, Manager = connect()
try:
pass
# restore = ['PROJECT1', 'PROJECT2']
# for proj in restore:
# restore_backups(proj, source_dir=Path(r'D:\ProficyBackup\BACKUPDIR'), skip_v1=True)
# rename_backups('OLDNAME', 'NEWNAME', Path(r'D:\ProficyBackup\SOURCEDIR'), Path(r'D:\ProficyBackup\BACKUPDIR'))
# input("\nManually restore version 1 of NEWNAME before continuting:")
# restore_backups('NEWNAME', source_dir=Path(r'D:\ProficyBackup\BACKUPDIR'), skip_v1=True)
finally:
proficy.Quit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment