Few months ago, I’ve been to an SAP migration project, where we had to migrate a large amount variant configuration master data. Hundreds of characteristics, several thousands of object dependencies (CU01/CU02). Dependencies  with roughly 100.000 lines of code.

The source system was not an SAP, I believe it was called Brain. The data conversion for object dependencies included also a translation from the programming language of Brain into the “ABAP-like” language used by the SAP dependencies. It was a bit tricky to write the translator and catch all the details of the correct translation.

In such an amount of code lines, it was difficult to determine, whether the translation has been done correctly. Therefore we decided to do verify the translation by making parallel product configuration. One in the legacy system and the same in the SAP – and compared the configured product – BOMs, routings, purchase texts generated etc…

If the result did not match 100% we were looking into the translator for corrections. Then we translated the object dependencies again and updated the target testing SAP environment.

To shorten the time required for each translate/update/try cycle, we have integrated the upload for object dependencies into the translator. The original code was written in Python and MS Access, however let me share the idea re-written in Powershell.

Beauty of the Powershell is that it’s based on .NET and therefore also nicely interfacing with .NET Connector. The below code is a bit long, I hope you’ll not get bored.

Few utility functions first, to determine the folder, where the script is located:


function Get-ScriptDirectory {
    Split-Path $script:MyInvocation.MyCommand.Path
}

A call to BAPI_MESSAGE_GETDETAIL in order to retrieve the ABAP Exception message. We will use it just at the end of the script. Let’s skip it for now.


function Get-AbapMessage($rfcDest, $class, $number,
        $param1 = "", $param2 = "", $param3 = "", $param4 = "") {
    $fn = $rfcDest.Repository.CreateFunction("BAPI_MESSAGE_GETDETAIL")
    $fn.SetValue("ID", $class)
    $fn.SetValue("NUMBER", $number)
    $fn.SetValue("TEXTFORMAT", "ASC")
    $fn.SetValue("MESSAGE_V1", $param1)
    $fn.SetValue("MESSAGE_V2", $param2)
    $fn.SetValue("MESSAGE_V3", $param3)
    $fn.SetValue("MESSAGE_V4", $param4)
    $fn.Invoke($rfcDest)
    $fn.GetValue("MESSAGE")
}

A way to import .NET Connector libraries is quite straightforward. Depending on which Powershell you run (32bit or 64bit), you shall import the appropriate dlls. Here I run   Windows Powershell (x86) and therefore importing the x86 version of NCO:


# Load NCO libraries
[void][Reflection.Assembly]::LoadFile("$mypath\x86\sapnco.dll")
[void][Reflection.Assembly]::LoadFile("$mypath\x86\sapnco_utils.dll")

Input parameters for the script. For a simplicity, I’ve hard-coded them here. $in_filename is a name of the file, which contains the dependency source code, the rest of parameters are respective input fields of the CU01/CU02 input screen.


$mypath = Get-ScriptDirectory
$in_filename = "$mypath\FRML002323_sap.txt"
$in_depname = "TEST" #CUKB-KNNAM
$in_depdesc = "Description"
$in_depgroup = "GROUP" #KNGRP
$in_deptype = "7" #KNART - Procedure

I was unable to make app.config based Rfc Configuration to work. Neither I was successful to implement the IDestinationConfiguration interface in the Powershell. Therefore I ended up loading destination parameters from a file:


$target_syst = "PR1"
# load connection parameters from file
. $mypath\$target_syst"_Params.ps1"
$rfcCfg = New-Object SAP.Middleware.Connector.RfcConfigParameters
$params.Keys | ForEach-Object { $rfcCfg.Add($_, $params.Item($_)) }

For different target systems I have different xxxx_params.ps1 files. Content of such file is very simple. It’s just a definition of $param dictionary variable. Each of the keys shall by name correspond to .NET Connector RfcConfigParameters parameters. E.g.:


$params = @{
    NAME = "PR1"
    SYSID ="PR1"
    ASHOST = "10.20.30.40" #  or host name
    SYSNR = "20"
    CLIENT = 020
    USER = "username"
    USE_SAPGUI = 0
    TRACE = 10
}

It’s not at all a good practice to store a password in a plain text, therefore we use an encrypted secure string to store the password to a local file. On a first run the script will prompt for a password and store it encrypted to a XXX_secret.txt file. On a next run the password will be loaded from that file:


# set password
$secstr = get-content $mypath\$target_syst"_secret.txt" | convertto-securestring
if($secstr.Length -eq 0) {
    $secstr = Read-Host -AsSecureString
    ConvertFrom-SecureString $secstr > $mypath\$target_syst"_secret.txt"
}
$pwdptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secstr)
$rfcCfg.Add("PASSWD", [Runtime.InteropServices.Marshal]::PtrToStringAuto($pwdptr) )
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pwdptr)

Get the destination object and test the connection with a ping


$rfcDest = [SAP.Middleware.Connector.RfcDestinationManager]::GetDestination($rfcCfg)
$rfcDest.Ping()

CAMA_DEPENDENCY_MAINTAIN is the FM to create/update an object dependency:


$fn = $rfcDest.Repository.CreateFunction("CAMA_DEPENDENCY_MAINTAIN")
$fn.SetValue("DEPENDENCY", $in_depname)
$fn.SetValue("COMMIT_AND_WAIT", "X")
$dep_data = $fn.GetStructure("DEPENDENCY_DATA")
$dep_data.SetValue("Status", "1") # 1 - Released, 2 - In Preparation, 3 - Locked
$dep_data.SetValue("Group", $in_depgroup)
$dep_data.SetValue("DEP_TYPE", $in_deptype) # 7 - Procedure, 3 - Action, 2 - Precondition, 5 - Selection condition

Here we load the content of the input file with dependency source code into the SOURCE table:


$tab = $fn.GetTable("SOURCE")
$tab.Clear()
$i = 0
(Get-Content $in_filename) | ForEach-Object {
    $tab.Append()
    $tab.SetValue("LINE_NO", $i * 10)
    $tab.SetValue("LINE", $_)
}

And the last mandatory parameter a DESCRIPTION:


$tab = $fn.GetTable("DESCRIPTION")
$tab.Append()
$tab.SetValue("LANGUAGE", "EN")
$tab.SetValue("DESCRIPT", $in_depdesc)

Finally we call the FM. Catching an eventual ABAP exception. Warning usually means a syntax error in the dependency source code, error then some missing input parameter. You shall see the the actual ABAP message for details. Note we reuse the $rfcDest object here, to get the ABAP message.


try {
    $fn.Invoke($rfcDest)
    "Dependency maintained" | Write-Output
} catch [SAP.Middleware.Connector.RfcAbapException] {
    $msg_class = $_.Exception.AbapMessageClass
    $msg_number = $_.Exception.AbapMessageNumber
    $msg_params = $_.Exception.AbapMessageParameters
    $_.Exception.Message | Write-Output
    Get-AbapMessage $rfcDest $msg_class $msg_number $msg_params[0] $msg_params[1] $msg_params[2] $msg_params[4]
}

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply