Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ISCSI development tutorial on Windows

0.00/5 (No votes)
6 Apr 2013 1  
This article will help you to integrate ISCSI functionalities available on Windows with your base application in C++
Protected by Copyscape Unique Content Checker

Introduction

With the limited support available on how to use ISCSI (Small Computer System Interface over TCP/IP) features in our application when we require to add any network storage to our machine as a separate device or drive, I thought of writing an article which will make life easy for people who are trying to achieve this. As we already know, there is limited documentation provided by MSDN for the API support for ISCSI. Also, the documentation in itself is so complex that it's very hard for someone new to ISCSI to understand it. So, here is a sample code which will enable you to get familiar with ISCSI on Windows and perform almost all the tasks which can be performed through ISCSI initiator programmatically. The language I have used for development is C++. Here is a brief about the functionality to be achieved.

Understanding the Concept of ISCSI on Windows

As discussed, ISCSI stands for Small Computer System Interface over TCP/IP. The major help we get from ISCSI is that we can have the required space available to us on demand provided we have enough space available in the form of SAN/NAS devices on our network. For ISCSI to work on our machine, we need to have Microsoft ISCSI initiator to be installed on our machines. The good news is that Microsoft has started shipping the initiator from versions after VISTA but for running this code on XP, we need to download the initiator from here depending on the architecture of the system we are using.

After this, we need to have a machine on the network which has ISCSI target installed on it and that also has shared ISCSI device configured on it which can be attached to this machine. One of the tools available for this purpose is provided by starwind and you can always use its free version for your purpose. You can get the free version available here. Now after configuring the target on the network machine and installing Microsoft ISCSI initiator on your base machine, you can test the connection by adding a target portal through ISCSI initiator by entering the IP address of the Network machine on which the ISCSI target has been configured. As soon as the target portal is added, the list of targets available is also shown on the 'Targets' tab. You can select any one of them and log on to the 'Target'. If this device or target is having a file system format supported by Windows, this target is automatically attached to the base machine as a separate logical disc, or else if the format of the target is unknown then the connection is established but the logical disc is not detected. You can however go to the 'Disk Management' section under 'Computer Management ' to see the unformatted space associated with the target available to the base machine.

I think that this explanation will remove your initial bumps while trying to understand ISCSI on Windows platform. But the real question that arises is 'How to do all this programmatically?' This is explained in the next section.

Implementation

As all the functionality which can be performed with ISCSI initiator is available with 'iscsidsc.dll', we will first have to load the DLL in order to make use of the functionality given to us by it. This can be done simply as shown below. The ideal place to load this would be the constructor of the class which is loading the library. For instance I have a class CIscsi whose constructor and destructor would look something like this:

public:
// This will hold the handle obtained after loading the library
HMODULE m_hMod; // Class Variable

CIscsi()
{
 m_hMod= NULL;
 HRESULT hr = S_OK;

 /* Loading iSCSI DLL to get its pointer 

 /*************************************/

  m_hMod = LoadLibrary(_T("iscsidsc.dll"));

  if ( !m_hMod )

  {
    // do error handling here
    ::MessageBox(NULL,_T("iscsidsc.dll failed to load "),_T("Iscsi"),
        MB_OK|MB_ICONINFORMATION);
  }
}

~CIscsi()
{
  if (m_hMod)
  {
    FreeLibrary (m_hMod);
    m_hMod = NULL;
  }
}

Step By Step Explanation  

Now, with the DLL loaded, you will have to make sure that you use the functions available with 'iscsidsc.dll' in a proper manner. Perform these steps:

  1. In stdafx.h of your project, add these two lines:
    #include "iscsierr.h" // This will include all the error codes for ISCSI
    #include "iscsidsc.h" // This will bring all function definitions available
                          // with iscsidsc.dll 
  2. In in the header file of the class CIscsi, add the following statements:
    public: // Access specifier can be private or public depending on the need.
            // As I had made a DLL I wanted to expose these functions because
            // of which I made these public
    
    // this function will fetch the error code, 
    // thus will allow to detect the proper errors
    STDMETHOD(GetErrorString)(/*[out , retval]*/ BSTR *mresult);
    
    // function used to initialize or load iscsidsc.dll
    STDMETHOD(InitializeISCSI)(/*[in]*/ BSTR bstDllPath );
    //This function is responsible for adding a specified target portal 
    STDMETHOD(AddIscsiTargetPortal)( BSTR bstIPAddress,BSTR bstUserName ,
        BSTR bstPassWord ,/*[out]*/ BSTR* bstTargetList,/*[out]*/BSTR* bstStatus );
    // Function used to log on to a target
    STDMETHOD(LogOnToTargetPortal)( BSTR bstTargetIdName, BSTR* bstStatus );
    // Function used to log off from the target
    STDMETHOD(LogOffTargetPortal)( BSTR bstTargetIdName, BSTR* bstStatus );
    // Function we will use to remove the target portal
    STDMETHOD(RemoveIscsiTargetPortal)( BSTR bstIPAddress,/*[out]*/ BSTR* bstStatus );
  3. Similarly, add these statements on top of the implementation file or in the header file but in a global namespace:
    #define MAX_LOADSTRING 200
    WCHAR wchRetVal[4096];
    //doing all function definitions here 
    // These all are function definitions which help us getting function
    // pointers of the existing functions in iscsidsc.dll
    
    typedef HRESULT(__stdcall *AddTargetPortal)(TCHAR,ULONG,PISCSI_LOGIN_OPTIONS ,
        ISCSI_SECURITY_FLAGS,PISCSI_TARGET_PORTAL);
    
    typedef HRESULT(__stdcall *GetSessionList)
    	( ULONG* , ULONG* , PISCSI_SESSION_INFO );
    
    typedef HRESULT(__stdcall *LogonIscsiTarget) (TCHAR* ,BOOLEAN ,TCHAR*,ULONG ,
        PISCSI_TARGET_PORTAL,ISCSI_SECURITY_FLAGS, PISCSI_TARGET_MAPPING, 
    
    PISCSI_LOGIN_OPTIONS ,ULONG ,CHAR*,BOOLEAN,
    
    PISCSI_UNIQUE_SESSION_ID ,PISCSI_UNIQUE_CONNECTION_ID );
    
    typedef HRESULT(__stdcall *ReportTargets)(BOOLEAN, ULONG* , TCHAR*);
    
    typedef HRESULT(__stdcall *GetDevices)(PISCSI_UNIQUE_SESSION_ID, ULONG* ,
        PISCSI_DEVICE_ON_SESSION);
    
    typedef HRESULT(__stdcall *LogOutOfIscsiTarget)(PISCSI_UNIQUE_SESSION_ID);
    
    typedef HRESULT(__stdcall *RemoveIScsiTargetPortal)
    			(TCHAR*,ULONG,PISCSI_TARGET_PORTAL);
    
    typedef HRESULT(__stdcall *ReportSendTargetPortals)
    			(ULONG*, PISCSI_TARGET_PORTAL_INFO);
  4. Now in the implementation file of the class CIscsi i.e., the .cpp file, you will have the implementation of the functions declared in the header file which will allow us to add a target portal, log on to a specified available target and add it as a separate disc to the system. After that, log off from the target and finally detach the target portal from the system. You can make targets as persistent device too (means even if the base machine gets rebooted, the target which was added as a persistent device will remain attached to the system). This is also implemented in the code. All other functionalities can be added on similar guidelines provided these steps are performed systematically.

    The first function that is to be added according to the order of calling is 'AddIscsiTargetPortal' which will be responsible for adding the specified target portal. This function will take the following arguments:

    1. type (in parameter) 'ipaddress' which will specify the address of the Target Portal to be added. This will be a string, i.e. BSTR
    2. type (in parameter) 'username' which will again be a BSTR but will be required only when authentication is required, else this will be NULL.
    3. type (in parameter) 'password' again a BSTR which again will be required only when authentication is required, else this will also be NULL.
    4. type (out parameter) 'targetlist', As this is an out parameter, this returns the target list in the form of a pointer to string after the target portal has been successfully added.
    5. type (out parameter) 'status', This is the parameter which is returned as a pointer to the string indicating the status of the function.
    STDMETHODIMP CIscsi::AddIscsiTargetPortal( /*[in]*/BSTR bstIPAddress,
        /*[in]*/BSTR bstUserName ,/*[in]*/BSTR bstPassWord, 
    	/*[out]*/ BSTR* bstTargetList ,
        	BSTR* bstStatus )
    
    {
      // Handling Cstring is always better for string manipulation 
      // to include the features
      CString strFileName(bstIPAddress) ;
      // if not found do include <atlstr.h> in stdafx.h of the project
      CString strUserName(bstUserName); // converting username into CString
      CString strPassWord(bstPassWord); // converting password into CString
      HRESULT hr = S_OK;
    
      if (!m_hMod)// if handle to library is not found
      {
        CString strTemp1 = _T("Failed");	// returning status only 
    				//if this is used as a part of DLL
        *bstStatus = strTemp1.AllocSysString();
        return S_FALSE;
      }
      else
      {
    
       HMODULE hMod = m_hMod;
       GetSessionList fpGetSessionlist = NULL; // making a function pointer
       ISCSI_SESSION_INFO m_sessionInfo[250];
       if (bstIPAddress == NULL)
       {
         return S_FALSE;
       }
       else
       {
         CString strTargetList;
         if (((strFileName.GetString()!= NULL) && (strFileName.GetLength() > 0) ) )
         {
          AddTargetPortal fpGetTargetPortal = NULL;
          // taking the process address out of the 
          // function available in 'iscsidsc.dll'
          fpGetTargetPortal = ( AddTargetPortal )GetProcAddress(hMod,
              "AddIScsiSendTargetPortalW"); 
          if ( fpGetTargetPortal!= NULL )
          {
           // Initializing the login options required
           // this structure is required for successfully adding the target portals
           ISCSI_LOGIN_OPTIONS sLoginOptions ; 
           sLoginOptions.Version = ISCSI_LOGIN_OPTIONS_VERSION;
           sLoginOptions.AuthType = ISCSI_NO_AUTH_TYPE;
           // can select on your choice out of the options available
           sLoginOptions.DataDigest = ISCSI_DIGEST_TYPE_NONE ;
    							// on MSDN
           sLoginOptions.DefaultTime2Retain = 10; // can be of your choice
           sLoginOptions.DefaultTime2Wait = 10;  // can be of your choice
           sLoginOptions.HeaderDigest =ISCSI_DIGEST_TYPE_NONE ; //None should be there
           sLoginOptions.InformationSpecified = 
    	 ISCSI_LOGIN_OPTIONS_DEFAULT_TIME_2_RETAIN |
              ISCSI_LOGIN_OPTIONS_AUTH_TYPE |
              ISCSI_LOGIN_OPTIONS_DATA_DIGEST | 
    	 ISCSI_LOGIN_OPTIONS_DEFAULT_TIME_2_WAIT | 
              ISCSI_LOGIN_OPTIONS_HEADER_DIGEST ; 
    
          sLoginOptions.LoginFlags = ISCSI_LOGIN_FLAG_MULTIPATH_ENABLED;
          sLoginOptions.MaximumConnections = 1; // depending on your choice
          sLoginOptions.UsernameLength = NULL;// 2;
          sLoginOptions.Username =/* NULL;*/(UCHAR*) strUserName.GetString();
          sLoginOptions.Password = /*NULL;*/(UCHAR*) strPassWord.GetString();
          sLoginOptions.PasswordLength = NULL ;
    
         // Initializing of target Portals are required
    
    	// setting values of sTargetPortal to be given to the function pointer
         ISCSI_TARGET_PORTAL sTargetPortal;
         memset(sTargetPortal.Address ,0, 256);
         sTargetPortal.Socket = NULL;
         memset(sTargetPortal.SymbolicName ,0, 256);
         USHORT uSocket = 3260 ;// usually this port is used for Iscsi purposes
         CString cIpAddress = strFileName.GetString();// retrieving the IP address of 
    					    //target portal 
         CString cSymbolicName;
         wcscpy(sTargetPortal.Address, cIpAddress.GetString());
         wcscpy(sTargetPortal.SymbolicName, cSymbolicName.GetString());
         sTargetPortal.Socket = uSocket;
    
    
         hr = fpGetTargetPortal( NULL, ISCSI_ALL_INITIATOR_PORTS ,
             &sLoginOptions,NULL ,&sTargetPortal);// calling functional pointers
         if( hr == S_OK )
         {
            // calling to get the list of all available targets
              CString strtemp = reportZiscsiTargets();
            // returning list of all targets available  
            *bstTargetList = strtemp.AllocSysString(); 
            CString strTemp1 = _T("Success");
            // returning the status only in case if this is in a DLL
            *bstStatus = strTemp1.AllocSysString();
            return S_OK;
         }
         else
         {
            // calling to get the list of all available targets
            CString strtemp = reportZiscsiTargets();
            // returning list of all targets available  
            *bstTargetList = strtemp.AllocSysString();
            CString strTemp1 = _T("Failed");
            // returning the status only in case if this is in a DLL
            *bstStatus = strTemp1.AllocSysString();
            return S_FALSE;
         }
      }
      else
      {
      // do error handling here
      }
     }
     else
     {
              // calling to get the list of all available targets
      	  WCHAR* strtemp = reportZiscsiTargets();
              // returning the status only in case if this is in a DLL
      	  *bstTargetList = strtemp;
      return S_FALSE;
     }
    }
    }
    
    // this function is responsible for fetching all available ISCSI targets in the
    // network whose target portals have been added to the machine
    WCHAR* CIscsi::reportZiscsiTargets()
    {
      HRESULT hr = S_OK;
      HMODULE hMod = m_hMod;
      CString strTargetList;
      ReportTargets fpReportTargets =NULL;
      CString tTargets;
    
     // getting the procedure address of ReportTargets function 
     // available with iscsidsc.dll
      fpReportTargets = ( ReportTargets )GetProcAddress(hMod, "ReportIScsiTargetsW"); 
      if(fpReportTargets != NULL)
      {
    	// size can be allocated according to your need          
             // but this is sufficient for the purpose
    	  ULONG uBuffSizeForTargets = 2048 ;
    	  TCHAR  tBuff[2048];		 
    	  hr = fpReportTargets(TRUE,&uBuffSizeForTargets,
                   tBuff/*(TCHAR*)tTargets.GetString()*/);
    	  if(hr == S_OK)
    	  {  
    		  for (int i = 0 ; i< uBuffSizeForTargets;i++)
    		  {
    			  if (tBuff[i] != '\0')
    			  {
    				  tTargets.AppendChar(tBuff[i]);
    			  }
    			  else if ( tBuff[i] == '\0' && tBuff[i+1] != 0 )
    			  {
    				  tTargets.Append(_T("$*$"));
    			  }
    		  }
    		  //return (WCHAR*)tTargets.GetString();
    		   memset(wchRetVal,0,4096);
    		   wcscpy(wchRetVal,tTargets.GetString());
    		 // test= retStr;
    		  return wchRetVal;
    	  }
    	  else
    	  {		  
    		  return NULL;
    	  }
      }
      else
      {
    	  return NULL;
      }
    }
  5. Now as you have already added the target portal, you will need to log on to the specified target which you will like to attach to your base machine as a separate disc or drive. This can be done in a very simple manner as it is depicted below. This function will add the ISCSI target to the machine if the format is recognized by Windows:
    // this function takes the target name which is unique and returns the
    // status...(status should be used only when using the function as a  DLL )
    STDMETHODIMP CIscsi::LogOnToTargetPortal( BSTR bstTargetIdName, BSTR* bstStatus ) 
    {
        HRESULT hr = NULL;
        HMODULE hMod =NULL;
        UINT uCountForLogicalDrives = 0;
        if (!m_hMod)
        { 
            CString strTemp = _T("Failed");
            *bstStatus  = strTemp.AllocSysString();
            return S_FALSE;
        }
        hMod = m_hMod;
        CString strDriveListbeforeLogin ;
        CString strDriveListAfterLogin ;
    
        // get all mapped drives (in form of bits) 
        DWORD iLogicalDrives = ::GetLogicalDrives();
    
        for (int j=1, i=0; i< 26; i++)
        {
            // is drive bit set?
            if (((1 << i) & iLogicalDrives) != 0)
            {
                // Add drive to list
                CString str;
                str.Format(_T("%c"), i+'A');
                strDriveListbeforeLogin.Append(str);
                strDriveListbeforeLogin.Append(_T(","));
                j++;
                uCountForLogicalDrives++;
            }// finally we have all the drives available on the system
            // before we have actually established the connection
        }
        CString strTargetSelected(bstTargetIdName);
        LogonIscsiTarget fpLogonIscsiTarget = NULL;
        ISCSI_UNIQUE_SESSION_ID pUniqueSessionId;
        pUniqueSessionId.AdapterSpecific = NULL;
        pUniqueSessionId.AdapterUnique = NULL;
        ISCSI_UNIQUE_CONNECTION_ID pUniqueConnectionId;
        pUniqueConnectionId.AdapterSpecific = NULL;
        pUniqueConnectionId.AdapterUnique = NULL;
        fpLogonIscsiTarget = ( LogonIscsiTarget )GetProcAddress(hMod,
            "LoginIScsiTargetW");
        if(fpLogonIscsiTarget != NULL)
        {	
            // here there are many options available for ways to login on
            // MSDN ..I have chosen the most  basic of them// also targets
            // can be added in  a persistent manner too
            hr = fpLogonIscsiTarget((TCHAR*)strTargetSelected.GetString() 
    	   /*tTargets.GetString()*/,
                false,NULL ,ISCSI_ANY_INITIATOR_PORT,NULL/*&sTargetPortal*/,
                NULL,NULL,NULL/*&sLoginOptions*/,NULL,
                NULL,false,&pUniqueSessionId,&pUniqueConnectionId );
            if( hr == S_OK )
            {
                CString strTemp = _T("Success");
                *bstStatus  = strTemp.AllocSysString();
                SetCursor(LoadCursor(NULL, IDC_APPSTARTING));
    
                // Added deliberately as windows takes some time
                // to add the target as a drive 
                Sleep(10000);
    
                UINT uCountForLogicalDrivesAfterLogin = 0;
                SetCursor(LoadCursor(NULL, IDC_ARROW));
                // get all mapped drives (in form of bits) 
                DWORD iLogicalDrivesAfterlogin = ::GetLogicalDrives();
    
                for (int j=1, i=0; i< 26; i++)
                {
                    // is drive bit set?
                    if (((1 << i) & iLogicalDrivesAfterlogin) != 0)
                    {
                        // Add drive to list
                        CString str;
                        str.Format(_T("%c"), i+'A');
                        strDriveListAfterLogin.Append(str);
                        strDriveListAfterLogin.Append(_T(","));
                        j++;
                        uCountForLogicalDrivesAfterLogin++;
                    }// checking the number of drives after the target has been added
                }
                if (uCountForLogicalDrivesAfterLogin == uCountForLogicalDrives)
                {
                    if (strDriveListAfterLogin == strDriveListbeforeLogin)
                    {
                        //No Drive has been Added ...Means the
                        //File system remains unknown
                        ::MessageBox(NULL,_T(
                            "The logged on ISCSI target could" +
                            "not be mounted as it has an unknown" +
                            "format"),_T("CIscsi"),MB_OK);
                        return S_OK;
                    }
                }
                if (uCountForLogicalDrivesAfterLogin != uCountForLogicalDrives)
                {
                    if(strDriveListAfterLogin != strDriveListbeforeLogin)
                    {
                        if (strDriveListAfterLogin.GetLength() > 
    				strDriveListbeforeLogin.GetLength())
                        {
                            strDriveListAfterLogin.Replace
    			(strDriveListbeforeLogin.GetString(),_T(""));
                            WCHAR* tempBuff = (WCHAR*) 
    				strDriveListAfterLogin.GetString();
                            strDriveListAfterLogin = tempBuff[0];
                            CString strMsg ;
                            strMsg.Format(_T(
                              " Logical drive mounted :: %s .\n Do you want to open" +
                              "this drive in Explorer?"),
    			strDriveListAfterLogin.GetString());
                            tempBuff = NULL;
                            strDriveListAfterLogin.Append(_T(":\\"));
                            if (::MessageBox(NULL,strMsg,_T("Kush_Iscsi"),
    						MB_YESNO) == IDYES)
                            {
                                // This will open the drive on the choice of user
                                ShellExecuteA(NULL, "open",
                                    CW2A(strDriveListAfterLogin.GetString()), 
    			     NULL, NULL,
                                    SW_SHOWNORMAL);
                            }
                        }
                    }
                }
                return S_OK;
            }
            else
            {		
                CString strTemp = _T("Failed");
                *bstStatus  = strTemp.AllocSysString();
                return S_FALSE;
            }  
        }
        else
        {
            CString strTemp = _T("Failed");
            *bstStatus  = strTemp.AllocSysString();
            return S_FALSE;
        }
    }
  6. Being logged in and having performed all the operations, the application now wants to log off this target. Logging off the target will only succeed when the target is logged on. So, first a check to make sure that the target is logged on is done followed by the process of logging out. This can be done as under:
    )// will take the target Name on whom the log off function has to be performed
    
    STDMETHODIMP CIscsi::LogOffTargetPortal( BSTR bstTargetIdName, BSTR* bstStatus 
    {
        HRESULT hr = S_OK;
        GetSessionList fpGetSessionlist = NULL;
        ISCSI_SESSION_INFO m_sessionInfo[125];
        if (!m_hMod)
        {
            CString strTemp = _T("Failed");
            *bstStatus = strTemp.AllocSysString();	
            return S_FALSE;
        }
    
        HMODULE hMod = m_hMod;
    
        ///Check if the target is already connected or is it not connected
        // As GetSessionApi is failing and not returning us the required result 
        // we will do a work around and will try to login if the login is not 
        //succeeded it will return -268500929 which will tell us that login has
        // already been done
        // and we can  remove it but if login succeeds we will immediately log out
        // using the session id we got and 
        /// will give a msg that the machine is not connected..
    
        CString strTargetSelected (bstTargetIdName);
        LogonIscsiTarget fpLogonIscsiTarget = NULL;
        ISCSI_UNIQUE_SESSION_ID pUniqueSessionId;
        pUniqueSessionId.AdapterSpecific = NULL;
        pUniqueSessionId.AdapterUnique = NULL;
        ISCSI_UNIQUE_CONNECTION_ID pUniqueConnectionId;
        pUniqueConnectionId.AdapterSpecific = NULL;
        pUniqueConnectionId.AdapterUnique = NULL;
        fpLogonIscsiTarget = ( LogonIscsiTarget )GetProcAddress(hMod,
            "LoginIScsiTargetW");
        if(fpLogonIscsiTarget != NULL)
        {		
            hr = fpLogonIscsiTarget((TCHAR*)strTargetSelected.GetString() 
    					/*tTargets.GetString()*/,
                false,NULL ,ISCSI_ANY_INITIATOR_PORT,NULL/*&sTargetPortal*/,NULL,
                NULL,NULL/*&sLoginOptions*/,NULL,
                NULL,false,&pUniqueSessionId,&pUniqueConnectionId );
            if(hr == S_OK)
            {
                LogOutOfIscsiTarget fpLogOut;
                fpLogOut = ( LogOutOfIscsiTarget  )GetProcAddress(hMod,
                    "LogoutIScsiTarget");
                if(fpLogOut)
                {
                    hr = fpLogOut( &pUniqueSessionId);
                    if (hr == S_OK)
                    {
                        CString strTemp = _T("Target Not Connected");
                        *bstStatus = strTemp.AllocSysString();
                        return S_FALSE;
                    }
                    else
                    {	
                        // do error handling here 
                    }  
                }
            }
            else if (hr == -268500929 || hr != S_OK )
            {
                fpGetSessionlist = ( GetSessionList )GetProcAddress(hMod,
                    "GetIScsiSessionListW"); // getting ISCSI session list
                if ( fpGetSessionlist != NULL )
                {
                    ULONG uBuffSize =  sizeof(m_sessionInfo);
                    ULONG uSessionCount = 0;
                    hr = fpGetSessionlist(&uBuffSize,&uSessionCount,
                        m_sessionInfo);
                    if( hr == S_OK  && uSessionCount != 0)
                    {
                        CString strTemp; 
                        int iCmp  = 1;
                        for ( int i = 0 ; i <  uSessionCount ; i ++ )
                        {
                            strTemp.Empty();
                            strTemp = m_sessionInfo[i].TargetName;
                            iCmp = wcscmp(m_sessionInfo[i].TargetName,
                                strTargetSelected.GetString());
                            if (iCmp == 0)
                            {
                                LogOutOfIscsiTarget fpLogOut;
                                fpLogOut = ( LogOutOfIscsiTarget  )GetProcAddress(hMod,
                                    "LogoutIScsiTarget");
                                if(fpLogOut)
                                { 
                                    hr = fpLogOut( &m_sessionInfo[i].SessionId);
                                    if (hr == S_OK)
                                    {
                                        CString strTemp = _T("Success");//returned 
    						//only when making a DLL
                                        *bstStatus = strTemp.AllocSysString();
                                        return S_OK;
                                    }	
                                    else
                                    {
                                        CString strTemp = _T("Failed");
                                        *bstStatus = strTemp.AllocSysString();
                                        ::MessageBox(NULL,_T(
                                            "The specified Target could not be logged" +
                                            "off as the drive associated 
    					with this target" +
                                            "might be in use.\n Close all the windows" +
                                            "opened in the Explorer and try again  "),
                                            _T("kush_Iscsi"),MB_OK|MB_ICONINFORMATION);
                                       	// frankly speaking I could not find any other
                                        	// way to detect if a drive is 
    			        	// opened in explorer
                                        	//with none of its file in operation
                                        return S_FALSE;
                                    }
                                }
                                else
                                {
                                    // error handling
                                }
                            }
                            else
                            {
                                // error handling
                            }
                        }
                    }
                    else
                    {
                        // error handling
                        CString strTemp = _T("Failed");
                        *bstStatus = strTemp.AllocSysString();
                        return S_FALSE;
                    }
                }
            }
        }
        else
        {
            // error handling
            CString strTemp = _T("Failed");
            *bstStatus = strTemp.AllocSysString();
            return S_FALSE;
        }
    }
  7. Finally if the user wants to remove the added target portal all together, he can do it freely in the following manner. This is described below with the code:
    // this takes the IP address of the target portal  to be removed from the
    // added list of target portals
    STDMETHODIMP CIscsi::RemoveIscsiTargetPortal( /*[in]*/BSTR  
    				bstIPAddress,BSTR* bstStatus )
    {
        HRESULT hr = S_OK;
        if (!m_hMod)// if no handle return from here
        {
            // this has to be done only in case when a  DLL is made and status
            // has to be returned in form of string
            CString strtemp = _T("Failed"); 
            *bstStatus = strtemp.AllocSysString();
            return S_FALSE;
        }
    
        HMODULE hMod = m_hMod;
        CString strListText(bstIPAddress);
    
        // Compare  the IP  selected with the addresses of ISCSI_TARGET_INFO
        // By using Report Target Portal
        ReportSendTargetPortals fpSendTargetportals = NULL;
        // getting the procedure address of function available called
        // ReportIScsiSendTargetPortals
        fpSendTargetportals = ( ReportSendTargetPortals )GetProcAddress(hMod,
            "ReportIScsiSendTargetPortalsW");
        if ( fpSendTargetportals )
        {
            ULONG uPortalCount = 0;
            //CString strTargetAddress = NULL;
            // getting the count of target portals for correct allocation of memory
            hr = fpSendTargetportals(&uPortalCount,NULL/*&pTargetPortalInfo*/);
            if (hr == S_OK || hr == 122)
            {
                ISCSI_TARGET_PORTAL_INFO *pTargetPortalInfo = 
                    new ISCSI_TARGET_PORTAL_INFO [uPortalCount];
                ISCSI_TARGET_PORTAL pFinalTargetPortal;
                ULONG uPortNumber = 0;
                int iCount = 0;
                // now calking to get the list of  target portals available
                hr = fpSendTargetportals(&uPortalCount,pTargetPortalInfo);
                if (hr == S_OK && uPortalCount > 0)
                {
                    for ( int i = 0 ; i < uPortalCount ; i++ )
                    {
                        // comparing as we have to remove the specified target portal 
                        int iCmp = wcscmp(pTargetPortalInfo[i].Address,
                            strListText.GetString());
                        if ( iCmp == 0 )// match found
                        {
                            wcscpy( pFinalTargetPortal.Address ,
                                pTargetPortalInfo[i].Address );
                            pFinalTargetPortal.Socket = pTargetPortalInfo[i].Socket ;
                            wcscpy( pFinalTargetPortal.SymbolicName ,
                                pTargetPortalInfo[i].SymbolicName );
                            // copying all essential information
                            uPortNumber = pTargetPortalInfo[i].InitiatorPortNumber;
                            iCount = i;
                            break;
                        }
                        else
                        {
                            //keep comparing
                        }
                    }
    
                    RemoveIScsiTargetPortal fpRemoveIscsiTargetPortal = NULL;
                    fpRemoveIscsiTargetPortal = 
    			( RemoveIScsiTargetPortal )GetProcAddress(hMod,
                        "RemoveIScsiSendTargetPortalW");
                    if ( fpRemoveIscsiTargetPortal )
                    {
                        // finally calling the function to remove the specified target
                        // portal by supplying the correct details with target portal
                        hr = fpRemoveIscsiTargetPortal( NULL,
    				uPortNumber,&pFinalTargetPortal);
                        if (hr == S_OK )
                        {
                            CString strtemp = _T("Success"); // returned in case of DLL
                            *bstStatus = strtemp.AllocSysString();
                            return S_OK;
    
                        }
                        else
                        {
                            CString strtemp = _T("Failed");// returned in case of DLL
                            *bstStatus = strtemp.AllocSysString();
                            return S_FALSE;
                        }
                    }
                    else
                    {
                        // logging and error handling
                    }
                }
            }
        }
    }

Conclusion

Thus, these are the basic functionalities which can be achieved through the wonderful tool provided by Microsoft. There is a lot more stuff which can be done though ISCSI which I have not covered here as this was meant to be only an initial tutorial on the basic implementation for ISCSI usage on Windows. I hope I covered the basic explanation from a developer's point of view and also the development part of it. In case of any queries, you are free to email me at tiwari.kushagra@gmail.com. I will get back to you as soon as possible.

Related Links

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here