Vue normale

Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.
À partir d’avant-hierTomasz Poszytek, Business Applications MVP

Aktualizacja Power Automate Desktop do v2 schema

Ogłoszony wraz z wersją PAD z lutego 2023 r. nowy schemat Microsoft Dataverse do przechowywania danych Power Automate Desktop, tzw. „v2”, wymaga uaktualnienia wszystkich przepływów pulpitu do pierwszego kwartału 2024 r. Ja już to zrobiłem i stanąłem przed kilkoma wyzwaniami, którymi chciałbym podzielić się z Tobą.

Nowy schemat w wersji 2 „ułatwia pracę z interfejsami API Dataverse i umożliwia przyszłe ulepszenia produktów dzięki przepływom pulpitu. Nowy schemat danych jest publicznie dostępny wraz z usługą Power Automate dla komputerów stacjonarnych (wersja 2.29)” i nowszymi. Przeczytaj więcej na ten temat tutaj: https://learn.microsoft.com/en-us/power-automate/desktop-flows/schema.

Kroki do zaktualizowania do schema v2

  1. Zainstaluj najnowszą wersję Power Automate Desktop (musi być v2.29 lub nowsza).
  2. Zaktualizuj rozszerzenie Power Automate dla przeglądarki, aby używało Manifest V3 przed lipcem 2022 (https://learn.microsoft.com/en-us/power-automate/desktop-flows/manifest).
Manifest V3 extension

https://learn.microsoft.com/en-us/power-automate/desktop-flows/install-browser-extensions

  1. W Power Platform Admin Center przejdź do Environments > Settings > Product > Features > Enable storage of desktop flow files into v2 schema i przestaw na „On”:
Enable desktop flow schema
  1. Otwórz i zapisz ponownie każdy desktop flow, który chcesz zaktualizować do schematu w wersji v2.
  2. Jeśli planujesz przenieść przepływy pulpitu do innego środowiska, przejdź do solucji, w których są one przechowywane i kliknij, aby dodać dla nich wymagane obiekty.
Add required objects for a desktop flow
  1. Ta akcja spowoduje dodanie „Desktop Flow Binaries” do solucji, które jeśli nieobecne, spowodują błędy w uruchomieniach przepływów pulpitu. Liczba binarek może się różnić. W przypadku dużych przepływów może to być nawet blisko 100.
Desktop Flow Binary

I to tyle 😉

Problemy po aktualizacji

Problemy, z którymi spotkałem się w całym procesie aktualizacji, dotyczyły raczej tylko sytuacji po zakończeniu aktualizacji. Zasadniczo – wokół uruchamiania zaktualizowanych procesów. A przede wszystkim dlatego, że lista plików binarnych Desktop Flow była niekompletna we wdrożonym rozwiązaniu lub zostały one ponownie dodane po kolejnym wdrożeniu (czyli ich liczba na środowisku źrodłowym vs. docelowym była inna).

Nowo zaktualizowane przepływy pulpitu nie powiodły się z powodu błędów wymienionych poniżej (tylko przykłady, mogą być niekompletne):

{
  "error": {
    "code": "MalformedFlowError",
    "message": "Malformed flow detected: ControlRepository"
  }
}

Wystąpiły również inne błędy związane z XrmApiRequestFailed, np.:

Encountered an unexpected error during Desktop flow initialization: 'Microsoft.Flow.RPA.Desktop.Robin.Core.Packager.Exceptions.PackagerValidationException: A validation error occurred during project unbundling.
at Microsoft.Flow.RPA.Desktop.Robin.Core.Packager.ProjectValidator.CreateAndReturnFinalValidationResult()
at Microsoft.Flow.RPA.Desktop.Robin.Core.Packager.ProjectValidator.ValidateProject()
at Microsoft.Flow.RPA.Desktop.Robin.Engine.Packager.PackageProvider.<>c__DisplayClass12_0.b__0()
at Microsoft.Flow.RPA.Desktop.Robin.Engine.Packager.PackageProvider.ExecuteAndLogOperation(String methodName, Action action, Func1 func) at Microsoft.Flow.RPA.Desktop.Robin.Engine.Packager.PackageHelper.PackProjectToDirectory(Project project, String directory, Guid flowId) at Microsoft.Flow.RPA.Agent.Server.Components.ScriptEngines.RobinEngine.DataResolvers.DataResolverV2.GetFlowDataAndSetState(Guid scriptId, Guid runId, Dictionary2 flowInfoCache, String directory)
at Microsoft.Flow.RPA.Agent.Server.Components.ScriptEngines.RobinEngine.RobinScriptProvider.ResolvePackagePath(Guid flowId)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.Flow.RPA.Agent.Server.Components.ScriptEngines.RobinEngine.RobinEngine.d__21.MoveNext()'.

Jednak były one bardzo zagmatwane i dlatego uznałem, że wszystkie są związane z procesem aktualizacji.

Rozwiązywanie problemów

Pierwszym krokiem jest sprawdzenie, czy wykonałeś kroki 5 i 6 powyżej, aby Twoje rozwiązanie zawierało pliki binarne Desktop Flow. Jeśli tak, ale nadal się kończy błędem, być może liczba plików binarnych między środowiskiem źródłowym i docelowym jest różna. W takim przypadku możesz spróbować ręcznie zaimportować solucję, korzystając z klasycznego interfejsu:

Swotch to classic interface

A następnie wybierz Upgrade (ponieważ ta czynność powinna usunąć komponenty nieobecne w twoim nowym wdrożeniu, więc w tym przypadku dodatkowe pliki binarne Desktop Flow) i Overwrite customizations (aby upewnić się, że żadne niezarządzane warstwy nie kolidują z nową wersją):

Classic solution import

Jeśli jednak to podejście również nie zadziała, jedynym rozwiązaniem, jakie znalazłem, jest usunięcie istniejącej solucji i ponowne wdrożenie.

Wzorzec ponownego uruchamiania przepływu, jeśli kod odpowiedzi to 400

Często zdarza się, że instancja przepływu desktopowego kończy się niepowodzeniem, a akcja w przepływie w chmurze, która uruchomiła RPA, wyświetla w odpowiedzi kod statusu „bad request” (400). Niektóre z możliwych przyczyn i możliwych rozwiązań można znaleźć tutaj. Ale liczba możliwych przyczyn jest znacznie większa. Wszystkie możliwe błędy można znaleźć w raporcie aktywności Desktop Flow:

Desktop flow activity report errors

Ale często chciałbyś, aby RPA zostało uruchomione ponownie, a nie zakończone niepowodzeniem. Możesz zastosować moje podejście tworzenia przepływu w chmurze, który uruchamia przepływ na pulpicie ponownie, jeśli ten kończy się kodem 400:

Pattern to run desktop flow as long as it ends up with bad requests

  1. Zmienna var_BotStatus służy do sterowania pętlą. Jest inicjowana wartością „Error”.
  2. Pętla działa tak długo, jak var_BotStatus jest równa „Error”, ale…
  3. Nie dłużej niż 1 godzina lub 4 razy – czyli aby cały proces nie ciągnął się w nieskończoność.
  4. Jeśli akcja wyzwalająca RPA zakończy się błędem, uruchamiana jest lewa gałąź, która sprawdza, czy outputs('Run_PAD_RPA')?['statusCode'] jest równe 400.
  5. Jeśli tak, var_BotStatus zachowuje wartość „Error”,
  6. W przeciwnym razie jest ustawiony na „OK”, co kończy pętlę.
  7. Jeśli akcja RPA zakończy się sukcesem, to…
  8. var_BotStatus jest ustawiony na „OK”, co kończy pętlę.

I to wszystko. Mam nadzieję, że pomoże Ci to stworzyć bardziej niezawodne procesy.

Artykuł Aktualizacja Power Automate Desktop do v2 schema pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Tworzenie elementu listy SharePoint w podfolderze używając SPO REST API

W tym poście chciałbym podzielić się z Wami tym, jak łatwo utworzyć element na liście, ale nie bezpośrednio razem z innymi elementami, ale w podfolderze.

Problem

Próba utworzenia elementu wewnątrz podfolderu na liście jest absolutnie niemożliwa przy użyciu istniejącej akcji SharePoint Online.

Create item action

Interfejs akcji nie pozwala na wybór ścieżki, pod którą ma zostać utworzony element. Najlepszym rozwiązaniem jest użycie API REST usługi SharePoint Online.

Użycie REST API SharePoint

Endpointem, którego należy użyć do wykonania takiej operacji, jest /AddValidateUpdateItemUsingPath z użyciem żądania POST:

Send HTTP Request to SharePoint
  1. Powinno być to żądanie POST
  2. Możesz wypróbować uri „GetByTitle”, chociaż nie mogłem sprawić, by to działało, więc przełączyłem się na guid i jest w porządku 🙂
  3. Użyj nagłówka Accept dla danych zwracanych z wykonanego żądania i Content-Type, aby poinformować punkt końcowy, jaki typ danych jest wysyłany. Używam ;odata=nometadata, aby otrzymać w odpowiedzi tylko minimalną ilość informacji.
  4. Parametr DecodedUrl określa ścieżkę do podfolderu, w którym ma zostać utworzony element.
  5. Lista FieldName + FieldValue może być wykorzystana do stworzenia elementu z metadanymi. Pamiętaj tylko, że musi to być prawidłowy JSON, więc jeśli jakaś wartość zawiera cudzysłowy, należy je wyescapeować.

Generalnie, aby ustawić wartość dla podstawowych typów kolumn, użyj kodu JSON jak poniżej:

   "formValues": [
    {
      "FieldName": "Title",
      "FieldValue": "VALUE"
    },
    {
      "FieldName": "SingleChoice",
      "FieldValue": "VALUE 1"
    },
    {
      "FieldName": "MultiChoice",
      "FieldValue": "VALUE 1;VALUE 2;VALUE 3"
    },
    {
      "FieldName": "DateTime",
      "FieldValue": "DATE IN CORRECT FORMAT"
    },
    {
      "FieldName": "RichText",
      "FieldValue": "VALUE - REMEMBER TO ESCAPE QUOTES!"
    },
    {
      "FieldName": "Number",
      "FieldValue": "1000.00"
    },
    {
      "FieldName": "YesNo",
      "FieldValue": "True or False"
    },
    {
      "FieldName": "SingleLookup",
      "FieldValue": "1"
    },
    {
      "FieldName": "MultiLookup",
      "FieldValue": "I FAILED HERE :("
    },
    {
      "FieldName": "MultiplePerson",
      "FieldValue": "I FAILED HERE AS WELL"
    }
  ]

Ważne! Nie udało mi się pomyślnie wypełnić danych w złożonych kolumnach, takich jak lookup, osoba lub zarządzane metadane. Moje rozwiązanie dla tego problemu znajdziesz poniżej poniżej.

Po pomyślnym wykonaniu akcji treść odpowiedzi będzie zawierała dane dla każdej wypełnionej kolumny wraz z identyfikatorem stworzonego elementu:

{
  "value": [
    {
      "ErrorCode": 0,
      "ErrorMessage": null,
      "FieldName": "Title",
      "FieldValue": "Test 1",
      "HasException": false,
      "ItemId": 0
    },
    {
      "ErrorCode": 0,
      "ErrorMessage": null,
      "FieldName": "Id",
      "FieldValue": "0",
      "HasException": false,
      "ItemId": 0
    }
  ]
}

Ważne! Nawet jeśli kod odpowiedzi wynosi 200, co sugeruje, że żądanie zakończyło się powodzeniem, elementu może nie zostać utworzony. Sprawdź treść odpowiedzi pod kątem komunikatów o błędach.

Jeśli wystąpi błąd, kolumna, która go spowodowała, będzie zawierać następującą odpowiedź JSON:

    {
      "ErrorCode": 0,
      "ErrorMessage": "Value does not fall within the expected range.",
      "FieldName": "Lookup",
      "FieldValue": "[1;2]",
      "HasException": true,
      "ItemId": 0
    },

Aby uzyskać wszystkie pola, w których wystąpił błąd podczas tworzenia elementu, możesz użyć akcji „Filter” dla body('Send an HTTP request to SharePoint')?['value'] i filtrować @equals(item() ?['HasException'], true):

Filter response to get array of errors.

W zamian otrzymasz tablicę wszystkich pól, które spowodowały błędy.

Ustaw wartości pól złożonych

Próbowałem ustawić wartości (ale nie udało mi się to) pola, takie jak:

  • Lookup
  • Person
  • Managed metadata

Ale udało mi się ustawić tylko wartość w kolumnie single-lookup 🙁 Więc wpadłem na inny pomysł. Użyj powyższej akcji, aby utworzyć element pod żądaną ścieżką podfolderu, a następnie pobrałem jego identyfikator i użyłem zwykłego „Update item”, aby ustawić żądane wartości nawet dla złożonych typów pól:

Update create item

Osiągnałem to ponownie używając akcji „Filter” dla body('Send an HTTP request to SharePoint')?['value'] i następnie filtrując @equals(item()?['FieldName'], 'Id'). Następnie możesz uzyskać wartość Id korzystając z poniższego wyrażenia:
body('Filter')?[0]?['FieldValue'].

I to wszystko! Daj mi znać, jak to działa u Ciebie.

Artykuł Tworzenie elementu listy SharePoint w podfolderze używając SPO REST API pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Kopiowanie załączników z taska do załączników elementu listy

W tym krótkim poście pomogę Ci zbudować przepływ pracy w Nintex for Office 365, który pozwoli Ci skopiować załączniki, które zostały dodane do zadania, do załączników powiązanego elementu listy. Element listy jest oczywiście tym, wokół którego działa cały przepływ pracy.

Formularz zadania

Najpierw musisz zbudować formularz zadania.

Type of the Nintex for Office 365 form designer

To naprawdę nie ma znaczenia, który typ designera wybierzesz – każdy pozwala na użycie kontrolki załączników (zaś w „New responsive designer” – kontrolki nazwanej „File upload”):

Attachments control in Nintex for Office 365 form designer

Skonfiguruj formularz tak, jak potrzebujesz. Po zakończeniu wróć do akcji zadania.

Akcja zadania

Niezależnie od tego, czy korzystasz z akcji „Assign a task”, czy „Start a task process”, musisz skonfigurować pole, które zwraca identyfikator zadania (lub zadań):

Grab generated tasks IDs in Nintex for Office 365 forms designer

Później użyjesz tych identyfikatorów, aby pobrać załączniki z powiązanych zadań (lub zadania).

Workflow

Put workflow on pause so that all attachetns are uploaded

Po zakończeniu zatwierdzania daj przepływowi pracy chwilę oddechu. Zauważyłem, że czasami workflow był wznawiany znacznie szybciej niż załączniki były ładowane do elementu zadania. W rezultacie wznowiony workflow posiadał info m.in. tylko o jednym załączniku z 3 przesłanych.

Następnie dla każdego zadania wykonaj poniższe kroki:

For each task
  1. Przygotuj nagłówek żądania HTTP
    Request headers
  2. Wykonaj żądanie GET HTTP do poniższego adresu URL:

    {ADRES URL WITRYNY}/_api/lists/getbytitle('Workflow Tasks')/items(TASK ID FOR CURRENT LOOP RUN)/AttachmentFiles?$select=FileName,ServerRelativeUrl
    HTTP Request
  3. Zwrócony wynik będzie posiadać poniższą strukturę JSON: {"value": [{"FileName": "xyz.jpg", "ServerRelativeUrl": "somepath/xyz.jpg"}]} – musisz wyciągnąć jedynie tabelkę.
    Extract table
  4. Następnie wykonaj poniższe kroki dla każdego elementu w tabelce.

Następnie dla każdego wyodrębnionego załącznika (w pętli iteruj po słowniku var_Data_dict i zapisz element jako np. var_Item_dict):

For each attachment
  1. Pobierz ze słownika nazwę pliku
  2. Pobierz ze słownika ścieżkę
    Get path/ filename from dictionary
  3. Przygotuj słownik z nagłówkiem żądania (jak powyżej, opcjonalnie możesz dodać linię z Content-Type: application/json)
  4. Wykonaj żądanie POST do poniższego adresu URL:
    ‍{Workflow Context:Current site URL}‍/_api/web/getfilebyserverrelativeurl(@v0)/copyto(strnewurl=@v1,boverwrite=true)?@v0='‍{Variable:var_Path_txt}‍'&@v1='/SITE RELATIVE URL TO LIST/Attachments/‍{Current Item:ID}‍/‍{Variable:var_Filename_txt}‍'
    HHTP Request "copyTo"

I to wszystko! Załącznik zadania jest teraz dodany do listy załączników powiązanego elementu listy. Powodzenia!

Artykuł Kopiowanie załączników z taska do załączników elementu listy pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Uprawnienia delegowanych zadań zatwierdzania w Power Platform

Ten post odnosi się bezpośrednio do mojego najnowszego filmu, w którym opisuję brakujące kroki wymagane do naprawdę pomyślnego delegowania zadania do użytkownika mającego rolę Environment Maker przy użyciu przepływów w chmurze Power Automate i danych zadan zatwierdzania przechowywanych w Microsoft Dataverse.

W filmie opowiadam, że nie wystarczy po prostu utworzyć i przypisać zadania nowemu zatwierdzającemu, poprzez dezaktywację istniejącego rekordu w tabeli Approval Request i utworzenie nowego, którego właścicielem jest nowy zatwierdzający. Należy również upewnić się, że rekord nagłówka procesu zatwierdzania, utworzony w tabeli Approval, jest udostępniany nowemu zatwierdzającemu.

Aby udostępnić ten rekord, w swoim przepływie w chmurze musisz dodać akcję „Perform an unbound action”, która wykona akcję „GrantAccess”.

Celem tej akcji jest powiązany wiersz z tabeli Approval – należy użyć następującego wyrażenia:

msdyn_flow_approvals(<<GUID rekordu APPROVAL - jako tekst>>)

Następnie musisz wstawić JSON, który zapewni nowemu zatwierdzającemu dostęp do odczytu tego rekordu. Aby to osiągnąć, skopiuj i wklej poniższy kod JSON:

{
  "Principal": {
    "systemuserid": "<<GUID nowego zatwierdzającego z tabeli Users>>",
    "@odata.type": "Microsoft.Dynamics.CRM.systemuser"
  },
  "AccessMask": "ReadAccess"
}

Pamiętaj, aby poprzedzić „@odata.type” dodatkowym symbolem „@”, aby wyglądało to tak: „@@odata.type”, w przeciwnym razie flow checker potraktuje to jako błąd. Akcja będzie wyglądać następująco (oprócz danych dynamicznych – być może w Twoim przypadku będzie inaczej):

Perform an unbound action configuration

To jest w zasadzie wszystko. Teraz, zanim utworzysz nowy rekord w tabeli Approval Request, upewnij się, że nagłówek w tabeli Approval jest udostępniony nowej osobie na poziomie odczytu.

Artykuł Uprawnienia delegowanych zadań zatwierdzania w Power Platform pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Kontrolka oceniania w Adaptive Cards

Kontrolka oceniania w Adaptive Cards była w planach. Dawno temu. Jednak żadne aktualizacje nie zostały opublikowane i nadal nie ma takiej kontrolki „z pudełka”. Mimo to nadal można ją zbudować, może nie idealną, ale na pewno działającą.

Być może kontrolka oceniania z użyciem gwiazdek, o której myślisz, wyświetla np. 5 ikon z rzędu i ma wypełnione gwiazdki od lewej strony, aż do tej, nad którą znajduje się kursor. A także zwraca dokładną wartość wybranej gwiazdki. Jak w przykładzie poniżej:

No cóż… To nie jest możliwe w Adaptive Cards 😉 W ramach rekompensaty mogę zaproponować Ci rozwiązanie, którego używam, wykorzystujące przyciski. Idź za mną krok po kroku, jak zawsze:

Krok 1 – zaprojektuj kartę

Najpierw zaprojektuj kartę i przeciągnij, i upuść element ActionSet w miejscu, w którym chcesz utworzyć kontrolkę oceny:

Drag&drop ActionSet element

Następnie dodaj 5 akcji w Action.Submit:

Add 5 Action.Submit actions

Po wykonaniu tej czynności skonfiguruj je w następujący sposób: usuń wartości Title i zamiast tego wypełnij właściwość Icon URL linkiem do ulubionej ikony gwiazdki:

Remove Titles and set Icon URLs properties

Co jest niezwykle ważne: wysyłając kartę do Microsoft Teams, zostanie zwrócony identyfikator klikniętego elementu, dlatego pamiętaj o wypełnieniu właściwości identyfikatora każdego przycisku. Wartości te muszą być unikalne!

Set unique IDs

Możesz później trochę pobawić się układem, jeśli potrzebujesz, ale w zasadzie ogólny pomysł jest gotowy! Kontrola oceny jest na miejscu:

Rating control

Krok 2 – skopiuj kod JSON karty i wyślij do Teams

Następnym i zasadniczo ostatnim krokiem jest skopiowanie kodu JSON własnej karty i wklejenie go w akcji „Post adaptive card and wait for a response” w przepływie cloud flow, wraz z logiką obsługi wybranej gwiazdki i jej wartości:

Post adaptive card and wait for a response action in cloud flow

Po uruchomieniu przepływu będziesz mógł znaleźć swoją kartę w wybranym kanale lub rozmowie:

Rating control in Teams

Po naciśnięciu dowolnej gwiazdki zauważysz, że akcja w przepływie zostaje wznowiona i zwracany jest identyfikator wciśniętego przycisku.

Krok 3 – wzbogać user experience

Możesz później użyć akcji „Update an adaptive card in a chat or channel”, aby zastąpić istniejącą kartę czytelnym potwierdzeniem:

Aby użytkownik sam zobaczył wybraną ocenę:

Update Adaptive Card using Update an adaptive card in a chat or channel action

I to wszystko! Mam nadzieję, że Ci się spodoba i post ten okaże się przydatny.

Artykuł Kontrolka oceniania w Adaptive Cards pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Dostęp do elementu udostępnionego przez link w SharePoint Online skutkuje odmową dostępu

Miałem do czynienia z bardzo dziwną sytuacją, gdy próbowałem udostępnić element w bibliotece SharePoint Online użytkownikowi, który w ogóle nie miał dostępu do witryny. Po udostępnieniu elementu użytkownik otrzymywał odmowę dostępu, chyba że został dodany do grupy uprawnień „Członkowie”.

Krótki opis problemu

Sytuacja kształtowała się następująco:

  1. Witryna była witryną zespołu SharePoint Online.
  2. Użytkownik nie miał dostępu do witryny.
  3. Folder w bibliotece został udostępniony użytkownikowi przez link.
  4. Użytkownik klikał link w wysłanej do niego wiadomości e-mail, ale otrzymywał odmówę dostępu.
  5. Gdy użytkownik został dodany do grupy uprawnień „członkowie” witryny, uzyskał dostęp do udostępnionego zasobu.

Jednak dodanie ich do grupy „członków” nie było rozwiązaniem, ponieważ nie chciałem udostępniać całej witryny.

W całym procesie udostępniania działy się też inne dziwne rzeczy. Próbowałem udostępnić zarówno poprzez link, jak i nadając bezpośredni dostęp. Krótkie przypomnienie na temat tego, jaka jest różnica między tymi dwiema metodami: Direct Access vs. Sharing Link in SharePoint Online – SharePoint Maven.

Było udostępnianie elementu w SharePoint Online - za pomocą łącza lub bezpośredniego dostępu.

W każdym razie: gdy próbowałem udostępnić przez „Wyślij link”, nie mogłem zmienić uprawnień na „Edytuj” (opcja była wyłączona), dostępny był tylko „Wyświetlanie”:

Możliwość edycji jest wyłączona w funkcji udostępniania „Wyślij link”

Ponadto, gdy kliknąłem na „Bezpośredni dostęp”, zostałem poinformowany, że nie mam uprawnień do udostępniania elementu, mimo że byłem administratorem zbioru witryn (sic!):

Nie możesz udostępniać bezpośrednio, mimo że jesteś administratorem zbioru witryn.

Okazało się jednak, że udało mi się w ten sposób przyznać dostęp. Mimo tego jednak użytkownik nadal widział ekran informujący o odmowie dostępu. W tej sytuacji, użytkownik poprosił o dostęp:

Requesting for access in SharePoint Online.

I prośba wylądowała w obszarze „Wnioski o dostęp”, ale znów – nawet pomimo wyrażenia zgody, użytkownik wciąż miał ten sam problem.

W końcu udało mi się umożliwić użytkownikowi na dostęp do zasobu, dodając go do grupy uprawnień „Członkowie” witryny, jednak nie było to rozwiązaniem, ponieważ nie chciałem pozwolić na dostęp do całej witryny, a jedynie do konkretnego zasobu.

Rozwiązanie

Zupełnie przypadkiem wpadłem na pomysł, co może być problemem, gdy próbowałem przyznać użytkownikowi dostęp bezpośrednio na stronie „Uprawnienia”. Kliknąłem przycisk „Przyznaj uprawnienia” i oto co się pojawiło:

Folder udostępniania jest wyłączony — funkcja zbioru witryn uniemożliwia udostępnianie zasobów.

Okazało się, że przyczyną moich problemów była funkcja zbioru witryn o nazwie Limited-access user permission lockdown mode„.

Limited-access user permission lockdown mode site collection feature.

Zgodnie z dokumentacją:

Włączenie tej funkcji powoduje zmniejszenie uprawnień użytkowników z „ograniczonym dostępem”, na przykład użytkowników anonimowych, co uniemożliwia dostęp do stron aplikacji, takich jak właściwości elementów i widoki list. Jeśli dokument, folder lub biblioteka ma unikatowe uprawnienia, użytkownicy ci nie będą również mogli wykonywać następujących czynności:
1. Przekazywanie dokumentów za pomocą funkcji przeciągania i upuszczania
2. Przechodzenie do określonego folderu
3. Korzystanie z funkcji udostępniania
4. Tworzenie zdarzeń kalendarza na podstawie list kalendarzy połączonych w programie Outlook
5. Otwieranie dokumentów w kliencie pakietu Office
6. Niektóre objaśnienia w dokumentach i folderach nie będą renderowane w oczekiwany sposób

Tutaj, zgodnie z informacją wyświetloną w powyższym oknie dialogowym, mogłem:

  1. Wyłączyć tę funkcję, aby użytkownicy z ograniczonym dostępem (jak w moim przypadku) mogli korzystać ze wszystkich funkcji po udostępnieniu lub
  2. Udostępnić witrynę, co w tym przypadku oznaczało, że musiałbym dodać użytkownika do grupy „członków”, tak jak to robiłem wcześniej.

Postanowiłem wyłączyć funkcję zbioru witryn i to rozwiązało wszystkie moje problemy. Od tego czasu znowu mogłem udostępniać przez link i udzielać uprawnień do edycji, a mylące informacje mówiące mi, że nie mam uprawnień do bezpośredniego udostępniania, zniknęły.

Nie ma za co! 🙂

Artykuł Dostęp do elementu udostępnionego przez link w SharePoint Online skutkuje odmową dostępu pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Jak wzmianiować użytkowników, tagi, kanały i zespoły używając Power Automate

W tym poście chciałbym podzielić się z Tobą moimi najnowszymi odkryciami na temat sposobów, w jakie można wzmiankować wszystko w Microsoft Teams wysyłając wiadomości z Power Automate, niezależnie od tego, czy jest to użytkownik, tag, kanał czy zespół.

Wbudowane wzmiankowanie

W Power Automate dostępne są dedykowane akcje, które pozwalają wzmiankować użytkownika lub tag.

@mention actions in Power Automate

Akcje „Get an @mention token for a user” oraz „Get an @mention token for a tag”. Te dwie akcje zwracają tokeny, które można użyć w dowolnej innej akcji, która wysyła wiadomość do Microsoft Teams, na przykład „Post adaptive card in a chat or channel” lub „Post message in a chat or channel”:

Post messages action in Power Automate

Ważne! Token @mention dla tagu może być używany tylko wtedy, gdy karta adaptacyjna lub wiadomość są publikowane przy użyciu kontekstu użytkownika. Wzmiankowanie tagu nie będzie działać w kontekście bota.

Musisz tylko dodać tokeny do swoich kart lub wiadomości:

@mention token and generated Adaptive Card

Wzmiankowanie korzystając z property msteams w Adaptive Card JSON

Drugim podejściem, którego możesz użyć, jest skorzystanie z właściwości msteams, którą można dodać na końcu karty adaptacyjnej wysłanej do Microsoft Teams. Przy okazji – ta właściwość może pomóc w wielu innych scenariuszach (źródło: Text formatting in cards – Teams | Microsoft Docs).

Mentioning using msteams property in Adaptive Card JSON

Dlaczego miałbyś to robić w ten sposób nie poprzez akcje @mention, nie wiem, ale podkreślam, że w ten sposób nie udało mi się wspomnieć o tagach, kanałach i zespołach. Chociaż „link” do kanału był wyświetlany na wysłanej karcie, nie zachowywał się prawidłowo, ponieważ najechanie kursorem nie rozwijało szczegółów kanału.

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.4",
    "body": [
        {
            "type": "TextBlock",
            "id": "MentionTextBlock",
            "text": "Fun with mentions!",
            "weight": "Bolder",
            "size": "Medium"
        },
        {
            "type": "TextBlock",
            "text": "This is user mention. Hi: <at>USER NAME</at>!",
            "size": "Medium"
        }
        {
            "type": "TextBlock",
            "text": "And this mentions tag <at>TAG NAME</at>!",
            "size": "Medium"
        },
        {
            "type": "TextBlock",
            "text": "This is channel mention. Hello: <at>CHANNEL NAME</at>!",
            "size": "Medium"
        }
    ],
    "msteams": {
        "entities": [
            {
                "type": "mention",
                "text": "<at>USER NAME</at>",
                "mentioned": {
                    "id": "8:orgid:USER AAD ID",
                    "name": "USER NAME"
                }
            }
            {
                "type": "mention",
                "text": "<at>TAG NAME</at>",
                "mentioned": {
                    "id": "TAG ID",
                    "name": "TAG NAME"
                }
            },
            {
                "type": "mention",
                "text": "<at>CHANNEL NAME</at>",
                "mentioned": {
                    "id": "CHANNEL ID",
                    "displayName": "CHANNEL NAME",
                    "conversationIdentityType": "channel"
                }
            }
        ]
    }
}

Ważne! By wzmiankować użytkownika musisz dodać prefix 8:orgid: przed wartością Azure AAD ID.

Co ważne, z powyższej metody mogłem skorzystać jedynie przy wysyłaniu kart w kontekście bota. Podczas korzystania z kontekstu użytkownika, akcja cały czas kończyła się błędem: „Message mention text needs to be specified.”. Bez względu na to, jak formatowałem obiekty wzmianek, nie mogłem tego rozwiązać. Więc się poddałem. Jeśli wiesz, jak sprawić by to zadziałało, napisz w komentarzach 🙂

Wzmiankowanie korzystając z webhook kanału

Po pierwsze, aby utworzyć webhooka do kanału, musisz przejść do menu „Connectors” w obrębie kanału:

Connectors menu in channel

Następnie skonfiguruj webhooka i skopiuj jego adres URL. Gdy to zrobisz, możesz użyć i skonfigurować akcję HTTP:

How to send Adaptive Card via channel's webhook

Musi to być żądanie POST wysłane do webhooka kanału. A w kwestii zawartości:

{
    "type": "message",
    "attachments": [
      {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": {
          "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
          "type": "AdaptiveCard",
          "version": "1.4",
          "body": [
            {
              "type": "TextBlock",
              "id": "MentionTextBlock",
              "text": "Fun with mentions!",
              "weight": "Bolder",
              "size": "Medium"
            },
            {
              "type": "TextBlock",
              "text": "This is user mention. Hi: <at>USER NAME</at>!",
              "size": "Medium"
            },
            {
              "type": "TextBlock",
              "text": "And this mentions tag <at>TAG NAME</at>!",
              "size": "Medium"
            },
            {
              "type": "TextBlock",
              "text": "This is channel mention. Hello: <at>CHANNEL NAME</at>!",
              "size": "Medium"
            }
          ],
          "msteams": {
            "entities": [
              {
                "type": "mention",
                "text": "<at>USER NAME</at>",
                "mentioned": {
                  "id": "8:orgid:USER AAD ID",
                  "name": "USER NAME"
                }
              },
              {
                "type": "mention",
                "text": "<at>TAG NAME</at>",
                "mentioned": {
                  "id": "TAG ID",
                  "name": "TAG NAME"
                }
              },
              {
                "type": "mention",
                "text": "<at>CHANNEL NAME</at>",
                "mentioned": {
                  "id": "CHANNEL ID",
                  "displayName": "CHANNEL NAME",
                  "conversationIdentityType": "channel",
                  "conversationIdentityType@odata.type": "#Microsoft.Teams.GraphSvc.conversationIdentityType"
                }
              }
            ]
          }
        }
      }
    ]
  }

Tutaj znowu, jak widać, używana jest właściwość msteams. W tym podejściu generowane są wszystkie wzmianki.

Mentions sent using team's channel webhook

Mimo to moim zdaniem nie działają. Ponieważ kiedy najeżdżam na któryś z nich, nie wyświetla żadnych szczegółów, a wiadomość nie jest podświetlana jako wzmianka o mnie. Jeśli to dla Ciebie działa, daj mi proszę znać.

Ważne! Karty adaptacyjne wysyłane przez webhook nie pozwalają na wykonywanie żadnych czynności związanych z przesyłaniem (submit). Jeśli więc planujesz dodać formularz do karty, który użytkownicy mogą wypełnić i wysłać, przy takim podejściu nie będziesz w stanie odebrać odpowiedzi.

Wzmiankowanie używając GraphAPI

W tym podejściu wysyłasz Adaptive Card do endpointa GraphAPI: /beta/teams/team-id/channels/channel-id/messages. Wiecej szczegółów na temat tego podejścia znajdziesz tutaj: Send chatMessage in a channel or a chat – Microsoft Graph beta | Microsoft Docs.

Robię to za pomocą akcji o nazwie „Send an HTTP request” z grupy akcji „Office 365 Groups”. Czemu? Ponieważ jest to czynność standardowa, więc nie wymaga dodatkowej licencji. Możesz osiągnąć to samo, używając akcji „Invoke an HTTP Request” z grupy „HTTP with Azure AD”. Jednak ta jest akcją premium.

Sending Adaptive Card with mentions through GraphAPI

Kod, który należy wysłać, składa się z następujących sekcji:

  1. Body – zawiera strukturę HTML, w tym miejsce na załącznik, którym w tym przypadku będzie karta adaptacyjna.
  2. Attachments – ta właściwość zawiera wyescapeowany kod JSON karty Adaptive Card.
  3. Mentions – ta właściwość zawiera obiekty wzmianek, dla każdego identyfikatora wzmianki obecnego w treści karty adaptacyjnej lub we właściwości Body -> Content.
{
    "subject": "Adaptive Cards test",
    "body": {
        "contentType": "html",
        "content": "<attachment id=\"AttachmentID\"></attachment>"
    },
    "attachments": [
        {
            "id": "AttachmentID",
            "contentType": "application/vnd.microsoft.card.adaptive",
            "contentUrl": null,
            "content": "{\r\n  \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\r\n  \"type\": \"AdaptiveCard\",\r\n  \"version\": \"1.4\",\r\n  \"body\": [\r\n    {\r\n      \"type\": \"TextBlock\",\r\n      \"id\": \"MentionTextBlock\",\r\n      \"text\": \"Fun with mentions!\",\r\n      \"weight\": \"Bolder\",\r\n      \"size\": \"Medium\"\r\n    },\r\n    {\r\n      \"type\": \"TextBlock\",\r\n      \"text\": \"This is user mention. Hi: <at id=\\\"0\\\">USER NAME</at>!\",\r\n      \"size\": \"Medium\"\r\n    },\r\n    {\r\n      \"type\": \"TextBlock\",\r\n      \"text\": \"This is channel mention. Hello: <at id=\\\"1\\\">CHANNEL NAME</at>!\",\r\n      \"size\": \"Medium\"\r\n    },\r\n    {\r\n      \"type\": \"TextBlock\",\r\n      \"text\": \"This is team mention. Hello: <at id=\\\"2\\\">TEAM NAME</at>!\",\r\n      \"size\": \"Medium\"\r\n    },\r\n    {\r\n      \"type\": \"TextBlock\",\r\n      \"text\": \"This mentions tag <at id=\\\"3\\\">TAG NAME</at>!\",\r\n      \"size\": \"Medium\"\r\n    }\r\n  ]\r\n}",
            "name": null,
            "thumbnailUrl": null
        }
    ],
    "mentions": [
        {
            "id": 0,
            "mentionText": "USER NAME",
            "mentioned": {
                "user": {
                    "id": "USER AAD ID",
                    "displayName": "USER NAME",
                    "userIdentityType": "aadUser"
                }
            }
        },
        {
            "id": 1,
            "mentionText": "CHANNEL NAME",
            "mentioned": {
                "conversation": {
                    "id": "CHANNEL ID",
                    "displayName": "CHANNEL NAME",
                    "conversationIdentityType": "channel"
                }
            }
        },
        {
            "id": 2,
            "mentionText": "TEAM NAME",
            "mentioned": {
                "conversation": {
                    "id": "TEAM ID",
                    "displayName": "TEAM NAME",
                    "conversationIdentityType": "team"
                }
            }
        },
        {
            "id": 3,
            "mentionText": "TAG NAME",
            "mentioned": {
                "tag": {
                    "id": "TAG ID",
                    "displayName": "TAG NAME"
                }
            }
        }
    ]
}

W ten sposób można naprawdę wzmiankować wszystko i działa to dla wszystkich typów wzmianek. A to dlatego, że akcja działa w kontekście użytkownika. Jeśli chcesz wysłać ją w kontekście aplikacji, musisz użyć akcji HTTP i podać szczegóły aplikacji usługi Azure AD, aby uwierzytelnić wywołanie.

Adaptive Card with mentions sent via GraphAPI

Ważne! Wysłane w ten sposób karty adaptacyjne nie pozwolą na wykonanie akcji przesyłania (submit). Jeśli planujesz umożliwić użytkownikom przesyłanie formularzy wysłanych w Adaptive Cards, musisz wybrać inne podejście.

I to wszystko! Ufam, że uznasz to za przydatne.

Materiały

Kilka przydatnych linków, które pomogły mi lepiej zrozumieć jak wzmiankować w Microsoft Teams:

Artykuł Jak wzmianiować użytkowników, tagi, kanały i zespoły używając Power Automate pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Osadzanie obrazków wewnątrz maili z Power Automate Desktop

Niedawno spotkałem się z koniecznością wysłania wiadomości e-mail z Power Automate Desktop, w której obrazek nie miał być po prostu załącznikiem, a widoczny w jej treści. Żadna funkcja, umożliwiająca zrealizowanie tego zadania nie jest dostępna z poziomu interfejsu użytkownika, jednak jest to możliwe!

Content-ID na ratunek!

Trick polega na użyciu CID wewnątrz treści wiadomości. Jak to zrobić? Wykonując poniższe kroki:

  1. Upewnij się, że opcja „Body is HTML” jest włączona,
  2. Dodaj załączniki, które zostaną wysłane z wiadomością wraz z tymi, które planujesz wyświetlić wewnątrz treści,
  3. Wewnątrz kodu HTML treści wiadomości, użyj <img src="cid:[nazwa załącznika wraz z rozszerzeniem]" /> wraz ze wszystkimi właściwościami HTML, jakie chcesz użyć.

I tyle! Gdy wyślesz wiadomość i otworzysz ją w Outlooku, załączniki do których istnieją referencje cid wewnątrz treści nie będą widoczne jako regularne załączniki (są ukryte).

Poniższy GIF prezentuje całe rozwiązanie. Po uruchomieniu RPA robi zrzut ekranu części mojego bloga, następnie wysyła go przy użyciu wiadomości w Outlook. Na koniec, wiadomość zostaje otworzona w Outlook i widać, jak zrzut ekranu wyświetlony jest wewnątrz treści, nie ma go zaś widocznego jako załącznik.

Teraz kolej na Ciebie 😉

Artykuł Osadzanie obrazków wewnątrz maili z Power Automate Desktop pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Timeouty w Actionable Messages

Ten post został zainspirowany wiadomością, którą Sergi Dominquez wysłał mi na Twitterze. Okazuje się, że gdy żądanie z Actionable Message w Outlooku jest wysyłane do serwera, Outlook czeka tylko określoną ilość czasu na odpowiedź.

1/2 Hey @TomaszPoszytek some days ago I opened a issue in the Adaptive Card github related to issue while trying to refresh the UI. Finally they inform that there are some Timeouts: Http Action – 15 Secs and Auto-invoke Http Action – 6 Secs, this is not already documented.

— Sergi Dominguez (@sergibarca) April 5, 2022

The timeouts are described in no official documentation. The only reason Sergi found out that is actually a case was an answer under his question posted on GitHub. Muthurathinam from Microsoft answered, that there are timeouts indeed:

Przekroczenia limitów czasu nie są opisane w żadnej oficjalnej dokumentacji. Jedynym powodem, dla którego Sergi dowiedział się, że tak naprawdę istniej, była odpowiedź na jego pytanie zamieszczone na GitHub. Muthurathinam z firmy Microsoft odpowiedział, że rzeczywiście istnieją takie ograniczenia:

  • Http Action – 15 sekund
  • Auto-invoke Http Action – 6 sekund (aczkolwiek podczas moich testów żądanie było kończone już po 4 sekundach)

Http Action

Ta akcja jest używana w Actionable Messages, aby umożliwić użytkownikom wysyłanie danych z karty na serwer. Ta akcja jest wywoływana przez użytkownika po kliknięciu przycisku. Jak w poniższym przykładzie:

Akcja może służyć do wywołania zarówno żądania POST jak i GET.

Więcej na jej temat możesz znaleźć w oficjalnej dokumentacji: Design actionable message cards using Adaptive Card format – Outlook Developer | Microsoft Docs.

Auto-invoke Http Action

Ta akcja nie jest tak powszechnie znana. Jej celem jest umożliwienie wykonania akcji, gdy karta jest wyświetlana. Na przykład, aby odświeżyć kartę najnowszymi danymi do zatwierdzenia. Można ją dodać do kodu Actionable Message jako osobną właściwość (tak jak dodajesz Actions – pamiętaj o nagłówkach):

Ta akcja obsługuje wyłącznie żądania POST.

Więcej o niej znajdziesz w dokumentacji: Refresh an actionable message on open – Outlook Developer | Microsoft Docs.

Timeouty w Actionable Messages

I tak, wykonałem swoje testy. Niestety, Muthurathinam miał rację. Dodałem „Delay” w przepływie, aby zatrzymać natychmiastowe wysyłanie odpowiedzi. Następnie wysłałem sobie Actionable Message, która zawierała zarówno akcję Auto-invoke, jak i zwykłą akcję HTTP.

W przypadku Auto-invoke, żądanie było kończone już po upływie zaledwie 4 sekund:

Żądanie nie było jednak anulowane ani nic w tym rodzaju. Z drugiej strony, w tym scenariuszu nasłuch na odpowiedź został faktycznie zakończony, ponieważ cloud flow nie mógł wysłać żadnych danych z powrotem do klienta:

W przypadku Http Action, logi sieciowe pokazują, że Outlook faktycznie czekał aż 15 sekund, zanim anulował żądanie:

Jednak, co jest dziwne, cloud flow który miał wysłać odpowiedź, mógł to zrobić nawet po upłynięciu tego czasu. Co istotne jednak, karta mimo to nie została odświeżona. Zamiast tego, po 15 sekundach karta wyświetliła po prostu błąd:

Podsumowanie

Mogę w tym miejscu napisać tylko jedną rzecz. Upewnij się, że logika, która jest wykonywana przez akcje w Actionable Messages działa naprawdę szybko. Nikt nie lubi długo czekać na wynik wykonanej przez siebie akcji. Karty również 😉 Powodzenia!

Artykuł Timeouty w Actionable Messages pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Pokazywanie i ukrywanie zawartości w Adaptive Cards

W kartach adaptacyjnych istnieje wiele sposobów pokazywania i ukrywania treści w zależności od innych treści lub warunków, a nawet interakcji użytkownika. Ale mimo iż większość z nich jest dostępna już od wersji 1.2 (a więc dość wczesnej) potrzeba jest trochę wiedzy, jak je faktycznie zaimplementować.

Istnieją trzy główne sposoby na pokazywanie i ukrywanie zawartości w Adaptive Cards:

  1. Action.ShowCard
  2. Action.ToggleVisibility
  3. Warunki zdefiniowane poprzez właściwość „Only show when”

Przyjrzyjmy się teraz każdej z powyższych metod.

Action.ShowCard

Aby z niego skorzystać należy najpierw dodać element „ActionSet” do kanwy projektanta, a następnie wybrać Action.ShowCard z listy dostępnych opcji:

Gdy to zrobisz, zdefiniuj właściwości przycisku w oknie „Element properties”, takie jak Title i Id.

Wskazówka! Aby wyświetlić drugą kartę, która pojawi się po kliknięciu w dodany przycisk, kliknij ów przycisk dwukrotnie! Spowoduje to pojawienie się ukrytej karty, dzięki czemu możesz łatwo przygotować jej zawartość.

Następnie kliknij dwukrotnie przycisk, aby wyświetlić ukrytą kartę i przygotować jej zawartość:

Następnie przejdź do „Trybu podglądu” (Preview mode), aby zobaczyć, jak karta jest pokazywana i ukrywana po kliknięciu przycisku:

Ważne! Jeśli dodasz „Action.Submit” do ukrytej karty, wyśle ona dane ze wszystkich pól obecnych na tej karcie i „w górę” – a więc od wszystkich rodziców tej ukrytej karty. Przycisk „Action.Submit” na najwyższej karcie spowoduje wysłanie danych tylko z tej najwyższej karty.

Dane z kart są wysyłane od poziomu „rodzica” do „potomków”.

Action.ToggleVisibility

Aby z tej metody skorzystać należy najpierw dodać element „ActionSet” do kanwy projektanta, a następnie wybrać Action.ToggleVisibility z listy dostępnych opcji:

Następnie, podobnie jak w scenariuszu ShowCard, zdefiniuj właściwości dodawanego przycisku.

Ważne! W przeciwieństwie do ShowCard, ToggleVisibility wymaga wykonania pewnej pracy bezpośrednio w wygenerowanym JSON payload karty. Nie istnieje interfejs użytkownika, aby skonfigurować to w inny sposób.

Teraz dodaj do karty elementy, które chcesz pokazać i ukryć po kliknięciu przycisku. Następnie zdefiniuj ich właściwości:

  1. Id – bezwzględnie wymagane jest zdefiniowanie unikalnych identyfikatorów dodawanych elementów. Identyfikatory służą do identyfikacji elementów i przełączania ich widoczności.
  2. Initially visible – określ, czy elementy powinny być widoczne, gdy karta jest renderowana, czy ukryte. Przełączanie będzie albo je wówczas ukrywać lub pokazywać. Po odznaczeniu pola wyboru na element zostanie nałożony na szary wzór.

Teraz przejdź do kodu JSON i znajdź definicję przycisku ToggleVisibility. Dodaj tam następujący kod:

"targetElements": [ "colon delimited list of elements' Ids to toggle" ]

Ważne! Jedynym „minusem” tego podejścia jest, że w przypadku dodania zawsze widocznego przycisku „Submit”, wyśle on dane ze wszystkich pól, nawet jeśli są ukryte.

Ostatnim krokiem jest przetestowanie tego. Przełącz się w tryb „Podgląd” i sprawdź zachowanie swojej karty:

Only show when

Podejście to wykorzystuje „język szablonów kart adaptacyjnych”. Aby móc skorzystać tej metody należy najpierw zdefiniować Dane (Data), które będą powiązane z kartą. Kiedy już masz dane, możesz stworzyć warunek, który określi, kiedy element powinien być widoczny:

Możesz używać różnych typów porównań, zarówno do typów danych całkowitych, dat, logicznych, jak i stringów.

Ważne! W przeciwieństwie do „Toggle”, pokazywanie i ukrywanie elementów w oparciu o warunki ma miejsce podczas renderowania karty. Na dzień dzisiejszy dane, z których korzysta karta, są tylko statyczne, więc nie można ich zmienić po wyświetleniu karty. Oznacza to, że jeśli pole ma warunek uniemożliwiający jego wyświetlenie, nawet jeśli ma ustawioną wartość domyślną, jego wartość nie zostanie przesłana z karty, dopóki pole nie jest widoczne.

Na koniec wejdź w tryb „Podgląd”, aby zobaczyć, jak zachowuje się Twoja karta:

I to wszystko! Mam nadzieję, że ten tutorial okaże się przydatny. Jeśli masz jakieś uwagi, zapisz je poniżej. Dzięki!

Artykuł Pokazywanie i ukrywanie zawartości w Adaptive Cards pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Rozwiązywanie problemów z Power Automate Desktop

Pracuję z Power Automate Desktop od ponad roku. W tym czasie spotkałem się z wieloma dziwnymi błędami, które pojawiały się gdzieś pomiędzy cloud flows, których używałem do wyzwalania RPA, a samymi botami. W tym poście spróbuję pomóc Ci zrozumieć, skąd pochodzą i jak je rozwiązać (lub obejść).

W tym poście skupiam się na błędach, które mogą wystąpić w nadzorowanym (attended) lub nienadzorowanym (unattended) przepływie Power Automate Desktop pojawiających się zasadniczo w warstwie sieciowej w sytuacjach, gdy instancje RPA są wyzwalane przez cloud flows. Błędy mogą wystąpić z obu stron, gdy cloud flow wyzwala desktop flow, a później, gdy desktop flow próbuje wysłać dane z powrotem do cloud flow, który go wyzwolił.

Błędy w Power Automate Desktop

Za każdym razem, gdy w przepływie desktop flow wystąpi błąd, zostanie on zwrócony jako poniższy obiekt JSON:

{
    "error": {
        "code": "[INTERNAL CODE]",
        "message": "[HUMAN READABLE ERROR DESCRIPTION]"
    }
}

Kody są oczywiście unikalne i są najważniejszymi informacjami, które pomagają nam debugować i rozwiązywać problemy. Skoncentruję się teraz na tych, z którymi miałem do czynienia najczęściej.

Rozwiązywanie problemów z Power Automate Desktop

Error code: NoCandidateMachine

Status code: 400

Kiedy? Ten błąd występuje, gdy przepływ cloud flow nie może połączyć się z żadną zapisaną maszyną przez czas dłuższy niż trzy godziny. Błąd może wystąpić, nawet jeśli maszyna jest dostępna, ale z powodu problemów z siecią (lub prawdopodobnie błędów w konfiguracji lokalnej zapory sieciowej na komputerze) przepływ cloud nie może się do niego dostać przy użyciu zapisanych informacji o rejestracji maszyny (machine registration). Występuje zawsze przed faktycznym wykonaniem przepływu pulpitu.

Jak to rozwiązać? Możesz oczywiście skontaktować się z pomocą techniczną firmy Microsoft, aby uzyskać pomoc w rozwiązaniu problemów z komunikacją sieciową w takim przypadku. To, co ja robię, to dwie rzeczy: zmiana konfiguracji ponawiania w ustawieniach akcji i ponowne uruchomienie przepływu pulpitu, o ile taki błąd nie zostanie zwrócony:

Przy takim ustawieniu zasad ponawiania w przypadku wystąpienia jakiegokolwiek problemu z grupy 5xx, flow spróbuje samodzielnie ponowić próbę. I druga sztuczka:

Ma na celu sprawdzenie, czy treść odpowiedzi z akcji PAD zawiera określony schemat błędu. Jeśli tak, czy „code” jest równy „NoCandidateMachine”. W takim przypadku wstrzymuję przepływ na 5 minut, a następnie próbuje ponownie uruchomić PAD. Pętla kończy się, gdy PAD zakończy się bez błędu lub zostanie zwrócony inny rodzaj kodu błędu.

Error code: NoListenerConnected

Status code: 400

Kiedy? Ten błąd pojawia się ponownie w sytuacji, gdy przepływ cloud próbuje wyzwolić przepływ desktop, ale z powodu problemów z siecią nie może „skonktaktować” się z komputerem. Nie wiem dokładnie na czym polega różnica między tymi błędami, ale do tej pory odkryłem, że ten błąd występuje tylko w fazie inicjacji, a więc nie podczas wykonywania przepływu desktop.

Jak to rozwiązać? Postanowiłem zaimplementować podobne obejście dla tego scenariusza, jak opisałem powyżej. Ale – rozszerzyłem mechanizm opisany dla błędu NoCandidateMachine o obsługę innych scenariuszy:

Jak widać, tutaj zmienna, która służy do weryfikacji, czy pętla powinna zostać zakończona, jest po prostu tekstem zawierającym wartość ERROR lub nie. Tak więc w przypadku wystąpienia któregokolwiek z wymienionych błędów, przepływ w chmurze jest ponownie wstrzymywany, a następnie wykonywana jest próba ponownego uruchomienia przepływ desktop.

Error code: ConnectionNotEstablished

Status code: 400

Kiedy? Błąd najprawdopodobniej występuje z powodu problemów z siecią/połączeniem między chmurą a maszyną, na której bot powinien zostać uruchomiony. Nie odkryłem jeszcze samego źródła tego zachowania. Pozytywną informacją jest to, że błąd występuje podczas inicjalizacji bota, więc żadne akcje nie są jeszcze wykonane.

Jak to rozwiązać? Polecam skorzystać z tego samego rozwiązania, co powyżej – tak aby wyzwalać akcję PAD, dokąd nie zwraca błędu.

Error message: Desktop flow execution failed. CorrelationId: '00000000-0000-0000-0000-000000000000′

Kiedy? Błąd pojawia się niestety w trakcie wykonywania przepływu desktop. I (w moim przypadku) tylko na końcu instancji PADa, a więc gdy bot próbuje zapisać swój log z powrotem do chmury i zwrócić dane zmiennych wyjściowych lub informacje o błędach. Ten problem jest bardzo kłopotliwy, ponieważ w rzeczywistości powoduje zakończenie przepływu cloud z błędem, mimo że przepływ desktop mógł zostać pomyślnie zakończony. Co więcej, po przejściu do „Monitora przepływów pulpitu” nie będzie dostępna żadna historia dla tej instancji 🙁

Ważne! Zanim powiem Ci, jak to rozwiązać, zawsze możesz znaleźć log instancji, gdy zalogujesz się do komputera, na którym bot został uruchomiony (najlepiej na to samo konto, które zostało użyte do połączenia), a następnie przejdziesz do: %LOCALAPPDATA%\Microsoft\Power Automate Desktop\Scripts. Znajdziesz tam folder z guid równym guid definicji przepływu desktop. Wewnątrz folderu przejdź do \Runs\ i poszukaj folderu z identyfikatorem guid równym identyfikatorowi określonej instancji tego przepływu desktop. Wewnątrz tego folderu znajdziesz plik „Actions.log”, który zawiera JSON z pełny logiem.

Jak to rozwiązać? Niestety nie ma łatwego sposobu na rozwiązanie tego problemu. Musisz znaleźć sposób na sprawdzenie, w ramach przepływu cloud, czy ten konkretny przepływ desktop rzeczywiście zakończył się pomyślnie, czy z błędem. To, co ja robię, to dodatkowe logowanie i rozszerzona obsługa błędów w przepływach desktop. Każda instancja tworzy osobny plik Excel, który jest używany przez przepływ desktop do zapisywania informacji z jego działania. Ostatni wiersz to zawsze albo informacja, że bot zakończył pomyślnie, albo informacja o przechwyconym wyjątku. Następnie w procesie przepływu cloud sprawdzam ostatni wiersz tego pliku Excel, aby określić, czy wystąpił błąd, czy nie:

W przypadku, gdy wartość w ostatnim wierszu jest inna niż „RPA completed”, przepływ cloud uznaje tę instancję przepływu desktop za nieudaną i działa odpowiednio. We wszystkich innych scenariuszach kończy się scenariuszem poprawnego uruchomienia.

Error code: ActionRuntimeError

Status code: 400

Kiedy? Błąd często jest uzupełniony np. komunikatem: "Runtime Error: Exception of type 'System.OutOfMemoryException' was thrown. - issue related to machine". I jak powyżej, ten problem może wystąpić w dowolnym momencie podczas wykonywania przepływu desktop. Jest jednak o wiele bardziej problematyczny niż wspomniany powyżej. Kiedy nastąpi, po prostu kończy instancję przepływu desktop, mimo tego, że może znajdować się nawet w środku jakiejś transakcji. Dzieje się tak z powodu problemów na maszynie, na której wykonywany jest bot. Na przykład z powodu niewystarczających zasobów: RAM, HDD itp. Może się zdarzyć, że bot zbiera dużo danych podczas działania (np. tworzy dużą zmienną tekstową przez doklejanie nowego tekstu przez cały czas działania) i wyczerpuje zasoby.

Jak to rozwiązać? Oczywiście sprawdź, jaki był powód błędu. Ewentualnie możesz to naprawić, dodając więcej zasobów do maszyny. Niestety nie mam łatwego do wdrożenia rozwiązania. W rzeczywistości musisz sprawdzić, na której akcji bot zwrócił wyjątek i na tej podstawie wybrać najlepsze podejście do ponownego uruchomienia go. Na przykład, jeśli przetwarzał listę rekordów, lepiej byłoby wyzwolić go tylko dla nieprzetworzonych rekordów. Oczywiście w tym przypadku przydatne może być również niestandardowe logowanie, na przykład jeśli bot aktualizuje listę przetworzonych rekordów, może później przekazać ją do przepływu w chmurze, który może następnie ponownie uruchomić bota dla pozostałych rekordów.

Error code: SessionNotFound

Status code: 400

Kiedy? W moim przypadku ten błąd występował tylko wtedy, gdy przepływ cloud próbował wyzwolić przepływ desktop. Został on uzupełniony komunikatem: "Can't find target session". Doradzono mi, aby sprawdzić, czy maszyna, na której miał być uruchomiony przepływ pulpitu, nie została utworzona przez procedurę „klonowania” na platformie Azure. W moim przypadku tak właśnie było. Powodem wystąpienia tego błędu było to, że wiele komputerów zostało faktycznie zarejestrowanych pod tym samym identyfikatorem, więc chmura nie była w stanie znaleźć tego konkretnego.

jak to rozwiązać? Po prostu ponownie zarejestruj urządzenie, które powoduje błąd. Aby to zrobić, przejdź do tego komputera, w ustawieniach „Machine registration” połącz go z innym środowiskiem, a następnie z powrotem z tym, w którym ma być dostępny. Na koniec odśwież zdefiniowane połączenia w powiązanym środowisku Power Platform.

Error code: RunFlowFailedError

Status code: 400

Kiedy? Błąd jest uzupełniony komunikatem: „Failed to run flow \r\n Timeout has expired.„. Błąd może wystąpić, gdy używasz akcji „Uruchom przepływ pulpitu” w przepływie desktop (który wyzwala inny przepływ desktop) i ów inny przepływ desktop działa zbyt długo (i przekrocza timeout). Ponownie – jest to kłopotliwy błąd, ponieważ zdarza się podczas wykonywania przepływu desktop.

Jak to rozwiązać? Podobnie jak w przypadku "ActionRuntimeError" musisz najpierw sprawdzić, co było przyczyną błędu, a następnie odpowiednio zareagować. Przejdź do monitora „przepływów pulpitu” i sprawdź nieudaną instancję przepływu, aby dowiedzieć się, która akcja się nie powiodła i być może dlaczego – miejmy nadzieję, że zrzut ekranu może Ci pomóc.

Error code: RunFlowFailedError – An error occurred while executing flow Stack empty.

Status code: 400

When? Błąd jest uzupełniony komunikatem: „Failed to run flow \r\n An error occurred while executing flow Stack empty.„. Błąd może wystąpić, gdy używasz akcji „Uruchom przepływ pulpitu” w przepływie desktop (który wyzwala inny przepływ desktop) i ów inny przepływ desktop został zmodyfikowany i zapisany przy użyciu nowszej wersji PAD Designer niż używana na maszynie, na której jest uruchamiany.

Jak to rozwiązać? W moim przypadku, wystarczającym zabiegiem była aktualizacjia PAD na maszynie, gdzie przepływ był uruchamiany, do najnowszej wersji.

Inne błędy

Jeśli napotkałeś inny typ błędu, daj mi znać w komentarzach, z przyjemnością zaktualizuję post i dołączę podjęte przez Ciebie kroki.

Ponadto w Microsoft Docs znajduje się lista znanych błędów, więc jeśli nie możesz znaleźć rozwiązania w tym poście, być może będziesz miał więcej szczęścia tam. Po prostu przejdź do: Windows sessions and UI flows and attended/unattended behavior – Power Automate | Microsoft Docs.

Artykuł Rozwiązywanie problemów z Power Automate Desktop pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Filtrowanie danych w Excel z użyciem Power Automate Desktop

W tym poście pokażę, jak filtrować dane w tabeli w programie Microsoft Excel.

Proces jest naprawdę prosty i łatwy. Wszystko kręci się wokół akcji o nazwie „Send keys” z ustawieniami mówiącymi akcji, aby wysłać je do określonego okna. Po pierwsze, kiedy otwierasz plik Excel, musisz włączyć filtrowanie. Można to zrobić, wysyłając skrót klawiaturowy Ctrl+Shift+L. To w PAD powinno być wyrażone jako {Control}({Shift}(L)).

Następnie musisz ustawić focus na komórce, która zawiera nagłówek kolumny, którą chcesz filtrować (lub innymi słowy – gdzie widoczny jest mały przycisk do otwierania okna filtrowania).

Po ustawieniu fokusu musisz wysłać kolejny skrót klawiaturowy, tym razem: Alt+strzałka w dół. W PAD byłoby to: {Alt}({Down}).

Następnie użyj „UI selector”, aby uzyskać elementy interfejsu użytkownika określonych części okna dialogowego filtra, np. pole wyszukiwania i przycisk OK. Gdy już je masz, po prostu zbuduj wokół nich logikę, na przykład wypełnij pole wyszukiwania terminem, którego musisz użyć do filtrowania, a następnie naciśnij przycisk OK.

I to wszystko!

Poniżej znajdziesz kod, który po skopiowaniu i wklejeniu do PAD-a zamieni się w 7 akcji wraz z selektorami, dzięki czemu możesz spróbować sam. Powodzenia!

Excel.LaunchExcel.LaunchAndOpen Path: $'''C:\\Users\\USER\\Downloads\\book.xlsx''' Visible: True ReadOnly: False LoadAddInsAndMacros: False Instance=> ExcelInstance
Excel.SelectCellsFromExcel.SelectCells Instance: ExcelInstance StartColumn: $'''A''' StartRow: 1 EndColumn: $'''A''' EndRow: 1
MouseAndKeyboard.SendKeys.FocusAndSendKeysByInstanceOrHandle WindowInstance: ExcelInstance TextToSend: $'''{Control}({Shift}(L))''' DelayBetweenKeystrokes: 10 SendTextAsHardwareKeys: True
Excel.SelectCellsFromExcel.SelectCells Instance: ExcelInstance StartColumn: $'''B''' StartRow: 1 EndColumn: $'''B''' EndRow: 1
MouseAndKeyboard.SendKeys.FocusAndSendKeysByInstanceOrHandle WindowInstance: ExcelInstance TextToSend: $'''{Alt}({Down})''' DelayBetweenKeystrokes: 10 SendTextAsHardwareKeys: True
UIAutomation.PopulateTextField TextField: appmask['Window \'book.xlsx - Excel\'']['Edit \'Type field name to search for\''] Text: 2 Mode: UIAutomation.PopulateTextMode.Replace ClickType: UIAutomation.PopulateMouseClickType.SingleClick
UIAutomation.PressButton Button: appmask['Window \'book.xlsx - Excel\'']['Button \'OK\'']

# [ControlRepository][PowerAutomateDesktop]
{
  "ApplicationInfo": {
    "Name": "ClipboardControlRepository",
    "Version": "1.0"
  },
  "Screens": [
    {
      "Controls": [
        {
          "AutomationProtocol": "uia3",
          "ElementTypeName": "Edit",
          "InstanceId": "85bbf960-d360-4df5-97e7-49f73a6e04fe",
          "Name": "Edit 'Type field name to search for'",
          "SelectorCount": 1,
          "Selectors": [
            {
              "CustomSelector": null,
              "Elements": [
                {
                  "Attributes": [
                    {
                      "Ignore": false,
                      "IsOrdinal": false,
                      "Name": "Class",
                      "Operation": "EqualTo",
                      "Value": "NetUIToolWindow"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Enabled",
                      "Operation": "EqualTo",
                      "Value": true
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Id",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Name",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": true,
                      "Name": "Ordinal",
                      "Operation": "EqualTo",
                      "Value": -1
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Visible",
                      "Operation": "EqualTo",
                      "Value": true
                    }
                  ],
                  "CustomValue": null,
                  "Ignore": false,
                  "Name": "Menu 'NetUIToolWindow'",
                  "Tag": "menu"
                },
                {
                  "Attributes": [
                    {
                      "Ignore": false,
                      "IsOrdinal": false,
                      "Name": "Class",
                      "Operation": "EqualTo",
                      "Value": "NetUIDismissBehavior"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Enabled",
                      "Operation": "EqualTo",
                      "Value": true
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Id",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Name",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": true,
                      "Name": "Ordinal",
                      "Operation": "EqualTo",
                      "Value": -1
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Visible",
                      "Operation": "EqualTo",
                      "Value": true
                    }
                  ],
                  "CustomValue": null,
                  "Ignore": false,
                  "Name": "UI Custom 'NetUIDismissBehavior'",
                  "Tag": "custom"
                },
                {
                  "Attributes": [
                    {
                      "Ignore": false,
                      "IsOrdinal": false,
                      "Name": "Class",
                      "Operation": "EqualTo",
                      "Value": "NetUITextbox"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Enabled",
                      "Operation": "EqualTo",
                      "Value": true
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Id",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Name",
                      "Operation": "EqualTo",
                      "Value": "Type field name to search for"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": true,
                      "Name": "Ordinal",
                      "Operation": "EqualTo",
                      "Value": -1
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Visible",
                      "Operation": "EqualTo",
                      "Value": true
                    }
                  ],
                  "CustomValue": null,
                  "Ignore": false,
                  "Name": "Edit 'Type field name to search for'",
                  "Tag": "edit"
                }
              ],
              "Ignore": false,
              "IsCustom": false,
              "IsWindowsInstance": false,
              "Order": 0
            }
          ],
          "Tag": "edit"
        },
        {
          "AutomationProtocol": "uia3",
          "ElementTypeName": "Button",
          "InstanceId": "509b6bbb-8b91-4426-81a1-459b9be42b3e",
          "Name": "Button 'OK'",
          "SelectorCount": 1,
          "Selectors": [
            {
              "CustomSelector": null,
              "Elements": [
                {
                  "Attributes": [
                    {
                      "Ignore": false,
                      "IsOrdinal": false,
                      "Name": "Class",
                      "Operation": "EqualTo",
                      "Value": "NetUIToolWindow"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Enabled",
                      "Operation": "EqualTo",
                      "Value": true
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Id",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Name",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": true,
                      "Name": "Ordinal",
                      "Operation": "EqualTo",
                      "Value": -1
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Visible",
                      "Operation": "EqualTo",
                      "Value": true
                    }
                  ],
                  "CustomValue": null,
                  "Ignore": false,
                  "Name": "Menu 'NetUIToolWindow'",
                  "Tag": "menu"
                },
                {
                  "Attributes": [
                    {
                      "Ignore": false,
                      "IsOrdinal": false,
                      "Name": "Class",
                      "Operation": "EqualTo",
                      "Value": "NetUIDismissBehavior"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Enabled",
                      "Operation": "EqualTo",
                      "Value": true
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Id",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Name",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": true,
                      "Name": "Ordinal",
                      "Operation": "EqualTo",
                      "Value": -1
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Visible",
                      "Operation": "EqualTo",
                      "Value": true
                    }
                  ],
                  "CustomValue": null,
                  "Ignore": false,
                  "Name": "UI Custom 'NetUIDismissBehavior'",
                  "Tag": "custom"
                },
                {
                  "Attributes": [
                    {
                      "Ignore": false,
                      "IsOrdinal": false,
                      "Name": "Class",
                      "Operation": "EqualTo",
                      "Value": "NetUIButton"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Enabled",
                      "Operation": "EqualTo",
                      "Value": true
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Id",
                      "Operation": "EqualTo",
                      "Value": ""
                    },
                    {
                      "Ignore": false,
                      "IsOrdinal": false,
                      "Name": "Name",
                      "Operation": "EqualTo",
                      "Value": "OK"
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": true,
                      "Name": "Ordinal",
                      "Operation": "EqualTo",
                      "Value": -1
                    },
                    {
                      "Ignore": true,
                      "IsOrdinal": false,
                      "Name": "Visible",
                      "Operation": "EqualTo",
                      "Value": true
                    }
                  ],
                  "CustomValue": null,
                  "Ignore": false,
                  "Name": "Button 'OK'",
                  "Tag": "button"
                }
              ],
              "Ignore": false,
              "IsCustom": false,
              "IsWindowsInstance": false,
              "Order": 0
            }
          ],
          "Tag": "button"
        }
      ],
      "ElementTypeName": "Window",
      "InstanceId": "2497b5ce-8aac-4541-b166-85d9d2dfed34",
      "Name": "Window 'book.xlsx - Excel'",
      "SelectorCount": 1,
      "Selectors": [
        {
          "CustomSelector": null,
          "Elements": [
            {
              "Attributes": [
                {
                  "Ignore": true,
                  "IsOrdinal": false,
                  "Name": "Class",
                  "Operation": "EqualTo",
                  "Value": "XLMAIN"
                },
                {
                  "Ignore": true,
                  "IsOrdinal": false,
                  "Name": "Enabled",
                  "Operation": "EqualTo",
                  "Value": true
                },
                {
                  "Ignore": true,
                  "IsOrdinal": false,
                  "Name": "Id",
                  "Operation": "EqualTo",
                  "Value": ""
                },
                {
                  "Ignore": false,
                  "IsOrdinal": false,
                  "Name": "Name",
                  "Operation": "EqualTo",
                  "Value": "book.xlsx - Excel"
                },
                {
                  "Ignore": true,
                  "IsOrdinal": true,
                  "Name": "Ordinal",
                  "Operation": "EqualTo",
                  "Value": -1
                },
                {
                  "Ignore": false,
                  "IsOrdinal": false,
                  "Name": "Process",
                  "Operation": "EqualTo",
                  "Value": "EXCEL"
                },
                {
                  "Ignore": true,
                  "IsOrdinal": false,
                  "Name": "Visible",
                  "Operation": "EqualTo",
                  "Value": true
                }
              ],
              "CustomValue": null,
              "Ignore": false,
              "Name": "Window 'book.xlsx - Excel'",
              "Tag": "window"
            }
          ],
          "Ignore": false,
          "IsCustom": false,
          "IsWindowsInstance": false,
          "Order": 0
        }
      ],
      "Tag": "window"
    }
  ],
  "Version": 1
}

Artykuł Filtrowanie danych w Excel z użyciem Power Automate Desktop pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Wyświetlanie obrazków w Adaptive Cards

Obrazy zawsze wzbogacają projekty Adaptive Cards. Jednak w Microsoft Teams max. payload (rozmiar) wiadomości to tylko 25KB i obejmuje on również rozmiar JSON samej karty. Dlatego bardzo często nie jest możliwe wyświetlenie nawet najmniejszego obrazu w trybie inline. Jakie zatem są inne opcje?

Obrazki w Adaptive Cards

Zasadniczo obrazy są wyświetlane na kartach adaptacyjnych przy użyciu elementu „Image”. W jego właściwościach musisz wpisać adres URL do obrazu:

Ważne! Adres URL obrazu musi zaczynać się od https i być bezpośrednim linkiem do samego pliku (kończyć się na jpg, png, tiff itp.).

Ok, więc jakie są opcje wyświetlania obrazu

  1. Poprzez bezwzględne hiperłącze do samego pliku
  2. Poprzez data uri – zakodowana do formatu base64 zawartość pliku

Pierwsze podejście wyjaśniłem już powyżej. Jest bardzo proste. Drugie wymaga pobrania zawartości pliku i zakodowania jej za do stringu base64. Można to zrobić samodzielnie, korzystając z usług online, np. Base64 Image Encoder. Lub używając wyrażenia dataUri(zawartość pliku) w Power Automate:

dataUri expression in Power Automate

To, co jest zwracane, wygląda jak następujący ciąg: data:image/jpeg;base64,/9j/4AAJRgABAQ….QP//Z. Im dłuższy jest ten ciąg, tym większy jest plik. Na koniec ten ciąg (lub wyrażenie w Power Automate) należy umieścić jako wartość we właściwości Url elementu Image w projekcie karty adaptacyjnej.

Ważne! Rozmiar wiadomości Microsoft Teams może mieć rozmiar max. 25 KB na dzień dzisiejszy – wliczając w to JSON. Więc jeśli zakodujesz obraz w base64, łatwo zorientujesz się, że posiadanie tak wielu znaków spowoduje, że rozmiar z łatwością przekroczy ten limit. 25 KB to 25 000 bajtów, a jeden znak jest kodowany jako od 1 do 4 bajtów. Na przykład $ potrzebuje jednego bajtu, podczas gdy € potrzebuje 3 bajtów.

Gdzie przechowywać obrazy?

Zasadniczo obraz, który ma być wyświetlany, musi być dostępny anonimowo. Dzieje się tak, ponieważ karta Adaptive Card w MS Teams jest wyświetlana przez bota Teams, a nie konto uwierzytelnionego użytkownika. W związku z tym nie ma ono takich samych uprawnień dostępu jak użytkownik, który wyświetla kartę i najpewniej nie ma dostępu do pliku.

Wiedząc o tym, nie jest możliwe przechowywanie i wyświetlanie obrazów z SharePoint przy użyciu ścieżki bezwzględnej. Możesz spróbować, najpierw przekonwertować obrazy na data uri, jednak pamiętaj o rozmiarze wiadomości 🙂

Jakie inne sposoby są zatem możliwe?

  1. Anonimowy serwer FTP
  2. Usługi online do przechowywania obrazów, np.: Imgur
  3. WordPress
  4. Azure Blob Storage
  5. i inne, pozwalające na udostępnianie plików nie wymagajace uwierzytelnienia

OneDrive nie może służyć jako magazyn do przechowywania obrazów, ponieważ nawet jeśli zdecydujemy się udostępnić obraz komukolwiek:

Wygenerowany link nadal nie jest prawidłowym, bezwzględnym linkiem do samego pliku, ale do strony, która wyświetla obraz, więc gdy umieścisz go jako wartość Url, obraz nie zostanie wyświetlony:

Image shared through link from OneDrive will not work

Jak bezpiecznie przechowywać obrazy, ale nadal wyświetlać je w Adaptive Cards?

W takim przypadku należy poszukać usług, które pozwalają zabezpieczyć zawartość za pomocą connection string/ SAS (shared access signature), które można dodać do bezwzględnego adresu URL jako parametry url. Moim ulubionym jest Azure Blob Storage. Pokażę ci teraz jak.

  1. Zaloguj się do portalu Azure i utwórz Storage account:
  1. Gdy konto jest utworzone, przejdź do niego i:
    1. Kliknij na Containers
    2. Kliknij link do stworzenia nowego kontenera
    3. Wpisz jego nazwę i ustaw poziom dostępu na Private
  1. Teraz prześlij obraz, który chcesz wyświetlić w Adaptive Card:
    1. Kliknij link Upload
    2. Wybierz plik, który chcesz przesłać i określ wszystkie właściwości zgodnie z własnymi potrzebami
    3. Wreszcie po przesłaniu przejdź do strony Shared access tokens
  1. Na koniec wygeneruj token SAS, który możesz dołączyć do linku bezwzględnego do obrazu, aby udostępnić go do wyświetlenia.
    1. Zdefiniuj poziom uprawnień – w tym scenariuszu wystarczy tylko Read
    2. Zdefiniuj daty wygaśnięcia – kiedy token wygaśnie, a zasób ponownie stanie się niedostępny
    3. Wybierz tylko HTTPS, ponieważ tylko taki protokół obsługują karty Adaptive Card
    4. Kliknij, aby wygenerować token SAS i URL
    5. Skopiuj token SAS

Teraz możesz wrócić do zawartości kontenera, otworzyć szczegóły pliku, skopiować jego ścieżkę i dołączyć token SAS:

Następnie wklej taki adres URL z powrotem do właściwości Url elementu Image w Adaptive Card i voilla! Obraz zostanie prawidłowo wyświetlony:

Mam nadzieję, że ten krótki poradnik okaże się przydatny. Napisz w komentarzach, jakich innych dostawców używasz do bezpiecznego przechowywania obrazów i uzyskiwania do nich dostępu za pomocą SAS/ tokenów dostępu.

Artykuł Wyświetlanie obrazków w Adaptive Cards pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

Jak automatycznie kończyć zadania zatwierdzania w Power Automate i Microsoft Teams

Pisząc mój poprzedni post dotyczący pracy z zadaniami w Power Automate, zdałem sobie sprawę, że w tabeli Flow Approval istnieje kolumna o nazwie Flow Notification URI, której do tej pory naprawdę nie doceniałem. Pokażę teraz, jaka drzemie w niej moc!

Do dziś byłem prawie pewien, że nie da się automatycznie ukończyć zadania w Power Automate/Microsoft Teams, a także wznowić procesu, który czeka na zatwierdzenie. Okazuje się, że naprawdę się myliłem.

Trochę podstaw

Za każdym razem, gdy przepływ pracy zawierający akcję, która przypisuje zadanie i czeka na jego zakończenie, jest wykonywany, nie tylko tworzy wpisy w tabelach Approval i Approval Request, ale także w Flow Approval. Co więcej, każdy wpis w tej ostatniej tabeli zawiera wartość w kolumnie o nazwie Flow Notification URI, który po wywołaniu z żądaniem POST po prostu kończy oczekiwanie i wznawia przepływ:

Flow Notification URI column

Adres URL jest zbudowany z następujących składowych (większość z nich pochodzi z innych kolumn tabeli Flow Approval):

https://[instancja].westeurope.logic.azure.com/workflows/[Flow Id]/runs/[Flow Run Sequence Id]/actions/[Nazwa akcji czekającej na zakończenie]/run?api-version=2016-06-01&sp=%2Fruns%2F[Flow Run Sequence Id]%2Factions%2F[Nazwa akcji czekającej na zakończenie]%2Frun%2C%2Fruns%2F[Flow Run Sequence Id]%2Factions%2F[Nazwa akcji czekającej na zakończenie]%2Fread&sv=1.0&sig=[klucz podpisujący żądanie]

Co dalej?

Wiedząc, że wysłanie żądania POST do tego adresu URL kończy oczekiwanie i wznawia przepływ, otwiera zupełnie nowy zakres scenariuszy, które można zbudować wokół podstawowych zadań akceptacji, na przykład przy użyciu niestandardowych aplikacji Power Apps. Wyobraź sobie tylko niektóre z nich:

  1. Zatwierdzanie w imieniu innej osoby,
  2. Oczekiwanie na większość odpowiedzi, nie wszystkich,
  3. Kończenie zatwierdzania w dowolnym momencie,
  4. I wiele, wiele innych!

Celem jest, aby najpierw zapisać i zaktualizować dane we wszystkich innych tabelach, z których korzystają Approvals (o których mowa w moim poście tutaj), uzyskać wszystkie informacje potrzebne do wysłania w odpowiedzi, a następnie wykonać POST do Notification URI.

Ponadto, wykonując taki POST, upewnij się, że jest zgodny z oczekiwanym formatem JSON i że umieściłeś „Content-Type: application/json” jako nagłówek:

{
   "responses":[
      {
         "responder":{
            "id":"GUID z tabeli Users",
            "displayName":"Imię kończącego zadanie",
            "email":"Mail kończącego zadanie",
            "tenantId":"Tenant ID",
            "userPrincipalName":"Principal name kończącego zadanie"
         },
         "requestDate":"Timestamp",
         "responseDate":"Timestamp",
         "approverResponse":"Wynik zadania",
         "comments":"Komentarz"
      }
   ],
   "responseSummary":"Tekst będący zlepkiem informacji o każdym zadaniu, kto, kiedy i z jakim wynikiem.",
   "completionDate":"Timestamp",
   "outcome":"Ogólny wynik zatwierdzania",
   "name":"Approval ID z tabeli Approval",
   "title":"Nazwa zadania",
   "details":"Opis zadania",
   "requestDate":"Timestamp",
   "expirationDate":"Timestamp"
}

Po wysłaniu takiego POST akcja zakończy oczekiwanie i wyświetli wszystkie dane jako dynamiczne wyniki, tak jakby została zakończona za pomocą interfejsu zadania:

Completed task in Power Automate

Mam nadzieję, że ten post cię tak saom podekscytował, jak mnie, gdy odkryłem tę możliwość. Daj znać w komentarzach, jakie scenariusze jesteś w stanie teraz zrealizować!

Artykuł Jak automatycznie kończyć zadania zatwierdzania w Power Automate i Microsoft Teams pochodzi z serwisu Tomasz Poszytek, Business Applications MVP.

❌
❌