Novell is now a part of Micro Focus

Incorporating NDS Schema Extensions into the NetWare Administrator Application

Articles and Tips: article

JOHN BUCKLE
Developer Support Engineer
Developer Support

01 Jan 1997


Describes how to incorporate NDS schema extensions into the new NetWare Administrator applications for Windows and Windows 95.

Introduction

This article describes how to incorporate NDS schema extensions into the new NetWare Administrator applications NWADMN3X for Windows and NWADMN95 for Windows 95. By extending the functionality of these applications the system administrator can use the same administration tool to perform all NDS configuration and maintenance. Moreover the schema extensions appear to be an integral part of NDS since the administrator is given a consistent interface using the standard dialog boxes and dialog controls. The described functionality is achieved by writing a dynamic link library that snaps into the NetWare Administrator application. These modules are referred to as snapin modules.

To illustrate the above, a simple snapin module for the public key encryption application Crypt is used. The module adds a new dialog page for the user object and a whole dialog box for a new extended schema class used by the Crypt application. The snapin module also demonstrates how to add new toolbar buttons and menu items.

Snapping Into NetWare Administrator

The NetWare Administrator application only recognizes NDS classes and properties that accompany the base schema supplied with NetWare 4.x. Objects of the base schema classes are represented by individual class icons in the browser window and can be created, modified, and deleted. Unfortunately, by default NWAdmin cannot be used to create or modify objects based on new extended schema classes since NWAdmin does not know how to interpret the properties of the class. Also, NWAdmin cannot be used to modify any new properties added to base schema classes for the same reason.

To achieve this new functionality it is necessary to provide additional code and resources for NWAdmin to use when an extended object is configured. This additional code is supplied in the form of a dynamic link library that NWAdmin invokes when it starts. The following modifications and additional functionality can be incorporated into NWAdmin using a DLL:

  • Add new menu items to the Tools menu

  • Add new buttons to the Toolbarbgcolor

  • Provide icons to represent new extended schema classes

  • Provide descriptive names for classes and properties

  • Define new dialog boxes for new schema classes

  • Define additional dialog pages for existing schema classes

  • Define special purpose browser windows

A snapin DLL is registered with NWAdmin through the normal Windows channels: using an INI file for NWADMN3X and the Registry for NWADMN95. In the case of the INI file, the path to the DLL is defined in the section [Snapin Object DLLs WIN3X]of NWADMN3X.INI as shown below.

[Snapin Object DLLs WIN3X]

Crypt=C:\PROGRAMS\OWL\CRYPT\SNAPIN.DLL

The subject name Crypt can be any unique token to identify the extension. This should be based on the unique schema extension prefix registered with Novell. The path is used to locate the DLL. The above example used an absolute path since it was copied from a development environment. In a production environment a relative path should be used with the DLL located in a search path directory. On installation of the snapin DLL the above modification to the INI file can be performed using the Windows API WritePrivateProfileString().

For NWADMN95 the DLL location is recorded as a string value under the registry entry:

HKEY_CURRENT_USER\

  Software\NetWare\Parameters\NetWare Administrator\

Snapin Object DLLs WIN95\

This value can be set using the WIN32 APIs RegCreateKeyEx() and RegSetValueEx().

Once NWAdmin has located the DLL, the DLL can be loaded into memory and NWAdmin can execute a predefined exported function in the DLL called InitSnapin(). InitSnapin() is used by NWAdmin to inform the DLL that NWAdmin is ready for the DLL to register the extensions. The DLL can then use the Snapin Services APIs to register the Windows resources and callback procedures used to implement the NWAdmin extensions. How these extensions are implemented will now be described in more detail.

Initializing the NetWare Client Interface

As with all client software, the first NetWare API to be called should be NWCallsInit(). If the function does not return zero then an error has occurred in initializing the NetWare Client interface. This is unlikely given that NWAdmin is running, however, should still be checked. In this case it is suggested that the DLL should abort by returning zero from LibMain()or DllMain().

/*

** int FAR PASCAL LibMain(HINSTANCE hInst, WORD, WORD cbHeapSize, LPSTR)

**

** Main entry for Windows 16 bit DLLs.

*/



int FAR PASCAL LibMain(HINSTANCE

hInst, WORD, WORD cbHeapSize, LPSTR)

{

   hInstance = hInst ;



   if (cbHeapSize) UnlockData(0);



   return NWCallsInit(0,0) == 0 ;

}

Adding Menu Items to the Tools Menu

The simplest modification to NWAdmin is to add new menu items to the Tools menu. It is recommended that only a single entry is added to the Tools menu to restrict the number of new menu items appearing. If multiple menu items are required then a pop-up menu item should be added to the Tools menu. When registering a menu item two callback functions have to be provided, one to indicate if the menu item is currently valid and the other to perform the command.

The following code registers a single menu item inside a pop-up menu. The second menu item is made a sub-menu of the pop-up menu by passing the menu handle and menu name in the second and third parameters on the second call. The new handle to the menu item is returned via the fifth parameter. Since a pop-up does not execute a command there is no need to provide a command callback in the eighth parameter, however, it still needs a validator callback that is passed in the ninth parameter.

nuint menuHandleRSAMenu = 0 ;

nuint menuHandleExtendSchema = 0 ;



// Create a POPUP sub menu on the Tools menu.

NWARegisterMenu(NWA_VIEW_BROWSER,   // View name

   0,                            // Parent menu handle

   NULL,                         // Parent menu name

   MF_POPUP,                     // Menu type

   & menuHandleRSAMenu,          // Return menu handle

   "RSA Encryption",             // Menu name

   "RSA Encryption Menu",        // Menu hint

   0,                            // Command callback

   SnapinPopupMenuValid,         // Validator callback

   NWA_SNAPIN_VERSION) ;         // NWAdmin version



// Create a menu item on new POPUP defined above.

NWARegisterMenu(NWA_VIEW_BROWSER,

   menuHandleRSAMenu,

   "RSA Encryption",

   MF_STRING,

   & menuHandleExtendSchema,

   "Extend Schema",

   "Extend NDS Schema for RSA Encryption",

   (NWASnapinMenuActionProc) SnapinMenuExtendSchema,

   (NWASnapinMenuValidProc)  SnapinMenuMenuValid,

   NWA_SNAPIN_VERSION) ;

The calls to NWARegisterMenu() are made from within the InitSnapin() function. The returned menu handle menuHandleExtendSchema is used later for defining a new Toolbar button. The menu callback functions require the following prototypes.

Note: When using C++ the functions should be enclosed within extern "C" to avoid name mangling.

extern "C" {

N_EXTERN_LIBRARY( void ) SnapinMenuExtendSchema(void) ;

N_EXTERN_LIBRARY( void ) SnapinMenuMenuValid(pnuint16 pFlags) ;

N_EXTERN_LIBRARY( void ) SnapinPopupMenuValid(pnuint16 pFlags) ;

}

The validator callback indicates through the parameter whether the menu item is applicable at the current time, returning either MF_ENABLEDor MF_DISABLED. Determining whether a menu item should be available would normally depend on the browser window and the current tree name and context. In the following code the menu item is enabled if the current tree does not have the required schema extensions. The current tree name depends on which browser window is active and can be obtained using the API NWAGetTreeName().

/*

** SnapinMenuMenuValid(pnuint16 pFlags)

**

** Return whether the Schema Extensions menu item is valid. This is

** determined by finding the current tree name and checking if the

** schema has not been extended.

*/



N_GLOBAL_LIBRARY( void ) _export

SnapinMenuMenuValid(pnuint16 pFlags)

{

   if (SnapinSetCurrentTree() && ! CryptIsSchemaExtended())

      * pFlags = MF_ENABLED ;

   else

      * pFlags = MF_DISABLED ;

}

The command callback is called when the menu item is selected. The callback function has no parameters and returns a void. The following code simply executes the procedures to extend the NDS schema. In this case there is no need to set the current tree name since this was done in the validator callback.

/*

** SnapinMenuExtendSchema(void)

**

** Execute the action associated with the Schema Extensions menu item.

*/



N_GLOBAL_LIBRARY( void ) _export

SnapinMenuExtendSchema(void)

{

   CryptCreateAttributes() && CryptCreateClass() ;&& CryptCreateClass() ;
}

Adding Buttons to the Toolbar

Adding buttons to the Toolbar involves slightly more configuration than adding menu items. Also, Toolbar buttons invoke menu item commands; hence, a menu item has to be registered before the Toolbar button. Since a Toolbar button is associated with a menu item, the button only requires a validator callback. The menu item command callback is used to perform the button action. So when defining a Toolbar button do not assume that the menu validator callback will be executed prior to the menu command callback, since the menu command can be invoked through the Toolbar.

The Toolbar button is defined by initializing a NWAToolBarRegistrationStruct structure. This structure is used to reference several resources located in a resource file. Normally the resource file is attached to the DLL when the DLL is linked; hence, this is not a particular worry. The resource file is specified by passing the instance handle. In this case, this is the instance handle passed to LibMain() or DllMain(). Each Toolbar button requires a unique name; again, this name should be based on the unique schema extension prefix registered with Novell.

NWAToolBarRegistrationStruct toolBarButton ;



toolBarButton.lStructSize     = sizeof(NWAToolBarRegistrationStruct) ;

toolBarButton.nameOfSnapinButton = "Novell:Crypt:SchemaExtension" ;

toolBarButton.menuId          = menuHandleExtendSchema ;

toolBarButton.hResourceDLL    = hInstance ;

toolBarButton.bitmapId        = IDB_BUTTON ;

toolBarButton.textStrId       = IDS_BUTTONTEXT ;

toolBarButton.hintStrId       = IDS_BUTTONHINT ;

toolBarButton.toolTipStrId    = IDS_BUTTONTIP ;

toolBarButton.buttonState     = TBUp ;

toolBarButton.buttonType      = TBCommand ;

toolBarButton.enableProc      = SnapinToolbarButtonEnabled ;

toolBarButton.version         = NWA_SNAPIN_VERSION ;



NWARegisterToolBarButton(& toolBarButton) ;& toolBarButton) ;

The Toolbar validation callback is similar to that used for menus. In this case the callback should use a boolean rather than a menu state.

/*

** SnapinToolbarButtonEnabled(nint menuId, pnbool pfEnabled)

**

** Return whether the Schema Extensions button is valid. This is

** determined by finding the current tree name and checking if the

** schema has not been extended.

*/



N_GLOBAL_LIBRARY( void ) _export

SnapinToolbarButtonEnabled(nint, pnbool pfEnabled)

{

   * pfEnabled = (SnapinSetCurrentTree() && ! CryptIsSchemaExtended()) ;&& ! CryptIsSchemaExtended()) ;
}

The Windows resource script has to define the bitmap used on the button and various strings associated with it. The bitmap should be 20*18 pixels with 16 colors. Upon experimenting it was found that the light grey color is used by NWAdmin to denote the background color.

# define IDB_BUTTON      120

# define IDS_BUTTONTEXT  121

# define IDS_BUTTONHINT  122

# define IDS_BUTTONTIP   123



STRINGTABLE

{

   IDS_BUTTONTEXT, "Crypt RSA Encryption"

   IDS_BUTTONHINT, "Extend NDS Schema for RSA Encryption"

   IDS_BUTTONTIP,  "Extend NDS Schema"

}



IDB_BUTTON BITMAP 

{

...

}

Creating Dialog Boxes and Pages for Extend Schema Classes

The process of adding new dialog pages to an existing NDS class and creating a dialog box for a new class follow the same procedure. In this case a single callback function is used to control the new dialog pages introduced by the DLL. NWAdmin sends messages to the callback function when it is first registered and when an object of the class is selected and when the dialog box is invoked.

The API to register a class callback function is given below.

// Register the extensions to the RSA Keys class.

NWARegisterObjectProc(

   NWA_DS_OBJECT_TYPE,           // Class type - DS objects

   XX_CLASSNAME,                 // Class name

   "Novell, Inc. (C) All rights reserved.",

   hInstance,                    // DLL instance handle

   SnapinClassProc,              // Dialog callback

   NWA_SNAPIN_VERSION) ;         // NWAdmin version

The class name given in the second parameter can be a base schema class. In this case additional pages are added to the standard dialog box. The prototype for the callback function is similar to the standard Windows message procedures, consisting of the object name, a message type, and two 32-bit parameters.

N_EXTERN_LIBRARY( NWRCODE )

SnapinClassProc(pnstr8 name, nuint16 msg, nparam p1, nparam p2);

Once the callback is registered, NWAdmin will send an initialization message to the callback. The callback should use this message to register the bitmaps used to represent objects of the class in the browser window. Three bitmaps are required to represent normal objects, alias objects and read-only objects. The bitmaps are normally held in the resource file attached to the DLL and are loaded using the Windows API LoadBitmap(). Once the bitmaps have been registered, it is possible to obtain the bitmap handles using the APIs NWAGetClass...Bitmap(); however, it is normally easier to record the bitmap handles in static variables. Another useful function to perform during initialisation is to set up the dialog page structures. These structures are used when NWAdmin requests the individual pages. Each page requires a page tab name, a dialog resource name and a dialog procedure.

static NWAPageStruct DialogPage[XX_USER_PAGES] ;

static HBITMAP hBitmap ;

static HBITMAP hAlias ;

static HBITMAP hReadOnly ;



switch(msg){

    case NWA_MSG_INITSNAPIN:{ // Prepare the dialog pages.

    DialogPage[0].dlgProc   = (FARPROC) SnapinCryptDialogProc ;

    DialogPage[0].resName   = "CryptDialog" ;

    DialogPage[0].pageTitle = "RSA Encryption" ;

    // Give the bitmaps and class description to NWAdmin.

    hBitmap   = LoadBitmap(hInstance,"RSAClass") ;

    hAlias    = LoadBitmap(hInstance,"RSAClassAlias") ;

    hReadOnly = LoadBitmap(hInstance,"RSAClassRO") ;

    NWAAddClassData(XX_CLASSNAME, "RSA Encryption Key",hBitmap,hAlias,hReadOnly) ;

    return NWA_RET_SUCCESS ;

    }

    case NWA_MSG_CLOSESNAPIN:{

    // Free resources used for the RSA Keys class.

    NWARemoveClassData(XX_CLASSNAME,& hBitmap,& hAlias,& hReadOnly) ;& hBitmap,& hAlias,& hReadOnly) ;
    if (hBitmap)

       DeleteObject(hBitmap) ;

    if (hAlias)

       DeleteObject(hAlias) ;

    if (hReadOnly)

       DeleteObject(hReadOnly) ;

    return NWA_RET_SUCCESS ;

}

In the above code only a single page is initialised. The resource script contains the dialog entry for the page. Each page on the dialog window is controlled by a separate dialog procedure. The dialog window is placed inside a control window that contains the page tabs and the Ok, Cancel, Help buttons. Hence, these controls do not need to be duplicated on each dialog window.

CryptDialog DIALOG 12, 20, 260, 180

STYLE WS_CHILD | WS_BORDER

FONT 8, "Arial"

{

 CONTROL "Input Text", ­1, "static", ...

 CONTROL "", IDC_EDIT1, "edit", ...

 CONTROL "Output text", ­1, "static", ...

 CONTROL "", IDC_EDIT2, "edit", ...

 CONTROL "Generate", IDC_BUTTON1, "button", ...

 CONTROL "Encode", IDC_BUTTON2, "button", ...

 CONTROL "Decode", IDC_BUTTON3, "button", ...

}

When an object in the browser window is selected and the menu bar or the right mouse button is activated NWAdmin will display a list of valid operations. For new extended schema classes NWAdmin has to send a message to the callback to ask what operations are valid. In this case the callback can return a bit-pattern indicating whether the object can be deleted, renamed, moved, modified, etc.

case NWA_MSG_GETVALIDOPERATIONS:{

   // Return the valid operations.

   return NWA_OP_DETAILS |

          NWA_OP_RENAME | NWA_OP_DELETE |

          NWA_OP_DSTYPE  |

          NWA_OP_CREATE ;

}

Normally the list of valid operations is constant, however, there is no reason why the list cannot change to reflect an object's context or current tree.

When the user wishes to create an instance of a new extended schema class, NWAdmin sends the callback an NWA_MSG_CREATEOBJECT message. The name of the container object is passed in the name parameter with the message. It is up to the developer to open a dialog box to get the necessary information and then create the object. Traditionally the dialog box should contain fields for the mandatory properties of the object plus two check boxes to indicate whether to create another object or edit the new object once it has been created. Given that the two check boxes are mutually exclusive, a check box should be disabled once the other has been selected.

case NWA_MSG_CREATEOBJECT:{

   // User wants to create a RSA Keys object.

   SnapinSetCurrentTree() ;

   DialogShowDetailsFlag   = 0 ;

   DialogCreateAnotherFlag = 0 ;

   // Get the RDN of the object.

   if (DialogBox(hInstance,"CreateDialog",0,

                 (FARPROC)SnapinCreateDialogProc)){

   // Convert RDN to DN and then create the object.

   strcat(DialogObjectName,".") ;

   strcat(DialogObjectName,name) ;

   if (CryptCreateClassObject(DialogObjectName)){

      strcpy((char *)p1, DialogObjectName) ;

      if (DialogCreateAnotherFlag)

         return NWA_RET_CREATEANOTHER ;

      if (DialogShowDetailsFlag)

         return NWA_RET_SHOWDETAILS ;

      }

   else

      return NWA_ERR_ERROR ;

}

return NWA_RET_SUCCESS

;

}

In the above code the current tree name and context are obtained before any other action is taken. The dialog box is created and the user is allowed to enter the mandatory details. When the Ok button is pressed, the details are copied from the dialog controls into global variables. The full distinguished name is obtained by concatenating the name entered in the dialog box with the container name given by NWAdmin through the name parameter. The distinguished name is then passed onto a procedure to create the object. If the object was successfully created, the distinguished name is passed back to NWAdmin through the p1 parameter. This is to help NWAdmin update the browser window. Finally the return code passed back to NWAdmin depends on whether any of the check boxes were set.

When the user selects to view an object, NWAdmin sends an NWA_MSG_GETPAGECOUNT to the callback to find out how many additional dialog pages are to be added. This is a good time to record the name of the object and load the object from the NDS before any of the dialog windows are opened.

case NWA_MSG_GETPAGECOUNT:{

   // Get current tree name and context

   SnapinSetCurrentTree() ;

   // Keep a record of the name passed by NWAdmin.

   if (name) strcpy(DialogObjectName,name) ;

   // Return the number of dialog pages needed.

   return XX_USER_PAGES ;

}

NWAdmin will then send the callback an NWA_MSG_REGISTERPAGE message for each page. It is through this message that the dialog script and dialog procedure are specified. Just because a page is registered does not imply that the dialog procedure will be executed. A page will only be invoked if the user selects the page tab. When defining multiple pages do not assume that a WM_INITDIALOG will be sent to all the dialog procedures, let alone in any particular order. All initialization, such as reading the object from the NDS, should be done when the NWA_MSG_GETPAGECOUNT message is received.

case NWA_MSG_REGISTERPAGE:{

   // Return the page details for the dialog box.

   NWAPageStruct * pageInfo = (NWAPageStruct *) p2 ;

   pageInfo­>dlgProc   = DialogPage[(int)p1].dlgProc ;

   pageInfo­>resName   = DialogPage[(int)p1].resName ;

   pageInfo­>pageTitle = DialogPage[(int)p1].pageTitle ;

   pageInfo­>hDLL      = hInstance ;

   pageInfo­>initParam = 0 ;

   return NWA_RET_SUCCESS ;

}

The parameter p1 contains the page index, starting from zero. The parameter p2 is a pointer to a NWAPageStruct to be initialized. In the above code this involves copying the details from the DialogPage array that was initialized during the NWA_MSG_INITSNAPIN message.

Dialog Control Procedures

The final step in defining a new dialog window is the dialog procedure used to control the dialog window. This is a standard Windows dialog callback procedure, except that there is no need to call EndDialog() since this is handled by the control window. The Ok button on the control window is disabled until one of the dialog windows indicates that a change has occurred. To signal that a change has occurred the dialog procedure should send itself an NWA_WM_SETPAGEMODIFY message. This will mark the page tab as modified and enable the Ok button. When the Ok button is pressed, the control window will send an NWA_WM_CANCLOSE message. This can be used by the dialog procedure to check that the information displayed is consistent and record the data held by the dialog box. If the user presses the F1 key to get context-sensitive help, an NWA_WM_F1HELP message is sent to the dialog procedure. The dialog procedure can then open a help box to guide the user.

/*

** SnapinCryptDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

**

** Window procedure for controlling the CryptDialog dialog box.

*/



BOOL _export FAR PASCAL

SnapinCryptDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM)

{

   switch(message){

      case WM_INITDIALOG:

         CryptSetCurrentContext("[Root]") ;

         SnapinEnableWindowButtons(hwnd) ;

         break ;

      case WM_COMMAND:

         switch (wParam){

            case IDC_BUTTON1:

               SnapinGenerateUserKey(hwnd);

               return TRUE ;

            case IDC_BUTTON2:

               SnapinEncodeText(hwnd);

               SendMessage(hwnd,NWA_WM_SETPAGEMODIFY,hwnd,MAKELONG(0, TRUE));

               return TRUE ;

            case IDC_BUTTON3:

               SnapinDecodeText(hwnd) ;

               return TRUE ;

         }

         break ;

      case NWA_WM_CANCLOSE:

         // Check dialog fields, return FALSE if incorrect/incomplete

         // Copy dialog data to static storage

         return TRUE ;

      case NWA_WM_F1HELP:

         // Execute WinHelp

         return TRUE ;

   }

   return FALSE ;

}

The dialog box can be closed through the Ok or Cancel buttons on the control window. If the Ok button is used, an NWA_MSG_MODIFY message is sent to the class callback procedure. At this point all the dialog windows have been notified through the NWA_WM_CANCLOSE message that they will be closed and hence the data has been transferred from the dialog windows. The class callback procedure can then modify the NDS object. Finally an NWA_MSG_MPEWCLOSE is sent to signal that the multiple page edit window has closed. This occurs irrespectively of whether the Ok or Cancel button was used.

case NWA_MSG_MODIFY:{

   MessageBox(0,"Object should be modified","Help",MB_OK) ;

   return NWA_RET_SUCCESS ;

   }

case NWA_MSG_MPEWCLOSE:{

   MessageBox(0,"Multiple page edit window closed","Help",MB_OK) ;

   return NWA_RET_SUCCESS ;

}

Which Version of NWAdmin?

This article uses features that are not available in the original version of NetWare Administrator, NWADMIN.EXE. These features include multiple tree support and toolbars. The current release of the Novell SDK only supplies import libraries for the new versions of NWAdmin NWADMN3X.EXE and NWADMN95.EXE. An import library for the NWADMIN.EXE is available on older copies of the Novell SDK in the Legacy directory and is accompanied with the following warning.

The DLL that exports the NWSnapin functions [that] is included with the NWADMIN.EXE product must use the APIs in the LEGACY directory. The currently released version of NWADMIN.EXE was built with the legacy API libraries. In order for NetWare Administrator snapin DLLs to function, they must be built with the libraries contained in the LEGACY directory.

Fortunately things are not as bad as that and it is possible to use the new APIs as long as the old import library is linked. As stated above the old import library is not available on the current release of the SDK and last appeared on Release 7.

To administer a cross platform snapin project it is suggested that the compiler project is configured to generate three different targets, for example SNAPIN.DLL, SNAPIN3X.DLL, and SNAPIN95.DLL. The SNAPIN.DLL target will define a compiler constant, for example SNAPIN, to distinguish it from the other two targets.

NWADMIN.EXE expects a different version number than the other two targets; hence, the following lines should be added after the file NWSNAPIN.H has been included. (The last define has been truncated for brevity; the exact version is the include file.)

# ifdef SNAPIN

# undef  NWA_SNAPIN_VER_MAJOR

# undef  NWA_SNAPIN_VER_MINOR

# undef  NWA_SNAPIN_VER_REVISION

# undef  NWA_SNAPIN_VERSION



# define NWA_SNAPIN_VER_MAJOR     1

# define NWA_SNAPIN_VER_MINOR     0

# define NWA_SNAPIN_VER_REVISION  0

# define NWA_SNAPIN_VERSION       Nmake32( etc...

# endif

Code that is only applicable for the new platforms should be bracketed by

#ifndef SNAPIN

  // ... NWADMN3X NWADMN95 code

#endif

so that it is not included in the SNAPIN.DLL target.

Finally, the DLL must be listed in the [Snapin Object DLLs] section of the INI file so that NWADMIN.EXEwill load the DLL.

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates