Menu Górne
Publish server
source: pexels.com

Z jednej strony w dobie CI/CD ten post wydaje się zbędny. Ale rzeczywistość, przynajmniej ta wokół mnie, jest troszkę inna. Nadal bardzo dużo web aplikacji publikujemy na serwerze IIS „ręcznie” z Visual Studio. Wiem, wiem jest oczywiście możliwość konfiguracji profilu publikacji dla IIS ale tam trzeba podać login i hasło, w przypadku gdy administrator naszej domeny wymaga częstej zmiany hasła to dość słabe rozwiązanie.

Kopiowanie ręczne

Publikacja do folderu i kopiowanie go na serwer potrafi być dość pracochłonne. Mimo tego bardzo często spotykam się z takim rozwiązaniem, kiedy programista tworzy profil publikacji do folderu lokalnego, a następnie folder ten kopiuje na serwer. Czasami folder pakujemy np. zipem. Powodów takiego zachowania może być kilka: czasami jest to niewiedza że można inaczej, czasami „brak czasu” na automatyzacje (sic!) co powoduje że defacto więcej czasu REGULARNIE tracimy logując się na serwer itd….

Publikacja przez Visual Studio

Pierwszą automatyzacją może być wrzucanie plików bezpośrednio na serwer. Możemy to zrobić w bardzo prosty sposób wystarczy w profilu publikacji zamiast folderu lokalnego użyć ścieżki sieciowej:

Przykładowa ścieżka

Możemy jak powyżej użyć adresu IP serwera lub jego nazwy np. „\serwer.domena.local\c$\inetpub\wwwroot\MojaAplikacja”. To spowoduje umieszczenie plików bezpośrednio na zasobie sieciowym.

Odkładanie wersji

Mając systemy kontroli wersji nie musimy się martwić o historię i jesteśmy w stanie przywrócić starą wersję kodu. Jednak to wymaga powrotu do ostatniego releas’a, kompilacji, publikacji… Zdecydowana większość osób które znam robi kopie działającej wersji do katalogu obok folderu produkcyjnego oznaczając ją wersją releasa, lub datą (sam tak robię :)). A co w przypadku publikacji bezpośrednio do zasobu sieciowego z Visual Studio? Czy trzeba wchodzić na serwer przed publikacją i odkładać działającą wersję? Na szczęście nie trzeba 🙂 Możemy dodać do pliku *.pubxml dodatkowe sekcje (pod końcem PropertyGroup):

 <Target Name="CustomBeforePublish">
	  <PropertyGroup>
	  <CurrentDate>$([System.DateTime]::Now.ToString(yyyy-MM-dd))</CurrentDate>
	  </PropertyGroup>
  <Message Text="********************************** Create folder backup ***********************************" Importance="high"/>
  <Exec Command="powershell -Command "Copy-Item \"\\10.10.10.27\c$\inetpub\wwwroot\MojaAplikacja\" -Destination \"\\10.10.10.27\c$\inetpub\wwwroot\MojaAplikacja - $(CurrentDate)\" -Recurse"" />
  <Message Text="********************************** Folder created ***********************************" Importance="high"/>
</Target>
<PropertyGroup>
    <PipelineDependsOn>
    CustomBeforePublish;
    $(PipelineDependsOn);
  </PipelineDependsOn>
  </PropertyGroup>

W sekcji target istotne są tak naprawdę 3 linie: 5, 6 oraz 7. Linie 5 i 7 wyświetlają jedynie informację w konsoli publikacji w Visual Studio. Linia 6 to natomiast linia która pozwala na uruchomienie komend (cmd). W tej linii wykonujemy całą naszą pracę, czyli: uruchamiamy powershell (powershell -Command), już w powershellu kopiujemy folder przez Copy-Item z parametrami lokalizacji kopiowania. Na te chwilę powershell nie jest niezbędny (można to zrobić przez zwykłe xcopy). Kolejna sekcja (PipelineDependsOn) wskazuje że wykonanie skryptu jest niepomijalne tzn. skrypt musi się wykonać prawidłowo aby publikacja doszła do skutku. Jeżeli skrypt zwróci błąd publikacja zostanie przerwana a użytkownik dostanie powiadomienie o błędzie.

A można to jeszcze przyspieszyć?

Kopiowanie plików z folderu do folderu nie jest zbyt szybkie (szczególnie jeśli mamy w projekcie dużą liczb plików np. *.js), a poza tym jaki jest sens kopiowania plików skoro źródło zaraz nadpiszemy? Niestety mamy tu pewne ograniczenie, bo pliki są używane przez serwer IIS. Aby zrobić to szybciej potrzebny będzie nam nowy skrypt, np.:

Invoke-Command -ComputerName "serwer.domena.local" 
-ScriptBlock {import-module WebAdministration; 
Stop-WebAppPool -Name "MojaAplikacja"; 
Start-Sleep -s 5; 
Rename-Item -Path "\\serwer.domena.local\c$\inetpub\wwwroot\MojaAplikacja" -NewName "MojaAplikacja - CurrentDate"; 
New-Item -Path "\\serwer.domena.local\c$\inetpub\wwwroot\" -Name "MojaAplikacja" -ItemType "directory"; 
Start-WebAppPool -Name "MojaAplikacja";}

Wytłumaczę działanie powyższego skryptu zanim przekształcimy go w mniej czytelną formę. A więc w linii numer 1 mamy Invoke-Command który wywołuje wszystkie komendy. Uruchamiamy go z dwoma parametrami: pierwszy określa komputer na jakim chcemy wykonać komendy, drugi zawiera same komendy. Import-module (linia 2) importuje nam moduł odpowiedzialny za obsługę serwera IIS, Stop-WebAppPool zatrzymuje nam pule aplikacji abyśmy mogli zmienić nazwę katalogu aplikacji (linia 5) dodając do nazwy aktualną datę, wcześniej czekamy 5s (linia 4) na zatrzymanie puli aplikacji. W kolejnym kroku tworzymy folder którego wcześniej „usunęliśmy” przez zmianę nazwy (linia 6) oraz uruchamiamy ponownie pulę aplikacji (linia 7).

Dostosowanie do użytku w pliku *.pubxml

Nie możemy umieścić powyższego skryptu bezpośrednio w pliku *.pubxml. Tak jak wcześniej wspomniałem jest tam uruchamiany cmd a nie powershell więc musimy dodać (jak wcześniej) polecenie „powershell -Command”. Dodatkowo jak wiemy w xml nie możemy umieścić znaków ” (cudzysłów), więc musimy je zastąpić escape sequence (&qu0t;). Dodatkowy escape character jest potrzebny w sekcji powershell’owej (\). Kod po zmianach powinien wyglądać tak:

  <Target Name="CustomBeforePublish">
    <PropertyGroup>
      <CurrentDate>$([System.DateTime]::Now.ToString(yyyy-MM-dd))</CurrentDate>
      <RemoteServer>serwer.domena.local</RemoteServer>
      <AppPoolName>MojaAplikacja</AppPoolName>
      <FolderName>Moja Aplikacja</FolderName>
    </PropertyGroup>
    <Message Text="********************************** Preparing environment ***********************************" Importance="high" />
    <Exec Command="powershell -Command "Invoke-Command -ComputerName \"$(RemoteServer)\" -ScriptBlock {import-module WebAdministration; Stop-WebAppPool -Name \"$(AppPoolName)\"; Start-Sleep -s 5; Rename-Item -Path \"\\$(RemoteServer)\c$\inetpub\wwwroot\$(FolderName)\" -NewName \"$(FolderName) - $(CurrentDate)\"; New-Item -Path \"\\$(RemoteServer)\c$\inetpub\wwwroot\" -Name \"$(FolderName)\" -ItemType \"directory\"; Start-WebAppPool -Name \"$(AppPoolName)\";}"" />
  </Target>

Efekt

Po zapisaniu (i dostosowaniu) pliku *.pubxml efekt będzie następujący: gdy klikniemy publish w Visual Studio wybierając profil który edytowaliśmy wykonają się następujące akcje:
1. Aplikacja się zbuduje.
2. Skrypt zatrzyma pulę aplikacji.
3. Pięć sekund później zmieni nazwę folderu aplikacji na „NazwaAplikacji – <aktualna data>”
4. Utworzy folder „Nazwa Aplikacji” Uruchomi ponownie pulę aplikacji.
5. Wykona się standardowe kopiowanie plików – deploy aplikacji.

Podsumowanie

Zdaję sobie sprawę że rozwiązanie nie jest dla każdego, ma swoje wady, np. to że w przypadku bardzo wielu plików spakowanie folderu i rozpakowanie go na serwerze może potrwać krócej, ale osobiście wolę kliknąć publikuj i iść na kawę niż siedzieć i kopiować pliki na serwerze. Skrypt jest rozwijalny, więc drogi czytelniku bierz zmienia dostosuj i używaj do woli 🙂

publish profiles (MS Docs)
Invoke-Command(MS Docs)

O autorze

Niepoprawny optymista. 100 pomysłów na sekundę, wielbiciel nowych technologii, nie tylko z rodziny .Net. Często nosi przy sobie jabłko, takie nadgryzione... ;)

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Możesz używać znaczników języka HTML i ich atrybutów: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Zamknij