I see. OS Loaders make use information in the associated Load Options (LOs), Windows Boot Manager does so as well. Tell me, when you load it, do you fill in LoadOptions pointer (inside of the ImageHandle's EFI_LOADED_IMAGE_PROTOCOL, LIP, instance) before starting the image?
The caller may fill in the image’s “load options” data, or add additional protocol support to the handle before passing control to the newly loaded image by calling StartImage().
Basically, you would need to take BootXXXX variable contents and pass its OptionalData address to bootmgr.efi. Because you load OSL instead of UEFI Boot Manager, you would need to mimick the process as if bootmgr.efi was picked during the Load Option processing, basically, you need to find what BootXXXX is related to the bootmgr.efi. For example, by enumerating BootXXXX and finding among them the one, having the same Device Path in the FilePathList[0], but remember, it almost certainly would be a relative one, thus starting with the 4.1 node, like HD()\<FilePath>. Then you should pass the pointer to OptionalData part of the LO via the LIP->LoadOptions field. Don't forget to fill in LoadOptionsSize field too. Useful quote from the spec (Boot Manager, Load Options):
OptionalData
The remaining bytes in the load option descriptor are a binary
data buffer that is passed to the loaded image. If the field is zero
bytes long, a NULL pointer is passed to the loaded image. The
number of bytes in OptionalData can be computed by
subtracting the starting offset of OptionalData from total size
in bytes of the EFI_LOAD_OPTION.
I haven't used LoadImage() and may be wrong, but I think, the sequence should be like this:
1. you call LoadImage.
2. On success, you open EFI_LOADED_IMAGE_PROTOCOL (LIP) on the ImageHandle
3. If its LoadOptions field is NULL and LoadOptionsSize is 0, then we got it right, so you proceed. If its not NULL, print it to see, what's there. have no clue what it can be there. because LoadImage() has nothing to write there.
4. Find Windows bootmgr.efi Load Option (BootXXXX) by enumerating these vars and searching in their FilePathList[0] for HD() node with exactly the same identifiers (UniquePartitionGUID for GPT, DiskId and Start for MBR)
5. On finding this LO and you already have its contents read as a buffer, calculate the address of its OptionalData part and pass it via LIP->LoadOptions, calculate its Size and fill in LIP->LoadOptionsSize as well.
ALSO, you might need to set BootCurrent variable with the index of BootXXXX, that corresponds to bootmgr.efi, since bootmgr.efi could use this mechanism to get its data. As we can see, there are many ways of how OSLs can be started and this complicates for them using persistently stored (in LO) information. Also, there are OSL launch scenarios, where the OS is not installed yet and where it is.
As an example. My loader distinguishes Preinstallation run and Postinstallation one, by reading the BootCurrent and analyzing contents of the corresponding BootXXXX variable. PreInst won't have any info set up by us in it, so we know, we are started not through LO processing, but manually - "Load From File" or via that poor efi\boot\bootxxxx.efi or whatever, but normal way and this is exactly the way of the preinstallation phase. If BootCurrent points to the LO, containing what we expect it to contain, then it's PostInstall, so we safely can read the needed info. I use FilePathList[1] for storing OS BootVolume HD() node (4.1) and System Root Directory (4.4). This is needed for finding where the OS is installed (I don't use my 2-part loader with my own "Boot Manager" as Windows does), but also - it allows to distinguish between PreInst and PostInst phases and serves as a marker of the LO, being ours (set by Installer). It's just an example to demonstrate, OSL needs to handle different scenarios of its launch. It is not brilliant, since, if a user launches our loader via, say, UEFI CLI, our loader would think it is PreInst or Live session and most probably will fail or even worse - would start the OS from the "setup" directory, that might screw up the already installed OS. Unlikely, but not too satisfying...
Windows doesn't use FPL[1], but it makes use OptionalData Load Option container (see the image below), storing there some BCD related stuff, basically, text represented GUID like BCD_OBJECT={<Guid here>}. I don't know, how bootmgr.efi handles this variety in the ways of starting it, but it apparently doesn't fail, when launched manually via UEFI Boot Manager or CLI, so, obviously, it uses some of the above - BootCurrent (may use it in PostInstall), LIP LoadOptions pointer. In the PreInst case, when started from UEFI CLI, LIP->LoadOptions points to the command line, you typed, when picking your .efi file. But bootmgr.efi doesn't fail in this case, doesn't it? Maybe it fails if LoadOptions is NULL?
1. If it's PostInstall, LoadOptions would have the contents of LO->OptionalData, set by the Windows Installer, apparently, it uses it.
2. If it's PreInstall, LoadOptions are not NULL and are filled with something, for example, as said above, for the CLI, it would be command like, so at least the image name would be there, kind of argv[0].
3. If it's a plain LoadImage() call, like you did, LoadOptions is NULL and then bootmgr.efi fails.
Try to pass to bootmgr.efi via LIP->LoadOptions the contents of its BootXXXX OptionalData buffer (1) or, at least its name (2). I feel, it will get healed.
You have touched a quite complex thing.