$symMatch = '(?:/p/(?<Pvar>\d+|)|/id/(?<IDvar>[^/]+)|/q/(?<Qvar>\d+))'
$regm = "^book/(?<Xvar>[^/]+)$($symMatch)$($symMatch)$($symMatch)(?:/|)`$"
То есть суммарно
^book/(?<Xvar>[^/]+)(?:/p/(?<Pvar>\d+|)|/id/(?<IDvar>[^/]+)|/q/(?<Qvar>\d+))(?:/p/(?<Pvar>\d+|)|/id/(?<IDvar>[^/]+)|/q/(?<Qvar>\d+))(?:/p/(?<Pvar>\d+|)|/id/(?<IDvar>[^/]+)|/q/(?<Qvar>\d+))(?:/|)$
Более кошерно, конечно, поиграть с LookAhead и LookBehind, но тут, увы, я не помогу.
* Не матчит
book/BOOK_ID_Q_noP/id/Aa12345/q/12345/p
так как после P нет слешей
* Некорректно матчит, если в строке, например, два и более одинаковых тега (P\Q\id), и не хватает одного (и более) тега. Тут LookAhead\LookBehind в помощь
Проходит по строкам
book/BOOK_P_ID_Q/p/12345/id/Aa12345/q/67890
book/BOOK_Q_ID_P/q/67890/id/Aa12345/q/12345
book/BOOK_ID_P_Q/id/Zz12345/p/12345/q/12345
book/BOOK_noP_ID_Q/p//id/Aa12345/q/12345
book/BOOK_ID_Q_noP/id/Aa12345/q/12345/p//
> $Matches
Name Value
---- -----
Qvar 67890
Pvar 12345
IDvar Aa12345
Xvar BOOK_P_ID_Q
0 book/BOOK_P_ID_Q/p/12345/id/Aa12345/q/67890
PS> PowerShell-синтаксис RegExp (именованые выделения как (?<SelectionName>) ; без выделения - (?:)UPD:
А вот вариант с LookBehind-ами. Избавит от "двойных" тегов.
$symMatch = '(?:(?<!.*/p/.*)/p/(?<Pvar>\d+|)|(?<!.*/id/.*)/id/(?<IDvar>[^/]+)|(?<!.*/q/.*)/q/(?<Qvar>\d+))'
$regm = "^book/(?<Xvar>[^/]+)$($symMatch)$($symMatch)$($symMatch)(?:/|)`$"
^book/(?<Xvar>[^/]+)(?:(?<!.*/p/.*)/p/(?<Pvar>\d+|)|(?<!.*/id/.*)/id/(?<IDvar>[^/]+)|(?<!.*/q/.*)/q/(?<Qvar>\d+))(?:(?<!.*/p/.*)/p/(?<Pvar>\d+|)|(?<!.*/id/.*)/id/(?<IDvar>[^/]+)|(?<!.*/q/.*)/q/(?<Qvar>\d+))(?:(?<!.*/p/.*)/p/(?<Pvar>\d+|)|(?<!.*/id/.*)/id/(?<IDvar>[^/]+)|(?<!.*/q/.*)/q/(?<Qvar>\d+))(?:/|)$
* LookBehind (?<= .... ); LookBehindNot (?<! ..... )