Working out statistics for unique users
I don't think you need a cursor, looks like a simple update:

 WITH cte AS
  ( SELECT -- do all the counts using conditional aggregation
       ModuleID 
       ,Count(DISTINCT ID) AS Started
       ,Count(DISTINCT CASE WHEN ModuleDatecomplete &lt;&gt; '' THEN ID END) AS Completed
       ,Count(DISTINCT CASE WHEN Pass = 'Yes' THEN ID END) AS Passed
    FROM TblResults
    GROUP BY ModuleID
  )
 UPDATE TblModules
 FROM cte 
 SET 
   Started = cte.Started
   ,Completed = cte.Completed
   ,Passed = cte.Passed
   ,PassedPercent = ((cte.Passed * 100.0) / cte.Started)
 WHERE 
    ModuleID = cte.ModuleID 


Of course this is fully untested, but the syntax should be ok as-is.

Edit:

In 99+% rewriting a cursor with set-based SQL improves performance.
In your case you run three Selects and one Update per row, e.g. for 100 rows returned by the cursor Select the DBMS needs to execute 400 statements sequentially, 300 Selects and 100 Updates. Combining the three SETs into one using conditional aggregation would reduce the amount of work to 100 Selects and 100 Updates and result in approx. half the runtime. But a single Update might run 10 or 100 or 1000 times faster.