Почему запускается деструктор в данном примере?
Просто у вас ошибка в этой строке
MyString MyString::Copy(const char *string) { }
Вы возвращаете копию объекта. Он нигде не используется:
MyString::MyString(const char *string)
{
  Copy(string); // Вот здесь
}
И для этой копии вызывается деструктор.

А должно быть так:
MyString& MyString::Copy(const char *string) { }
В этом случае метод вернёт ссылку на объект. 

Но тогда вам следует объявить этот метод как статичный и назвать его не "копировать", а "создать":
class MyString {
   MyString(const char* string);
   static MyString* Create(const char *string);
};

MyString::MyString(const char *string)
{
  m_length = StrLen(string);
  m_string = new char[m_length + 1];

  if (m_string != nullptr) {
     for (size_t i = 0; i < m_length; ++i)
       m_string[i] = string[i];

       m_string[m_length] = '\0';
   }
}
   
MyString* MyString::Create(const char *string)
{
  return new MyString(string);
}
Обратите внимание - мой метод возвращает указатель. Это такое правило - никогда не возвращайте по ссылке объект, который создаётся внутри функции. Иначе потом будете рефакторить весь код.

В C++ нет стандартного соглашения, как должен поступить компилятор с объектом, возвращённым из функции по ссылке. Одни компиляторы такие объекты просто тихо удаляют, другие - нет.
И вот, у вас объект в памяти есть, вы его не удаляли, и он даже доступен. Всё работает.
Но потом вы создаёте какой-то новый объект, и у вас крашится программе при попытке доступа к прошлому объекту.
Что такое? Почему компилятор сгенерил код, который затирает память? Куда подевался объект в том месте программы, в котором раньше всё работало?
Просто физически нереально отыскать место возникновения ошибки через неделю, за которую вы написали ещё 30 тысяч строк кода. 
Поэтому всегда возвращайте объект из функции только по указателю.
Сам copy никак не вызывает деструктор
деструктор будет вызван автоматически по завершению области действия места где объект был создан, т.е. после завершения main 

Если же ты добавляешь *this в return Copy то у тебя возникает вызов деструктора этого возвращенного объекта, который не используется, а затем еще раз по завершению программы, собственно программа у тебя будет мусор на экран выводить вместо ожидаемого hi.

Добавь в деструктор вывод на экран "destructor", увидишь

Причина (если честно для меня не очевидная) в том что ты мешаешь два подхода - работа с объектами и работа со ссылкой на объект, преобразованный в сам объект (return *this), рекомендуется не мешать эти два подхода